From ebf3b511a9b58c9b25ce1de9aaacb6af74774236 Mon Sep 17 00:00:00 2001 From: Alexey Gopachenko Date: Fri, 12 Nov 2010 11:31:01 +0300 Subject: [PATCH] CRLF Dmitry Trofimov MUST DIE %E~ --- bin/nix/idea.sh | 222 +- .../intellij/compiler/impl/CompileDriver.java | 5144 ++++---- .../impl/TranslatingCompilerFilesMonitor.java | 3010 ++--- .../javaCompiler/BackendCompilerWrapper.java | 1934 +-- .../javaCompiler/jikes/JikesCompiler.java | 474 +- .../BuildInstructionBase.java | 144 +- .../compiler/progress/CompilerTask.java | 1316 +- .../actions/SmartStepIntoActionHandler.java | 312 +- .../debugger/ui/RunHotswapDialog.java | 288 +- .../ui/impl/UpdatableDebuggerView.java | 274 +- .../debugger/ui/impl/watch/DebuggerTree.java | 1484 +-- .../ui/tree/render/ClassRenderer.java | 558 +- .../engine/evaluation/EvaluateException.java | 136 +- .../execution/applet/AppletConfiguration.java | 738 +- .../execution/filters/ExceptionFilter.java | 284 +- .../com/intellij/ide/impl/NewProjectUtil.java | 408 +- .../AbstractStepWithProgress.java | 384 +- .../util/projectWizard/JdkChooserPanel.java | 582 +- .../util/projectWizard/NameLocationStep.java | 580 +- .../projectWizard/ProjectJdkListRenderer.java | 92 +- .../project/impl/IdeaProjectManagerImpl.java | 154 +- .../ui/configuration/ConfigurationError.java | 182 +- .../ConfigurationErrorsComponent.java | 1444 +-- .../roots/ui/configuration/JdkComboBox.java | 590 +- .../ui/configuration/LanguageLevelCombo.java | 120 +- .../roots/ui/configuration/ModuleEditor.java | 1224 +- .../ui/configuration/ProjectConfigurable.java | 762 +- .../configuration/ProjectJdkConfigurable.java | 334 +- .../ProjectStructureConfigurable.java | 1164 +- .../artifacts/ArtifactProblemDescription.java | 108 +- .../artifacts/ArtifactProblemsHolderImpl.java | 118 +- .../BaseStructureConfigurable.java | 954 +- .../projectRoot/LibraryConfigurable.java | 360 +- .../daemon/ConfigurationErrorQuickFix.java | 60 +- .../LibraryProjectStructureElement.java | 318 +- .../daemon/ModuleProjectStructureElement.java | 266 +- .../daemon/ProjectConfigurationProblem.java | 142 +- .../daemon/ProjectConfigurationProblems.java | 168 +- .../ProjectStructureProblemDescription.java | 130 +- .../ProjectStructureProblemsHolder.java | 62 +- .../ProjectStructureProblemsHolderImpl.java | 150 +- .../codeInsight/ExpectedTypesProvider.java | 2458 ++-- .../BasicExpressionCompletionContributor.java | 364 +- .../completion/JavaAwareCompletionData.java | 316 +- .../completion/JavaCompletionContributor.java | 1112 +- .../completion/JavaCompletionData.java | 1328 +- ...GlobalMemberNameCompletionContributor.java | 156 +- .../completion/ModifierChooser.java | 356 +- .../impl/analysis/HighlightClassUtil.java | 1662 +-- .../daemon/impl/analysis/HighlightUtil.java | 3970 +++--- .../CreateConstructorFromThisOrSuperFix.java | 300 +- .../quickfix/CreateMethodFromUsageFix.java | 574 +- .../generation/OverrideImplementUtil.java | 1480 +-- .../inferNullity/NullityInferrer.java | 1250 +- .../javadoc/JavadocConfiguration.java | 694 +- .../lang/java/parser/StatementParser.java | 1464 +-- .../roots/impl/JavaLanguageLevelPusher.java | 198 +- .../psi/impl/compiled/ClsClassImpl.java | 1076 +- .../impl/search/AllClassesSearchExecutor.java | 262 +- .../impl/source/codeStyle/ImportHelper.java | 1706 +-- .../extractMethod/InputVariables.java | 624 +- .../IntroduceVariableBase.java | 1736 +-- .../ReassignVariableUtil.java | 414 +- .../memberPullUp/PullUpHelper.java | 1736 +-- .../intellij/refactoring/ui/InfoDialog.java | 174 +- .../ui/TypeSelectorManagerImpl.java | 638 +- .../completion/normal/FinalInForLoop.java | 14 +- .../normal/PrimitiveTypesInForLoopSpace.java | 16 +- .../PrimitiveTypesInForLoopSpace_after.java | 16 +- .../completion/normal/SpaceAfterReturn.java | 14 +- .../normal/SpaceAfterReturn_after.java | 14 +- .../daemonCodeAnalyzer/lossyEncoding/Text.txt | 2 +- .../changeExtendsToImplements/after7.java | 18 +- .../changeExtendsToImplements/before7.java | 18 +- .../afterAssertParamNotNull.java | 26 +- .../afterConditionalReturnNotNull.java | 16 +- .../afterParameterCheckedForNull.java | 72 +- .../beforeAssertParamNotNull.java | 20 +- .../beforeConditionalReturnNotNull.java | 8 +- .../annotations/ExtraCommaInList.txt | 42 +- .../declarations/VarArgBrackets.txt | 62 +- .../PinesInReferenceExpression2.txt | 32 +- .../extractMethod/Cast4ParamGeneration.java | 20 +- .../Cast4ParamGeneration_after.java | 28 +- ...ivateInnerClassWithPrivateConstructor.java | 28 +- ...nnerClassWithPrivateConstructor_after.java | 28 +- .../suggestedTypes/NoCastWhenWrapped.java | 20 +- .../codeInsight/NullityInferrerTest.java | 216 +- .../completion/NormalCompletionTest.groovy | 1732 +-- .../codeInsight/daemon/ImportHelperTest.java | 760 +- .../codeInsight/daemon/LossyEncodingTest.java | 188 +- .../parser/partial/AnnotationParserTest.java | 98 +- .../parser/partial/DeclarationParserTest.java | 230 +- .../psi/impl/cache/impl/SCR17650Test.java | 148 +- .../refactoring/ExtractMethodTest.java | 1112 +- .../com/intellij/refactoring/PullUpTest.java | 296 +- .../ReplaceMethodDuplicatesTestSuite.java | 48 +- .../refactoring/SuggestedParamTypesTest.java | 286 +- .../roots/ModuleRootsExternalizationTest.java | 558 +- .../src/com/intellij/psi/PsiClassType.java | 486 +- .../codeInsight/CodeInsightTestCase.java | 1378 +- .../bootstrap/src/com/intellij/idea/Main.java | 424 +- .../completion/CompletionConfidence.java | 70 +- .../completion/CompletionService.java | 304 +- .../RunnerAndConfigurationSettings.java | 118 +- .../src/com/intellij/facet/FacetType.java | 350 +- .../util/OrderEntryCellAppearanceUtils.java | 416 +- .../src/com/intellij/psi/PsiElementRef.java | 378 +- .../com/intellij/psi/impl/ElementBase.java | 516 +- .../psi/search/scope/ProjectFilesScope.java | 86 +- .../psi/stubs/SerializationManagerImpl.java | 482 +- .../intellij/psi/util/ProximityLocation.java | 138 +- .../com/intellij/psi/util/PsiTreeUtil.java | 1702 +-- .../src/com/intellij/util/indexing/ID.java | 280 +- .../codeInsight/CodeInsightUtilBase.java | 284 +- .../completion/CodeCompletionHandlerBase.java | 1486 +-- .../completion/CompletionData.java | 744 +- .../CompletionProgressIndicator.java | 1356 +- .../completion/CompletionVariant.java | 508 +- .../FilePathCompletionContributor.java | 710 +- .../completion/WordCompletionContributor.java | 244 +- .../impl/CompletionServiceImpl.java | 326 +- .../daemon/impl/FileStatusMap.java | 596 +- .../daemon/impl/StatusBarUpdater.java | 210 +- .../daemon/impl/UpdateHighlightersUtil.java | 1412 +-- .../actions/ShowJavaDocInfoAction.java | 288 +- .../CompletionAutoPopupHandler.java | 468 +- .../editorActions/EnterHandler.java | 1174 +- .../editorActions/TypedHandler.java | 1094 +- .../EnterAfterUnmatchedBraceHandler.java | 374 +- .../codeInsight/lookup/TailTypeDecorator.java | 120 +- .../codeInsight/lookup/impl/LookupImpl.java | 1992 +-- .../lookup/impl/LookupManagerImpl.java | 448 +- .../lookup/impl/actions/ChooseItemAction.java | 104 +- .../impl/actions/ChooseItemReplaceAction.java | 142 +- .../LiveTemplateCompletionContributor.java | 176 +- .../template/impl/TemplateSegments.java | 262 +- .../template/impl/TemplateSettings.java | 1404 +-- .../intellij/execution/ProgramRunnerUtil.java | 236 +- .../com/intellij/execution/RunManagerEx.java | 166 +- .../actions/ChooseRunConfigurationAction.java | 1786 +-- .../ConfigurationSettingsEditorWrapper.java | 476 +- .../execution/impl/RunManagerImpl.java | 1818 +-- .../RunnerAndConfigurationSettingsImpl.java | 784 +- .../execution/rmi/RemoteProcessSupport.java | 666 +- .../com/intellij/formatting/WhiteSpace.java | 1416 +-- .../intellij/ide/actions/GotoActionBase.java | 176 +- .../intellij/ide/actions/GotoClassAction.java | 152 +- .../intellij/ide/actions/GotoFileAction.java | 288 +- .../ide/actions/GotoSymbolAction.java | 118 +- .../ide/commander/ProjectListBuilder.java | 462 +- .../ide/fileTemplates/FileTemplateUtil.java | 746 +- .../ide/navigationToolbar/NavBarPanel.java | 2660 ++-- .../actions/MarkExcludeRootAction.java | 90 +- .../projectView/actions/MarkRootAction.java | 244 +- .../projectView/actions/MarkRootGroup.java | 58 +- .../actions/MarkTestSourceRootAction.java | 50 +- .../ide/projectView/impl/ProjectViewImpl.java | 3680 +++--- .../impl/nodes/AbstractModuleNode.java | 200 +- .../impl/nodes/NamedLibraryElementNode.java | 264 +- .../impl/nodes/PsiDirectoryNode.java | 554 +- .../LangScriptingContextConfigurable.java | 186 +- .../LangScriptingContextProvider.java | 90 +- .../ScriptingLibraryMappings.java | 410 +- .../ui/EditLibraryDialog.form | 196 +- .../ui/EditLibraryDialog.java | 654 +- .../ui/ScriptingContextsConfigurable.java | 144 +- .../ide/ui/search/TraverseUIStarter.java | 344 +- .../ide/util/FileStructureDialog.java | 952 +- .../com/intellij/ide/util/MemberChooser.java | 1476 +-- .../ide/util/gotoByName/ChooseByNameBase.java | 2794 ++-- .../util/gotoByName/ChooseByNamePopup.java | 584 +- .../injected/editor/DocumentWindowImpl.java | 1572 +-- .../internal/psiView/PsiViewerDialog.java | 1894 +-- .../openapi/roots/impl/ContentEntryImpl.java | 704 +- .../roots/impl/DirectoryIndexImpl.java | 1690 +-- .../roots/impl/libraries/LibraryEx.java | 92 +- .../roots/impl/libraries/LibraryImpl.java | 1278 +- .../scripting/ScriptingLibraryManager.java | 442 +- .../scripting/ScriptingLibraryTable.java | 336 +- .../wm/impl/status/TogglePopupHintsPanel.java | 360 +- .../DefaultScopesProvider.java | 204 +- .../ui/InspectionToolsConfigurable.java | 790 +- .../src/com/intellij/psi/PsiAnchor.java | 590 +- .../psi/impl/PsiCachedValuesFactory.java | 138 +- .../cache/impl/IndexCacheManagerImpl.java | 394 +- .../psi/impl/file/impl/FileManagerImpl.java | 2348 ++-- .../intellij/psi/impl/source/PsiFileImpl.java | 1910 +-- .../codeStyle/CodeStyleManagerImpl.java | 952 +- .../psi/impl/source/text/ASTDiffBuilder.java | 288 +- .../tree/injected/MultiHostRegistrarImpl.java | 992 +- .../com/intellij/psi/stubs/StubIndexImpl.java | 836 +- .../proximity/PsiProximityComparator.java | 212 +- .../copy/CopyFilesOrDirectoriesDialog.java | 516 +- .../refactoring/rename/RenameUtil.java | 680 +- .../src/com/intellij/ui/DeferredIconImpl.java | 582 +- .../src/com/intellij/ui/IconDeferrerImpl.java | 230 +- .../ui/tabs/FileColorManagerImpl.java | 460 +- .../ui/tabs/FileColorSettingsTable.java | 600 +- .../util/indexing/FileBasedIndex.java | 3968 +++--- .../intellij/util/indexing/IndexingStamp.java | 358 +- .../execution/process/OSProcessHandler.java | 624 +- .../execution/util/ExecutableDialog.form | 68 +- .../execution/util/ExecutableDialog.java | 152 +- .../execution/util/ExecutablePathPanel.java | 120 +- .../execution/util/ExecutableValidator.java | 434 +- .../src/com/intellij/ide/BrowserUtil.java | 936 +- .../src/com/intellij/ide/GeneralSettings.java | 1024 +- .../com/intellij/ide/caches/FileContent.java | 168 +- .../openapi/components/ComponentManager.java | 218 +- .../editor/actions/ContentChooser.java | 482 +- .../com/intellij/openapi/project/Project.java | 136 +- .../roots/ui/util/CompositeAppearance.java | 434 +- .../ui/util/ModifiableCellAppearance.java | 52 +- .../ui/util/SimpleTextCellAppearance.java | 138 +- .../openapi/ui/ComboBoxWithWidePopup.java | 144 +- .../intellij/openapi/ui/DescriptionLabel.java | 76 +- .../openapi/ui/FixedComboBoxEditor.java | 654 +- .../intellij/openapi/ui/LoadingDecorator.java | 544 +- .../openapi/ui/NamedItemsListEditor.java | 654 +- .../com/intellij/openapi/ui/TreeComboBox.java | 466 +- .../com/intellij/openapi/vfs/VirtualFile.java | 1282 +- .../openapi/vfs/newvfs/FileAttribute.java | 242 +- .../com/intellij/openapi/wm/StatusBar.java | 200 +- .../intellij/openapi/wm/StatusBarWidget.java | 278 +- .../intellij/openapi/wm/WindowManager.java | 158 +- .../com/intellij/pom/NavigatableWithText.java | 52 +- .../src/com/intellij/ui/ColorPanel.java | 660 +- .../src/com/intellij/ui/GuiUtils.java | 766 +- .../com/intellij/ui/HtmlListCellRenderer.java | 240 +- .../com/intellij/ui/SeparatorWithText.java | 190 +- .../intellij/ui/SimpleColoredComponent.java | 1230 +- .../com/intellij/ui/SimpleTextAttributes.java | 436 +- .../platform-api/src/com/intellij/ui/UI.java | 154 +- .../panels/ValidatingComponent.java | 176 +- .../com/intellij/ui/docking/DockManager.java | 104 +- .../src/com/intellij/ui/table/JBTable.java | 872 +- .../com/intellij/ui/tabs/impl/JBTabsImpl.java | 6042 ++++----- .../intellij/ui/treeStructure/SimpleTree.java | 1222 +- .../com/intellij/util/ui/AnimatedIcon.java | 402 +- .../hint/EditorFragmentComponent.java | 446 +- .../intellij/diagnostic/IdeErrorsDialog.java | 1578 +-- .../ide/GeneralSettingsConfigurable.java | 364 +- .../src/com/intellij/ide/IdeEventQueue.java | 1788 +-- .../com/intellij/ide/IdeTooltipManager.java | 1100 +- .../com/intellij/ide/SaveAndSyncHandler.java | 348 +- .../actions/BaseNavigateToSourceAction.java | 154 +- .../ChooseComponentsToExportDialog.java | 446 +- .../ide/actions/CloseProjectAction.java | 88 +- .../SaveAsDirectoryBasedFormatAction.java | 178 +- .../com/intellij/ide/dnd/Highlighters.java | 568 +- .../com/intellij/ide/impl/ProjectUtil.java | 506 +- .../com/intellij/ide/ui/LafManagerImpl.java | 1408 +-- .../impl/ui/NotificationsListPanel.java | 1722 +-- .../impl/ActionButtonWithText.java | 194 +- .../actionSystem/impl/ActionManagerImpl.java | 2690 ++-- .../application/ex/ApplicationManagerEx.java | 104 +- .../impl/ModalityInvokatorImpl.java | 88 +- .../openapi/command/impl/DummyProject.java | 344 +- .../components/impl/ComponentManagerImpl.java | 1422 +-- .../openapi/diff/ex/DiffStatusBar.java | 332 +- .../diff/impl/DiffToolbarComponent.java | 92 +- .../openapi/diff/impl/DiffToolbarImpl.java | 182 +- .../diff/impl/external/CompositeDiffTool.java | 124 +- .../diff/impl/external/ExtCompareFiles.java | 246 +- .../editor/actions/CutLineEndAction.java | 196 +- .../editor/actions/DeleteToLineEndAction.java | 54 +- .../editor/actions/DuplicateAction.java | 206 +- .../editor/actions/DuplicateLinesAction.java | 102 +- .../editor/actions/SimplePasteAction.java | 116 +- .../openapi/editor/ex/util/EditorUtil.java | 1148 +- .../openapi/editor/impl/CaretModelImpl.java | 1166 +- .../openapi/editor/impl/DocumentImpl.java | 1610 +-- .../openapi/editor/impl/EditorImpl.java | 10528 ++++++++-------- .../mapping/CachingSoftWrapDataMapper.java | 1306 +- .../impl/softwrap/mapping/MappingUtil.java | 168 +- .../fileEditor/impl/EditorComposite.java | 878 +- .../openapi/fileEditor/impl/EditorWindow.java | 1790 +-- .../fileEditor/impl/EditorsSplitters.java | 1474 +-- .../impl/FileEditorManagerImpl.java | 3048 ++--- .../keymap/impl/ui/MouseShortcutDialog.java | 572 +- .../keymap/impl/ui/QuickListPanel.java | 742 +- .../options/newEditor/OptionsTree.java | 1630 +-- .../openapi/project/CacheUpdateRunner.java | 474 +- .../openapi/project/ex/ProjectManagerEx.java | 138 +- .../openapi/project/impl/ProjectImpl.java | 1016 +- .../project/impl/ProjectManagerImpl.java | 2150 ++-- .../com/intellij/openapi/ui/FrameWrapper.java | 710 +- .../ui/impl/DialogWrapperPeerImpl.java | 2380 ++-- .../updateSettings/impl/PluginDownloader.java | 618 +- .../vcs/impl/FileStatusManagerImpl.java | 394 +- .../vfs/impl/VirtualFilePointerImpl.java | 372 +- .../vfs/newvfs/impl/VirtualFileImpl.java | 216 +- .../vfs/newvfs/persistent/FSRecords.java | 2562 ++-- .../vfs/newvfs/persistent/PersistentFS.java | 1964 +-- .../openapi/wm/impl/CommandProcessor.java | 260 +- .../openapi/wm/impl/FocusManagerImpl.java | 1430 +-- .../openapi/wm/impl/IdeFrameImpl.java | 704 +- .../intellij/openapi/wm/impl/IdeRootPane.java | 652 +- .../com/intellij/openapi/wm/impl/Stripe.java | 972 +- .../openapi/wm/impl/TestWindowManager.java | 590 +- .../wm/impl/ToolWindowManagerImpl.java | 4216 +++---- .../openapi/wm/impl/WindowManagerImpl.java | 1460 +-- .../wm/impl/status/EditorBasedWidget.java | 202 +- .../openapi/wm/impl/status/EncodingPanel.java | 252 +- .../wm/impl/status/IdeStatusBarImpl.java | 1468 +-- .../wm/impl/status/InfoAndProgressPanel.java | 1204 +- .../impl/status/InlineProgressIndicator.java | 786 +- .../wm/impl/status/InsertOverwritePanel.java | 174 +- .../openapi/wm/impl/status/PositionPanel.java | 380 +- .../openapi/wm/impl/status/StatusBarUI.java | 224 +- .../status/ToggleReadOnlyAttributePanel.java | 272 +- .../PlatformProjectOpenProcessor.java | 290 +- .../intellij/ui/HorizontalLabeledIcon.java | 250 +- .../src/com/intellij/ui/LabeledIcon.java | 266 +- .../com/intellij/ui/SeparatorComponent.java | 220 +- .../ui/docking/impl/DockManagerImpl.java | 1018 +- .../intellij/ui/plaf/beg/BegCellRenderer.java | 534 +- .../com/intellij/ui/popup/PopupComponent.java | 340 +- .../com/intellij/util/CachedValueBase.java | 446 +- ...apApplianceOnDocumentModificationTest.java | 454 +- .../src/messages/ActionsBundle.properties | 2396 ++-- .../src/messages/ExecutionBundle.properties | 608 +- .../src/messages/UIBundle.properties | 330 +- .../src/messages/XmlBundle.properties | 482 +- .../src/misc/registry.properties | 214 +- .../src/idea/Keymap_Eclipse.xml | 662 +- .../src/idea/LangActions.xml | 1550 +-- .../src/idea/PlatformActions.xml | 918 +- .../intellij/mock/MockComponentManager.java | 318 +- .../src/com/intellij/mock/MockProject.java | 302 +- .../testFramework/LightPlatformTestCase.java | 1446 +-- .../com/intellij/testFramework/TempFiles.java | 236 +- .../com/intellij/testFramework/Timings.java | 124 +- .../testFramework/UsefulTestCase.java | 1358 +- .../impl/CodeInsightTestFixtureImpl.java | 3022 ++--- .../impl/HeavyIdeaTestFixtureImpl.java | 474 +- .../testframework/CompositePrintable.java | 452 +- .../testframework/ui/TestResultsPanel.java | 368 +- .../intellij/execution/rmi/RemoteServer.java | 134 +- .../ide/ui/ListCellRendererWrapper.java | 196 +- .../com/intellij/openapi/util/JDOMUtil.java | 1376 +- .../com/intellij/openapi/util/TextRange.java | 278 +- .../intellij/openapi/util/io/FileUtil.java | 1968 +-- .../util/src/com/intellij/ui/TitlePanel.java | 148 +- .../com/intellij/util/ConcurrencyUtil.java | 252 +- .../util/io/EnumeratorStringDescriptor.java | 92 +- .../util/io/PersistentEnumerator.java | 1194 +- .../util/io/storage/AbstractStorage.java | 726 +- .../util/io/storage/RefCountingStorage.java | 234 +- .../src/com/intellij/util/ui/MacUIUtil.java | 500 +- .../util/src/com/intellij/util/ui/UIUtil.java | 3470 ++--- .../util/io/PagedFileStorageTest.java | 124 +- .../intellij/util/io/storage/StorageTest.java | 238 +- .../util/io/zip/UpdateableZipTest.java | 320 +- .../openapi/vcs/ProjectLevelVcsManager.java | 556 +- .../intellij/vcsUtil/ActionWithTempFile.java | 142 +- .../vcs/actions/AnnotateToggleAction.java | 642 +- .../openapi/vcs/actions/AnnotationColors.java | 80 +- .../vcs/actions/AnnotationFieldGutter.java | 376 +- .../actions/ShowAnnotationColorsAction.java | 106 +- .../committed/IncomingChangesIndicator.java | 384 +- .../shelf/CompoundShelfFileProcessor.java | 502 +- .../ShelfManagerConfigurationMerger.java | 302 +- .../changes/shelf/ShelveChangesManager.java | 1160 +- .../openapi/vcs/ex/LineStatusTracker.java | 1138 +- .../vcs/ex/LineStatusTrackerDrawing.java | 502 +- .../vcs/impl/LineStatusTrackerManager.java | 838 +- .../vcs/impl/ProjectLevelVcsManagerImpl.java | 1450 +-- .../vcs/impl/projectlevelman/NewMappings.java | 1170 +- .../xdebugger/frame/XCompositeNode.java | 116 +- .../frame/XDebuggerTreeNodeHyperlink.java | 80 +- .../impl/ui/ExecutionPointHighlighter.java | 282 +- .../xdebugger/impl/ui/tree/XDebuggerTree.java | 418 +- .../impl/ui/tree/XDebuggerTreeRenderer.java | 88 +- .../impl/ui/tree/nodes/MessageTreeNode.java | 232 +- .../impl/ui/tree/nodes/XDebuggerTreeNode.java | 334 +- .../ui/tree/nodes/XValueContainerNode.java | 336 +- .../impl/ui/tree/nodes/XValueNodeImpl.java | 308 +- .../siyeh/InspectionGadgetsBundle.properties | 3600 +++--- .../com/siyeh/ig/InspectionGadgetsPlugin.java | 2172 ++-- .../ConstantIfStatementInspection.java | 306 +- .../InfiniteLoopStatementInspection.java | 174 +- .../com/siyeh/ig/fixes/MakeFieldFinalFix.java | 164 +- .../siyeh/ig/psiutils/ControlFlowUtils.java | 1500 +-- .../ig/psiutils/SynchronizationUtil.java | 78 +- .../ig/psiutils/VariableSearchUtils.java | 136 +- ...lyQualifiedInnerClassAccessInspection.java | 320 +- .../ig/style/UnnecessaryThisInspection.java | 386 +- ...UnqualifiedInnerClassAccessInspection.java | 590 +- ...micOperationOnVolatileFieldInspection.java | 266 +- .../NonAtomicOperationOnVolatileField.html | 24 +- .../InfiniteLoopStatement.java | 56 +- .../infinite_loop_statement/expected.xml | 18 +- .../Test.java | 56 +- .../expected.xml | 48 +- .../UnnecessaryThisInspection.java | 144 +- .../style/unnecessary_this/expected.xml | 48 +- .../unqualified_inner_class_access/Test.java | 36 +- .../expected.xml | 60 +- .../InfiniteLoopStatementInspectionTest.java | 20 +- ...alifiedInnerClassAccessInspectionTest.java | 22 +- .../style/UnnecessaryThisInspectionTest.java | 20 +- ...alifiedInnerClassAccessInspectionTest.java | 22 +- .../config/ui/MethodParameterPanel.java | 816 +- .../inject/java/ConcatenationInjector.java | 820 +- .../java/JavaLanguageInjectionSupport.java | 990 +- .../inject/DefaultLanguageInjector.java | 146 +- .../inject/EditInjectionSettingsAction.java | 178 +- .../inject/InjectLanguageAction.java | 306 +- .../intelliLang/inject/InjectorUtils.java | 454 +- .../inject/LanguageInjectionSupport.java | 160 +- .../inject/TemporaryPlacesInjector.java | 134 +- .../inject/TemporaryPlacesRegistry.java | 312 +- .../inject/UnInjectLanguageAction.java | 214 +- .../inject/quickedit/QuickEditAction.java | 680 +- .../inject/xml/XmlLanguageInjector.java | 486 +- .../messages/AndroidBundle.properties | 438 +- .../android/AndroidCompletionContributor.java | 306 +- .../org/jetbrains/android/AndroidIdIndex.java | 250 +- .../android/compiler/AndroidAptCompiler.java | 632 +- .../compiler/AndroidPackagingCompiler.java | 702 +- .../AndroidResourcesPackagingCompiler.java | 298 +- .../compiler/ResourcesValidityState.java | 242 +- .../android/dom/AndroidDomExtender.java | 1050 +- .../jetbrains/android/dom/layout/Include.java | 86 +- .../android/exportSignedPackage/ApkStep.java | 474 +- .../jetbrains/android/facet/AndroidFacet.java | 1130 +- .../facet/AndroidFacetConfiguration.java | 272 +- .../android/facet/AndroidFacetEditorTab.form | 766 +- .../android/facet/AndroidFacetEditorTab.java | 1754 +-- .../android/facet/AndroidRootUtil.java | 456 +- .../AndroidInspectionToolProvider.java | 64 +- .../logcat/AndroidLogcatToolWindowView.form | 120 +- .../logcat/AndroidLogcatToolWindowView.java | 544 +- .../android/maven/AndroidFacetImporter.java | 432 +- .../LocalResourceManager.java | 606 +- .../android/run/AndroidDebugRunner.java | 534 +- .../run/AndroidRunConfigurationBase.java | 840 +- .../android/run/AndroidRunningState.java | 1324 +- .../jetbrains/android/run/DeviceChooser.java | 688 +- .../android/util/AndroidResourceUtil.java | 802 +- plugins/android/testData/dom/layout/hl.xml | 50 +- plugins/android/testData/dom/layout/idh.xml | 14 +- .../testData/dom/layout/layoutAttrs.xml | 20 +- plugins/android/testData/dom/layout/merge.xml | 6 +- .../testData/dom/layout/primValues.xml | 14 +- .../android/testData/dom/layout/systemRes.xml | 14 +- plugins/android/testData/dom/layout/tn5.xml | 4 +- .../android/testData/dom/layout/tn5_after.xml | 4 +- plugins/android/testData/dom/layout/vcr1.xml | 12 +- .../android/dom/Android11LayoutDomTest.java | 136 +- .../jetbrains/android/dom/AndroidDomTest.java | 158 +- .../android/dom/AndroidLayoutDomTest.java | 510 +- .../lang/ant/ForcedAntFileAttribute.java | 164 +- .../ant/config/impl/AntConfigurationImpl.java | 1860 +-- .../impl/reference/AntFileReferenceSet.java | 252 +- plugins/ant/tests/data/psi/Dirname_u.txt | 112 +- .../io/SendTextFilePreprocessor.java | 128 +- .../lib/cvsclient/file/LocalFileWriter.java | 362 +- .../conversion/IdeaSpecificSettings.java | 786 +- .../tostring/template/TemplateResource.java | 552 +- plugins/git4idea/git4idea.iml | 70 +- plugins/git4idea/src/META-INF/plugin.xml | 242 +- plugins/git4idea/src/git4idea/GitVcs.java | 1396 +- .../git4idea/changes/GitChangeProvider.java | 294 +- .../checkin/GitCheckinEnvironment.java | 1444 +-- .../config/GitExecutableValidator.java | 156 +- .../git4idea/history/GitHistoryProvider.java | 306 +- .../src/git4idea/i18n/GitBundle.properties | 972 +- .../src/git4idea/ui/GitTagDialog.java | 566 +- .../git4idea/src/git4idea/ui/GitUIUtil.java | 840 +- .../src/git4idea/update/GitStashUtils.java | 392 +- .../jetbrains/plugins/github/GithubUtil.java | 192 +- .../github/ui/GitHubSettingsConfigurable.java | 146 +- .../github/ui/GithubCloneProjectPane.form | 170 +- .../plugins/github/ui/GithubLoginDialog.java | 128 +- .../plugins/github/ui/GithubLoginPanel.form | 152 +- .../plugins/github/ui/GithubLoginPanel.java | 176 +- .../github/ui/GithubSettingsPanel.form | 160 +- .../github/ui/GithubSettingsPanel.java | 172 +- plugins/groovy/src/META-INF/plugin.xml | 1194 +- .../groovy/compiler/GroovyCompilerBase.java | 872 +- .../CompleteReferenceExpression.java | 824 +- .../path/GrCallExpressionTypeCalculator.java | 116 +- .../lang/psi/util/GroovyPropertyUtils.java | 836 +- .../plugins/groovy/lang/psi/util/PsiUtil.java | 2004 +-- .../GroovyStringLiteralManipulator.java | 110 +- .../groovy/refactoring/GroovyNamesUtil.java | 184 +- .../RenameJavaFileToGroovyFileAction.java | 218 +- .../rename/RenameGroovyPropertyProcessor.java | 460 +- .../refactoring/rename/RenameTest.groovy | 824 +- plugins/hg4idea/hg4idea.iml | 44 +- .../org/zmlx/hg4idea/HgVcsMessages.properties | 238 +- .../zmlx/hg4idea/HgExecutableValidator.java | 100 +- .../hg4idea/src/org/zmlx/hg4idea/HgUtil.java | 548 +- .../hg4idea/src/org/zmlx/hg4idea/HgVcs.java | 822 +- .../hg4idea/command/HgChangesetsCommand.java | 180 +- .../hg4idea/command/HgCommandService.java | 568 +- .../hg4idea/command/HgIncomingCommand.java | 70 +- .../hg4idea/command/HgOutgoingCommand.java | 68 +- .../hg4idea/command/HgTagBranchCommand.java | 154 +- .../intellij/execution/junit/TestObject.java | 774 +- .../intellij/execution/junit/TestPackage.java | 578 +- .../facade/MavenFacadeGlobalsManager.java | 76 +- .../maven/facade/embedder/CustomArtifact.java | 658 +- .../embedder/MavenFacadeConsoleWrapper.java | 254 +- .../embedder/MavenFacadeEmbedderImpl.java | 1310 +- .../embedder/MavenFacadeIndexerImpl.java | 664 +- .../embedder/MavenFacadeLoggerWrapper.java | 90 +- .../facade/embedder/MavenIndexFetcher.java | 216 +- .../facade/embedder/MavenModelConverter.java | 1070 +- .../embedder/RuntimeRemoteException.java | 58 +- .../embedder/TransferListenerAdapter.java | 284 +- .../embedder/MavenFacadeEmbedderTest.java | 734 +- .../idea/maven/facade/MavenFacadeManager.java | 874 +- .../maven/facade/RemoteObjectWrapper.java | 290 +- .../maven/importing/MavenProjectImporter.java | 1132 +- .../maven/indices/MavenIndicesManager.java | 778 +- .../indices/MavenProjectIndicesManager.java | 522 +- .../navigator/MavenProjectsNavigator.java | 744 +- .../navigator/MavenProjectsStructure.java | 2238 ++-- .../project/MavenGeneralConfigurable.java | 384 +- .../maven/project/MavenGeneralSettings.java | 700 +- .../project/MavenModificationTracker.java | 158 +- .../maven/project/MavenProjectsManager.java | 2160 ++-- .../project/MavenProjectsManagerWatcher.java | 964 +- ...ProjectsProcessorFoldersResolvingTask.java | 84 +- .../MavenProjectsProcessorReadingTask.java | 136 +- .../MavenProjectsProcessorResolvingTask.java | 84 +- .../idea/maven/project/MavenProjectsTree.java | 2770 ++-- .../project/actions/UpdateFoldersAction.java | 48 +- .../UpdateFoldersForProjectAction.java | 54 +- .../maven/tasks/MavenShortcutsManager.java | 488 +- .../utils/MavenEditorTabTitleUpdater.java | 110 +- .../idea/maven/utils/MavenImportNotifier.java | 282 +- .../idea/maven/utils/MavenRehighlighter.java | 284 +- .../jetbrains/idea/maven/utils/MavenUtil.java | 1178 +- .../wizards/MavenModuleBuilderHelper.java | 458 +- .../maven/wizards/MavenProjectBuilder.java | 570 +- .../main/resources/ProjectBundle.properties | 180 +- .../idea/maven/MavenImportingTestCase.java | 1122 +- .../jetbrains/idea/maven/MavenTestCase.java | 1116 +- .../maven/compiler/ResourceFilteringTest.java | 1782 +-- .../maven/execution/MavenExecutionTest.java | 250 +- .../maven/importing/AddingDependencyTest.java | 216 +- .../maven/importing/IgnoresImportingTest.java | 206 +- .../maven/importing/MiscImportingTest.java | 630 +- .../idea/maven/importing/ReimportingTest.java | 512 +- .../importing/StructureImportingTest.java | 2394 ++-- .../importing/WorkingWithOpenProjectTest.java | 168 +- .../indices/MavenIndicesTestFixture.java | 160 +- .../project/MavenProjectsManagerTest.java | 2102 +-- .../project/MavenProjectsTreeIgnoresTest.java | 192 +- .../project/MavenProjectsTreeReadingTest.java | 4448 +++---- .../project/MavenProjectsTreeTestCase.java | 104 +- .../idea/maven/project/MavenSettingsTest.java | 188 +- .../maven/wizards/MavenModuleBuilderTest.java | 692 +- plugins/properties/properties.iml | 46 +- .../messages/PropertiesBundle.properties | 70 +- .../ResourceBundleKeyRenameHandler.java | 252 +- .../ResourceBundleRenameHandler.java | 232 +- .../lang/properties/ResourceBundleUtil.java | 198 +- ...pertiesGroupingStructureViewComponent.java | 290 +- .../editor/ResourceBundleEditor.java | 1462 +-- .../structureView/GroupByWordPrefixes.java | 362 +- .../model/resolve/RelaxSymbolIndex.java | 584 +- .../idea/svn/SvnAuthenticationManager.java | 1240 +- .../idea/svn/actions/SelectBranchPopup.java | 620 +- .../idea/svn/dialogs/CopiesPanel.java | 844 +- .../configuration/TestNGRunnableState.java | 752 +- .../uiDesigner/actions/PreviewFormAction.java | 714 +- .../componentTree/ComponentTree.java | 1254 +- .../componentTree/ComponentTreeStructure.java | 366 +- .../lang/xpath/xslt/impl/XsltSymbolIndex.java | 488 +- resources/src/idea/IdeaActions.xml | 1346 +- .../xml/impl/PhysicalDomParentStrategy.java | 226 +- .../WebEditorAppearanceConfigurable.java | 64 +- .../options/editor/WebEditorOptions.java | 308 +- .../analysis/InsertRequiredAttributeFix.java | 382 +- .../impl/analysis/XmlHighlightVisitor.java | 1516 +-- .../generators/ZenCodingGenerator.java | 254 +- ...ml5CustomAttributeDescriptorsProvider.java | 144 +- .../index/Html5CustomAttributesIndex.java | 226 +- .../source/xml/XmlAttributeValueImpl.java | 340 +- .../source/xml/XmlConditionalSectionImpl.java | 162 +- .../psi/impl/source/xml/XmlDoctypeImpl.java | 494 +- ...erateInstanceDocumentFromSchemaAction.java | 358 +- .../BreadcrumbsLoaderComponent.java | 208 +- .../breadcrumbs/BreadcrumbsXmlWrapper.java | 650 +- .../XmlLanguageBreadcrumbsInfoProvider.java | 190 +- .../xml/util/XmlDuplicatedIdInspection.java | 256 +- .../XmlDocumentationProvider.java | 1078 +- 593 files changed, 190706 insertions(+), 190706 deletions(-) diff --git a/bin/nix/idea.sh b/bin/nix/idea.sh index 609cea5404ef..cf3b52de4e7e 100755 --- a/bin/nix/idea.sh +++ b/bin/nix/idea.sh @@ -1,111 +1,111 @@ -#!/bin/sh -# -# ------------------------------------------------------ -# IntelliJ IDEA Startup Script for Unix -# ------------------------------------------------------ -# - -# --------------------------------------------------------------------- -# Before you run IntelliJ IDEA specify the location of the -# JDK 1.6 installation directory which will be used for running IDEA -# --------------------------------------------------------------------- -if [ -z "$IDEA_JDK" ]; then - IDEA_JDK=$JDK_HOME - if [ -z "$IDEA_JDK" -a -e "$JAVA_HOME/lib/tools.jar" ]; then - IDEA_JDK=$JAVA_HOME - fi - if [ -z "$IDEA_JDK" ]; then - # Try to get the jdk path from java binary path - JAVA_BIN_PATH=`which java` - if [ -n "$JAVA_BIN_PATH" ]; then - JAVA_LOCATION=`readlink -f $JAVA_BIN_PATH | xargs dirname | xargs dirname | xargs dirname` - if [ -x "$JAVA_LOCATION/bin/java" -a -e "$JAVA_LOCATION/lib/tools.jar" ]; then - IDEA_JDK=$JAVA_LOCATION - fi - fi - fi - if [ -z "$IDEA_JDK" ]; then - echo ERROR: cannot start IntelliJ IDEA. - echo No JDK found to run IDEA. Please validate either IDEA_JDK, JDK_HOME or JAVA_HOME points to valid JDK installation. - echo - echo Press Enter to continue. - read IGNORE - exit 1 - fi -fi - -VERSION_LOG='/tmp/java.version.log' -$IDEA_JDK/bin/java -version 2> $VERSION_LOG -grep 'OpenJDK' $VERSION_LOG -OPEN_JDK=$? -grep '64-Bit' $VERSION_LOG -BITS=$? -rm $VERSION_LOG -if [ $OPEN_JDK -eq 0 ]; then - echo WARNING: You are launching IDE using OpenJDK Java runtime - echo - echo THIS IS STRICTLY UNSUPPORTED DUE TO KNOWN PERFORMANCE AND GRAPHICS PROBLEMS - echo - echo NOTE: If you have both Sun JDK and OpenJDK installed - echo please validate either IDEA_JDK or JDK_HOME points to valid Sun JDK installation - echo - echo Press Enter to continue. - read IGNORE -fi -if [ $BITS -eq 0 ]; then - BITS="64" -else - BITS="" -fi - -#-------------------------------------------------------------------------- -# Ensure the IDEA_HOME var for this script points to the -# home directory where IntelliJ IDEA is installed on your system. - -SCRIPT_LOCATION=$0 -# Step through symlinks to find where the script really is -while [ -L "$SCRIPT_LOCATION" ]; do - SCRIPT_LOCATION=`readlink -e "$SCRIPT_LOCATION"` -done - -IDEA_HOME=`dirname "$SCRIPT_LOCATION"`/.. -IDEA_BIN_HOME=`dirname "$SCRIPT_LOCATION"` - -export JAVA_HOME -export IDEA_HOME - -if [ -n "$IDEA_PROPERTIES" ]; then - IDEA_PROPERTIES_PROPERTY=-Didea.properties.file=$IDEA_PROPERTIES -fi - -if [ -z "$IDEA_MAIN_CLASS_NAME" ]; then - IDEA_MAIN_CLASS_NAME="com.intellij.idea.Main" -fi - -if [ -z "$IDEA_VM_OPTIONS" ]; then - IDEA_VM_OPTIONS="$IDEA_HOME/bin/idea.vmoptions" -fi - -REQUIRED_JVM_ARGS="-Xbootclasspath/a:../lib/boot.jar $IDEA_PROPERTIES_PROPERTY $REQUIRED_JVM_ARGS" -JVM_ARGS=`tr '\n' ' ' < "$IDEA_VM_OPTIONS"` -JVM_ARGS=`eval echo $JVM_ARGS $REQUIRED_JVM_ARGS` - -CLASSPATH=../lib/bootstrap.jar -CLASSPATH=$CLASSPATH:../lib/util.jar -CLASSPATH=$CLASSPATH:../lib/jdom.jar -CLASSPATH=$CLASSPATH:../lib/log4j.jar -CLASSPATH=$CLASSPATH:../lib/extensions.jar -CLASSPATH=$CLASSPATH:../lib/trove4j.jar -CLASSPATH=$CLASSPATH:$IDEA_JDK/lib/tools.jar -CLASSPATH=$CLASSPATH:$IDEA_CLASSPATH - -export CLASSPATH - -LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH -export LD_LIBRARY_PATH - -cd "$IDEA_BIN_HOME" -while true ; do - $IDEA_JDK/bin/java $JVM_ARGS -Djb.restart.code=88 $IDEA_MAIN_CLASS_NAME $* - test $? -ne 88 && break -done +#!/bin/sh +# +# ------------------------------------------------------ +# IntelliJ IDEA Startup Script for Unix +# ------------------------------------------------------ +# + +# --------------------------------------------------------------------- +# Before you run IntelliJ IDEA specify the location of the +# JDK 1.6 installation directory which will be used for running IDEA +# --------------------------------------------------------------------- +if [ -z "$IDEA_JDK" ]; then + IDEA_JDK=$JDK_HOME + if [ -z "$IDEA_JDK" -a -e "$JAVA_HOME/lib/tools.jar" ]; then + IDEA_JDK=$JAVA_HOME + fi + if [ -z "$IDEA_JDK" ]; then + # Try to get the jdk path from java binary path + JAVA_BIN_PATH=`which java` + if [ -n "$JAVA_BIN_PATH" ]; then + JAVA_LOCATION=`readlink -f $JAVA_BIN_PATH | xargs dirname | xargs dirname | xargs dirname` + if [ -x "$JAVA_LOCATION/bin/java" -a -e "$JAVA_LOCATION/lib/tools.jar" ]; then + IDEA_JDK=$JAVA_LOCATION + fi + fi + fi + if [ -z "$IDEA_JDK" ]; then + echo ERROR: cannot start IntelliJ IDEA. + echo No JDK found to run IDEA. Please validate either IDEA_JDK, JDK_HOME or JAVA_HOME points to valid JDK installation. + echo + echo Press Enter to continue. + read IGNORE + exit 1 + fi +fi + +VERSION_LOG='/tmp/java.version.log' +$IDEA_JDK/bin/java -version 2> $VERSION_LOG +grep 'OpenJDK' $VERSION_LOG +OPEN_JDK=$? +grep '64-Bit' $VERSION_LOG +BITS=$? +rm $VERSION_LOG +if [ $OPEN_JDK -eq 0 ]; then + echo WARNING: You are launching IDE using OpenJDK Java runtime + echo + echo THIS IS STRICTLY UNSUPPORTED DUE TO KNOWN PERFORMANCE AND GRAPHICS PROBLEMS + echo + echo NOTE: If you have both Sun JDK and OpenJDK installed + echo please validate either IDEA_JDK or JDK_HOME points to valid Sun JDK installation + echo + echo Press Enter to continue. + read IGNORE +fi +if [ $BITS -eq 0 ]; then + BITS="64" +else + BITS="" +fi + +#-------------------------------------------------------------------------- +# Ensure the IDEA_HOME var for this script points to the +# home directory where IntelliJ IDEA is installed on your system. + +SCRIPT_LOCATION=$0 +# Step through symlinks to find where the script really is +while [ -L "$SCRIPT_LOCATION" ]; do + SCRIPT_LOCATION=`readlink -e "$SCRIPT_LOCATION"` +done + +IDEA_HOME=`dirname "$SCRIPT_LOCATION"`/.. +IDEA_BIN_HOME=`dirname "$SCRIPT_LOCATION"` + +export JAVA_HOME +export IDEA_HOME + +if [ -n "$IDEA_PROPERTIES" ]; then + IDEA_PROPERTIES_PROPERTY=-Didea.properties.file=$IDEA_PROPERTIES +fi + +if [ -z "$IDEA_MAIN_CLASS_NAME" ]; then + IDEA_MAIN_CLASS_NAME="com.intellij.idea.Main" +fi + +if [ -z "$IDEA_VM_OPTIONS" ]; then + IDEA_VM_OPTIONS="$IDEA_HOME/bin/idea.vmoptions" +fi + +REQUIRED_JVM_ARGS="-Xbootclasspath/a:../lib/boot.jar $IDEA_PROPERTIES_PROPERTY $REQUIRED_JVM_ARGS" +JVM_ARGS=`tr '\n' ' ' < "$IDEA_VM_OPTIONS"` +JVM_ARGS=`eval echo $JVM_ARGS $REQUIRED_JVM_ARGS` + +CLASSPATH=../lib/bootstrap.jar +CLASSPATH=$CLASSPATH:../lib/util.jar +CLASSPATH=$CLASSPATH:../lib/jdom.jar +CLASSPATH=$CLASSPATH:../lib/log4j.jar +CLASSPATH=$CLASSPATH:../lib/extensions.jar +CLASSPATH=$CLASSPATH:../lib/trove4j.jar +CLASSPATH=$CLASSPATH:$IDEA_JDK/lib/tools.jar +CLASSPATH=$CLASSPATH:$IDEA_CLASSPATH + +export CLASSPATH + +LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH + +cd "$IDEA_BIN_HOME" +while true ; do + $IDEA_JDK/bin/java $JVM_ARGS -Djb.restart.code=88 $IDEA_MAIN_CLASS_NAME $* + test $? -ne 88 && break +done diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 9c8b32f5a8d8..6e6bd9102279 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -1,2572 +1,2572 @@ -/* - * 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. - */ - -/** - * @author: Eugene Zhuravlev - * Date: Jan 17, 2003 - * Time: 1:42:26 PM - */ -package com.intellij.compiler.impl; - -import com.intellij.CommonBundle; -import com.intellij.analysis.AnalysisScope; -import com.intellij.compiler.*; -import com.intellij.compiler.make.CacheCorruptedException; -import com.intellij.compiler.make.CacheUtils; -import com.intellij.compiler.make.DependencyCache; -import com.intellij.compiler.progress.CompilerTask; -import com.intellij.diagnostic.IdeErrorsDialog; -import com.intellij.diagnostic.PluginException; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.application.WriteAction; -import com.intellij.openapi.compiler.*; -import com.intellij.openapi.compiler.Compiler; -import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.compiler.ex.CompilerPathsEx; -import com.intellij.openapi.compiler.generic.GenericCompiler; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.module.LanguageLevelUtil; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.ex.ProjectRootManagerEx; -import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor; -import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.newvfs.ManagingFS; -import com.intellij.openapi.vfs.newvfs.RefreshQueue; -import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.packageDependencies.DependenciesBuilder; -import com.intellij.packageDependencies.ForwardDependenciesBuilder; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.PsiCompiledElement; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.util.*; -import com.intellij.util.concurrency.Semaphore; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.containers.MultiMap; -import com.intellij.util.containers.OrderedSet; -import gnu.trove.TIntHashSet; -import gnu.trove.TObjectHashingStrategy; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.io.*; -import java.util.*; - -public class CompileDriver { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver"); - // to be used in tests only for debug output - public static boolean ourDebugMode = false; - - private final Project myProject; - private final Map, Pair> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources] - private final String myCachesDirectoryPath; - private boolean myShouldClearOutputDirectory; - - private final Map myModuleOutputPaths = new HashMap(); - private final Map myModuleTestOutputPaths = new HashMap(); - - @NonNls private static final String VERSION_FILE_NAME = "version.dat"; - @NonNls private static final String LOCK_FILE_NAME = "in_progress.dat"; - - private static final boolean GENERATE_CLASSPATH_INDEX = "true".equals(System.getProperty("generate.classpath.index")); - private static final String PROP_PERFORM_INITIAL_REFRESH = "compiler.perform.outputs.refresh.on.start"; - private static final Key REFRESH_DONE_KEY = Key.create("_compiler.initial.refresh.done_"); - - private static final FileProcessingCompilerAdapterFactory FILE_PROCESSING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { - public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { - return new FileProcessingCompilerAdapter(context, compiler); - } - }; - private static final FileProcessingCompilerAdapterFactory FILE_PACKAGING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { - public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { - return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler); - } - }; - private CompilerFilter myCompilerFilter = CompilerFilter.ALL; - private static final CompilerFilter SOURCE_PROCESSING_ONLY = new CompilerFilter() { - public boolean acceptCompiler(Compiler compiler) { - return compiler instanceof SourceProcessingCompiler; - } - }; - private static final CompilerFilter ALL_EXCEPT_SOURCE_PROCESSING = new CompilerFilter() { - public boolean acceptCompiler(Compiler compiler) { - return !SOURCE_PROCESSING_ONLY.acceptCompiler(compiler); - } - }; - - private OutputPathFinder myOutputFinder; // need this for updating zip archives (experimental feature) - - private Set myAllOutputDirectories; - private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/; - - public CompileDriver(Project project) { - myProject = project; - myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar); - myShouldClearOutputDirectory = CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY; - - myGenerationCompilerModuleToOutputDirMap = new HashMap, Pair>(); - - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter); - final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); - final CompilerConfiguration config = CompilerConfiguration.getInstance(project); - for (Module module : allModules) { - for (IntermediateOutputCompiler compiler : generatingCompilers) { - final VirtualFile productionOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, false)); - final VirtualFile testOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, true)); - final Pair pair = new Pair(compiler, module); - final Pair outputs = new Pair(productionOutput, testOutput); - myGenerationCompilerModuleToOutputDirMap.put(pair, outputs); - } - if (config.isAnnotationProcessorsEnabled()) { - if (config.isAnnotationProcessingEnabled(module)) { - final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); - if (path != null) { - lookupVFile(lfs, path); // ensure the file is created and added to VFS - } - } - } - } - } - - public void setCompilerFilter(CompilerFilter compilerFilter) { - myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter; - } - - public void rebuild(CompileStatusNotification callback) { - doRebuild(callback, null, true, addAdditionalRoots(new ProjectCompileScope(myProject), ALL_EXCEPT_SOURCE_PROCESSING)); - } - - public void make(CompileScope scope, CompileStatusNotification callback) { - scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); - if (validateCompilerConfiguration(scope, false)) { - startup(scope, false, false, callback, null, true, false); - } - else { - callback.finished(true, 0, 0, DummyCompileContext.getInstance()); - } - } - - public boolean isUpToDate(CompileScope scope) { - if (LOG.isDebugEnabled()) { - LOG.debug("isUpToDate operation started"); - } - scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); - - final CompilerTask task = new CompilerTask(myProject, true, "", true); - final CompileContextImpl compileContext = new CompileContextImpl(myProject, task, scope, createDependencyCache(), true, false); - - checkCachesVersion(compileContext, ((PersistentFS)ManagingFS.getInstance()).getCreationTimestamp()); - if (compileContext.isRebuildRequested()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Rebuild requested, up-to-date=false"); - } - return false; - } - - for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { - final Pair outputs = entry.getValue(); - Module module = entry.getKey().getSecond(); - compileContext.assignModule(outputs.getFirst(), module, false); - compileContext.assignModule(outputs.getSecond(), module, true); - } - - final Ref status = new Ref(); - - task.start(new Runnable() { - public void run() { - try { - myAllOutputDirectories = getAllOutputDirectories(); - // need this for updating zip archives experiment, uncomment if the feature is turned on - //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); - status.set(doCompile(compileContext, false, false, false, true)); - } - finally { - compileContext.commitZipFiles(); // just to be on the safe side; normally should do nothing if called in isUpToDate() - } - } - }, null); - - if (LOG.isDebugEnabled()) { - LOG.debug("isUpToDate operation finished"); - } - - return ExitStatus.UP_TO_DATE.equals(status.get()); - } - - private DependencyCache createDependencyCache() { - return new DependencyCache(myCachesDirectoryPath + File.separator + ".dependency-info"); - } - - public void compile(CompileScope scope, CompileStatusNotification callback, boolean trackDependencies, boolean clearingOutputDirsPossible) { - myShouldClearOutputDirectory &= clearingOutputDirsPossible; - if (trackDependencies) { - scope = new TrackDependenciesScope(scope); - } - if (validateCompilerConfiguration(scope, false)) { - startup(scope, false, true, callback, null, true, trackDependencies); - } - else { - callback.finished(true, 0, 0, DummyCompileContext.getInstance()); - } - } - - private static class CompileStatus { - final int CACHE_FORMAT_VERSION; - final boolean COMPILATION_IN_PROGRESS; - final long VFS_CREATION_STAMP; - - private CompileStatus(int cacheVersion, boolean isCompilationInProgress, long vfsStamp) { - CACHE_FORMAT_VERSION = cacheVersion; - COMPILATION_IN_PROGRESS = isCompilationInProgress; - VFS_CREATION_STAMP = vfsStamp; - } - } - - private CompileStatus readStatus() { - final boolean isInProgress = getLockFile().exists(); - int version = -1; - long vfsStamp = -1L; - try { - final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); - DataInputStream in = new DataInputStream(new FileInputStream(versionFile)); - try { - version = in.readInt(); - try { - vfsStamp = in.readLong(); - } - catch (IOException ignored) { - } - } - finally { - in.close(); - } - } - catch (FileNotFoundException e) { - // ignore - } - catch (IOException e) { - LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly - return null; - } - return new CompileStatus(version, isInProgress, vfsStamp); - } - - private void writeStatus(CompileStatus status, CompileContext context) { - final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); - - final File lockFile = getLockFile(); - try { - FileUtil.createIfDoesntExist(statusFile); - DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile)); - try { - out.writeInt(status.CACHE_FORMAT_VERSION); - out.writeLong(status.VFS_CREATION_STAMP); - } - finally { - out.close(); - } - if (status.COMPILATION_IN_PROGRESS) { - FileUtil.createIfDoesntExist(lockFile); - } - else { - deleteFile(lockFile); - } - } - catch (IOException e) { - context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); - } - } - - private File getLockFile() { - return new File(CompilerPaths.getCompilerSystemDirectory(myProject), LOCK_FILE_NAME); - } - - private void doRebuild(CompileStatusNotification callback, - CompilerMessage message, - final boolean checkCachesVersion, - final CompileScope compileScope) { - if (validateCompilerConfiguration(compileScope, true)) { - startup(compileScope, true, false, callback, message, checkCachesVersion, false); - } - else { - callback.finished(true, 0, 0, DummyCompileContext.getInstance()); - } - } - - private CompileScope addAdditionalRoots(CompileScope originalScope, final CompilerFilter filter) { - CompileScope scope = attachIntermediateOutputDirectories(originalScope, filter); - - final AdditionalCompileScopeProvider[] scopeProviders = Extensions.getExtensions(AdditionalCompileScopeProvider.EXTENSION_POINT_NAME); - CompileScope baseScope = scope; - for (AdditionalCompileScopeProvider scopeProvider : scopeProviders) { - final CompileScope additionalScope = scopeProvider.getAdditionalScope(baseScope, filter, myProject); - if (additionalScope != null) { - scope = new CompositeScope(scope, additionalScope); - } - } - return scope; - } - - private CompileScope attachIntermediateOutputDirectories(CompileScope originalScope, CompilerFilter filter) { - CompileScope scope = originalScope; - final Set affected = new HashSet(Arrays.asList(originalScope.getAffectedModules())); - for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { - final Module module = entry.getKey().getSecond(); - if (affected.contains(module) && filter.acceptCompiler(entry.getKey().getFirst())) { - final Pair outputs = entry.getValue(); - scope = new CompositeScope(scope, new FileSetCompileScope(Arrays.asList(outputs.getFirst(), outputs.getSecond()), new Module[]{module})); - } - } - return scope; - } - - private void attachAnnotationProcessorsOutputDirectories(CompileContextEx context) { - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); - final Set affected = new HashSet(Arrays.asList(context.getCompileScope().getAffectedModules())); - for (Module module : affected) { - if (!config.isAnnotationProcessingEnabled(module)) { - continue; - } - final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); - if (path == null) { - continue; - } - final VirtualFile vFile = lfs.findFileByPath(path); - if (vFile == null) { - continue; - } - if (ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(vFile)) { - // no need to add, is already marked as source - continue; - } - context.addScope(new FileSetCompileScope(Collections.singletonList(vFile), new Module[]{module})); - context.assignModule(vFile, module, false); - } - } - - public static final Key COMPILATION_START_TIMESTAMP = Key.create("COMPILATION_START_TIMESTAMP"); - - private void startup(final CompileScope scope, - final boolean isRebuild, - final boolean forceCompile, - final CompileStatusNotification callback, - final CompilerMessage message, - final boolean checkCachesVersion, - final boolean trackDependencies) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final CompilerTask compileTask = new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, - forceCompile - ? CompilerBundle.message("compiler.content.name.compile") - : CompilerBundle.message("compiler.content.name.make"), ApplicationManager.getApplication().isUnitTestMode()); - final WindowManager windowManager = WindowManager.getInstance(); - if (windowManager != null) { - windowManager.getStatusBar(myProject).setInfo(""); - } - - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - FileDocumentManager.getInstance().saveAllDocuments(); - - final DependencyCache dependencyCache = createDependencyCache(); - final CompileContextImpl compileContext = - new CompileContextImpl(myProject, compileTask, scope, dependencyCache, !isRebuild && !forceCompile, isRebuild); - compileContext.putUserData(COMPILATION_START_TIMESTAMP, LocalTimeCounter.currentTime()); - for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { - final Pair outputs = entry.getValue(); - final Module module = entry.getKey().getSecond(); - compileContext.assignModule(outputs.getFirst(), module, false); - compileContext.assignModule(outputs.getSecond(), module, true); - } - attachAnnotationProcessorsOutputDirectories(compileContext); - - compileTask.start(new Runnable() { - public void run() { - if (compileContext.getProgressIndicator().isCanceled()) { - if (callback != null) { - callback.finished(true, 0, 0, compileContext); - } - return; - } - long start = System.currentTimeMillis(); - try { - if (myProject.isDisposed()) { - return; - } - LOG.info("COMPILATION STARTED"); - if (message != null) { - compileContext.addMessage(message); - } - TranslatingCompilerFilesMonitor.getInstance().ensureInitializationCompleted(myProject); - doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion, trackDependencies); - } - finally { - compileContext.commitZipFiles(); - final long finish = System.currentTimeMillis(); - CompilerUtil.logDuration( - "\tCOMPILATION FINISHED; Errors: " + - compileContext.getMessageCount(CompilerMessageCategory.ERROR) + - "; warnings: " + - compileContext.getMessageCount(CompilerMessageCategory.WARNING), - finish - start - ); - //if (LOG.isDebugEnabled()) { - // LOG.debug("COMPILATION FINISHED"); - //} - } - } - }, new Runnable() { - public void run() { - if (isRebuild) { - final int rv = Messages.showDialog( - myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild", - new String[]{"Make", "Rebuild"}, 0, Messages.getQuestionIcon() - ); - if (rv == 0 /*yes, please, do run make*/) { - startup(scope, false, false, callback, null, checkCachesVersion, trackDependencies); - return; - } - } - startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion, trackDependencies); - } - }); - } - - private void doCompile(final CompileContextImpl compileContext, - final boolean isRebuild, - final boolean forceCompile, - final CompileStatusNotification callback, - final boolean checkCachesVersion, - final boolean trackDependencies) { - ExitStatus status = ExitStatus.ERRORS; - boolean wereExceptions = false; - final long vfsTimestamp = ((PersistentFS)ManagingFS.getInstance()).getCreationTimestamp(); - try { - if (checkCachesVersion) { - checkCachesVersion(compileContext, vfsTimestamp); - if (compileContext.isRebuildRequested()) { - return; - } - } - writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, true, vfsTimestamp), compileContext); - if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - return; - } - - myAllOutputDirectories = getAllOutputDirectories(); - // need this for updating zip archives experiment, uncomment if the feature is turned on - //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); - status = doCompile(compileContext, isRebuild, forceCompile, trackDependencies, false); - } - catch (Throwable ex) { - if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(ex); - wereExceptions = true; - final PluginId pluginId = IdeErrorsDialog.findPluginId(ex); - - final StringBuffer message = new StringBuffer(); - message.append("Internal error"); - if (pluginId != null) { - message.append(" (Plugin: ").append(pluginId).append(")"); - } - message.append(": ").append(ex.getMessage()); - compileContext.addMessage(CompilerMessageCategory.ERROR, message.toString(), null, -1, -1); - - if (pluginId != null) { - throw new PluginException(ex, pluginId); - } - throw new RuntimeException(ex); - } - finally { - dropDependencyCache(compileContext); - final ExitStatus _status = status; - if (compileContext.isRebuildRequested()) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - doRebuild(callback, new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason(), - null, -1, -1, null), false, compileContext.getCompileScope()); - } - }, ModalityState.NON_MODAL); - } - else { - final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp(); - if (!myProject.isDisposed()) { - writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, wereExceptions, vfsTimestamp), compileContext); - } - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR); - final int warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING); - final String statusMessage = createStatusMessage(_status, warningCount, errorCount); - final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); - if (statusBar != null) { // because this code is in invoke later, the code may work for already closed project - // in case another project was opened in the frame while the compiler was working (See SCR# 28591) - StatusBar.Info.set(statusMessage, myProject); - if (duration > ONE_MINUTE_MS) { - final MessageType messageType = errorCount > 0 ? MessageType.ERROR : warningCount > 0 ? MessageType.WARNING : MessageType.INFO; - ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage); - } - } - if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) { - compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1); - } - if (callback != null) { - callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext); - } - } - }, ModalityState.NON_MODAL); - } - } - } - - private void checkCachesVersion(final CompileContextImpl compileContext, final long currentVFSTimestamp) { - final CompileStatus compileStatus = readStatus(); - if (compileStatus == null) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); - } - else if (compileStatus.CACHE_FORMAT_VERSION != -1 && - compileStatus.CACHE_FORMAT_VERSION != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.caches.old.format")); - } - else if (compileStatus.COMPILATION_IN_PROGRESS) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.previous.compilation.failed")); - } - else if (compileStatus.VFS_CREATION_STAMP >= 0L){ - if (currentVFSTimestamp != compileStatus.VFS_CREATION_STAMP) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.vfs.was.rebuilt")); - } - } - } - - private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount) { - if (status == ExitStatus.CANCELLED) { - return CompilerBundle.message("status.compilation.aborted"); - } - if (status == ExitStatus.UP_TO_DATE) { - return CompilerBundle.message("status.all.up.to.date"); - } - if (status == ExitStatus.SUCCESS) { - return warningCount > 0 - ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount) - : CompilerBundle.message("status.compilation.completed.successfully"); - } - return CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount); - } - - static class ExitStatus { - private final String myName; - - private ExitStatus(@NonNls String name) { - myName = name; - } - - public String toString() { - return myName; - } - - public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED"); - public static final ExitStatus ERRORS = new ExitStatus("ERRORS"); - public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS"); - public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE"); - } - - static class ExitException extends Exception { - private final ExitStatus myStatus; - - ExitException(ExitStatus status) { - myStatus = status; - } - - public ExitStatus getExitStatus() { - return myStatus; - } - } - - private ExitStatus doCompile(final CompileContextEx context, - boolean isRebuild, - final boolean forceCompile, - final boolean trackDependencies, final boolean onlyCheckStatus) { - try { - if (isRebuild) { - deleteAll(context); - } - else if (forceCompile) { - if (myShouldClearOutputDirectory) { - clearAffectedOutputPathsIfPossible(context); - } - } - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - if (LOG.isDebugEnabled()) { - logErrorMessages(context); - } - return ExitStatus.ERRORS; - } - - if (!onlyCheckStatus) { - if (!executeCompileTasks(context, true)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Compilation cancelled"); - } - return ExitStatus.CANCELLED; - } - } - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - if (LOG.isDebugEnabled()) { - logErrorMessages(context); - } - return ExitStatus.ERRORS; - } - - boolean needRecalcOutputDirs = false; - if (Registry.is(PROP_PERFORM_INITIAL_REFRESH) || !Boolean.valueOf(REFRESH_DONE_KEY.get(myProject, Boolean.FALSE))) { - REFRESH_DONE_KEY.set(myProject, Boolean.TRUE); - final long refreshStart = System.currentTimeMillis(); - - //need this to make sure the VFS is built - final List outputsToRefresh = new ArrayList(); - - final VirtualFile[] all = context.getAllOutputDirectories(); - - final ProgressIndicator progressIndicator = context.getProgressIndicator(); - - final int totalCount = all.length + myGenerationCompilerModuleToOutputDirMap.size() * 2; - progressIndicator.pushState(); - progressIndicator.setText("Inspecting output directories..."); - try { - for (VirtualFile output : all) { - if (output.isValid()) { - walkChildren(output, context); - } - else { - needRecalcOutputDirs = true; - final File file = new File(output.getPath()); - if (!file.exists()) { - final boolean created = file.mkdirs(); - if (!created) { - context.addMessage(CompilerMessageCategory.ERROR, "Failed to create output directory " + file.getPath(), null, 0, 0); - return ExitStatus.ERRORS; - } - } - output = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); - if (output == null) { - context.addMessage(CompilerMessageCategory.ERROR, "Failed to locate output directory " + file.getPath(), null, 0, 0); - return ExitStatus.ERRORS; - } - } - outputsToRefresh.add(output); - } - for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { - final Pair generated = myGenerationCompilerModuleToOutputDirMap.get(pair); - walkChildren(generated.getFirst(), context); - outputsToRefresh.add(generated.getFirst()); - walkChildren(generated.getSecond(), context); - outputsToRefresh.add(generated.getSecond()); - } - - RefreshQueue.getInstance().refresh(false, true, null, outputsToRefresh.toArray(new VirtualFile[outputsToRefresh.size()])); - if (progressIndicator.isCanceled()) { - return ExitStatus.CANCELLED; - } - } - finally { - progressIndicator.popState(); - } - - final long initialRefreshTime = System.currentTimeMillis() - refreshStart; - CompilerUtil.logDuration("Initial VFS refresh", initialRefreshTime); - } - - //DumbService.getInstance(myProject).waitForSmartMode(); - final Semaphore semaphore = new Semaphore(); - semaphore.down(); - DumbService.getInstance(myProject).runWhenSmart(new Runnable() { - public void run() { - semaphore.up(); - } - }); - while (!semaphore.waitFor(500)) { - if (context.getProgressIndicator().isCanceled()) { - return ExitStatus.CANCELLED; - } - } - - if (needRecalcOutputDirs) { - context.recalculateOutputDirs(); - } - - boolean didSomething = false; - - final CompilerManager compilerManager = CompilerManager.getInstance(myProject); - GenericCompilerRunner runner = new GenericCompilerRunner(context, myCompilerFilter, compilerManager, isRebuild, onlyCheckStatus); - try { - didSomething |= generateSources(compilerManager, context, forceCompile, onlyCheckStatus); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceProcessingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus); - - final CompileScope intermediateSources = attachIntermediateOutputDirectories(new CompositeScope(CompileScope.EMPTY_ARRAY) { - @NotNull - public Module[] getAffectedModules() { - return context.getCompileScope().getAffectedModules(); - } - }, SOURCE_PROCESSING_ONLY); - context.addScope(intermediateSources); - - didSomething |= translate(context, compilerManager, forceCompile, isRebuild, trackDependencies, onlyCheckStatus); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); - didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.CLASS_INSTRUMENTING); - - // explicitly passing forceCompile = false because in scopes that is narrower than ProjectScope it is impossible - // to understand whether the class to be processed is in scope or not. Otherwise compiler may process its items even if - // there were changes in completely independent files. - didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); - didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.CLASS_POST_PROCESSING); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class, - FILE_PACKAGING_COMPILER_ADAPTER_FACTORY, - isRebuild, false, onlyCheckStatus); - didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.PACKAGING); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, - forceCompile, true, onlyCheckStatus); - didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.VALIDATING); - } - catch (ExitException e) { - if (LOG.isDebugEnabled()) { - LOG.debug(e); - logErrorMessages(context); - } - return e.getExitStatus(); - } - finally { - // drop in case it has not been dropped yet. - dropDependencyCache(context); - - final VirtualFile[] allOutputDirs = context.getAllOutputDirectories(); - - if (didSomething && GENERATE_CLASSPATH_INDEX) { - CompilerUtil.runInContext(context, "Generating classpath index...", new ThrowableRunnable(){ - public void run() { - int count = 0; - for (VirtualFile file : allOutputDirs) { - context.getProgressIndicator().setFraction((double)++count / allOutputDirs.length); - createClasspathIndex(file); - } - } - }); - } - - } - - if (!onlyCheckStatus) { - if (!executeCompileTasks(context, false)) { - return ExitStatus.CANCELLED; - } - } - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - if (LOG.isDebugEnabled()) { - logErrorMessages(context); - } - return ExitStatus.ERRORS; - } - if (!didSomething) { - return ExitStatus.UP_TO_DATE; - } - return ExitStatus.SUCCESS; - } - catch (ProcessCanceledException e) { - return ExitStatus.CANCELLED; - } - } - - private void clearAffectedOutputPathsIfPossible(CompileContextEx context) { - final MultiMap outputToModulesMap = new MultiMap(); - for (Module module : ModuleManager.getInstance(myProject).getModules()) { - final CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(module); - if (compilerModuleExtension == null) { - continue; - } - final String outputPathUrl = compilerModuleExtension.getCompilerOutputUrl(); - if (outputPathUrl != null) { - final String path = VirtualFileManager.extractPath(outputPathUrl); - outputToModulesMap.putValue(new File(path), module); - } - - final String outputPathForTestsUrl = compilerModuleExtension.getCompilerOutputUrlForTests(); - if (outputPathForTestsUrl != null) { - final String path = VirtualFileManager.extractPath(outputPathForTestsUrl); - outputToModulesMap.putValue(new File(path), module); - } - } - final Set affectedModules = new HashSet(Arrays.asList(context.getCompileScope().getAffectedModules())); - final List scopeOutputs = new ArrayList(affectedModules.size() * 2); - for (File output : outputToModulesMap.keySet()) { - final Collection modules = outputToModulesMap.get(output); - boolean shouldInclude = true; - for (Module module : modules) { - if (!affectedModules.contains(module)) { - shouldInclude = false; - break; - } - } - if (shouldInclude) { - scopeOutputs.add(output); - } - } - if (scopeOutputs.size() > 0) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable() { - public void run() { - clearOutputDirectories(scopeOutputs); - } - }); - } - } - - private static void logErrorMessages(final CompileContext context) { - final CompilerMessage[] errors = context.getMessages(CompilerMessageCategory.ERROR); - if (errors.length > 0) { - LOG.debug("Errors reported: "); - for (CompilerMessage error : errors) { - LOG.debug("\t" + error.getMessage()); - } - } - } - - private static void walkChildren(VirtualFile from, final CompileContext context) { - final VirtualFile[] files = from.getChildren(); - if (files != null && files.length > 0) { - context.getProgressIndicator().checkCanceled(); - context.getProgressIndicator().setText2(from.getPresentableUrl()); - for (VirtualFile file : files) { - walkChildren(file, context); - } - } - } - - private static void createClasspathIndex(final VirtualFile file) { - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(new File(VfsUtil.virtualToIoFile(file), "classpath.index"))); - try { - writeIndex(writer, file, file); - } - finally { - writer.close(); - } - } - catch (IOException e) { - // Ignore. Failed to create optional classpath index - } - } - - private static void writeIndex(final BufferedWriter writer, final VirtualFile root, final VirtualFile file) throws IOException { - writer.write(VfsUtil.getRelativePath(file, root, '/')); - writer.write('\n'); - - for (VirtualFile child : file.getChildren()) { - writeIndex(writer, root, child); - } - } - - private static void dropDependencyCache(final CompileContextEx context) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.saving.caches"), new ThrowableRunnable(){ - public void run() { - context.getDependencyCache().resetState(); - } - }); - } - - private boolean generateSources(final CompilerManager compilerManager, - CompileContextEx context, - final boolean forceCompile, - final boolean onlyCheckStatus) throws ExitException { - boolean didSomething = false; - - final SourceGeneratingCompiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class, myCompilerFilter); - for (final SourceGeneratingCompiler sourceGenerator : sourceGenerators) { - if (context.getProgressIndicator().isCanceled()) { - throw new ExitException(ExitStatus.CANCELLED); - } - - final boolean generatedSomething = generateOutput(context, sourceGenerator, forceCompile, onlyCheckStatus); - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - throw new ExitException(ExitStatus.ERRORS); - } - didSomething |= generatedSomething; - } - return didSomething; - } - - private boolean translate(final CompileContextEx context, - final CompilerManager compilerManager, - final boolean forceCompile, - boolean isRebuild, - final boolean trackDependencies, final boolean onlyCheckStatus) throws ExitException { - - boolean didSomething = false; - - final TranslatingCompiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class, myCompilerFilter); - - - final List> sortedChunks = Collections.unmodifiableList(ApplicationManager.getApplication().runReadAction(new Computable>>() { - public List> compute() { - final ModuleManager moduleManager = ModuleManager.getInstance(myProject); - return ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(moduleManager.getModules())); - } - })); - - final DumbService dumbService = DumbService.getInstance(myProject); - try { - final Set processedModules = new HashSet(); - VirtualFile[] snapshot = null; - final Map, Collection> chunkMap = new HashMap, Collection>(); - int total = 0; - int processed = 0; - for (final Chunk currentChunk : sortedChunks) { - final TranslatorsOutputSink sink = new TranslatorsOutputSink(context, translators); - final Set generatedTypes = new HashSet(); - Collection chunkFiles = chunkMap.get(currentChunk); - final Set filesToRecompile = new HashSet(); - final Set allDependent = new HashSet(); - try { - int round = 0; - boolean compiledSomethingForThisChunk = false; - Collection dependentFiles = Collections.emptyList(); - final Function>, Pair>> dependencyFilter = new DependentClassesCumulativeFilter(); - - do { - for (int currentCompiler = 0, translatorsLength = translators.length; currentCompiler < translatorsLength; currentCompiler++) { - sink.setCurrentCompilerIndex(currentCompiler); - final TranslatingCompiler compiler = translators[currentCompiler]; - if (context.getProgressIndicator().isCanceled()) { - throw new ExitException(ExitStatus.CANCELLED); - } - - dumbService.waitForSmartMode(); - - if (round == 0) { - if (snapshot == null || ContainerUtil.intersects(generatedTypes, compilerManager.getRegisteredInputTypes(compiler))) { - // rescan snapshot if previously generated files may influence the input of this compiler - snapshot = ApplicationManager.getApplication().runReadAction(new Computable() { - public VirtualFile[] compute() { - return context.getCompileScope().getFiles(null, true); - } - }); - recalculateChunkToFilesMap(context, sortedChunks, snapshot, chunkMap); - chunkFiles = chunkMap.get(currentChunk); - total = snapshot.length * translatorsLength; - } - } - - final CompileContextEx _context; - if (compiler instanceof IntermediateOutputCompiler) { - // wrap compile context so that output goes into intermediate directories - final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler; - _context = new CompileContextExProxy(context) { - public VirtualFile getModuleOutputDirectory(final Module module) { - return getGenerationOutputDir(_compiler, module, false); - } - - public VirtualFile getModuleOutputDirectoryForTests(final Module module) { - return getGenerationOutputDir(_compiler, module, true); - } - }; - } - else { - _context = context; - } - final boolean compiledSomething = - compileSources(_context, currentChunk, compiler, chunkFiles, round == 0? forceCompile : true, isRebuild, trackDependencies, onlyCheckStatus, sink); - - processed += chunkFiles.size(); - _context.getProgressIndicator().setFraction(((double)processed) / total); - - if (compiledSomething) { - generatedTypes.addAll(compilerManager.getRegisteredOutputTypes(compiler)); - } - - if (_context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - throw new ExitException(ExitStatus.ERRORS); - } - - didSomething |= compiledSomething; - compiledSomethingForThisChunk |= didSomething; - } - - final boolean hasUnprocessedTraverseRoots = context.getDependencyCache().hasUnprocessedTraverseRoots(); - if (!isRebuild && (compiledSomethingForThisChunk || hasUnprocessedTraverseRoots)) { - final Set compiledWithSuccess; - final Set compiledWithErrors = CacheUtils.getFilesCompiledWithErrors(context); - if (compiledWithErrors.isEmpty()) { - compiledWithSuccess = sink.getCompiledSources(); - } - else { - compiledWithSuccess = new HashSet(); - compiledWithSuccess.addAll(sink.getCompiledSources()); - compiledWithSuccess.removeAll(compiledWithErrors); - } - filesToRecompile.removeAll(compiledWithSuccess); - filesToRecompile.addAll(compiledWithErrors); - - dependentFiles = CacheUtils.findDependentFiles(context, compiledWithSuccess, dependencyFilter); - if (!processedModules.isEmpty()) { - for (Iterator it = dependentFiles.iterator(); it.hasNext();) { - final VirtualFile next = it.next(); - final Module module = context.getModuleByFile(next); - if (module != null && processedModules.contains(module)) { - it.remove(); - } - } - } - - if (ourDebugMode) { - if (!dependentFiles.isEmpty()) { - for (VirtualFile dependentFile : dependentFiles) { - System.out.println("FOUND TO RECOMPILE: " + dependentFile.getPresentableUrl()); - } - } - else { - System.out.println("NO FILES TO RECOMPILE"); - } - } - - if (!dependentFiles.isEmpty()) { - filesToRecompile.addAll(dependentFiles); - allDependent.addAll(dependentFiles); - if (context.getProgressIndicator().isCanceled() || context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - break; - } - final List filesInScope = getFilesInScope(context, currentChunk, dependentFiles); - if (filesInScope.isEmpty()) { - break; - } - context.getDependencyCache().clearTraverseRoots(); - chunkFiles = filesInScope; - total += chunkFiles.size() * translators.length; - } - - didSomething |= (hasUnprocessedTraverseRoots != context.getDependencyCache().hasUnprocessedTraverseRoots()); - } - - round++; - } - while (!dependentFiles.isEmpty() && context.getMessageCount(CompilerMessageCategory.ERROR) == 0); - - if (CompilerConfiguration.MAKE_ENABLED) { - if (!context.getProgressIndicator().isCanceled()) { - // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch - final ProgressIndicator indicator = context.getProgressIndicator(); - final DependencyCache cache = context.getDependencyCache(); - - indicator.pushState(); - indicator.setText(CompilerBundle.message("progress.updating.caches")); - indicator.setText2(""); - - cache.update(); - - indicator.setText(CompilerBundle.message("progress.saving.caches")); - cache.resetState(); - processedModules.addAll(currentChunk.getNodes()); - indicator.popState(); - } - } - } - catch (CacheCorruptedException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - } - finally { - if (context.getMessageCount(CompilerMessageCategory.ERROR) != 0) { - filesToRecompile.addAll(allDependent); - } - if (filesToRecompile.size() > 0) { - sink.add(null, Collections.emptyList(), VfsUtil.toVirtualFileArray(filesToRecompile)); - } - if (context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - // perform update only if there were no errors, so it is guaranteed that the file was processd by all neccesary compilers - sink.flushPostponedItems(); - } - } - } - } - catch (ProcessCanceledException e) { - ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { - public void run() { - try { - final Collection deps = CacheUtils.findDependentFiles(context, Collections.emptySet(), null); - if (deps.size() > 0) { - TranslatingCompilerFilesMonitor.getInstance().update(context, null, Collections.emptyList(), - VfsUtil.toVirtualFileArray(deps)); - } - } - catch (IOException ignored) { - LOG.info(ignored); - } - catch (CacheCorruptedException ignored) { - LOG.info(ignored); - } - } - }); - throw e; - } - finally { - dropDependencyCache(context); - if (didSomething) { - TranslatingCompilerFilesMonitor.getInstance().updateOutputRootsLayout(myProject); - } - } - return didSomething; - } - - private static List getFilesInScope(final CompileContextEx context, final Chunk chunk, final Collection files) { - final List filesInScope = new ArrayList(files.size()); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (VirtualFile file : files) { - if (context.getCompileScope().belongs(file.getUrl())) { - final Module module = context.getModuleByFile(file); - if (chunk.getNodes().contains(module)) { - filesInScope.add(file); - } - } - } - } - }); - return filesInScope; - } - - private static void recalculateChunkToFilesMap(CompileContextEx context, List> allChunks, VirtualFile[] snapshot, Map, Collection> chunkMap) { - final Map> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(context, snapshot); - for (Chunk moduleChunk : allChunks) { - List files = Collections.emptyList(); - for (Module module : moduleChunk.getNodes()) { - final List moduleFiles = moduleToFilesMap.get(module); - if (moduleFiles != null) { - files = ContainerUtil.concat(files, moduleFiles); - } - } - chunkMap.put(moduleChunk, files); - } - } - - private interface FileProcessingCompilerAdapterFactory { - FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler); - } - - private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager, - CompileContextEx context, - Class fileProcessingCompilerClass, - FileProcessingCompilerAdapterFactory factory, - boolean forceCompile, - final boolean checkScope, - final boolean onlyCheckStatus) throws ExitException { - boolean didSomething = false; - final FileProcessingCompiler[] compilers = compilerManager.getCompilers(fileProcessingCompilerClass, myCompilerFilter); - if (compilers.length > 0) { - try { - CacheDeferredUpdater cacheUpdater = new CacheDeferredUpdater(); - try { - for (final FileProcessingCompiler compiler : compilers) { - if (context.getProgressIndicator().isCanceled()) { - throw new ExitException(ExitStatus.CANCELLED); - } - - CompileContextEx _context = context; - if (compiler instanceof IntermediateOutputCompiler) { - final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler; - _context = new CompileContextExProxy(context) { - public VirtualFile getModuleOutputDirectory(final Module module) { - return getGenerationOutputDir(_compiler, module, false); - } - - public VirtualFile getModuleOutputDirectoryForTests(final Module module) { - return getGenerationOutputDir(_compiler, module, true); - } - }; - } - - final boolean processedSomething = processFiles(factory.create(_context, compiler), forceCompile, checkScope, onlyCheckStatus, cacheUpdater); - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - throw new ExitException(ExitStatus.ERRORS); - } - - didSomething |= processedSomething; - } - } - finally { - cacheUpdater.doUpdate(); - } - } - catch (IOException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - throw new ExitException(ExitStatus.ERRORS); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (ExitException e) { - throw e; - } - catch (Exception e) { - context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); - LOG.error(e); - } - } - - return didSomething; - } - - private static Map> buildModuleToGenerationItemMap(GeneratingCompiler.GenerationItem[] items) { - final Map> map = new HashMap>(); - for (GeneratingCompiler.GenerationItem item : items) { - Module module = item.getModule(); - LOG.assertTrue(module != null); - Set itemSet = map.get(module); - if (itemSet == null) { - itemSet = new HashSet(); - map.put(module, itemSet); - } - itemSet.add(item); - } - return map; - } - - private void deleteAll(final CompileContextEx context) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable() { - public void run() { - final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); - final VirtualFile[] allSources = context.getProjectCompileScope().getFiles(null, true); - if (myShouldClearOutputDirectory) { - clearOutputDirectories(myAllOutputDirectories); - } - else { // refresh is still required - try { - for (final Compiler compiler : CompilerManager.getInstance(myProject).getCompilers(Compiler.class)) { - try { - if (compiler instanceof GeneratingCompiler) { - final StateCache cache = getGeneratingCompilerCache((GeneratingCompiler)compiler); - final Iterator urlIterator = cache.getUrlsIterator(); - while (urlIterator.hasNext()) { - context.getProgressIndicator().checkCanceled(); - deleteFile(new File(VirtualFileManager.extractPath(urlIterator.next()))); - } - } - else if (compiler instanceof TranslatingCompiler) { - final ArrayList> toDelete = new ArrayList>(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - TranslatingCompilerFilesMonitor.getInstance().collectFiles( - context, - (TranslatingCompiler)compiler, Arrays.asList(allSources).iterator(), - true /*pass true to make sure that every source in scope file is processed*/, - false /*important! should pass false to enable collection of files to delete*/, - new ArrayList(), - toDelete - ); - } - }); - for (Trinity trinity : toDelete) { - context.getProgressIndicator().checkCanceled(); - final File file = trinity.getFirst(); - deleteFile(file); - if (isTestMode) { - CompilerManagerImpl.addDeletedPath(file.getPath()); - } - } - } - } - catch (IOException e) { - LOG.info(e); - } - } - pruneEmptyDirectories(context.getProgressIndicator(), myAllOutputDirectories); // to avoid too much files deleted events - } - finally { - CompilerUtil.refreshIODirectories(myAllOutputDirectories); - } - } - dropScopesCaches(); - - clearCompilerSystemDirectory(context); - } - }); - } - - private void dropScopesCaches() { - // hack to be sure the classpath will include the output directories - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules(); - } - }); - } - - private static void pruneEmptyDirectories(ProgressIndicator progress, final Set directories) { - for (File directory : directories) { - doPrune(progress, directory, directories); - } - } - - private static boolean doPrune(ProgressIndicator progress, final File directory, final Set outPutDirectories) { - progress.checkCanceled(); - final File[] files = directory.listFiles(); - boolean isEmpty = true; - if (files != null) { - for (File file : files) { - if (!outPutDirectories.contains(file)) { - if (doPrune(progress, file, outPutDirectories)) { - deleteFile(file); - } - else { - isEmpty = false; - } - } - else { - isEmpty = false; - } - } - } - else { - isEmpty = false; - } - - return isEmpty; - } - - private Set getAllOutputDirectories() { - final Set outputDirs = new OrderedSet((TObjectHashingStrategy)TObjectHashingStrategy.CANONICAL); - final Module[] modules = ModuleManager.getInstance(myProject).getModules(); - for (final String path : CompilerPathsEx.getOutputPaths(modules)) { - outputDirs.add(new File(path)); - } - for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { - outputDirs.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false))); - outputDirs.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true))); - } - final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); - if (config.isAnnotationProcessorsEnabled()) { - for (Module module : modules) { - if (config.isAnnotationProcessingEnabled(module)) { - final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); - if (path != null) { - outputDirs.add(new File(path)); - } - } - } - } - return outputDirs; - } - - private static void clearOutputDirectories(final Collection outputDirectories) { - final long start = System.currentTimeMillis(); - // do not delete directories themselves, or we'll get rootsChanged() otherwise - final Collection filesToDelete = new ArrayList(outputDirectories.size() * 2); - for (File outputDirectory : outputDirectories) { - File[] files = outputDirectory.listFiles(); - if (files != null) { - ContainerUtil.addAll(filesToDelete, files); - } - } - if (filesToDelete.size() > 0) { - FileUtil.asyncDelete(filesToDelete); - - // ensure output directories exist - for (final File file : outputDirectories) { - file.mkdirs(); - } - final long clearStop = System.currentTimeMillis(); - - CompilerUtil.refreshIODirectories(outputDirectories); - - final long refreshStop = System.currentTimeMillis(); - - CompilerUtil.logDuration("Clearing output dirs", clearStop - start); - CompilerUtil.logDuration("Refreshing output directories", refreshStop - clearStop); - } - } - - private void clearCompilerSystemDirectory(final CompileContextEx context) { - CompilerCacheManager.getInstance(myProject).clearCaches(context); - FileUtil.delete(CompilerPathsEx.getZipStoreDirectory(myProject)); - dropDependencyCache(context); - - for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { - final File[] outputs = { - new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)), - new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true)) - }; - for (File output : outputs) { - final File[] files = output.listFiles(); - if (files != null) { - for (final File file : files) { - final boolean deleteOk = deleteFile(file); - if (!deleteOk) { - context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", file.getPath()), - null, -1, -1); - } - } - } - } - } - } - - /** - * @param file a file to delete - * @return true if and only if the file existed and was successfully deleted - * Note: the behaviour is different from FileUtil.delete() which returns true if the file absent on the disk - */ - private static boolean deleteFile(final File file) { - File[] files = file.listFiles(); - if (files != null) { - for (File file1 : files) { - deleteFile(file1); - } - } - - for (int i = 0; i < 10; i++){ - if (file.delete()) { - return true; - } - if (!file.exists()) { - return false; - } - try { - Thread.sleep(50); - } - catch (InterruptedException ignored) { - } - } - return false; - } - - private VirtualFile getGenerationOutputDir(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { - final Pair outputs = - myGenerationCompilerModuleToOutputDirMap.get(new Pair(compiler, module)); - return forTestSources? outputs.getSecond() : outputs.getFirst(); - } - - private boolean generateOutput(final CompileContextEx context, - final GeneratingCompiler compiler, - final boolean forceGenerate, - final boolean onlyCheckStatus) throws ExitException { - final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context); - final List toGenerate = new ArrayList(); - final List filesToRefresh = new ArrayList(); - final List generatedFiles = new ArrayList(); - final List affectedModules = new ArrayList(); - try { - final StateCache cache = getGeneratingCompilerCache(compiler); - final Set pathsToRemove = new HashSet(cache.getUrls()); - - final Map itemToOutputPathMap = new HashMap(); - final IOException[] ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (final GeneratingCompiler.GenerationItem item : allItems) { - final Module itemModule = item.getModule(); - final String outputDirPath = CompilerPaths.getGenerationOutputPath(compiler, itemModule, item.isTestSource()); - final String outputPath = outputDirPath + "/" + item.getPath(); - itemToOutputPathMap.put(item, outputPath); - - try { - final ValidityState savedState = cache.getState(outputPath); - - if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) { - final String outputPathUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, outputPath); - if (context.getCompileScope().belongs(outputPathUrl)) { - toGenerate.add(item); - } - else { - pathsToRemove.remove(outputPath); - } - } - else { - pathsToRemove.remove(outputPath); - } - } - catch (IOException e) { - ex[0] = e; - } - } - } - }); - if (ex[0] != null) { - throw ex[0]; - } - - if (onlyCheckStatus) { - if (toGenerate.isEmpty() && pathsToRemove.isEmpty()) { - return false; - } - if (LOG.isDebugEnabled()) { - if (!toGenerate.isEmpty()) { - LOG.debug("Found items to generate, compiler " + compiler.getDescription()); - } - if (!pathsToRemove.isEmpty()) { - LOG.debug("Found paths to remove, compiler " + compiler.getDescription()); - } - } - throw new ExitException(ExitStatus.CANCELLED); - } - - if (!pathsToRemove.isEmpty()) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ - public void run() throws IOException { - for (final String path : pathsToRemove) { - final File file = new File(path); - final boolean deleted = deleteFile(file); - if (deleted) { - cache.remove(path); - filesToRefresh.add(file); - } - } - } - }); - } - - final Map> moduleToItemMap = - buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()])); - List modules = new ArrayList(moduleToItemMap.size()); - for (final Module module : moduleToItemMap.keySet()) { - modules.add(module); - } - ModuleCompilerUtil.sortModules(myProject, modules); - - for (final Module module : modules) { - CompilerUtil.runInContext(context, "Generating output from "+compiler.getDescription(),new ThrowableRunnable(){ - public void run() throws IOException { - final Set items = moduleToItemMap.get(module); - if (items != null && !items.isEmpty()) { - final GeneratingCompiler.GenerationItem[][] productionAndTestItems = splitGenerationItems(items); - for (GeneratingCompiler.GenerationItem[] _items : productionAndTestItems) { - if (_items.length == 0) continue; - final VirtualFile outputDir = getGenerationOutputDir(compiler, module, _items[0].isTestSource()); - final GeneratingCompiler.GenerationItem[] successfullyGenerated = compiler.generate(context, _items, outputDir); - - CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { - public void run() throws IOException { - if (successfullyGenerated.length > 0) { - affectedModules.add(module); - } - for (final GeneratingCompiler.GenerationItem item : successfullyGenerated) { - final String fullOutputPath = itemToOutputPathMap.get(item); - cache.update(fullOutputPath, item.getValidityState()); - final File file = new File(fullOutputPath); - filesToRefresh.add(file); - generatedFiles.add(file); - context.getProgressIndicator().setText2(file.getPath()); - } - } - }); - } - } - } - }); - } - } - catch (IOException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - throw new ExitException(ExitStatus.ERRORS); - } - finally { - CompilerUtil.refreshIOFiles(filesToRefresh); - if (!generatedFiles.isEmpty()) { - DumbService.getInstance(myProject).waitForSmartMode(); - List vFiles = ApplicationManager.getApplication().runReadAction(new Computable>() { - public List compute() { - final ArrayList vFiles = new ArrayList(generatedFiles.size()); - for (File generatedFile : generatedFiles) { - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(generatedFile); - if (vFile != null) { - vFiles.add(vFile); - } - } - return vFiles; - } - }); - if (forceGenerate) { - context.addScope(new FileSetCompileScope(vFiles, affectedModules.toArray(new Module[affectedModules.size()]))); - } - context.markGenerated(vFiles); - } - } - return !toGenerate.isEmpty() || !filesToRefresh.isEmpty(); - } - - private static GeneratingCompiler.GenerationItem[][] splitGenerationItems(final Set items) { - final List production = new ArrayList(); - final List tests = new ArrayList(); - for (GeneratingCompiler.GenerationItem item : items) { - if (item.isTestSource()) { - tests.add(item); - } - else { - production.add(item); - } - } - return new GeneratingCompiler.GenerationItem[][]{ - production.toArray(new GeneratingCompiler.GenerationItem[production.size()]), - tests.toArray(new GeneratingCompiler.GenerationItem[tests.size()]) - }; - } - - private boolean compileSources(final CompileContextEx context, final Chunk moduleChunk, final TranslatingCompiler compiler, final Collection srcSnapshot, - final boolean forceCompile, - final boolean isRebuild, - final boolean trackDependencies, - final boolean onlyCheckStatus, - TranslatingCompiler.OutputSink sink) throws ExitException { - - final Set toCompile = new HashSet(); - final List> toDelete = new ArrayList>(); - context.getProgressIndicator().pushState(); - - final boolean[] wereFilesDeleted = {false}; - try { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - - TranslatingCompilerFilesMonitor.getInstance().collectFiles( - context, compiler, srcSnapshot.iterator(), forceCompile, isRebuild, toCompile, toDelete - ); - if (trackDependencies && !toCompile.isEmpty()) { // should add dependent files - // todo: drop this? - final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); - final PsiManager psiManager = PsiManager.getInstance(myProject); - for (final VirtualFile file : VfsUtil.toVirtualFileArray(toCompile)) { - if (fileTypeManager.getFileTypeByFile(file) == StdFileTypes.JAVA) { - final PsiFile psiFile = psiManager.findFile(file); - if (psiFile != null) { - addDependentFiles(psiFile, toCompile, compiler, context); - } - } - } - } - } - }); - - if (onlyCheckStatus) { - if (toDelete.isEmpty() && toCompile.isEmpty()) { - return false; - } - if (LOG.isDebugEnabled() || ourDebugMode) { - if (!toDelete.isEmpty()) { - final String message = "Found items to delete, compiler " + compiler.getDescription(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - if (!toCompile.isEmpty()) { - final String message = "Found items to compile, compiler " + compiler.getDescription(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - } - throw new ExitException(ExitStatus.CANCELLED); - } - - if (!toDelete.isEmpty()) { - try { - wereFilesDeleted[0] = syncOutputDir(context, toDelete); - } - catch (CacheCorruptedException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - } - } - - if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - compiler.compile(context, moduleChunk, VfsUtil.toVirtualFileArray(toCompile), sink); - } - } - finally { - context.getProgressIndicator().popState(); - } - return !toCompile.isEmpty() || wereFilesDeleted[0]; - } - - private static boolean syncOutputDir(final CompileContextEx context, final Collection> toDelete) throws CacheCorruptedException { - final DependencyCache dependencyCache = context.getDependencyCache(); - final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); - - final List filesToRefresh = new ArrayList(); - final boolean[] wereFilesDeleted = {false}; - CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ - public void run() throws CacheCorruptedException { - final long start = System.currentTimeMillis(); - try { - for (final Trinity trinity : toDelete) { - final File outputPath = trinity.getFirst(); - context.getProgressIndicator().checkCanceled(); - context.getProgressIndicator().setText2(outputPath.getPath()); - filesToRefresh.add(outputPath); - if (isTestMode) { - LOG.assertTrue(outputPath.exists()); - } - if (!deleteFile(outputPath)) { - if (isTestMode && outputPath.exists()) { - LOG.error("Was not able to delete output file: " + outputPath.getPath()); - } - continue; - } - wereFilesDeleted[0] = true; - - // update zip here - //final String outputDir = myOutputFinder.lookupOutputPath(outputPath); - //if (outputDir != null) { - // try { - // context.updateZippedOuput(outputDir, FileUtil.toSystemIndependentName(outputPath.getPath()).substring(outputDir.length() + 1)); - // } - // catch (IOException e) { - // LOG.info(e); - // } - //} - - final String className = trinity.getSecond(); - if (className != null) { - final int id = dependencyCache.getSymbolTable().getId(className); - dependencyCache.addTraverseRoot(id); - final boolean sourcePresent = trinity.getThird().booleanValue(); - if (!sourcePresent) { - dependencyCache.markSourceRemoved(id); - } - } - if (isTestMode) { - CompilerManagerImpl.addDeletedPath(outputPath.getPath()); - } - } - } - finally { - CompilerUtil.logDuration("Sync output directory", System.currentTimeMillis() - start); - CompilerUtil.refreshIOFiles(filesToRefresh); - } - } - }); - return wereFilesDeleted[0]; - } - - private void addDependentFiles(final PsiFile psiFile, Set toCompile, TranslatingCompiler compiler, CompileContext context) { - final DependenciesBuilder builder = new ForwardDependenciesBuilder(myProject, new AnalysisScope(psiFile)); - builder.analyze(); - final Map> dependencies = builder.getDependencies(); - final Set dependentFiles = dependencies.get(psiFile); - if (dependentFiles != null && !dependentFiles.isEmpty()) { - final TranslatingCompilerFilesMonitor monitor = TranslatingCompilerFilesMonitor.getInstance(); - for (final PsiFile dependentFile : dependentFiles) { - if (dependentFile instanceof PsiCompiledElement) { - continue; - } - final VirtualFile vFile = dependentFile.getVirtualFile(); - if (vFile == null || toCompile.contains(vFile)) { - continue; - } - if (!compiler.isCompilableFile(vFile, context)) { - continue; - } - if (!monitor.isMarkedForCompilation(myProject, vFile)) { - continue; // no need to compile since already compiled - } - toCompile.add(vFile); - addDependentFiles(dependentFile, toCompile, compiler, context); - } - } - } - - // [mike] performance optimization - this method is accessed > 15,000 times in Aurora - private String getModuleOutputPath(final Module module, boolean inTestSourceContent) { - final Map map = inTestSourceContent ? myModuleTestOutputPaths : myModuleOutputPaths; - String path = map.get(module); - if (path == null) { - path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent); - map.put(module, path); - } - - return path; - } - - private boolean processFiles(final FileProcessingCompilerAdapter adapter, - final boolean forceCompile, - final boolean checkScope, - final boolean onlyCheckStatus, final CacheDeferredUpdater cacheUpdater) throws ExitException, IOException { - final CompileContextEx context = (CompileContextEx)adapter.getCompileContext(); - final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler()); - final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems(); - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - return false; - } - if (LOG.isDebugEnabled() && items.length > 0) { - LOG.debug("Start processing files by " + adapter.getCompiler().getDescription()); - } - final CompileScope scope = context.getCompileScope(); - final List toProcess = new ArrayList(); - final Set allUrls = new HashSet(); - final IOException[] ex = {null}; - DumbService.getInstance(myProject).waitForSmartMode(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - for (FileProcessingCompiler.ProcessingItem item : items) { - final VirtualFile file = item.getFile(); - if (file == null) { - LOG.error("FileProcessingCompiler.ProcessingItem.getFile() must not return null: compiler " + adapter.getCompiler().getDescription()); - continue; - } - final String url = file.getUrl(); - allUrls.add(url); - if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) { - final ValidityState state = cache.getExtState(url); - final ValidityState itemState = item.getValidityState(); - if (state != null ? state.equalsTo(itemState) : itemState == null) { - continue; - } - } - if (LOG.isDebugEnabled()) { - LOG.debug("Adding item to process: " + url + "; saved ts= " + cache.getTimestamp(url) + "; VFS ts=" + file.getTimeStamp()); - } - toProcess.add(item); - } - } - catch (IOException e) { - ex[0] = e; - } - } - }); - - if (ex[0] != null) { - throw ex[0]; - } - - final Collection urls = cache.getUrls(); - final List urlsToRemove = new ArrayList(); - if (!urls.isEmpty()) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.processing.outdated.files"), new ThrowableRunnable(){ - public void run() throws IOException { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (final String url : urls) { - if (!allUrls.contains(url)) { - if (!checkScope || scope.belongs(url)) { - urlsToRemove.add(url); - } - } - } - } - }); - if (!onlyCheckStatus && !urlsToRemove.isEmpty()) { - for (final String url : urlsToRemove) { - adapter.processOutdatedItem(context, url, cache.getExtState(url)); - cache.remove(url); - } - } - } - }); - } - - if (onlyCheckStatus) { - if (urlsToRemove.isEmpty() && toProcess.isEmpty()) { - return false; - } - if (LOG.isDebugEnabled()) { - if (!urlsToRemove.isEmpty()) { - LOG.debug("Found urls to remove, compiler " + adapter.getCompiler().getDescription()); - for (String url : urlsToRemove) { - LOG.debug("\t" + url); - } - } - if (!toProcess.isEmpty()) { - LOG.debug("Found items to compile, compiler " + adapter.getCompiler().getDescription()); - for (FileProcessingCompiler.ProcessingItem item : toProcess) { - LOG.debug("\t" + item.getFile().getPresentableUrl()); - } - } - } - throw new ExitException(ExitStatus.CANCELLED); - } - - if (toProcess.isEmpty()) { - return false; - } - - final FileProcessingCompiler.ProcessingItem[] processed = - adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()])); - - if (processed.length == 0) { - return true; - } - CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { - public void run() { - final List vFiles = new ArrayList(processed.length); - for (FileProcessingCompiler.ProcessingItem aProcessed : processed) { - final VirtualFile file = aProcessed.getFile(); - vFiles.add(file); - if (LOG.isDebugEnabled()) { - LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); - } - - //final String path = file.getPath(); - //final String outputDir = myOutputFinder.lookupOutputPath(path); - //if (outputDir != null) { - // context.updateZippedOuput(outputDir, path.substring(outputDir.length() + 1)); - //} - } - LocalFileSystem.getInstance().refreshFiles(vFiles); - if (LOG.isDebugEnabled()) { - LOG.debug("Files after VFS refresh:"); - for (VirtualFile file : vFiles) { - LOG.debug("\t" + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); - } - } - for (FileProcessingCompiler.ProcessingItem item : processed) { - cacheUpdater.addFileForUpdate(item, cache); - } - } - }); - return true; - } - - private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) throws IOException { - return CompilerCacheManager.getInstance(myProject).getFileProcessingCompilerCache(compiler); - } - - private StateCache getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException { - return CompilerCacheManager.getInstance(myProject).getGeneratingCompilerCache(compiler); - } - - public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) { - final CompilerTask progressManagerTask = - new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, contentName, false); - final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, null, false, false); - - FileDocumentManager.getInstance().saveAllDocuments(); - - progressManagerTask.start(new Runnable() { - public void run() { - try { - task.execute(compileContext); - } - catch (ProcessCanceledException ex) { - // suppressed - } - finally { - compileContext.commitZipFiles(); - if (onTaskFinished != null) { - onTaskFinished.run(); - } - } - } - }, null); - } - - private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) { - final CompilerManager manager = CompilerManager.getInstance(myProject); - final ProgressIndicator progressIndicator = context.getProgressIndicator(); - progressIndicator.pushState(); - try { - CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks(); - if (tasks.length > 0) { - progressIndicator.setText(beforeTasks - ? CompilerBundle.message("progress.executing.precompile.tasks") - : CompilerBundle.message("progress.executing.postcompile.tasks")); - for (CompileTask task : tasks) { - if (!task.execute(context)) { - return false; - } - } - } - } - finally { - progressIndicator.popState(); - WindowManager.getInstance().getStatusBar(myProject).setInfo(""); - if (progressIndicator instanceof CompilerTask) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - ((CompilerTask)progressIndicator).showCompilerContent(); - } - }); - } - } - return true; - } - - private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) { - final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/; - final List modulesWithoutOutputPathSpecified = new ArrayList(); - boolean isProjectCompilePathSpecified = true; - final List modulesWithoutJdkAssigned = new ArrayList(); - final Set nonExistingOutputPaths = new HashSet(); - final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); - - for (final Module module : scopeModules) { - final boolean hasSources = hasSources(module, false); - final boolean hasTestSources = hasSources(module, true); - if (!hasSources && !hasTestSources) { - // If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333) - // todo still there may be problems with this approach if some generated files are attributed by this module - continue; - } - final Sdk jdk = ModuleRootManager.getInstance(module).getSdk(); - if (jdk == null) { - modulesWithoutJdkAssigned.add(module.getName()); - } - final String outputPath = getModuleOutputPath(module, false); - final String testsOutputPath = getModuleOutputPath(module, true); - if (outputPath == null && testsOutputPath == null) { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - else { - if (outputPath != null) { - final File file = new File(outputPath.replace('/', File.separatorChar)); - if (!file.exists()) { - nonExistingOutputPaths.add(file); - } - } - else { - if (hasSources) { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - } - if (testsOutputPath != null) { - final File f = new File(testsOutputPath.replace('/', File.separatorChar)); - if (!f.exists()) { - nonExistingOutputPaths.add(f); - } - } - else { - if (hasTestSources) { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - } - if (config.isAnnotationProcessorsEnabled() && config.isAnnotationProcessingEnabled(module)) { - final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); - if (path == null) { - final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(module.getProject()); - if (extension == null || extension.getCompilerOutputUrl() == null) { - isProjectCompilePathSpecified = false; - } - else { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - } - else { - final File file = new File(path); - if (!file.exists()) { - nonExistingOutputPaths.add(file); - } - } - } - } - } - if (!modulesWithoutJdkAssigned.isEmpty()) { - showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title")); - return false; - } - - if (!isProjectCompilePathSpecified) { - final String message = CompilerBundle.message("error.project.output.not.specified"); - if (ApplicationManager.getApplication().isUnitTestMode()) { - LOG.error(message); - } - - Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - ProjectSettingsService.getInstance(myProject).openProjectSettings(); - return false; - } - - if (!modulesWithoutOutputPathSpecified.isEmpty()) { - showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME); - return false; - } - - if (!nonExistingOutputPaths.isEmpty()) { - for (File file : nonExistingOutputPaths) { - final boolean succeeded = file.mkdirs(); - if (!succeeded) { - if (file.exists()) { - // for overlapping paths, this one might have been created as an intermediate path on a previous iteration - continue; - } - Messages.showMessageDialog(myProject, CompilerBundle.message("error.failed.to.create.directory", file.getPath()), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - return false; - } - } - final Boolean refreshSuccess = - new WriteAction() { - @Override - protected void run(Result result) throws Throwable { - LocalFileSystem.getInstance().refreshIoFiles(nonExistingOutputPaths); - Boolean res = Boolean.TRUE; - for (File file : nonExistingOutputPaths) { - if (LocalFileSystem.getInstance().findFileByIoFile(file) == null) { - res = Boolean.FALSE; - break; - } - } - result.setResult(res); - } - }.execute().getResultObject(); - - if (!refreshSuccess.booleanValue()) { - return false; - } - dropScopesCaches(); - } - - if (checkOutputAndSourceIntersection) { - if (myShouldClearOutputDirectory) { - if (!validateOutputAndSourcePathsIntersection()) { - return false; - } - } - } - final List> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules)); - for (final Chunk chunk : chunks) { - final Set chunkModules = chunk.getNodes(); - if (chunkModules.size() <= 1) { - continue; // no need to check one-module chunks - } - if (config.isAnnotationProcessorsEnabled()) { - for (Module chunkModule : chunkModules) { - if (config.isAnnotationProcessingEnabled(chunkModule)) { - showCyclesNotSupportedForAnnotationProcessors(chunkModules.toArray(new Module[chunkModules.size()])); - return false; - } - } - } - Sdk jdk = null; - LanguageLevel languageLevel = null; - for (final Module module : chunkModules) { - final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk(); - if (jdk == null) { - jdk = moduleJdk; - } - else { - if (!jdk.equals(moduleJdk)) { - showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()])); - return false; - } - } - - LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); - if (languageLevel == null) { - languageLevel = moduleLanguageLevel; - } - else { - if (!languageLevel.equals(moduleLanguageLevel)) { - showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()])); - return false; - } - } - } - } - final Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class); - for (Compiler compiler : allCompilers) { - if (!compiler.validateConfiguration(scope)) { - return false; - } - } - return true; - } - - private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) { - LOG.assertTrue(modulesInChunk.length > 0); - String moduleNameToSelect = modulesInChunk[0].getName(); - final String moduleNames = getModulesString(modulesInChunk); - Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(moduleNameToSelect, null); - } - - private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) { - LOG.assertTrue(modulesInChunk.length > 0); - String moduleNameToSelect = modulesInChunk[0].getName(); - final String moduleNames = getModulesString(modulesInChunk); - Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(moduleNameToSelect, null); - } - - private void showCyclesNotSupportedForAnnotationProcessors(Module[] modulesInChunk) { - LOG.assertTrue(modulesInChunk.length > 0); - String moduleNameToSelect = modulesInChunk[0].getName(); - final String moduleNames = getModulesString(modulesInChunk); - Messages.showMessageDialog(myProject, CompilerBundle.message("error.annotation.processing.not.supported.for.module.cycles", moduleNames), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(moduleNameToSelect, null); - } - - private static String getModulesString(Module[] modulesInChunk) { - final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc(); - try { - for (Module module : modulesInChunk) { - if (moduleNames.length() > 0) { - moduleNames.append("\n"); - } - moduleNames.append("\"").append(module.getName()).append("\""); - } - return moduleNames.toString(); - } - finally { - StringBuilderSpinAllocator.dispose(moduleNames); - } - } - - private static boolean hasSources(Module module, boolean checkTestSources) { - final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); - for (final ContentEntry contentEntry : contentEntries) { - final SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); - for (final SourceFolder sourceFolder : sourceFolders) { - if (sourceFolder.getFile() == null) { - continue; // skip invalid source folders - } - if (checkTestSources) { - if (sourceFolder.isTestSource()) { - return true; - } - } - else { - if (!sourceFolder.isTestSource()) { - return true; - } - } - } - } - return false; - } - - private void showNotSpecifiedError(@NonNls final String resourceId, List modules, String tabNameToSelect) { - String nameToSelect = null; - final StringBuilder names = StringBuilderSpinAllocator.alloc(); - final String message; - try { - final int maxModulesToShow = 10; - for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) { - if (nameToSelect == null) { - nameToSelect = name; - } - if (names.length() > 0) { - names.append(",\n"); - } - names.append("\""); - names.append(name); - names.append("\""); - } - if (modules.size() > maxModulesToShow) { - names.append(",\n..."); - } - message = CompilerBundle.message(resourceId, modules.size(), names.toString()); - } - finally { - StringBuilderSpinAllocator.dispose(names); - } - - if (ApplicationManager.getApplication().isUnitTestMode()) { - LOG.error(message); - } - - Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(nameToSelect, tabNameToSelect); - } - - private boolean validateOutputAndSourcePathsIntersection() { - final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); - final VirtualFile[] outputPaths = CompilerPathsEx.getOutputDirectories(allModules); - final Set affectedOutputPaths = new HashSet(); - for (Module allModule : allModules) { - final ModuleRootManager rootManager = ModuleRootManager.getInstance(allModule); - final VirtualFile[] sourceRoots = rootManager.getSourceRoots(); - for (final VirtualFile outputPath : outputPaths) { - for (VirtualFile sourceRoot : sourceRoots) { - if (VfsUtil.isAncestor(outputPath, sourceRoot, true) || VfsUtil.isAncestor(sourceRoot, outputPath, false)) { - affectedOutputPaths.add(outputPath); - } - } - } - } - if (!affectedOutputPaths.isEmpty()) { - final StringBuilder paths = new StringBuilder(); - for (final VirtualFile affectedOutputPath : affectedOutputPaths) { - if (paths.length() < 0) { - paths.append("\n"); - } - paths.append(affectedOutputPath.getPath().replace('/', File.separatorChar)); - } - final int answer = Messages.showOkCancelDialog(myProject, - CompilerBundle.message("warning.sources.under.output.paths", paths.toString()), - CommonBundle.getErrorTitle(), Messages.getWarningIcon()); - if (answer == 0) { // ok - myShouldClearOutputDirectory = false; - return true; - } - else { - return false; - } - } - return true; - } - - private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) { - ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect, false); - } - - private static VirtualFile lookupVFile(final LocalFileSystem lfs, final String path) { - final File file = new File(path); - - VirtualFile vFile = lfs.findFileByIoFile(file); - if (vFile != null) { - return vFile; - } - - final boolean justCreated = file.mkdirs(); - vFile = lfs.refreshAndFindFileByIoFile(file); - - assert vFile != null: "Virtual file not found for " + file.getPath() + "; mkdirs() exit code is " + justCreated; - - return vFile; - } - - private static class CacheDeferredUpdater { - private final Map>> myData = new java.util.HashMap>>(); - - public void addFileForUpdate(final FileProcessingCompiler.ProcessingItem item, FileProcessingCompilerStateCache cache) { - final VirtualFile file = item.getFile(); - List> list = myData.get(file); - if (list == null) { - list = new ArrayList>(); - myData.put(file, list); - } - list.add(new Pair(cache, item)); - } - - public void doUpdate() throws IOException{ - final IOException[] ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - for (Map.Entry>> entry : myData.entrySet()) { - for (Pair pair : entry.getValue()) { - final FileProcessingCompiler.ProcessingItem item = pair.getSecond(); - pair.getFirst().update(entry.getKey(), item.getValidityState()); - } - } - } - catch (IOException e) { - ex[0] = e; - } - } - }); - if (ex[0] != null) { - throw ex[0]; - } - } - } - - private static class TranslatorsOutputSink implements TranslatingCompiler.OutputSink { - final Map> myPostponedItems = new HashMap>(); - private final CompileContextEx myContext; - private final TranslatingCompiler[] myCompilers; - private int myCurrentCompilerIdx; - private final Set myCompiledSources = new HashSet(); - //private LinkedBlockingQueue myFutures = new LinkedBlockingQueue(); - - private TranslatorsOutputSink(CompileContextEx context, TranslatingCompiler[] compilers) { - myContext = context; - myCompilers = compilers; - } - - public void setCurrentCompilerIndex(int index) { - myCurrentCompilerIdx = index; - } - - public Set getCompiledSources() { - return Collections.unmodifiableSet(myCompiledSources); - } - - public void add(final String outputRoot, final Collection items, final VirtualFile[] filesToRecompile) { - for (TranslatingCompiler.OutputItem item : items) { - final VirtualFile file = item.getSourceFile(); - if (file != null) { - myCompiledSources.add(file); - } - } - final TranslatingCompiler compiler = myCompilers[myCurrentCompilerIdx]; - if (compiler instanceof IntermediateOutputCompiler) { - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - final List outputs = new ArrayList(); - for (TranslatingCompiler.OutputItem item : items) { - final VirtualFile vFile = lfs.findFileByPath(item.getOutputPath()); - if (vFile != null) { - outputs.add(vFile); - } - } - myContext.markGenerated(outputs); - } - final int nextCompilerIdx = myCurrentCompilerIdx + 1; - try { - if (nextCompilerIdx < myCompilers.length ) { - final Map> updateNow = new java.util.HashMap>(); - // process postponed - for (Map.Entry> entry : myPostponedItems.entrySet()) { - final String outputDir = entry.getKey(); - final Collection postponed = entry.getValue(); - for (Iterator it = postponed.iterator(); it.hasNext();) { - TranslatingCompiler.OutputItem item = it.next(); - boolean shouldPostpone = false; - for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { - shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext); - if (shouldPostpone) { - break; - } - } - if (!shouldPostpone) { - // the file is not compilable by the rest of compilers, so it is safe to update it now - it.remove(); - addItemToMap(updateNow, outputDir, item); - } - } - } - // process items from current compilation - for (TranslatingCompiler.OutputItem item : items) { - boolean shouldPostpone = false; - for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { - shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext); - if (shouldPostpone) { - break; - } - } - if (shouldPostpone) { - // the file is compilable by the next compiler in row, update should be postponed - addItemToMap(myPostponedItems, outputRoot, item); - } - else { - addItemToMap(updateNow, outputRoot, item); - } - } - - if (updateNow.size() == 1) { - final Map.Entry> entry = updateNow.entrySet().iterator().next(); - final String outputDir = entry.getKey(); - final Collection itemsToUpdate = entry.getValue(); - TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, filesToRecompile); - } - else { - for (Map.Entry> entry : updateNow.entrySet()) { - final String outputDir = entry.getKey(); - final Collection itemsToUpdate = entry.getValue(); - TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, VirtualFile.EMPTY_ARRAY); - } - if (filesToRecompile.length > 0) { - TranslatingCompilerFilesMonitor.getInstance().update(myContext, null, Collections.emptyList(), filesToRecompile); - } - } - } - else { - TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputRoot, items, filesToRecompile); - } - } - catch (IOException e) { - LOG.info(e); - myContext.requestRebuildNextTime(e.getMessage()); - } - } - - private static void addItemToMap(Map> map, String outputDir, TranslatingCompiler.OutputItem item) { - Collection collection = map.get(outputDir); - if (collection == null) { - collection = new ArrayList(); - map.put(outputDir, collection); - } - collection.add(item); - } - - public void flushPostponedItems() { - final TranslatingCompilerFilesMonitor filesMonitor = TranslatingCompilerFilesMonitor.getInstance(); - try { - for (Map.Entry> entry : myPostponedItems.entrySet()) { - final String outputDir = entry.getKey(); - final Collection items = entry.getValue(); - filesMonitor.update(myContext, outputDir, items, VirtualFile.EMPTY_ARRAY); - } - } - catch (IOException e) { - LOG.info(e); - myContext.requestRebuildNextTime(e.getMessage()); - } - } - } - - private static class DependentClassesCumulativeFilter implements Function>, Pair>> { - - private final TIntHashSet myProcessedNames = new TIntHashSet(); - private final Set myProcessedFiles = new HashSet(); - - public Pair> fun(Pair> deps) { - final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst()); - currentDeps.removeAll(myProcessedNames.toArray()); - myProcessedNames.addAll(deps.getFirst()); - - final Set depFiles = new HashSet(deps.getSecond()); - depFiles.removeAll(myProcessedFiles); - myProcessedFiles.addAll(deps.getSecond()); - return new Pair>(currentDeps.toArray(), depFiles); - } - } -} +/* + * 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. + */ + +/** + * @author: Eugene Zhuravlev + * Date: Jan 17, 2003 + * Time: 1:42:26 PM + */ +package com.intellij.compiler.impl; + +import com.intellij.CommonBundle; +import com.intellij.analysis.AnalysisScope; +import com.intellij.compiler.*; +import com.intellij.compiler.make.CacheCorruptedException; +import com.intellij.compiler.make.CacheUtils; +import com.intellij.compiler.make.DependencyCache; +import com.intellij.compiler.progress.CompilerTask; +import com.intellij.diagnostic.IdeErrorsDialog; +import com.intellij.diagnostic.PluginException; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.Compiler; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.compiler.ex.CompilerPathsEx; +import com.intellij.openapi.compiler.generic.GenericCompiler; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.module.LanguageLevelUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor; +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.openapi.vfs.newvfs.RefreshQueue; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.packageDependencies.DependenciesBuilder; +import com.intellij.packageDependencies.ForwardDependenciesBuilder; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiCompiledElement; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.util.*; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.containers.OrderedSet; +import gnu.trove.TIntHashSet; +import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.util.*; + +public class CompileDriver { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver"); + // to be used in tests only for debug output + public static boolean ourDebugMode = false; + + private final Project myProject; + private final Map, Pair> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources] + private final String myCachesDirectoryPath; + private boolean myShouldClearOutputDirectory; + + private final Map myModuleOutputPaths = new HashMap(); + private final Map myModuleTestOutputPaths = new HashMap(); + + @NonNls private static final String VERSION_FILE_NAME = "version.dat"; + @NonNls private static final String LOCK_FILE_NAME = "in_progress.dat"; + + private static final boolean GENERATE_CLASSPATH_INDEX = "true".equals(System.getProperty("generate.classpath.index")); + private static final String PROP_PERFORM_INITIAL_REFRESH = "compiler.perform.outputs.refresh.on.start"; + private static final Key REFRESH_DONE_KEY = Key.create("_compiler.initial.refresh.done_"); + + private static final FileProcessingCompilerAdapterFactory FILE_PROCESSING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { + public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { + return new FileProcessingCompilerAdapter(context, compiler); + } + }; + private static final FileProcessingCompilerAdapterFactory FILE_PACKAGING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { + public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { + return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler); + } + }; + private CompilerFilter myCompilerFilter = CompilerFilter.ALL; + private static final CompilerFilter SOURCE_PROCESSING_ONLY = new CompilerFilter() { + public boolean acceptCompiler(Compiler compiler) { + return compiler instanceof SourceProcessingCompiler; + } + }; + private static final CompilerFilter ALL_EXCEPT_SOURCE_PROCESSING = new CompilerFilter() { + public boolean acceptCompiler(Compiler compiler) { + return !SOURCE_PROCESSING_ONLY.acceptCompiler(compiler); + } + }; + + private OutputPathFinder myOutputFinder; // need this for updating zip archives (experimental feature) + + private Set myAllOutputDirectories; + private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/; + + public CompileDriver(Project project) { + myProject = project; + myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar); + myShouldClearOutputDirectory = CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY; + + myGenerationCompilerModuleToOutputDirMap = new HashMap, Pair>(); + + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter); + final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); + final CompilerConfiguration config = CompilerConfiguration.getInstance(project); + for (Module module : allModules) { + for (IntermediateOutputCompiler compiler : generatingCompilers) { + final VirtualFile productionOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, false)); + final VirtualFile testOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, true)); + final Pair pair = new Pair(compiler, module); + final Pair outputs = new Pair(productionOutput, testOutput); + myGenerationCompilerModuleToOutputDirMap.put(pair, outputs); + } + if (config.isAnnotationProcessorsEnabled()) { + if (config.isAnnotationProcessingEnabled(module)) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path != null) { + lookupVFile(lfs, path); // ensure the file is created and added to VFS + } + } + } + } + } + + public void setCompilerFilter(CompilerFilter compilerFilter) { + myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter; + } + + public void rebuild(CompileStatusNotification callback) { + doRebuild(callback, null, true, addAdditionalRoots(new ProjectCompileScope(myProject), ALL_EXCEPT_SOURCE_PROCESSING)); + } + + public void make(CompileScope scope, CompileStatusNotification callback) { + scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); + if (validateCompilerConfiguration(scope, false)) { + startup(scope, false, false, callback, null, true, false); + } + else { + callback.finished(true, 0, 0, DummyCompileContext.getInstance()); + } + } + + public boolean isUpToDate(CompileScope scope) { + if (LOG.isDebugEnabled()) { + LOG.debug("isUpToDate operation started"); + } + scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); + + final CompilerTask task = new CompilerTask(myProject, true, "", true); + final CompileContextImpl compileContext = new CompileContextImpl(myProject, task, scope, createDependencyCache(), true, false); + + checkCachesVersion(compileContext, ((PersistentFS)ManagingFS.getInstance()).getCreationTimestamp()); + if (compileContext.isRebuildRequested()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Rebuild requested, up-to-date=false"); + } + return false; + } + + for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { + final Pair outputs = entry.getValue(); + Module module = entry.getKey().getSecond(); + compileContext.assignModule(outputs.getFirst(), module, false); + compileContext.assignModule(outputs.getSecond(), module, true); + } + + final Ref status = new Ref(); + + task.start(new Runnable() { + public void run() { + try { + myAllOutputDirectories = getAllOutputDirectories(); + // need this for updating zip archives experiment, uncomment if the feature is turned on + //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); + status.set(doCompile(compileContext, false, false, false, true)); + } + finally { + compileContext.commitZipFiles(); // just to be on the safe side; normally should do nothing if called in isUpToDate() + } + } + }, null); + + if (LOG.isDebugEnabled()) { + LOG.debug("isUpToDate operation finished"); + } + + return ExitStatus.UP_TO_DATE.equals(status.get()); + } + + private DependencyCache createDependencyCache() { + return new DependencyCache(myCachesDirectoryPath + File.separator + ".dependency-info"); + } + + public void compile(CompileScope scope, CompileStatusNotification callback, boolean trackDependencies, boolean clearingOutputDirsPossible) { + myShouldClearOutputDirectory &= clearingOutputDirsPossible; + if (trackDependencies) { + scope = new TrackDependenciesScope(scope); + } + if (validateCompilerConfiguration(scope, false)) { + startup(scope, false, true, callback, null, true, trackDependencies); + } + else { + callback.finished(true, 0, 0, DummyCompileContext.getInstance()); + } + } + + private static class CompileStatus { + final int CACHE_FORMAT_VERSION; + final boolean COMPILATION_IN_PROGRESS; + final long VFS_CREATION_STAMP; + + private CompileStatus(int cacheVersion, boolean isCompilationInProgress, long vfsStamp) { + CACHE_FORMAT_VERSION = cacheVersion; + COMPILATION_IN_PROGRESS = isCompilationInProgress; + VFS_CREATION_STAMP = vfsStamp; + } + } + + private CompileStatus readStatus() { + final boolean isInProgress = getLockFile().exists(); + int version = -1; + long vfsStamp = -1L; + try { + final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); + DataInputStream in = new DataInputStream(new FileInputStream(versionFile)); + try { + version = in.readInt(); + try { + vfsStamp = in.readLong(); + } + catch (IOException ignored) { + } + } + finally { + in.close(); + } + } + catch (FileNotFoundException e) { + // ignore + } + catch (IOException e) { + LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly + return null; + } + return new CompileStatus(version, isInProgress, vfsStamp); + } + + private void writeStatus(CompileStatus status, CompileContext context) { + final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); + + final File lockFile = getLockFile(); + try { + FileUtil.createIfDoesntExist(statusFile); + DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile)); + try { + out.writeInt(status.CACHE_FORMAT_VERSION); + out.writeLong(status.VFS_CREATION_STAMP); + } + finally { + out.close(); + } + if (status.COMPILATION_IN_PROGRESS) { + FileUtil.createIfDoesntExist(lockFile); + } + else { + deleteFile(lockFile); + } + } + catch (IOException e) { + context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); + } + } + + private File getLockFile() { + return new File(CompilerPaths.getCompilerSystemDirectory(myProject), LOCK_FILE_NAME); + } + + private void doRebuild(CompileStatusNotification callback, + CompilerMessage message, + final boolean checkCachesVersion, + final CompileScope compileScope) { + if (validateCompilerConfiguration(compileScope, true)) { + startup(compileScope, true, false, callback, message, checkCachesVersion, false); + } + else { + callback.finished(true, 0, 0, DummyCompileContext.getInstance()); + } + } + + private CompileScope addAdditionalRoots(CompileScope originalScope, final CompilerFilter filter) { + CompileScope scope = attachIntermediateOutputDirectories(originalScope, filter); + + final AdditionalCompileScopeProvider[] scopeProviders = Extensions.getExtensions(AdditionalCompileScopeProvider.EXTENSION_POINT_NAME); + CompileScope baseScope = scope; + for (AdditionalCompileScopeProvider scopeProvider : scopeProviders) { + final CompileScope additionalScope = scopeProvider.getAdditionalScope(baseScope, filter, myProject); + if (additionalScope != null) { + scope = new CompositeScope(scope, additionalScope); + } + } + return scope; + } + + private CompileScope attachIntermediateOutputDirectories(CompileScope originalScope, CompilerFilter filter) { + CompileScope scope = originalScope; + final Set affected = new HashSet(Arrays.asList(originalScope.getAffectedModules())); + for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { + final Module module = entry.getKey().getSecond(); + if (affected.contains(module) && filter.acceptCompiler(entry.getKey().getFirst())) { + final Pair outputs = entry.getValue(); + scope = new CompositeScope(scope, new FileSetCompileScope(Arrays.asList(outputs.getFirst(), outputs.getSecond()), new Module[]{module})); + } + } + return scope; + } + + private void attachAnnotationProcessorsOutputDirectories(CompileContextEx context) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); + final Set affected = new HashSet(Arrays.asList(context.getCompileScope().getAffectedModules())); + for (Module module : affected) { + if (!config.isAnnotationProcessingEnabled(module)) { + continue; + } + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path == null) { + continue; + } + final VirtualFile vFile = lfs.findFileByPath(path); + if (vFile == null) { + continue; + } + if (ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(vFile)) { + // no need to add, is already marked as source + continue; + } + context.addScope(new FileSetCompileScope(Collections.singletonList(vFile), new Module[]{module})); + context.assignModule(vFile, module, false); + } + } + + public static final Key COMPILATION_START_TIMESTAMP = Key.create("COMPILATION_START_TIMESTAMP"); + + private void startup(final CompileScope scope, + final boolean isRebuild, + final boolean forceCompile, + final CompileStatusNotification callback, + final CompilerMessage message, + final boolean checkCachesVersion, + final boolean trackDependencies) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final CompilerTask compileTask = new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, + forceCompile + ? CompilerBundle.message("compiler.content.name.compile") + : CompilerBundle.message("compiler.content.name.make"), ApplicationManager.getApplication().isUnitTestMode()); + final WindowManager windowManager = WindowManager.getInstance(); + if (windowManager != null) { + windowManager.getStatusBar(myProject).setInfo(""); + } + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + FileDocumentManager.getInstance().saveAllDocuments(); + + final DependencyCache dependencyCache = createDependencyCache(); + final CompileContextImpl compileContext = + new CompileContextImpl(myProject, compileTask, scope, dependencyCache, !isRebuild && !forceCompile, isRebuild); + compileContext.putUserData(COMPILATION_START_TIMESTAMP, LocalTimeCounter.currentTime()); + for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { + final Pair outputs = entry.getValue(); + final Module module = entry.getKey().getSecond(); + compileContext.assignModule(outputs.getFirst(), module, false); + compileContext.assignModule(outputs.getSecond(), module, true); + } + attachAnnotationProcessorsOutputDirectories(compileContext); + + compileTask.start(new Runnable() { + public void run() { + if (compileContext.getProgressIndicator().isCanceled()) { + if (callback != null) { + callback.finished(true, 0, 0, compileContext); + } + return; + } + long start = System.currentTimeMillis(); + try { + if (myProject.isDisposed()) { + return; + } + LOG.info("COMPILATION STARTED"); + if (message != null) { + compileContext.addMessage(message); + } + TranslatingCompilerFilesMonitor.getInstance().ensureInitializationCompleted(myProject); + doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion, trackDependencies); + } + finally { + compileContext.commitZipFiles(); + final long finish = System.currentTimeMillis(); + CompilerUtil.logDuration( + "\tCOMPILATION FINISHED; Errors: " + + compileContext.getMessageCount(CompilerMessageCategory.ERROR) + + "; warnings: " + + compileContext.getMessageCount(CompilerMessageCategory.WARNING), + finish - start + ); + //if (LOG.isDebugEnabled()) { + // LOG.debug("COMPILATION FINISHED"); + //} + } + } + }, new Runnable() { + public void run() { + if (isRebuild) { + final int rv = Messages.showDialog( + myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild", + new String[]{"Make", "Rebuild"}, 0, Messages.getQuestionIcon() + ); + if (rv == 0 /*yes, please, do run make*/) { + startup(scope, false, false, callback, null, checkCachesVersion, trackDependencies); + return; + } + } + startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion, trackDependencies); + } + }); + } + + private void doCompile(final CompileContextImpl compileContext, + final boolean isRebuild, + final boolean forceCompile, + final CompileStatusNotification callback, + final boolean checkCachesVersion, + final boolean trackDependencies) { + ExitStatus status = ExitStatus.ERRORS; + boolean wereExceptions = false; + final long vfsTimestamp = ((PersistentFS)ManagingFS.getInstance()).getCreationTimestamp(); + try { + if (checkCachesVersion) { + checkCachesVersion(compileContext, vfsTimestamp); + if (compileContext.isRebuildRequested()) { + return; + } + } + writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, true, vfsTimestamp), compileContext); + if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + return; + } + + myAllOutputDirectories = getAllOutputDirectories(); + // need this for updating zip archives experiment, uncomment if the feature is turned on + //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); + status = doCompile(compileContext, isRebuild, forceCompile, trackDependencies, false); + } + catch (Throwable ex) { + if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(ex); + wereExceptions = true; + final PluginId pluginId = IdeErrorsDialog.findPluginId(ex); + + final StringBuffer message = new StringBuffer(); + message.append("Internal error"); + if (pluginId != null) { + message.append(" (Plugin: ").append(pluginId).append(")"); + } + message.append(": ").append(ex.getMessage()); + compileContext.addMessage(CompilerMessageCategory.ERROR, message.toString(), null, -1, -1); + + if (pluginId != null) { + throw new PluginException(ex, pluginId); + } + throw new RuntimeException(ex); + } + finally { + dropDependencyCache(compileContext); + final ExitStatus _status = status; + if (compileContext.isRebuildRequested()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + doRebuild(callback, new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason(), + null, -1, -1, null), false, compileContext.getCompileScope()); + } + }, ModalityState.NON_MODAL); + } + else { + final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp(); + if (!myProject.isDisposed()) { + writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, wereExceptions, vfsTimestamp), compileContext); + } + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR); + final int warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING); + final String statusMessage = createStatusMessage(_status, warningCount, errorCount); + final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); + if (statusBar != null) { // because this code is in invoke later, the code may work for already closed project + // in case another project was opened in the frame while the compiler was working (See SCR# 28591) + StatusBar.Info.set(statusMessage, myProject); + if (duration > ONE_MINUTE_MS) { + final MessageType messageType = errorCount > 0 ? MessageType.ERROR : warningCount > 0 ? MessageType.WARNING : MessageType.INFO; + ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage); + } + } + if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) { + compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1); + } + if (callback != null) { + callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext); + } + } + }, ModalityState.NON_MODAL); + } + } + } + + private void checkCachesVersion(final CompileContextImpl compileContext, final long currentVFSTimestamp) { + final CompileStatus compileStatus = readStatus(); + if (compileStatus == null) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); + } + else if (compileStatus.CACHE_FORMAT_VERSION != -1 && + compileStatus.CACHE_FORMAT_VERSION != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.caches.old.format")); + } + else if (compileStatus.COMPILATION_IN_PROGRESS) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.previous.compilation.failed")); + } + else if (compileStatus.VFS_CREATION_STAMP >= 0L){ + if (currentVFSTimestamp != compileStatus.VFS_CREATION_STAMP) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.vfs.was.rebuilt")); + } + } + } + + private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount) { + if (status == ExitStatus.CANCELLED) { + return CompilerBundle.message("status.compilation.aborted"); + } + if (status == ExitStatus.UP_TO_DATE) { + return CompilerBundle.message("status.all.up.to.date"); + } + if (status == ExitStatus.SUCCESS) { + return warningCount > 0 + ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount) + : CompilerBundle.message("status.compilation.completed.successfully"); + } + return CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount); + } + + static class ExitStatus { + private final String myName; + + private ExitStatus(@NonNls String name) { + myName = name; + } + + public String toString() { + return myName; + } + + public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED"); + public static final ExitStatus ERRORS = new ExitStatus("ERRORS"); + public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS"); + public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE"); + } + + static class ExitException extends Exception { + private final ExitStatus myStatus; + + ExitException(ExitStatus status) { + myStatus = status; + } + + public ExitStatus getExitStatus() { + return myStatus; + } + } + + private ExitStatus doCompile(final CompileContextEx context, + boolean isRebuild, + final boolean forceCompile, + final boolean trackDependencies, final boolean onlyCheckStatus) { + try { + if (isRebuild) { + deleteAll(context); + } + else if (forceCompile) { + if (myShouldClearOutputDirectory) { + clearAffectedOutputPathsIfPossible(context); + } + } + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + if (LOG.isDebugEnabled()) { + logErrorMessages(context); + } + return ExitStatus.ERRORS; + } + + if (!onlyCheckStatus) { + if (!executeCompileTasks(context, true)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Compilation cancelled"); + } + return ExitStatus.CANCELLED; + } + } + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + if (LOG.isDebugEnabled()) { + logErrorMessages(context); + } + return ExitStatus.ERRORS; + } + + boolean needRecalcOutputDirs = false; + if (Registry.is(PROP_PERFORM_INITIAL_REFRESH) || !Boolean.valueOf(REFRESH_DONE_KEY.get(myProject, Boolean.FALSE))) { + REFRESH_DONE_KEY.set(myProject, Boolean.TRUE); + final long refreshStart = System.currentTimeMillis(); + + //need this to make sure the VFS is built + final List outputsToRefresh = new ArrayList(); + + final VirtualFile[] all = context.getAllOutputDirectories(); + + final ProgressIndicator progressIndicator = context.getProgressIndicator(); + + final int totalCount = all.length + myGenerationCompilerModuleToOutputDirMap.size() * 2; + progressIndicator.pushState(); + progressIndicator.setText("Inspecting output directories..."); + try { + for (VirtualFile output : all) { + if (output.isValid()) { + walkChildren(output, context); + } + else { + needRecalcOutputDirs = true; + final File file = new File(output.getPath()); + if (!file.exists()) { + final boolean created = file.mkdirs(); + if (!created) { + context.addMessage(CompilerMessageCategory.ERROR, "Failed to create output directory " + file.getPath(), null, 0, 0); + return ExitStatus.ERRORS; + } + } + output = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + if (output == null) { + context.addMessage(CompilerMessageCategory.ERROR, "Failed to locate output directory " + file.getPath(), null, 0, 0); + return ExitStatus.ERRORS; + } + } + outputsToRefresh.add(output); + } + for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { + final Pair generated = myGenerationCompilerModuleToOutputDirMap.get(pair); + walkChildren(generated.getFirst(), context); + outputsToRefresh.add(generated.getFirst()); + walkChildren(generated.getSecond(), context); + outputsToRefresh.add(generated.getSecond()); + } + + RefreshQueue.getInstance().refresh(false, true, null, outputsToRefresh.toArray(new VirtualFile[outputsToRefresh.size()])); + if (progressIndicator.isCanceled()) { + return ExitStatus.CANCELLED; + } + } + finally { + progressIndicator.popState(); + } + + final long initialRefreshTime = System.currentTimeMillis() - refreshStart; + CompilerUtil.logDuration("Initial VFS refresh", initialRefreshTime); + } + + //DumbService.getInstance(myProject).waitForSmartMode(); + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + DumbService.getInstance(myProject).runWhenSmart(new Runnable() { + public void run() { + semaphore.up(); + } + }); + while (!semaphore.waitFor(500)) { + if (context.getProgressIndicator().isCanceled()) { + return ExitStatus.CANCELLED; + } + } + + if (needRecalcOutputDirs) { + context.recalculateOutputDirs(); + } + + boolean didSomething = false; + + final CompilerManager compilerManager = CompilerManager.getInstance(myProject); + GenericCompilerRunner runner = new GenericCompilerRunner(context, myCompilerFilter, compilerManager, isRebuild, onlyCheckStatus); + try { + didSomething |= generateSources(compilerManager, context, forceCompile, onlyCheckStatus); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceProcessingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus); + + final CompileScope intermediateSources = attachIntermediateOutputDirectories(new CompositeScope(CompileScope.EMPTY_ARRAY) { + @NotNull + public Module[] getAffectedModules() { + return context.getCompileScope().getAffectedModules(); + } + }, SOURCE_PROCESSING_ONLY); + context.addScope(intermediateSources); + + didSomething |= translate(context, compilerManager, forceCompile, isRebuild, trackDependencies, onlyCheckStatus); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); + didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.CLASS_INSTRUMENTING); + + // explicitly passing forceCompile = false because in scopes that is narrower than ProjectScope it is impossible + // to understand whether the class to be processed is in scope or not. Otherwise compiler may process its items even if + // there were changes in completely independent files. + didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); + didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.CLASS_POST_PROCESSING); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class, + FILE_PACKAGING_COMPILER_ADAPTER_FACTORY, + isRebuild, false, onlyCheckStatus); + didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.PACKAGING); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, + forceCompile, true, onlyCheckStatus); + didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.VALIDATING); + } + catch (ExitException e) { + if (LOG.isDebugEnabled()) { + LOG.debug(e); + logErrorMessages(context); + } + return e.getExitStatus(); + } + finally { + // drop in case it has not been dropped yet. + dropDependencyCache(context); + + final VirtualFile[] allOutputDirs = context.getAllOutputDirectories(); + + if (didSomething && GENERATE_CLASSPATH_INDEX) { + CompilerUtil.runInContext(context, "Generating classpath index...", new ThrowableRunnable(){ + public void run() { + int count = 0; + for (VirtualFile file : allOutputDirs) { + context.getProgressIndicator().setFraction((double)++count / allOutputDirs.length); + createClasspathIndex(file); + } + } + }); + } + + } + + if (!onlyCheckStatus) { + if (!executeCompileTasks(context, false)) { + return ExitStatus.CANCELLED; + } + } + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + if (LOG.isDebugEnabled()) { + logErrorMessages(context); + } + return ExitStatus.ERRORS; + } + if (!didSomething) { + return ExitStatus.UP_TO_DATE; + } + return ExitStatus.SUCCESS; + } + catch (ProcessCanceledException e) { + return ExitStatus.CANCELLED; + } + } + + private void clearAffectedOutputPathsIfPossible(CompileContextEx context) { + final MultiMap outputToModulesMap = new MultiMap(); + for (Module module : ModuleManager.getInstance(myProject).getModules()) { + final CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(module); + if (compilerModuleExtension == null) { + continue; + } + final String outputPathUrl = compilerModuleExtension.getCompilerOutputUrl(); + if (outputPathUrl != null) { + final String path = VirtualFileManager.extractPath(outputPathUrl); + outputToModulesMap.putValue(new File(path), module); + } + + final String outputPathForTestsUrl = compilerModuleExtension.getCompilerOutputUrlForTests(); + if (outputPathForTestsUrl != null) { + final String path = VirtualFileManager.extractPath(outputPathForTestsUrl); + outputToModulesMap.putValue(new File(path), module); + } + } + final Set affectedModules = new HashSet(Arrays.asList(context.getCompileScope().getAffectedModules())); + final List scopeOutputs = new ArrayList(affectedModules.size() * 2); + for (File output : outputToModulesMap.keySet()) { + final Collection modules = outputToModulesMap.get(output); + boolean shouldInclude = true; + for (Module module : modules) { + if (!affectedModules.contains(module)) { + shouldInclude = false; + break; + } + } + if (shouldInclude) { + scopeOutputs.add(output); + } + } + if (scopeOutputs.size() > 0) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable() { + public void run() { + clearOutputDirectories(scopeOutputs); + } + }); + } + } + + private static void logErrorMessages(final CompileContext context) { + final CompilerMessage[] errors = context.getMessages(CompilerMessageCategory.ERROR); + if (errors.length > 0) { + LOG.debug("Errors reported: "); + for (CompilerMessage error : errors) { + LOG.debug("\t" + error.getMessage()); + } + } + } + + private static void walkChildren(VirtualFile from, final CompileContext context) { + final VirtualFile[] files = from.getChildren(); + if (files != null && files.length > 0) { + context.getProgressIndicator().checkCanceled(); + context.getProgressIndicator().setText2(from.getPresentableUrl()); + for (VirtualFile file : files) { + walkChildren(file, context); + } + } + } + + private static void createClasspathIndex(final VirtualFile file) { + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(new File(VfsUtil.virtualToIoFile(file), "classpath.index"))); + try { + writeIndex(writer, file, file); + } + finally { + writer.close(); + } + } + catch (IOException e) { + // Ignore. Failed to create optional classpath index + } + } + + private static void writeIndex(final BufferedWriter writer, final VirtualFile root, final VirtualFile file) throws IOException { + writer.write(VfsUtil.getRelativePath(file, root, '/')); + writer.write('\n'); + + for (VirtualFile child : file.getChildren()) { + writeIndex(writer, root, child); + } + } + + private static void dropDependencyCache(final CompileContextEx context) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.saving.caches"), new ThrowableRunnable(){ + public void run() { + context.getDependencyCache().resetState(); + } + }); + } + + private boolean generateSources(final CompilerManager compilerManager, + CompileContextEx context, + final boolean forceCompile, + final boolean onlyCheckStatus) throws ExitException { + boolean didSomething = false; + + final SourceGeneratingCompiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class, myCompilerFilter); + for (final SourceGeneratingCompiler sourceGenerator : sourceGenerators) { + if (context.getProgressIndicator().isCanceled()) { + throw new ExitException(ExitStatus.CANCELLED); + } + + final boolean generatedSomething = generateOutput(context, sourceGenerator, forceCompile, onlyCheckStatus); + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + throw new ExitException(ExitStatus.ERRORS); + } + didSomething |= generatedSomething; + } + return didSomething; + } + + private boolean translate(final CompileContextEx context, + final CompilerManager compilerManager, + final boolean forceCompile, + boolean isRebuild, + final boolean trackDependencies, final boolean onlyCheckStatus) throws ExitException { + + boolean didSomething = false; + + final TranslatingCompiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class, myCompilerFilter); + + + final List> sortedChunks = Collections.unmodifiableList(ApplicationManager.getApplication().runReadAction(new Computable>>() { + public List> compute() { + final ModuleManager moduleManager = ModuleManager.getInstance(myProject); + return ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(moduleManager.getModules())); + } + })); + + final DumbService dumbService = DumbService.getInstance(myProject); + try { + final Set processedModules = new HashSet(); + VirtualFile[] snapshot = null; + final Map, Collection> chunkMap = new HashMap, Collection>(); + int total = 0; + int processed = 0; + for (final Chunk currentChunk : sortedChunks) { + final TranslatorsOutputSink sink = new TranslatorsOutputSink(context, translators); + final Set generatedTypes = new HashSet(); + Collection chunkFiles = chunkMap.get(currentChunk); + final Set filesToRecompile = new HashSet(); + final Set allDependent = new HashSet(); + try { + int round = 0; + boolean compiledSomethingForThisChunk = false; + Collection dependentFiles = Collections.emptyList(); + final Function>, Pair>> dependencyFilter = new DependentClassesCumulativeFilter(); + + do { + for (int currentCompiler = 0, translatorsLength = translators.length; currentCompiler < translatorsLength; currentCompiler++) { + sink.setCurrentCompilerIndex(currentCompiler); + final TranslatingCompiler compiler = translators[currentCompiler]; + if (context.getProgressIndicator().isCanceled()) { + throw new ExitException(ExitStatus.CANCELLED); + } + + dumbService.waitForSmartMode(); + + if (round == 0) { + if (snapshot == null || ContainerUtil.intersects(generatedTypes, compilerManager.getRegisteredInputTypes(compiler))) { + // rescan snapshot if previously generated files may influence the input of this compiler + snapshot = ApplicationManager.getApplication().runReadAction(new Computable() { + public VirtualFile[] compute() { + return context.getCompileScope().getFiles(null, true); + } + }); + recalculateChunkToFilesMap(context, sortedChunks, snapshot, chunkMap); + chunkFiles = chunkMap.get(currentChunk); + total = snapshot.length * translatorsLength; + } + } + + final CompileContextEx _context; + if (compiler instanceof IntermediateOutputCompiler) { + // wrap compile context so that output goes into intermediate directories + final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler; + _context = new CompileContextExProxy(context) { + public VirtualFile getModuleOutputDirectory(final Module module) { + return getGenerationOutputDir(_compiler, module, false); + } + + public VirtualFile getModuleOutputDirectoryForTests(final Module module) { + return getGenerationOutputDir(_compiler, module, true); + } + }; + } + else { + _context = context; + } + final boolean compiledSomething = + compileSources(_context, currentChunk, compiler, chunkFiles, round == 0? forceCompile : true, isRebuild, trackDependencies, onlyCheckStatus, sink); + + processed += chunkFiles.size(); + _context.getProgressIndicator().setFraction(((double)processed) / total); + + if (compiledSomething) { + generatedTypes.addAll(compilerManager.getRegisteredOutputTypes(compiler)); + } + + if (_context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + throw new ExitException(ExitStatus.ERRORS); + } + + didSomething |= compiledSomething; + compiledSomethingForThisChunk |= didSomething; + } + + final boolean hasUnprocessedTraverseRoots = context.getDependencyCache().hasUnprocessedTraverseRoots(); + if (!isRebuild && (compiledSomethingForThisChunk || hasUnprocessedTraverseRoots)) { + final Set compiledWithSuccess; + final Set compiledWithErrors = CacheUtils.getFilesCompiledWithErrors(context); + if (compiledWithErrors.isEmpty()) { + compiledWithSuccess = sink.getCompiledSources(); + } + else { + compiledWithSuccess = new HashSet(); + compiledWithSuccess.addAll(sink.getCompiledSources()); + compiledWithSuccess.removeAll(compiledWithErrors); + } + filesToRecompile.removeAll(compiledWithSuccess); + filesToRecompile.addAll(compiledWithErrors); + + dependentFiles = CacheUtils.findDependentFiles(context, compiledWithSuccess, dependencyFilter); + if (!processedModules.isEmpty()) { + for (Iterator it = dependentFiles.iterator(); it.hasNext();) { + final VirtualFile next = it.next(); + final Module module = context.getModuleByFile(next); + if (module != null && processedModules.contains(module)) { + it.remove(); + } + } + } + + if (ourDebugMode) { + if (!dependentFiles.isEmpty()) { + for (VirtualFile dependentFile : dependentFiles) { + System.out.println("FOUND TO RECOMPILE: " + dependentFile.getPresentableUrl()); + } + } + else { + System.out.println("NO FILES TO RECOMPILE"); + } + } + + if (!dependentFiles.isEmpty()) { + filesToRecompile.addAll(dependentFiles); + allDependent.addAll(dependentFiles); + if (context.getProgressIndicator().isCanceled() || context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + break; + } + final List filesInScope = getFilesInScope(context, currentChunk, dependentFiles); + if (filesInScope.isEmpty()) { + break; + } + context.getDependencyCache().clearTraverseRoots(); + chunkFiles = filesInScope; + total += chunkFiles.size() * translators.length; + } + + didSomething |= (hasUnprocessedTraverseRoots != context.getDependencyCache().hasUnprocessedTraverseRoots()); + } + + round++; + } + while (!dependentFiles.isEmpty() && context.getMessageCount(CompilerMessageCategory.ERROR) == 0); + + if (CompilerConfiguration.MAKE_ENABLED) { + if (!context.getProgressIndicator().isCanceled()) { + // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch + final ProgressIndicator indicator = context.getProgressIndicator(); + final DependencyCache cache = context.getDependencyCache(); + + indicator.pushState(); + indicator.setText(CompilerBundle.message("progress.updating.caches")); + indicator.setText2(""); + + cache.update(); + + indicator.setText(CompilerBundle.message("progress.saving.caches")); + cache.resetState(); + processedModules.addAll(currentChunk.getNodes()); + indicator.popState(); + } + } + } + catch (CacheCorruptedException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + } + finally { + if (context.getMessageCount(CompilerMessageCategory.ERROR) != 0) { + filesToRecompile.addAll(allDependent); + } + if (filesToRecompile.size() > 0) { + sink.add(null, Collections.emptyList(), VfsUtil.toVirtualFileArray(filesToRecompile)); + } + if (context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + // perform update only if there were no errors, so it is guaranteed that the file was processd by all neccesary compilers + sink.flushPostponedItems(); + } + } + } + } + catch (ProcessCanceledException e) { + ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { + public void run() { + try { + final Collection deps = CacheUtils.findDependentFiles(context, Collections.emptySet(), null); + if (deps.size() > 0) { + TranslatingCompilerFilesMonitor.getInstance().update(context, null, Collections.emptyList(), + VfsUtil.toVirtualFileArray(deps)); + } + } + catch (IOException ignored) { + LOG.info(ignored); + } + catch (CacheCorruptedException ignored) { + LOG.info(ignored); + } + } + }); + throw e; + } + finally { + dropDependencyCache(context); + if (didSomething) { + TranslatingCompilerFilesMonitor.getInstance().updateOutputRootsLayout(myProject); + } + } + return didSomething; + } + + private static List getFilesInScope(final CompileContextEx context, final Chunk chunk, final Collection files) { + final List filesInScope = new ArrayList(files.size()); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (VirtualFile file : files) { + if (context.getCompileScope().belongs(file.getUrl())) { + final Module module = context.getModuleByFile(file); + if (chunk.getNodes().contains(module)) { + filesInScope.add(file); + } + } + } + } + }); + return filesInScope; + } + + private static void recalculateChunkToFilesMap(CompileContextEx context, List> allChunks, VirtualFile[] snapshot, Map, Collection> chunkMap) { + final Map> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(context, snapshot); + for (Chunk moduleChunk : allChunks) { + List files = Collections.emptyList(); + for (Module module : moduleChunk.getNodes()) { + final List moduleFiles = moduleToFilesMap.get(module); + if (moduleFiles != null) { + files = ContainerUtil.concat(files, moduleFiles); + } + } + chunkMap.put(moduleChunk, files); + } + } + + private interface FileProcessingCompilerAdapterFactory { + FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler); + } + + private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager, + CompileContextEx context, + Class fileProcessingCompilerClass, + FileProcessingCompilerAdapterFactory factory, + boolean forceCompile, + final boolean checkScope, + final boolean onlyCheckStatus) throws ExitException { + boolean didSomething = false; + final FileProcessingCompiler[] compilers = compilerManager.getCompilers(fileProcessingCompilerClass, myCompilerFilter); + if (compilers.length > 0) { + try { + CacheDeferredUpdater cacheUpdater = new CacheDeferredUpdater(); + try { + for (final FileProcessingCompiler compiler : compilers) { + if (context.getProgressIndicator().isCanceled()) { + throw new ExitException(ExitStatus.CANCELLED); + } + + CompileContextEx _context = context; + if (compiler instanceof IntermediateOutputCompiler) { + final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler; + _context = new CompileContextExProxy(context) { + public VirtualFile getModuleOutputDirectory(final Module module) { + return getGenerationOutputDir(_compiler, module, false); + } + + public VirtualFile getModuleOutputDirectoryForTests(final Module module) { + return getGenerationOutputDir(_compiler, module, true); + } + }; + } + + final boolean processedSomething = processFiles(factory.create(_context, compiler), forceCompile, checkScope, onlyCheckStatus, cacheUpdater); + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + throw new ExitException(ExitStatus.ERRORS); + } + + didSomething |= processedSomething; + } + } + finally { + cacheUpdater.doUpdate(); + } + } + catch (IOException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + throw new ExitException(ExitStatus.ERRORS); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (ExitException e) { + throw e; + } + catch (Exception e) { + context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); + LOG.error(e); + } + } + + return didSomething; + } + + private static Map> buildModuleToGenerationItemMap(GeneratingCompiler.GenerationItem[] items) { + final Map> map = new HashMap>(); + for (GeneratingCompiler.GenerationItem item : items) { + Module module = item.getModule(); + LOG.assertTrue(module != null); + Set itemSet = map.get(module); + if (itemSet == null) { + itemSet = new HashSet(); + map.put(module, itemSet); + } + itemSet.add(item); + } + return map; + } + + private void deleteAll(final CompileContextEx context) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable() { + public void run() { + final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); + final VirtualFile[] allSources = context.getProjectCompileScope().getFiles(null, true); + if (myShouldClearOutputDirectory) { + clearOutputDirectories(myAllOutputDirectories); + } + else { // refresh is still required + try { + for (final Compiler compiler : CompilerManager.getInstance(myProject).getCompilers(Compiler.class)) { + try { + if (compiler instanceof GeneratingCompiler) { + final StateCache cache = getGeneratingCompilerCache((GeneratingCompiler)compiler); + final Iterator urlIterator = cache.getUrlsIterator(); + while (urlIterator.hasNext()) { + context.getProgressIndicator().checkCanceled(); + deleteFile(new File(VirtualFileManager.extractPath(urlIterator.next()))); + } + } + else if (compiler instanceof TranslatingCompiler) { + final ArrayList> toDelete = new ArrayList>(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + TranslatingCompilerFilesMonitor.getInstance().collectFiles( + context, + (TranslatingCompiler)compiler, Arrays.asList(allSources).iterator(), + true /*pass true to make sure that every source in scope file is processed*/, + false /*important! should pass false to enable collection of files to delete*/, + new ArrayList(), + toDelete + ); + } + }); + for (Trinity trinity : toDelete) { + context.getProgressIndicator().checkCanceled(); + final File file = trinity.getFirst(); + deleteFile(file); + if (isTestMode) { + CompilerManagerImpl.addDeletedPath(file.getPath()); + } + } + } + } + catch (IOException e) { + LOG.info(e); + } + } + pruneEmptyDirectories(context.getProgressIndicator(), myAllOutputDirectories); // to avoid too much files deleted events + } + finally { + CompilerUtil.refreshIODirectories(myAllOutputDirectories); + } + } + dropScopesCaches(); + + clearCompilerSystemDirectory(context); + } + }); + } + + private void dropScopesCaches() { + // hack to be sure the classpath will include the output directories + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules(); + } + }); + } + + private static void pruneEmptyDirectories(ProgressIndicator progress, final Set directories) { + for (File directory : directories) { + doPrune(progress, directory, directories); + } + } + + private static boolean doPrune(ProgressIndicator progress, final File directory, final Set outPutDirectories) { + progress.checkCanceled(); + final File[] files = directory.listFiles(); + boolean isEmpty = true; + if (files != null) { + for (File file : files) { + if (!outPutDirectories.contains(file)) { + if (doPrune(progress, file, outPutDirectories)) { + deleteFile(file); + } + else { + isEmpty = false; + } + } + else { + isEmpty = false; + } + } + } + else { + isEmpty = false; + } + + return isEmpty; + } + + private Set getAllOutputDirectories() { + final Set outputDirs = new OrderedSet((TObjectHashingStrategy)TObjectHashingStrategy.CANONICAL); + final Module[] modules = ModuleManager.getInstance(myProject).getModules(); + for (final String path : CompilerPathsEx.getOutputPaths(modules)) { + outputDirs.add(new File(path)); + } + for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { + outputDirs.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false))); + outputDirs.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true))); + } + final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); + if (config.isAnnotationProcessorsEnabled()) { + for (Module module : modules) { + if (config.isAnnotationProcessingEnabled(module)) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path != null) { + outputDirs.add(new File(path)); + } + } + } + } + return outputDirs; + } + + private static void clearOutputDirectories(final Collection outputDirectories) { + final long start = System.currentTimeMillis(); + // do not delete directories themselves, or we'll get rootsChanged() otherwise + final Collection filesToDelete = new ArrayList(outputDirectories.size() * 2); + for (File outputDirectory : outputDirectories) { + File[] files = outputDirectory.listFiles(); + if (files != null) { + ContainerUtil.addAll(filesToDelete, files); + } + } + if (filesToDelete.size() > 0) { + FileUtil.asyncDelete(filesToDelete); + + // ensure output directories exist + for (final File file : outputDirectories) { + file.mkdirs(); + } + final long clearStop = System.currentTimeMillis(); + + CompilerUtil.refreshIODirectories(outputDirectories); + + final long refreshStop = System.currentTimeMillis(); + + CompilerUtil.logDuration("Clearing output dirs", clearStop - start); + CompilerUtil.logDuration("Refreshing output directories", refreshStop - clearStop); + } + } + + private void clearCompilerSystemDirectory(final CompileContextEx context) { + CompilerCacheManager.getInstance(myProject).clearCaches(context); + FileUtil.delete(CompilerPathsEx.getZipStoreDirectory(myProject)); + dropDependencyCache(context); + + for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { + final File[] outputs = { + new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)), + new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true)) + }; + for (File output : outputs) { + final File[] files = output.listFiles(); + if (files != null) { + for (final File file : files) { + final boolean deleteOk = deleteFile(file); + if (!deleteOk) { + context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", file.getPath()), + null, -1, -1); + } + } + } + } + } + } + + /** + * @param file a file to delete + * @return true if and only if the file existed and was successfully deleted + * Note: the behaviour is different from FileUtil.delete() which returns true if the file absent on the disk + */ + private static boolean deleteFile(final File file) { + File[] files = file.listFiles(); + if (files != null) { + for (File file1 : files) { + deleteFile(file1); + } + } + + for (int i = 0; i < 10; i++){ + if (file.delete()) { + return true; + } + if (!file.exists()) { + return false; + } + try { + Thread.sleep(50); + } + catch (InterruptedException ignored) { + } + } + return false; + } + + private VirtualFile getGenerationOutputDir(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { + final Pair outputs = + myGenerationCompilerModuleToOutputDirMap.get(new Pair(compiler, module)); + return forTestSources? outputs.getSecond() : outputs.getFirst(); + } + + private boolean generateOutput(final CompileContextEx context, + final GeneratingCompiler compiler, + final boolean forceGenerate, + final boolean onlyCheckStatus) throws ExitException { + final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context); + final List toGenerate = new ArrayList(); + final List filesToRefresh = new ArrayList(); + final List generatedFiles = new ArrayList(); + final List affectedModules = new ArrayList(); + try { + final StateCache cache = getGeneratingCompilerCache(compiler); + final Set pathsToRemove = new HashSet(cache.getUrls()); + + final Map itemToOutputPathMap = new HashMap(); + final IOException[] ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (final GeneratingCompiler.GenerationItem item : allItems) { + final Module itemModule = item.getModule(); + final String outputDirPath = CompilerPaths.getGenerationOutputPath(compiler, itemModule, item.isTestSource()); + final String outputPath = outputDirPath + "/" + item.getPath(); + itemToOutputPathMap.put(item, outputPath); + + try { + final ValidityState savedState = cache.getState(outputPath); + + if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) { + final String outputPathUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, outputPath); + if (context.getCompileScope().belongs(outputPathUrl)) { + toGenerate.add(item); + } + else { + pathsToRemove.remove(outputPath); + } + } + else { + pathsToRemove.remove(outputPath); + } + } + catch (IOException e) { + ex[0] = e; + } + } + } + }); + if (ex[0] != null) { + throw ex[0]; + } + + if (onlyCheckStatus) { + if (toGenerate.isEmpty() && pathsToRemove.isEmpty()) { + return false; + } + if (LOG.isDebugEnabled()) { + if (!toGenerate.isEmpty()) { + LOG.debug("Found items to generate, compiler " + compiler.getDescription()); + } + if (!pathsToRemove.isEmpty()) { + LOG.debug("Found paths to remove, compiler " + compiler.getDescription()); + } + } + throw new ExitException(ExitStatus.CANCELLED); + } + + if (!pathsToRemove.isEmpty()) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ + public void run() throws IOException { + for (final String path : pathsToRemove) { + final File file = new File(path); + final boolean deleted = deleteFile(file); + if (deleted) { + cache.remove(path); + filesToRefresh.add(file); + } + } + } + }); + } + + final Map> moduleToItemMap = + buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()])); + List modules = new ArrayList(moduleToItemMap.size()); + for (final Module module : moduleToItemMap.keySet()) { + modules.add(module); + } + ModuleCompilerUtil.sortModules(myProject, modules); + + for (final Module module : modules) { + CompilerUtil.runInContext(context, "Generating output from "+compiler.getDescription(),new ThrowableRunnable(){ + public void run() throws IOException { + final Set items = moduleToItemMap.get(module); + if (items != null && !items.isEmpty()) { + final GeneratingCompiler.GenerationItem[][] productionAndTestItems = splitGenerationItems(items); + for (GeneratingCompiler.GenerationItem[] _items : productionAndTestItems) { + if (_items.length == 0) continue; + final VirtualFile outputDir = getGenerationOutputDir(compiler, module, _items[0].isTestSource()); + final GeneratingCompiler.GenerationItem[] successfullyGenerated = compiler.generate(context, _items, outputDir); + + CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { + public void run() throws IOException { + if (successfullyGenerated.length > 0) { + affectedModules.add(module); + } + for (final GeneratingCompiler.GenerationItem item : successfullyGenerated) { + final String fullOutputPath = itemToOutputPathMap.get(item); + cache.update(fullOutputPath, item.getValidityState()); + final File file = new File(fullOutputPath); + filesToRefresh.add(file); + generatedFiles.add(file); + context.getProgressIndicator().setText2(file.getPath()); + } + } + }); + } + } + } + }); + } + } + catch (IOException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + throw new ExitException(ExitStatus.ERRORS); + } + finally { + CompilerUtil.refreshIOFiles(filesToRefresh); + if (!generatedFiles.isEmpty()) { + DumbService.getInstance(myProject).waitForSmartMode(); + List vFiles = ApplicationManager.getApplication().runReadAction(new Computable>() { + public List compute() { + final ArrayList vFiles = new ArrayList(generatedFiles.size()); + for (File generatedFile : generatedFiles) { + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(generatedFile); + if (vFile != null) { + vFiles.add(vFile); + } + } + return vFiles; + } + }); + if (forceGenerate) { + context.addScope(new FileSetCompileScope(vFiles, affectedModules.toArray(new Module[affectedModules.size()]))); + } + context.markGenerated(vFiles); + } + } + return !toGenerate.isEmpty() || !filesToRefresh.isEmpty(); + } + + private static GeneratingCompiler.GenerationItem[][] splitGenerationItems(final Set items) { + final List production = new ArrayList(); + final List tests = new ArrayList(); + for (GeneratingCompiler.GenerationItem item : items) { + if (item.isTestSource()) { + tests.add(item); + } + else { + production.add(item); + } + } + return new GeneratingCompiler.GenerationItem[][]{ + production.toArray(new GeneratingCompiler.GenerationItem[production.size()]), + tests.toArray(new GeneratingCompiler.GenerationItem[tests.size()]) + }; + } + + private boolean compileSources(final CompileContextEx context, final Chunk moduleChunk, final TranslatingCompiler compiler, final Collection srcSnapshot, + final boolean forceCompile, + final boolean isRebuild, + final boolean trackDependencies, + final boolean onlyCheckStatus, + TranslatingCompiler.OutputSink sink) throws ExitException { + + final Set toCompile = new HashSet(); + final List> toDelete = new ArrayList>(); + context.getProgressIndicator().pushState(); + + final boolean[] wereFilesDeleted = {false}; + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + + TranslatingCompilerFilesMonitor.getInstance().collectFiles( + context, compiler, srcSnapshot.iterator(), forceCompile, isRebuild, toCompile, toDelete + ); + if (trackDependencies && !toCompile.isEmpty()) { // should add dependent files + // todo: drop this? + final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); + final PsiManager psiManager = PsiManager.getInstance(myProject); + for (final VirtualFile file : VfsUtil.toVirtualFileArray(toCompile)) { + if (fileTypeManager.getFileTypeByFile(file) == StdFileTypes.JAVA) { + final PsiFile psiFile = psiManager.findFile(file); + if (psiFile != null) { + addDependentFiles(psiFile, toCompile, compiler, context); + } + } + } + } + } + }); + + if (onlyCheckStatus) { + if (toDelete.isEmpty() && toCompile.isEmpty()) { + return false; + } + if (LOG.isDebugEnabled() || ourDebugMode) { + if (!toDelete.isEmpty()) { + final String message = "Found items to delete, compiler " + compiler.getDescription(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + if (!toCompile.isEmpty()) { + final String message = "Found items to compile, compiler " + compiler.getDescription(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + } + throw new ExitException(ExitStatus.CANCELLED); + } + + if (!toDelete.isEmpty()) { + try { + wereFilesDeleted[0] = syncOutputDir(context, toDelete); + } + catch (CacheCorruptedException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + } + } + + if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + compiler.compile(context, moduleChunk, VfsUtil.toVirtualFileArray(toCompile), sink); + } + } + finally { + context.getProgressIndicator().popState(); + } + return !toCompile.isEmpty() || wereFilesDeleted[0]; + } + + private static boolean syncOutputDir(final CompileContextEx context, final Collection> toDelete) throws CacheCorruptedException { + final DependencyCache dependencyCache = context.getDependencyCache(); + final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); + + final List filesToRefresh = new ArrayList(); + final boolean[] wereFilesDeleted = {false}; + CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ + public void run() throws CacheCorruptedException { + final long start = System.currentTimeMillis(); + try { + for (final Trinity trinity : toDelete) { + final File outputPath = trinity.getFirst(); + context.getProgressIndicator().checkCanceled(); + context.getProgressIndicator().setText2(outputPath.getPath()); + filesToRefresh.add(outputPath); + if (isTestMode) { + LOG.assertTrue(outputPath.exists()); + } + if (!deleteFile(outputPath)) { + if (isTestMode && outputPath.exists()) { + LOG.error("Was not able to delete output file: " + outputPath.getPath()); + } + continue; + } + wereFilesDeleted[0] = true; + + // update zip here + //final String outputDir = myOutputFinder.lookupOutputPath(outputPath); + //if (outputDir != null) { + // try { + // context.updateZippedOuput(outputDir, FileUtil.toSystemIndependentName(outputPath.getPath()).substring(outputDir.length() + 1)); + // } + // catch (IOException e) { + // LOG.info(e); + // } + //} + + final String className = trinity.getSecond(); + if (className != null) { + final int id = dependencyCache.getSymbolTable().getId(className); + dependencyCache.addTraverseRoot(id); + final boolean sourcePresent = trinity.getThird().booleanValue(); + if (!sourcePresent) { + dependencyCache.markSourceRemoved(id); + } + } + if (isTestMode) { + CompilerManagerImpl.addDeletedPath(outputPath.getPath()); + } + } + } + finally { + CompilerUtil.logDuration("Sync output directory", System.currentTimeMillis() - start); + CompilerUtil.refreshIOFiles(filesToRefresh); + } + } + }); + return wereFilesDeleted[0]; + } + + private void addDependentFiles(final PsiFile psiFile, Set toCompile, TranslatingCompiler compiler, CompileContext context) { + final DependenciesBuilder builder = new ForwardDependenciesBuilder(myProject, new AnalysisScope(psiFile)); + builder.analyze(); + final Map> dependencies = builder.getDependencies(); + final Set dependentFiles = dependencies.get(psiFile); + if (dependentFiles != null && !dependentFiles.isEmpty()) { + final TranslatingCompilerFilesMonitor monitor = TranslatingCompilerFilesMonitor.getInstance(); + for (final PsiFile dependentFile : dependentFiles) { + if (dependentFile instanceof PsiCompiledElement) { + continue; + } + final VirtualFile vFile = dependentFile.getVirtualFile(); + if (vFile == null || toCompile.contains(vFile)) { + continue; + } + if (!compiler.isCompilableFile(vFile, context)) { + continue; + } + if (!monitor.isMarkedForCompilation(myProject, vFile)) { + continue; // no need to compile since already compiled + } + toCompile.add(vFile); + addDependentFiles(dependentFile, toCompile, compiler, context); + } + } + } + + // [mike] performance optimization - this method is accessed > 15,000 times in Aurora + private String getModuleOutputPath(final Module module, boolean inTestSourceContent) { + final Map map = inTestSourceContent ? myModuleTestOutputPaths : myModuleOutputPaths; + String path = map.get(module); + if (path == null) { + path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent); + map.put(module, path); + } + + return path; + } + + private boolean processFiles(final FileProcessingCompilerAdapter adapter, + final boolean forceCompile, + final boolean checkScope, + final boolean onlyCheckStatus, final CacheDeferredUpdater cacheUpdater) throws ExitException, IOException { + final CompileContextEx context = (CompileContextEx)adapter.getCompileContext(); + final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler()); + final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems(); + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + return false; + } + if (LOG.isDebugEnabled() && items.length > 0) { + LOG.debug("Start processing files by " + adapter.getCompiler().getDescription()); + } + final CompileScope scope = context.getCompileScope(); + final List toProcess = new ArrayList(); + final Set allUrls = new HashSet(); + final IOException[] ex = {null}; + DumbService.getInstance(myProject).waitForSmartMode(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + for (FileProcessingCompiler.ProcessingItem item : items) { + final VirtualFile file = item.getFile(); + if (file == null) { + LOG.error("FileProcessingCompiler.ProcessingItem.getFile() must not return null: compiler " + adapter.getCompiler().getDescription()); + continue; + } + final String url = file.getUrl(); + allUrls.add(url); + if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) { + final ValidityState state = cache.getExtState(url); + final ValidityState itemState = item.getValidityState(); + if (state != null ? state.equalsTo(itemState) : itemState == null) { + continue; + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Adding item to process: " + url + "; saved ts= " + cache.getTimestamp(url) + "; VFS ts=" + file.getTimeStamp()); + } + toProcess.add(item); + } + } + catch (IOException e) { + ex[0] = e; + } + } + }); + + if (ex[0] != null) { + throw ex[0]; + } + + final Collection urls = cache.getUrls(); + final List urlsToRemove = new ArrayList(); + if (!urls.isEmpty()) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.processing.outdated.files"), new ThrowableRunnable(){ + public void run() throws IOException { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (final String url : urls) { + if (!allUrls.contains(url)) { + if (!checkScope || scope.belongs(url)) { + urlsToRemove.add(url); + } + } + } + } + }); + if (!onlyCheckStatus && !urlsToRemove.isEmpty()) { + for (final String url : urlsToRemove) { + adapter.processOutdatedItem(context, url, cache.getExtState(url)); + cache.remove(url); + } + } + } + }); + } + + if (onlyCheckStatus) { + if (urlsToRemove.isEmpty() && toProcess.isEmpty()) { + return false; + } + if (LOG.isDebugEnabled()) { + if (!urlsToRemove.isEmpty()) { + LOG.debug("Found urls to remove, compiler " + adapter.getCompiler().getDescription()); + for (String url : urlsToRemove) { + LOG.debug("\t" + url); + } + } + if (!toProcess.isEmpty()) { + LOG.debug("Found items to compile, compiler " + adapter.getCompiler().getDescription()); + for (FileProcessingCompiler.ProcessingItem item : toProcess) { + LOG.debug("\t" + item.getFile().getPresentableUrl()); + } + } + } + throw new ExitException(ExitStatus.CANCELLED); + } + + if (toProcess.isEmpty()) { + return false; + } + + final FileProcessingCompiler.ProcessingItem[] processed = + adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()])); + + if (processed.length == 0) { + return true; + } + CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { + public void run() { + final List vFiles = new ArrayList(processed.length); + for (FileProcessingCompiler.ProcessingItem aProcessed : processed) { + final VirtualFile file = aProcessed.getFile(); + vFiles.add(file); + if (LOG.isDebugEnabled()) { + LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); + } + + //final String path = file.getPath(); + //final String outputDir = myOutputFinder.lookupOutputPath(path); + //if (outputDir != null) { + // context.updateZippedOuput(outputDir, path.substring(outputDir.length() + 1)); + //} + } + LocalFileSystem.getInstance().refreshFiles(vFiles); + if (LOG.isDebugEnabled()) { + LOG.debug("Files after VFS refresh:"); + for (VirtualFile file : vFiles) { + LOG.debug("\t" + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); + } + } + for (FileProcessingCompiler.ProcessingItem item : processed) { + cacheUpdater.addFileForUpdate(item, cache); + } + } + }); + return true; + } + + private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) throws IOException { + return CompilerCacheManager.getInstance(myProject).getFileProcessingCompilerCache(compiler); + } + + private StateCache getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException { + return CompilerCacheManager.getInstance(myProject).getGeneratingCompilerCache(compiler); + } + + public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) { + final CompilerTask progressManagerTask = + new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, contentName, false); + final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, null, false, false); + + FileDocumentManager.getInstance().saveAllDocuments(); + + progressManagerTask.start(new Runnable() { + public void run() { + try { + task.execute(compileContext); + } + catch (ProcessCanceledException ex) { + // suppressed + } + finally { + compileContext.commitZipFiles(); + if (onTaskFinished != null) { + onTaskFinished.run(); + } + } + } + }, null); + } + + private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) { + final CompilerManager manager = CompilerManager.getInstance(myProject); + final ProgressIndicator progressIndicator = context.getProgressIndicator(); + progressIndicator.pushState(); + try { + CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks(); + if (tasks.length > 0) { + progressIndicator.setText(beforeTasks + ? CompilerBundle.message("progress.executing.precompile.tasks") + : CompilerBundle.message("progress.executing.postcompile.tasks")); + for (CompileTask task : tasks) { + if (!task.execute(context)) { + return false; + } + } + } + } + finally { + progressIndicator.popState(); + WindowManager.getInstance().getStatusBar(myProject).setInfo(""); + if (progressIndicator instanceof CompilerTask) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + ((CompilerTask)progressIndicator).showCompilerContent(); + } + }); + } + } + return true; + } + + private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) { + final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/; + final List modulesWithoutOutputPathSpecified = new ArrayList(); + boolean isProjectCompilePathSpecified = true; + final List modulesWithoutJdkAssigned = new ArrayList(); + final Set nonExistingOutputPaths = new HashSet(); + final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); + + for (final Module module : scopeModules) { + final boolean hasSources = hasSources(module, false); + final boolean hasTestSources = hasSources(module, true); + if (!hasSources && !hasTestSources) { + // If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333) + // todo still there may be problems with this approach if some generated files are attributed by this module + continue; + } + final Sdk jdk = ModuleRootManager.getInstance(module).getSdk(); + if (jdk == null) { + modulesWithoutJdkAssigned.add(module.getName()); + } + final String outputPath = getModuleOutputPath(module, false); + final String testsOutputPath = getModuleOutputPath(module, true); + if (outputPath == null && testsOutputPath == null) { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + else { + if (outputPath != null) { + final File file = new File(outputPath.replace('/', File.separatorChar)); + if (!file.exists()) { + nonExistingOutputPaths.add(file); + } + } + else { + if (hasSources) { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + } + if (testsOutputPath != null) { + final File f = new File(testsOutputPath.replace('/', File.separatorChar)); + if (!f.exists()) { + nonExistingOutputPaths.add(f); + } + } + else { + if (hasTestSources) { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + } + if (config.isAnnotationProcessorsEnabled() && config.isAnnotationProcessingEnabled(module)) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path == null) { + final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(module.getProject()); + if (extension == null || extension.getCompilerOutputUrl() == null) { + isProjectCompilePathSpecified = false; + } + else { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + } + else { + final File file = new File(path); + if (!file.exists()) { + nonExistingOutputPaths.add(file); + } + } + } + } + } + if (!modulesWithoutJdkAssigned.isEmpty()) { + showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title")); + return false; + } + + if (!isProjectCompilePathSpecified) { + final String message = CompilerBundle.message("error.project.output.not.specified"); + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.error(message); + } + + Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + ProjectSettingsService.getInstance(myProject).openProjectSettings(); + return false; + } + + if (!modulesWithoutOutputPathSpecified.isEmpty()) { + showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME); + return false; + } + + if (!nonExistingOutputPaths.isEmpty()) { + for (File file : nonExistingOutputPaths) { + final boolean succeeded = file.mkdirs(); + if (!succeeded) { + if (file.exists()) { + // for overlapping paths, this one might have been created as an intermediate path on a previous iteration + continue; + } + Messages.showMessageDialog(myProject, CompilerBundle.message("error.failed.to.create.directory", file.getPath()), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + return false; + } + } + final Boolean refreshSuccess = + new WriteAction() { + @Override + protected void run(Result result) throws Throwable { + LocalFileSystem.getInstance().refreshIoFiles(nonExistingOutputPaths); + Boolean res = Boolean.TRUE; + for (File file : nonExistingOutputPaths) { + if (LocalFileSystem.getInstance().findFileByIoFile(file) == null) { + res = Boolean.FALSE; + break; + } + } + result.setResult(res); + } + }.execute().getResultObject(); + + if (!refreshSuccess.booleanValue()) { + return false; + } + dropScopesCaches(); + } + + if (checkOutputAndSourceIntersection) { + if (myShouldClearOutputDirectory) { + if (!validateOutputAndSourcePathsIntersection()) { + return false; + } + } + } + final List> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules)); + for (final Chunk chunk : chunks) { + final Set chunkModules = chunk.getNodes(); + if (chunkModules.size() <= 1) { + continue; // no need to check one-module chunks + } + if (config.isAnnotationProcessorsEnabled()) { + for (Module chunkModule : chunkModules) { + if (config.isAnnotationProcessingEnabled(chunkModule)) { + showCyclesNotSupportedForAnnotationProcessors(chunkModules.toArray(new Module[chunkModules.size()])); + return false; + } + } + } + Sdk jdk = null; + LanguageLevel languageLevel = null; + for (final Module module : chunkModules) { + final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk(); + if (jdk == null) { + jdk = moduleJdk; + } + else { + if (!jdk.equals(moduleJdk)) { + showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()])); + return false; + } + } + + LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); + if (languageLevel == null) { + languageLevel = moduleLanguageLevel; + } + else { + if (!languageLevel.equals(moduleLanguageLevel)) { + showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()])); + return false; + } + } + } + } + final Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class); + for (Compiler compiler : allCompilers) { + if (!compiler.validateConfiguration(scope)) { + return false; + } + } + return true; + } + + private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) { + LOG.assertTrue(modulesInChunk.length > 0); + String moduleNameToSelect = modulesInChunk[0].getName(); + final String moduleNames = getModulesString(modulesInChunk); + Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(moduleNameToSelect, null); + } + + private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) { + LOG.assertTrue(modulesInChunk.length > 0); + String moduleNameToSelect = modulesInChunk[0].getName(); + final String moduleNames = getModulesString(modulesInChunk); + Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(moduleNameToSelect, null); + } + + private void showCyclesNotSupportedForAnnotationProcessors(Module[] modulesInChunk) { + LOG.assertTrue(modulesInChunk.length > 0); + String moduleNameToSelect = modulesInChunk[0].getName(); + final String moduleNames = getModulesString(modulesInChunk); + Messages.showMessageDialog(myProject, CompilerBundle.message("error.annotation.processing.not.supported.for.module.cycles", moduleNames), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(moduleNameToSelect, null); + } + + private static String getModulesString(Module[] modulesInChunk) { + final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc(); + try { + for (Module module : modulesInChunk) { + if (moduleNames.length() > 0) { + moduleNames.append("\n"); + } + moduleNames.append("\"").append(module.getName()).append("\""); + } + return moduleNames.toString(); + } + finally { + StringBuilderSpinAllocator.dispose(moduleNames); + } + } + + private static boolean hasSources(Module module, boolean checkTestSources) { + final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); + for (final ContentEntry contentEntry : contentEntries) { + final SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); + for (final SourceFolder sourceFolder : sourceFolders) { + if (sourceFolder.getFile() == null) { + continue; // skip invalid source folders + } + if (checkTestSources) { + if (sourceFolder.isTestSource()) { + return true; + } + } + else { + if (!sourceFolder.isTestSource()) { + return true; + } + } + } + } + return false; + } + + private void showNotSpecifiedError(@NonNls final String resourceId, List modules, String tabNameToSelect) { + String nameToSelect = null; + final StringBuilder names = StringBuilderSpinAllocator.alloc(); + final String message; + try { + final int maxModulesToShow = 10; + for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) { + if (nameToSelect == null) { + nameToSelect = name; + } + if (names.length() > 0) { + names.append(",\n"); + } + names.append("\""); + names.append(name); + names.append("\""); + } + if (modules.size() > maxModulesToShow) { + names.append(",\n..."); + } + message = CompilerBundle.message(resourceId, modules.size(), names.toString()); + } + finally { + StringBuilderSpinAllocator.dispose(names); + } + + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.error(message); + } + + Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(nameToSelect, tabNameToSelect); + } + + private boolean validateOutputAndSourcePathsIntersection() { + final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); + final VirtualFile[] outputPaths = CompilerPathsEx.getOutputDirectories(allModules); + final Set affectedOutputPaths = new HashSet(); + for (Module allModule : allModules) { + final ModuleRootManager rootManager = ModuleRootManager.getInstance(allModule); + final VirtualFile[] sourceRoots = rootManager.getSourceRoots(); + for (final VirtualFile outputPath : outputPaths) { + for (VirtualFile sourceRoot : sourceRoots) { + if (VfsUtil.isAncestor(outputPath, sourceRoot, true) || VfsUtil.isAncestor(sourceRoot, outputPath, false)) { + affectedOutputPaths.add(outputPath); + } + } + } + } + if (!affectedOutputPaths.isEmpty()) { + final StringBuilder paths = new StringBuilder(); + for (final VirtualFile affectedOutputPath : affectedOutputPaths) { + if (paths.length() < 0) { + paths.append("\n"); + } + paths.append(affectedOutputPath.getPath().replace('/', File.separatorChar)); + } + final int answer = Messages.showOkCancelDialog(myProject, + CompilerBundle.message("warning.sources.under.output.paths", paths.toString()), + CommonBundle.getErrorTitle(), Messages.getWarningIcon()); + if (answer == 0) { // ok + myShouldClearOutputDirectory = false; + return true; + } + else { + return false; + } + } + return true; + } + + private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) { + ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect, false); + } + + private static VirtualFile lookupVFile(final LocalFileSystem lfs, final String path) { + final File file = new File(path); + + VirtualFile vFile = lfs.findFileByIoFile(file); + if (vFile != null) { + return vFile; + } + + final boolean justCreated = file.mkdirs(); + vFile = lfs.refreshAndFindFileByIoFile(file); + + assert vFile != null: "Virtual file not found for " + file.getPath() + "; mkdirs() exit code is " + justCreated; + + return vFile; + } + + private static class CacheDeferredUpdater { + private final Map>> myData = new java.util.HashMap>>(); + + public void addFileForUpdate(final FileProcessingCompiler.ProcessingItem item, FileProcessingCompilerStateCache cache) { + final VirtualFile file = item.getFile(); + List> list = myData.get(file); + if (list == null) { + list = new ArrayList>(); + myData.put(file, list); + } + list.add(new Pair(cache, item)); + } + + public void doUpdate() throws IOException{ + final IOException[] ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + for (Map.Entry>> entry : myData.entrySet()) { + for (Pair pair : entry.getValue()) { + final FileProcessingCompiler.ProcessingItem item = pair.getSecond(); + pair.getFirst().update(entry.getKey(), item.getValidityState()); + } + } + } + catch (IOException e) { + ex[0] = e; + } + } + }); + if (ex[0] != null) { + throw ex[0]; + } + } + } + + private static class TranslatorsOutputSink implements TranslatingCompiler.OutputSink { + final Map> myPostponedItems = new HashMap>(); + private final CompileContextEx myContext; + private final TranslatingCompiler[] myCompilers; + private int myCurrentCompilerIdx; + private final Set myCompiledSources = new HashSet(); + //private LinkedBlockingQueue myFutures = new LinkedBlockingQueue(); + + private TranslatorsOutputSink(CompileContextEx context, TranslatingCompiler[] compilers) { + myContext = context; + myCompilers = compilers; + } + + public void setCurrentCompilerIndex(int index) { + myCurrentCompilerIdx = index; + } + + public Set getCompiledSources() { + return Collections.unmodifiableSet(myCompiledSources); + } + + public void add(final String outputRoot, final Collection items, final VirtualFile[] filesToRecompile) { + for (TranslatingCompiler.OutputItem item : items) { + final VirtualFile file = item.getSourceFile(); + if (file != null) { + myCompiledSources.add(file); + } + } + final TranslatingCompiler compiler = myCompilers[myCurrentCompilerIdx]; + if (compiler instanceof IntermediateOutputCompiler) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final List outputs = new ArrayList(); + for (TranslatingCompiler.OutputItem item : items) { + final VirtualFile vFile = lfs.findFileByPath(item.getOutputPath()); + if (vFile != null) { + outputs.add(vFile); + } + } + myContext.markGenerated(outputs); + } + final int nextCompilerIdx = myCurrentCompilerIdx + 1; + try { + if (nextCompilerIdx < myCompilers.length ) { + final Map> updateNow = new java.util.HashMap>(); + // process postponed + for (Map.Entry> entry : myPostponedItems.entrySet()) { + final String outputDir = entry.getKey(); + final Collection postponed = entry.getValue(); + for (Iterator it = postponed.iterator(); it.hasNext();) { + TranslatingCompiler.OutputItem item = it.next(); + boolean shouldPostpone = false; + for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { + shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext); + if (shouldPostpone) { + break; + } + } + if (!shouldPostpone) { + // the file is not compilable by the rest of compilers, so it is safe to update it now + it.remove(); + addItemToMap(updateNow, outputDir, item); + } + } + } + // process items from current compilation + for (TranslatingCompiler.OutputItem item : items) { + boolean shouldPostpone = false; + for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { + shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext); + if (shouldPostpone) { + break; + } + } + if (shouldPostpone) { + // the file is compilable by the next compiler in row, update should be postponed + addItemToMap(myPostponedItems, outputRoot, item); + } + else { + addItemToMap(updateNow, outputRoot, item); + } + } + + if (updateNow.size() == 1) { + final Map.Entry> entry = updateNow.entrySet().iterator().next(); + final String outputDir = entry.getKey(); + final Collection itemsToUpdate = entry.getValue(); + TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, filesToRecompile); + } + else { + for (Map.Entry> entry : updateNow.entrySet()) { + final String outputDir = entry.getKey(); + final Collection itemsToUpdate = entry.getValue(); + TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, VirtualFile.EMPTY_ARRAY); + } + if (filesToRecompile.length > 0) { + TranslatingCompilerFilesMonitor.getInstance().update(myContext, null, Collections.emptyList(), filesToRecompile); + } + } + } + else { + TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputRoot, items, filesToRecompile); + } + } + catch (IOException e) { + LOG.info(e); + myContext.requestRebuildNextTime(e.getMessage()); + } + } + + private static void addItemToMap(Map> map, String outputDir, TranslatingCompiler.OutputItem item) { + Collection collection = map.get(outputDir); + if (collection == null) { + collection = new ArrayList(); + map.put(outputDir, collection); + } + collection.add(item); + } + + public void flushPostponedItems() { + final TranslatingCompilerFilesMonitor filesMonitor = TranslatingCompilerFilesMonitor.getInstance(); + try { + for (Map.Entry> entry : myPostponedItems.entrySet()) { + final String outputDir = entry.getKey(); + final Collection items = entry.getValue(); + filesMonitor.update(myContext, outputDir, items, VirtualFile.EMPTY_ARRAY); + } + } + catch (IOException e) { + LOG.info(e); + myContext.requestRebuildNextTime(e.getMessage()); + } + } + } + + private static class DependentClassesCumulativeFilter implements Function>, Pair>> { + + private final TIntHashSet myProcessedNames = new TIntHashSet(); + private final Set myProcessedFiles = new HashSet(); + + public Pair> fun(Pair> deps) { + final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst()); + currentDeps.removeAll(myProcessedNames.toArray()); + myProcessedNames.addAll(deps.getFirst()); + + final Set depFiles = new HashSet(deps.getSecond()); + depFiles.removeAll(myProcessedFiles); + myProcessedFiles.addAll(deps.getSecond()); + return new Pair>(currentDeps.toArray(), depFiles); + } + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java b/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java index ffdbe7c9ecd1..5c7be0535dca 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java @@ -1,1505 +1,1505 @@ -/* - * 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.compiler.impl; - -import com.intellij.ProjectTopics; -import com.intellij.compiler.CompilerConfiguration; -import com.intellij.compiler.CompilerIOUtil; -import com.intellij.compiler.make.MakeUtil; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.compiler.*; -import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ProjectManagerAdapter; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; -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.util.containers.ContainerUtil; -import com.intellij.util.containers.SLRUCache; -import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.indexing.IndexInfrastructure; -import com.intellij.util.io.*; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.*; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.util.*; - -/** - * @author Eugene Zhuravlev - * Date: Jun 3, 2008 - * - * A source file is scheduled for recompilation if - * 1. its timestamp has changed - * 2. one of its corresponding output files was deleted - * 3. output root of containing module has changed - * - * An output file is scheduled for deletion if: - * 1. corresponding source file has been scheduled for recompilation (see above) - * 2. corresponding source file has been deleted - */ - -public class TranslatingCompilerFilesMonitor implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.TranslatingCompilerFilesMonitor"); - public static boolean ourDebugMode = false; - @NonNls - private static final String PATHS_TO_DELETE_FILENAME = "paths_to_delete.dat"; - private static final String OUTPUT_ROOTS_FILENAME = "output_roots.dat"; - private static final FileAttribute ourSourceFileAttribute = new FileAttribute("_make_source_file_info_", 3); - private static final FileAttribute ourOutputFileAttribute = new FileAttribute("_make_output_file_info_", 3); - - private final Object myDataLock = new Object(); - private final TIntObjectHashMap mySourcesToRecompile = new TIntObjectHashMap(); // ProjectId->set of source file paths - private PersistentHashMap>> myOutputRootsStorage; // ProjectId->map[moduleId->Pair(outputDirId, testOutputDirId)] - private final TIntObjectHashMap> myOutputsToDelete = new TIntObjectHashMap>(); // Map: projectId -> Map{output path -> [sourceUrl; classname]} - private final SLRUCache myGeneratedDataPaths = new SLRUCache(8, 8) { - @NotNull - public File createValue(final Project project) { - Disposer.register(project, new Disposable() { - public void dispose() { - myGeneratedDataPaths.remove(project); - } - }); - return CompilerPaths.getGeneratedDataDirectory(project); - } - }; - private final SLRUCache>> myProjectOutputRoots = new SLRUCache>>(2, 2) { - protected void onDropFromCache(Integer key, TIntObjectHashMap> value) { - try { - myOutputRootsStorage.put(key, value); - } - catch (IOException e) { - LOG.info(e); - } - } - - @NotNull - public TIntObjectHashMap> createValue(Integer key) { - TIntObjectHashMap> map = null; - try { - ensureOutputStorageInitialized(); - map = myOutputRootsStorage.get(key); - } - catch (IOException e) { - LOG.info(e); - } - return map != null? map : new TIntObjectHashMap>(); - } - }; - private final ProjectManager myProjectManager; - private final TIntHashSet myInitInProgress = new TIntHashSet(); // projectId fior successfully initialized projects - private final Object myInitializationLock = new Object(); - - public TranslatingCompilerFilesMonitor(VirtualFileManager vfsManager, ProjectManager projectManager, Application application) { - myProjectManager = projectManager; - - projectManager.addProjectManagerListener(new MyProjectManagerListener()); - vfsManager.addVirtualFileListener(new MyVfsListener(), application); - } - - public static TranslatingCompilerFilesMonitor getInstance() { - return ApplicationManager.getApplication().getComponent(TranslatingCompilerFilesMonitor.class); - } - - @Nullable - public static VirtualFile getSourceFileByOutput(VirtualFile outputFile) { - final OutputFileInfo outputFileInfo = loadOutputInfo(outputFile); - if (outputFileInfo != null) { - final String path = outputFileInfo.getSourceFilePath(); - if (path != null) { - return LocalFileSystem.getInstance().findFileByPath(path); - } - } - return null; - } - - public void collectFiles(CompileContext context, final TranslatingCompiler compiler, Iterator scopeSrcIterator, boolean forceCompile, - final boolean isRebuild, - Collection toCompile, - Collection> toDelete) { - final Project project = context.getProject(); - final int projectId = getProjectId(project); - final CompilerConfiguration configuration = CompilerConfiguration.getInstance(project); - final boolean _forceCompile = forceCompile || isRebuild; - final Set selectedForRecompilation = new HashSet(); - synchronized (myDataLock) { - final TIntHashSet pathsToRecompile = mySourcesToRecompile.get(projectId); - if (_forceCompile || pathsToRecompile != null && !pathsToRecompile.isEmpty()) { - if (ourDebugMode) { - System.out.println("Analysing potentially recompilable files for " + compiler.getDescription()); - } - while (scopeSrcIterator.hasNext()) { - final VirtualFile file = scopeSrcIterator.next(); - if (!file.isValid()) { - if (LOG.isDebugEnabled() || ourDebugMode) { - LOG.debug("Skipping invalid file " + file.getPresentableUrl()); - if (ourDebugMode) { - System.out.println("\t SKIPPED(INVALID) " + file.getPresentableUrl()); - } - } - continue; - } - final int fileId = getFileId(file); - if (_forceCompile) { - if (compiler.isCompilableFile(file, context) && !configuration.isExcludedFromCompilation(file)) { - toCompile.add(file); - if (ourDebugMode) { - System.out.println("\t INCLUDED " + file.getPresentableUrl()); - } - selectedForRecompilation.add(file); - if (pathsToRecompile == null || !pathsToRecompile.contains(fileId)) { - addSourceForRecompilation(projectId, file, null); - } - } - else { - if (ourDebugMode) { - System.out.println("\t NOT COMPILABLE OR EXCLUDED " + file.getPresentableUrl()); - } - } - } - else if (pathsToRecompile.contains(fileId)) { - if (compiler.isCompilableFile(file, context) && !configuration.isExcludedFromCompilation(file)) { - toCompile.add(file); - if (ourDebugMode) { - System.out.println("\t INCLUDED " + file.getPresentableUrl()); - } - selectedForRecompilation.add(file); - } - else { - if (ourDebugMode) { - System.out.println("\t NOT COMPILABLE OR EXCLUDED " + file.getPresentableUrl()); - } - } - } - else { - if (ourDebugMode) { - System.out.println("\t NOT INCLUDED " + file.getPresentableUrl()); - } - } - } - } - // it is important that files to delete are collected after the files to compile (see what happens if forceCompile == true) - if (!isRebuild) { - final Map outputsToDelete = myOutputsToDelete.get(projectId); - if (outputsToDelete != null) { - final VirtualFileManager vfm = VirtualFileManager.getInstance(); - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - final List zombieEntries = new ArrayList(); - for (String outputPath : outputsToDelete.keySet()) { - final SourceUrlClassNamePair classNamePair = outputsToDelete.get(outputPath); - final String sourceUrl = classNamePair.getSourceUrl(); - final VirtualFile srcFile = vfm.findFileByUrl(sourceUrl); - final boolean sourcePresent = srcFile != null; - if (sourcePresent) { - if (!compiler.isCompilableFile(srcFile, context)) { - continue; // do not collect files that were compiled by another compiler - } - if (!selectedForRecompilation.contains(srcFile)) { - if (!isMarkedForRecompilation(projectId, getFileId(srcFile))) { - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "Found zombie entry (output is marked, but source is present and up-to-date): " + outputPath; - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - zombieEntries.add(outputPath); - } - continue; - } - } - if (lfs.findFileByPath(outputPath) != null) { - //noinspection UnnecessaryBoxing - final File file = new File(outputPath); - toDelete.add(new Trinity(file, classNamePair.getClassName(), Boolean.valueOf(sourcePresent))); - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "Found file to delete: " + file; - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - } - else { - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "Found zombie entry marked for deletion: " + outputPath; - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - // must be gagbage entry, should cleanup - zombieEntries.add(outputPath); - } - } - for (String path : zombieEntries) { - unmarkOutputPathForDeletion(path); - } - } - } - } - } - - private static int getFileId(final VirtualFile file) { - return FileBasedIndex.getFileId(file); - } - - private static VirtualFile findFileById(int id) { - return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id); - } - - public void update(final CompileContext context, final String outputRoot, final Collection successfullyCompiled, final VirtualFile[] filesToRecompile) - throws IOException { - final Project project = context.getProject(); - final int projectId = getProjectId(project); - if (successfullyCompiled.size() > 0) { - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - final IOException[] exceptions = {null}; - // need read action here to ensure that no modifications were made to VFS while updating file attributes - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - final Map compiledSources = new HashMap(); - final Set forceRecompile = new HashSet(); - - for (TranslatingCompiler.OutputItem item : successfullyCompiled) { - final VirtualFile sourceFile = item.getSourceFile(); - final boolean isSourceValid = sourceFile.isValid(); - SourceFileInfo srcInfo = compiledSources.get(sourceFile); - if (isSourceValid && srcInfo == null) { - srcInfo = loadSourceInfo(sourceFile); - if (srcInfo != null) { - srcInfo.clearPaths(projectId); - } - else { - srcInfo = new SourceFileInfo(); - } - compiledSources.put(sourceFile, srcInfo); - } - - final String outputPath = item.getOutputPath(); - if (outputPath != null) { // can be null for packageinfo - final VirtualFile outputFile = lfs.findFileByPath(outputPath); - - //assert outputFile != null : "Virtual file was not found for \"" + outputPath + "\""; - assert outputRoot != null; - - if (outputFile != null) { - if (!sourceFile.equals(outputFile)) { - final String className = MakeUtil.relativeClassPathToQName(outputPath.substring(outputRoot.length()), '/'); - if (isSourceValid) { - srcInfo.addOutputPath(projectId, outputPath); - saveOutputInfo(outputFile, new OutputFileInfo(sourceFile.getPath(), className)); - } - else { - markOutputPathForDeletion(projectId, outputPath, className, sourceFile.getUrl()); - } - } - } - else { // output file was not found - LOG.warn("TranslatingCompilerFilesMonitor.update(): Virtual file was not found for \"" + outputPath + "\""); - if (isSourceValid) { - forceRecompile.add(sourceFile); - } - } - } - } - final long compilationStartStamp = ((CompileContextEx)context).getStartCompilationStamp(); - for (Map.Entry entry : compiledSources.entrySet()) { - final SourceFileInfo info = entry.getValue(); - final VirtualFile file = entry.getKey(); - - final long fileStamp = file.getTimeStamp(); - info.updateTimestamp(projectId, fileStamp); - saveSourceInfo(file, info); - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "Unschedule recompilation (successfully compiled) " + file.getPresentableUrl(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - removeSourceForRecompilation(projectId, Math.abs(getFileId(file))); - if ((fileStamp > compilationStartStamp && !((CompileContextEx)context).isGenerated(file)) || forceRecompile.contains(file)) { - // changes were made during compilation, need to re-schedule compilation - // it is important to invoke removeSourceForRecompilation() before this call to make sure - // the corresponding output paths will be scheduled for deletion - addSourceForRecompilation(projectId, file, info); - } - } - } - catch (IOException e) { - exceptions[0] = e; - } - } - }); - if (exceptions[0] != null) { - throw exceptions[0]; - } - } - - if (filesToRecompile.length > 0) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (VirtualFile file : filesToRecompile) { - if (file.isValid()) { - addSourceForRecompilation(projectId, file, null); - } - } - } - }); - } - } - - public void updateOutputRootsLayout(Project project) { - final TIntObjectHashMap> map = buildOutputRootsLayout(new ProjectRef(project)); - synchronized (myProjectOutputRoots) { - myProjectOutputRoots.put(getProjectId(project), map); - } - } - - @NotNull - public String getComponentName() { - return "TranslatingCompilerFilesMonitor"; - } - - public void initComponent() { - final File file = new File(CompilerPaths.getCompilerSystemDirectory(), PATHS_TO_DELETE_FILENAME); - try { - final DataInputStream is = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); - try { - final int projectsCount = is.readInt(); - synchronized (myDataLock) { - for (int idx = 0; idx < projectsCount; idx++) { - final int projectId = is.readInt(); - final int size = is.readInt(); - if (size > 0) { - final Map map = new HashMap(); - myOutputsToDelete.put(projectId, map); - for (int i = 0; i < size; i++) { - final String outputPath = FileUtil.toSystemIndependentName(CompilerIOUtil.readString(is)); - final String srcUrl = CompilerIOUtil.readString(is); - final String className = CompilerIOUtil.readString(is); - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "INIT path to delete: " + outputPath; - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - map.put(outputPath, new SourceUrlClassNamePair(srcUrl, className)); - } - } - } - } - } - finally { - is.close(); - } - } - catch (FileNotFoundException ignored) { - } - catch (IOException e) { - LOG.info(e); - synchronized (myDataLock) { - myOutputsToDelete.clear(); - } - FileUtil.delete(file); - } - - ensureOutputStorageInitialized(); - } - - private void ensureOutputStorageInitialized() { - if (myOutputRootsStorage != null) { - return; - } - final File rootsFile = new File(CompilerPaths.getCompilerSystemDirectory(), OUTPUT_ROOTS_FILENAME); - try { - initOutputRootsFile(rootsFile); - } - catch (IOException e) { - LOG.info(e); - FileUtil.delete(rootsFile); - try { - initOutputRootsFile(rootsFile); - } - catch (IOException e1) { - LOG.error(e1); - } - } - } - - private TIntObjectHashMap> buildOutputRootsLayout(ProjectRef projRef) { - final TIntObjectHashMap> map = new TIntObjectHashMap>(); - for (Module module : ModuleManager.getInstance(projRef.get()).getModules()) { - final CompilerModuleExtension manager = CompilerModuleExtension.getInstance(module); - if (manager != null) { - final VirtualFile output = manager.getCompilerOutputPath(); - final int first = output != null? Math.abs(getFileId(output)) : -1; - final VirtualFile testsOutput = manager.getCompilerOutputPathForTests(); - final int second = testsOutput != null? Math.abs(getFileId(testsOutput)) : -1; - map.put(getModuleId(module), new Pair(first, second)); - } - } - return map; - } - - private void initOutputRootsFile(File rootsFile) throws IOException { - myOutputRootsStorage = new PersistentHashMap>>(rootsFile, new EnumeratorIntegerDescriptor(), new DataExternalizer>>() { - public void save(DataOutput out, TIntObjectHashMap> value) throws IOException { - for (final TIntObjectIterator> it = value.iterator(); it.hasNext();) { - it.advance(); - DataInputOutputUtil.writeINT(out, it.key()); - final Pair pair = it.value(); - DataInputOutputUtil.writeINT(out, pair.first); - DataInputOutputUtil.writeINT(out, pair.second); - } - } - - public TIntObjectHashMap> read(DataInput in) throws IOException { - final DataInputStream _in = (DataInputStream)in; - final TIntObjectHashMap> map = new TIntObjectHashMap>(); - while (_in.available() > 0) { - final int key = DataInputOutputUtil.readINT(_in); - final int first = DataInputOutputUtil.readINT(_in); - final int second = DataInputOutputUtil.readINT(_in); - map.put(key, new Pair(first, second)); - } - return map; - } - }); - } - - public void disposeComponent() { - final File file = new File(CompilerPaths.getCompilerSystemDirectory(), PATHS_TO_DELETE_FILENAME); - try { - FileUtil.createParentDirs(file); - final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); - try { - synchronized (myDataLock) { - final int[] keys = myOutputsToDelete.keys(); - os.writeInt(keys.length); - for (int projectId : keys) { - final Map projectOutputs = myOutputsToDelete.get(projectId); - os.writeInt(projectId); - if (projectOutputs != null) { - os.writeInt(projectOutputs.size()); - for (Map.Entry entry : projectOutputs.entrySet()) { - CompilerIOUtil.writeString(entry.getKey(), os); - final SourceUrlClassNamePair pair = entry.getValue(); - CompilerIOUtil.writeString(pair.getSourceUrl(), os); - CompilerIOUtil.writeString(pair.getClassName(), os); - } - } - else { - os.writeInt(0); - } - } - } - } - finally { - os.close(); - synchronized (myProjectOutputRoots) { - myProjectOutputRoots.clear(); - } - myOutputRootsStorage.close(); - } - } - catch (IOException e) { - LOG.error(e); - } - } - - @Nullable - private static SourceFileInfo loadSourceInfo(final VirtualFile file) { - try { - final DataInputStream is = ourSourceFileAttribute.readAttribute(file); - if (is != null) { - try { - return new SourceFileInfo(is); - } - finally { - is.close(); - } - } - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException) { - LOG.info(e); // ignore IOExceptions - } - else { - throw e; - } - } - catch (IOException ignored) { - LOG.info(ignored); - } - return null; - } - - public static void removeSourceInfo(VirtualFile file) { - saveSourceInfo(file, new SourceFileInfo()); - } - - private static void saveSourceInfo(VirtualFile file, SourceFileInfo descriptor) { - final DataOutputStream out = ourSourceFileAttribute.writeAttribute(file); - try { - try { - descriptor.save(out); - } - finally { - out.close(); - } - } - catch (IOException ignored) { - LOG.info(ignored); - } - } - - @Nullable - private static OutputFileInfo loadOutputInfo(final VirtualFile file) { - try { - final DataInputStream is = ourOutputFileAttribute.readAttribute(file); - if (is != null) { - try { - return new OutputFileInfo(is); - } - finally { - is.close(); - } - } - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException) { - LOG.info(e); // ignore IO exceptions - } - else { - throw e; - } - } - catch (IOException ignored) { - LOG.info(ignored); - } - return null; - } - - private static void saveOutputInfo(VirtualFile file, OutputFileInfo descriptor) { - final DataOutputStream out = ourOutputFileAttribute.writeAttribute(file); - try { - try { - descriptor.save(out); - } - finally { - out.close(); - } - } - catch (IOException ignored) { - LOG.info(ignored); - } - } - - private static int getProjectId(Project project) { - try { - return FSRecords.getNames().enumerate(project.getLocationHash()); - } - catch (IOException e) { - LOG.info(e); - } - return -1; - } - - private static int getModuleId(Module module) { - try { - return FSRecords.getNames().enumerate(module.getName()); - } - catch (IOException e) { - LOG.info(e); - } - return -1; - } - - private static class OutputFileInfo { - private final int mySourcePath; - - private final int myClassName; - - OutputFileInfo(final String sourcePath, @Nullable String className) throws IOException { - final PersistentStringEnumerator symtable = FSRecords.getNames(); - mySourcePath = symtable.enumerate(sourcePath); - myClassName = className != null? symtable.enumerate(className) : -1; - } - - OutputFileInfo(final DataInput in) throws IOException { - mySourcePath = in.readInt(); - myClassName = in.readInt(); - } - - String getSourceFilePath() { - try { - return FSRecords.getNames().valueOf(mySourcePath); - } - catch (IOException e) { - LOG.info(e); - } - return null; - } - - @Nullable - public String getClassName() { - try { - return myClassName < 0? null : FSRecords.getNames().valueOf(myClassName); - } - catch (IOException e) { - LOG.info(e); - } - return null; - } - - public void save(final DataOutput out) throws IOException { - out.writeInt(mySourcePath); - out.writeInt(myClassName); - } - } - - private static class SourceFileInfo { - private TIntLongHashMap myTimestamps; // ProjectId -> last compiled stamp - private TIntObjectHashMap myProjectToOutputPathMap; // ProjectId -> either a single output path or a set of output paths - - private SourceFileInfo() { - } - - private SourceFileInfo(@NotNull DataInput in) throws IOException { - final int projCount = in.readInt(); - for (int idx = 0; idx < projCount; idx++) { - final int projectId = in.readInt(); - final long stamp = in.readLong(); - updateTimestamp(projectId, stamp); - - final int pathsCount = in.readInt(); - for (int i = 0; i < pathsCount; i++) { - final int path = in.readInt(); - addOutputPath(projectId, path); - } - } - } - - public void save(@NotNull final DataOutput out) throws IOException { - final int[] projects = getProjectIds().toArray(); - out.writeInt(projects.length); - for (int projectId : projects) { - out.writeInt(projectId); - out.writeLong(getTimestamp(projectId)); - final Object value = myProjectToOutputPathMap != null? myProjectToOutputPathMap.get(projectId) : null; - if (value instanceof Integer) { - out.writeInt(1); - out.writeInt(((Integer)value).intValue()); - } - else if (value instanceof TIntHashSet) { - final TIntHashSet set = (TIntHashSet)value; - out.writeInt(set.size()); - final IOException[] ex = new IOException[] {null}; - set.forEach(new TIntProcedure() { - public boolean execute(final int value) { - try { - out.writeInt(value); - return true; - } - catch (IOException e) { - ex[0] = e; - return false; - } - } - }); - if (ex[0] != null) { - throw ex[0]; - } - } - else { - out.writeInt(0); - } - } - } - - private void updateTimestamp(final int projectId, final long stamp) { - if (stamp > 0L) { - if (myTimestamps == null) { - myTimestamps = new TIntLongHashMap(1, 0.98f); - } - myTimestamps.put(projectId, stamp); - } - else { - if (myTimestamps != null) { - myTimestamps.remove(projectId); - } - } - } - - TIntHashSet getProjectIds() { - final TIntHashSet result = new TIntHashSet(); - if (myTimestamps != null) { - result.addAll(myTimestamps.keys()); - } - if (myProjectToOutputPathMap != null) { - result.addAll(myProjectToOutputPathMap.keys()); - } - return result; - } - - private void addOutputPath(final int projectId, String outputPath) { - try { - addOutputPath(projectId, FSRecords.getNames().enumerate(outputPath)); - } - catch (IOException e) { - LOG.info(e); - } - } - - private void addOutputPath(final int projectId, final int outputPath) { - if (myProjectToOutputPathMap == null) { - myProjectToOutputPathMap = new TIntObjectHashMap(1, 0.98f); - myProjectToOutputPathMap.put(projectId, outputPath); - } - else { - final Object val = myProjectToOutputPathMap.get(projectId); - if (val == null) { - myProjectToOutputPathMap.put(projectId, outputPath); - } - else { - TIntHashSet set; - if (val instanceof Integer) { - set = new TIntHashSet(); - set.add(((Integer)val).intValue()); - myProjectToOutputPathMap.put(projectId, set); - } - else { - assert val instanceof TIntHashSet; - set = (TIntHashSet)val; - } - set.add(outputPath); - } - } - } - - public void clearPaths(final int projectId){ - if (myProjectToOutputPathMap != null) { - myProjectToOutputPathMap.remove(projectId); - } - } - - long getTimestamp(final int projectId) { - return myTimestamps == null? -1L : myTimestamps.get(projectId); - } - - void processOutputPaths(final int projectId, final Proc proc){ - if (myProjectToOutputPathMap != null) { - try { - final PersistentStringEnumerator symtable = FSRecords.getNames(); - final Object val = myProjectToOutputPathMap.get(projectId); - if (val instanceof Integer) { - proc.execute(projectId, symtable.valueOf(((Integer)val).intValue())); - } - else if (val instanceof TIntHashSet) { - ((TIntHashSet)val).forEach(new TIntProcedure() { - public boolean execute(final int value) { - try { - proc.execute(projectId, symtable.valueOf(value)); - return true; - } - catch (IOException e) { - LOG.info(e); - return false; - } - } - }); - } - } - catch (IOException e) { - LOG.info(e); - } - } - } - - boolean isAssociated(int projectId, String outputPath) { - if (myProjectToOutputPathMap != null) { - try { - final Object val = myProjectToOutputPathMap.get(projectId); - if (val instanceof Integer) { - return FileUtil.pathsEqual(outputPath, FSRecords.getNames().valueOf(((Integer)val).intValue())); - } - if (val instanceof TIntHashSet) { - final int _outputPath = FSRecords.getNames().enumerate(outputPath); - return ((TIntHashSet)val).contains(_outputPath); - } - } - catch (IOException e) { - LOG.info(e); - } - } - return false; - } - } - - - private interface FileProcessor { - void execute(VirtualFile file); - } - - private static void processRecursively(VirtualFile file, boolean dbOnly, final FileProcessor processor) { - if (file.getFileSystem() instanceof LocalFileSystem) { - if (file.isDirectory()) { - if (dbOnly) { - for (VirtualFile child : ((NewVirtualFile)file).iterInDbChildren()) { - processRecursively(child, true, processor); - } - } - else { - for (VirtualFile child : file.getChildren()) { - processRecursively(child, false, processor); - } - } - } - else { - processor.execute(file); - } - } - } - - // made public for tests - public void scanSourceContent(final ProjectRef projRef, final Collection roots, final int totalRootCount, final boolean isNewRoots) { - if (roots.size() == 0) { - return; - } - final int projectId = getProjectId(projRef.get()); - - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(projRef.get()).getFileIndex(); - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - int processed = 0; - for (VirtualFile srcRoot : roots) { - if (indicator != null) { - projRef.get(); - indicator.setText2(srcRoot.getPresentableUrl()); - indicator.setFraction(++processed / (double)totalRootCount); - } - if (isNewRoots) { - fileIndex.iterateContentUnderDirectory(srcRoot, new ContentIterator() { - public boolean processFile(final VirtualFile file) { - if (!file.isDirectory()) { - if (!isMarkedForRecompilation(projectId, Math.abs(getFileId(file)))) { - final SourceFileInfo srcInfo = loadSourceInfo(file); - if (srcInfo == null || srcInfo.getTimestamp(projectId) != file.getTimeStamp()) { - addSourceForRecompilation(projectId, file, srcInfo); - } - } - } - else { - projRef.get(); - } - return true; - } - }); - } - else { - final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); - new Object() { - void processFile(VirtualFile file) { - if (fileTypeManager.isFileIgnored(file.getName())) { - return; - } - final int fileId = getFileId(file); - if (fileId > 0 /*file is valid*/) { - if (file.isDirectory()) { - projRef.get(); - for (VirtualFile child : file.getChildren()) { - processFile(child); - } - } - else { - if (!isMarkedForRecompilation(projectId, fileId)) { - final SourceFileInfo srcInfo = loadSourceInfo(file); - if (srcInfo != null) { - addSourceForRecompilation(projectId, file, srcInfo); - } - } - } - } - } - }.processFile(srcRoot); - } - } - } - - public void ensureInitializationCompleted(Project project) { - final int id = getProjectId(project); - synchronized (myInitializationLock) { - while (myInitInProgress.contains(id)) { - if (!project.isOpen() || project.isDisposed()) { - // makes no sense to continue waiting - break; - } - try { - myInitializationLock.wait(); - } - catch (InterruptedException ignored) { - break; - } - } - } - } - - private void markOldOutputRoots(final ProjectRef projRef, final TIntObjectHashMap> currentLayout) { - final int projectId = getProjectId(projRef.get()); - - final TIntHashSet rootsToMark = new TIntHashSet(); - synchronized (myProjectOutputRoots) { - final TIntObjectHashMap> oldLayout = myProjectOutputRoots.get(projectId); - for (final TIntObjectIterator> it = oldLayout.iterator(); it.hasNext();) { - it.advance(); - final Pair currentRoots = currentLayout.get(it.key()); - final Pair oldRoots = it.value(); - if (shouldMark(oldRoots.first, currentRoots != null? currentRoots.first : -1)) { - rootsToMark.add(oldRoots.first); - } - if (shouldMark(oldRoots.second, currentRoots != null? currentRoots.second : -1)) { - rootsToMark.add(oldRoots.second); - } - } - } - - for (TIntIterator it = rootsToMark.iterator(); it.hasNext();) { - final int id = it.next(); - final VirtualFile outputRoot = findFileById(id); - if (outputRoot != null) { - processOldOutputRoot(projectId, outputRoot); - } - } - } - - private boolean shouldMark(Integer oldOutputRoot, Integer currentOutputRoot) { - return oldOutputRoot != null && oldOutputRoot.intValue() > 0 && !Comparing.equal(oldOutputRoot, currentOutputRoot); - } - - private void processOldOutputRoot(int projectId, VirtualFile outputRoot) { - // recursively mark all corresponding sources for recompilation - if (outputRoot.isDirectory()) { - for (VirtualFile child : outputRoot.getChildren()) { - processOldOutputRoot(projectId, child); - } - } - else { - // todo: possible optimization - process only those outputs that are not marked for deletion yet - final OutputFileInfo outputInfo = loadOutputInfo(outputRoot); - if (outputInfo != null) { - final String srcPath = outputInfo.getSourceFilePath(); - final VirtualFile srcFile = srcPath != null? LocalFileSystem.getInstance().findFileByPath(srcPath) : null; - if (srcFile != null) { - addSourceForRecompilation(projectId, srcFile, null); - } - } - } - } - - public void scanSourcesForCompilableFiles(final Project project) { - final int projectId = getProjectId(project); - synchronized (myInitializationLock) { - myInitInProgress.add(projectId); - myInitializationLock.notifyAll(); - } - StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() { - public void run() { - new Task.Backgroundable(project, CompilerBundle.message("compiler.initial.scanning.progress.text"), false) { - public void run(@NotNull final ProgressIndicator indicator) { - final ProjectRef projRef = new ProjectRef(project); - try { - final IntermediateOutputCompiler[] compilers = - CompilerManager.getInstance(projRef.get()).getCompilers(IntermediateOutputCompiler.class); - - final Set intermediateRoots = new HashSet(); - if (compilers.length > 0) { - final Module[] modules = ModuleManager.getInstance(projRef.get()).getModules(); - for (IntermediateOutputCompiler compiler : compilers) { - for (Module module : modules) { - if (module.isDisposed()) { - continue; - } - final VirtualFile outputRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(CompilerPaths.getGenerationOutputPath(compiler, module, false)); - if (outputRoot != null) { - intermediateRoots.add(outputRoot); - } - final VirtualFile testsOutputRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(CompilerPaths.getGenerationOutputPath(compiler, module, true)); - if (testsOutputRoot != null) { - intermediateRoots.add(testsOutputRoot); - } - } - } - } - - final List projectRoots = Arrays.asList(ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots()); - final int totalRootsCount = projectRoots.size() + intermediateRoots.size(); - scanSourceContent(projRef, projectRoots, totalRootsCount, true); - - if (!intermediateRoots.isEmpty()) { - final FileProcessor processor = new FileProcessor() { - public void execute(final VirtualFile file) { - if (!isMarkedForRecompilation(projectId, Math.abs(getFileId(file)))) { - final SourceFileInfo srcInfo = loadSourceInfo(file); - if (srcInfo == null || srcInfo.getTimestamp(projectId) != file.getTimeStamp()) { - addSourceForRecompilation(projectId, file, srcInfo); - } - } - } - }; - int processed = projectRoots.size(); - for (VirtualFile root : intermediateRoots) { - projRef.get(); - indicator.setText2(root.getPresentableUrl()); - indicator.setFraction(++processed / (double)totalRootsCount); - processRecursively(root, false, processor); - } - } - - markOldOutputRoots(projRef, buildOutputRootsLayout(projRef)); - } - catch (ProjectRef.ProjectClosedException swallowed) { - } - finally { - synchronized (myInitializationLock) { - if (myInitInProgress.remove(projectId)) { - myInitializationLock.notifyAll(); - } - } - } - } - }.queue(); - } - }); - } - - private class MyProjectManagerListener extends ProjectManagerAdapter { - - final Map myConnections = new HashMap(); - - public void projectOpened(final Project project) { - final MessageBusConnection conn = project.getMessageBus().connect(); - myConnections.put(project, conn); - final ProjectRef projRef = new ProjectRef(project); - conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { - private VirtualFile[] myRootsBefore; - - public void beforeRootsChange(final ModuleRootEvent event) { - try { - myRootsBefore = ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots(); - } - catch (ProjectRef.ProjectClosedException e) { - myRootsBefore = null; - } - } - - public void rootsChanged(final ModuleRootEvent event) { - try { - try { - final VirtualFile[] rootsAfter = ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots(); - - { - final Set newRoots = new HashSet(); - ContainerUtil.addAll(newRoots, rootsAfter); - if (myRootsBefore != null) { - newRoots.removeAll(Arrays.asList(myRootsBefore)); - } - scanSourceContent(projRef, newRoots, newRoots.size(), true); - } - - { - final Set oldRoots = new HashSet(); - if (myRootsBefore != null) { - ContainerUtil.addAll(oldRoots, myRootsBefore); - } - if (!oldRoots.isEmpty()) { - oldRoots.removeAll(Arrays.asList(rootsAfter)); - } - scanSourceContent(projRef, oldRoots, oldRoots.size(), false); - } - } - finally { - myRootsBefore = null; - } - - - markOldOutputRoots(projRef, buildOutputRootsLayout(projRef)); - } - catch (ProjectRef.ProjectClosedException e) { - LOG.info(e); - } - } - }); - - scanSourcesForCompilableFiles(project); - } - - public void projectClosed(final Project project) { - final int projectId = getProjectId(project); - synchronized (myInitializationLock) { - if (myInitInProgress.remove(projectId)) { - myInitializationLock.notifyAll(); - } - } - myConnections.remove(project).disconnect(); - synchronized (myDataLock) { - mySourcesToRecompile.remove(projectId); - } - } - } - - private class MyVfsListener extends VirtualFileAdapter { - public void propertyChanged(final VirtualFilePropertyEvent event) { - if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { - markDirtyIfSource(event.getFile()); - } - } - - public void contentsChanged(final VirtualFileEvent event) { - markDirtyIfSource(event.getFile()); - } - - public void fileCreated(final VirtualFileEvent event) { - processNewFile(event.getFile()); - } - - public void fileCopied(final VirtualFileCopyEvent event) { - processNewFile(event.getFile()); - } - - public void fileMoved(VirtualFileMoveEvent event) { - processNewFile(event.getFile()); - } - - public void beforeFileDeletion(final VirtualFileEvent event) { - final VirtualFile eventFile = event.getFile(); - if ((LOG.isDebugEnabled() && eventFile.isDirectory()) || ourDebugMode) { - final String message = "Processing file deletion: " + eventFile.getPresentableUrl(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - processRecursively(eventFile, true, new FileProcessor() { - public void execute(final VirtualFile file) { - final String filePath = file.getPath(); - try { - final OutputFileInfo outputInfo = loadOutputInfo(file); - if (outputInfo != null) { - final String srcPath = outputInfo.getSourceFilePath(); - final VirtualFile srcFile = srcPath != null? LocalFileSystem.getInstance().findFileByPath(srcPath) : null; - if (srcFile != null) { - final SourceFileInfo srcInfo = loadSourceInfo(srcFile); - if (srcInfo != null) { - final boolean srcWillBeDeleted = VfsUtil.isAncestor(eventFile, srcFile, false); - for (int projectId : srcInfo.getProjectIds().toArray()) { - if (srcInfo.isAssociated(projectId, filePath)) { - if (srcWillBeDeleted) { - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "Unschedule recompilation because of deletion " + srcFile.getPresentableUrl(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - removeSourceForRecompilation(projectId, Math.abs(getFileId(srcFile))); - } - else { - addSourceForRecompilation(projectId, srcFile, srcInfo); - } - } - } - } - } - } - - final SourceFileInfo srcInfo = loadSourceInfo(file); - if (srcInfo != null) { - final TIntHashSet projects = srcInfo.getProjectIds(); - if (!projects.isEmpty()) { - final ScheduleOutputsForDeletionProc deletionProc = new ScheduleOutputsForDeletionProc(file.getUrl()); - deletionProc.setRootBeingDeleted(eventFile); - final int sourceFileId = Math.abs(getFileId(file)); - for (int projectId : projects.toArray()) { - // mark associated outputs for deletion - srcInfo.processOutputPaths(projectId, deletionProc); - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "Unschedule recompilation because of deletion " + file.getPresentableUrl(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - removeSourceForRecompilation(projectId, sourceFileId); - } - } - } - } - finally { - // it is important that update of myOutputsToDelete is done at the end - // otherwise the filePath of the file that is about to be deleted may be re-scheduled for deletion in addSourceForRecompilation() - unmarkOutputPathForDeletion(filePath); - } - } - }); - } - - public void beforeFileMovement(final VirtualFileMoveEvent event) { - markDirtyIfSource(event.getFile()); - } - - private void markDirtyIfSource(final VirtualFile file) { - processRecursively(file, false, new FileProcessor() { - public void execute(final VirtualFile file) { - final SourceFileInfo srcInfo = file.isValid()? loadSourceInfo(file) : null; - if (srcInfo != null) { - for (int projectId : srcInfo.getProjectIds().toArray()) { - addSourceForRecompilation(projectId, file, srcInfo); - } - } - else { - processNewFile(file); - } - } - }); - } - - private void processNewFile(final VirtualFile file) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - // need read action to ensure that the project was not disposed during the iteration over the project list - public void run() { - for (final Project project : myProjectManager.getOpenProjects()) { - if (!project.isInitialized()) { - continue; // the content of this project will be scanned during its post-startup activities - } - final ProjectRootManager rootManager = ProjectRootManager.getInstance(project); - final int projectId = getProjectId(project); - if (rootManager.getFileIndex().isInSourceContent(file)) { - final TranslatingCompiler[] translators = CompilerManager.getInstance(project).getCompilers(TranslatingCompiler.class); - processRecursively(file, false, new FileProcessor() { - public void execute(final VirtualFile file) { - if (isCompilable(file)) { - addSourceForRecompilation(projectId, file, null); - } - } - - boolean isCompilable(VirtualFile file) { - for (TranslatingCompiler translator : translators) { - if (translator.isCompilableFile(file, DummyCompileContext.getInstance())) { - return true; - } - } - return false; - } - }); - } - else { - if (belongsToIntermediateSources(file, project)) { - processRecursively(file, false, new FileProcessor() { - public void execute(final VirtualFile file) { - addSourceForRecompilation(projectId, file, null); - } - }); - } - } - } - } - }); - } - } - - private boolean belongsToIntermediateSources(VirtualFile file, Project project) { - try { - return FileUtil.isAncestor(myGeneratedDataPaths.get(project), new File(file.getPath()), true); - } - catch (IOException e) { - LOG.error(e); // according to javadoc of FileUtil.isAncestor(), this should never happen - } - return false; - } - - private void addSourceForRecompilation(final int projectId, final VirtualFile srcFile, @Nullable final SourceFileInfo preloadedInfo) { - final SourceFileInfo srcInfo = preloadedInfo != null? preloadedInfo : loadSourceInfo(srcFile); - - final boolean alreadyMarked; - synchronized (myDataLock) { - TIntHashSet set = mySourcesToRecompile.get(projectId); - if (set == null) { - set = new TIntHashSet(); - mySourcesToRecompile.put(projectId, set); - } - alreadyMarked = !set.add(Math.abs(getFileId(srcFile))); - if (!alreadyMarked && (LOG.isDebugEnabled() || ourDebugMode)) { - final String message = "Scheduled recompilation " + srcFile.getPresentableUrl(); - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - } - - if (!alreadyMarked && srcInfo != null) { - srcInfo.updateTimestamp(projectId, -1L); - srcInfo.processOutputPaths(projectId, new ScheduleOutputsForDeletionProc(srcFile.getUrl())); - saveSourceInfo(srcFile, srcInfo); - } - } - - private void removeSourceForRecompilation(final int projectId, final int srcId) { - synchronized (myDataLock) { - TIntHashSet set = mySourcesToRecompile.get(projectId); - if (set != null) { - set.remove(srcId); - if (set.isEmpty()) { - mySourcesToRecompile.remove(projectId); - } - } - } - } - - public boolean isMarkedForCompilation(Project project, VirtualFile file) { - return isMarkedForRecompilation(getProjectId(project), getFileId(file)); - } - - private boolean isMarkedForRecompilation(int projectId, final int srcId) { - synchronized (myDataLock) { - final TIntHashSet set = mySourcesToRecompile.get(projectId); - return set != null && set.contains(srcId); - } - } - - private interface Proc { - boolean execute(final int projectId, String outputPath); - } - - private class ScheduleOutputsForDeletionProc implements Proc { - private final String mySrcUrl; - private final LocalFileSystem myFileSystem; - @Nullable - private VirtualFile myRootBeingDeleted; - - private ScheduleOutputsForDeletionProc(final String srcUrl) { - mySrcUrl = srcUrl; - myFileSystem = LocalFileSystem.getInstance(); - } - - public void setRootBeingDeleted(@Nullable VirtualFile rootBeingDeleted) { - myRootBeingDeleted = rootBeingDeleted; - } - - public boolean execute(final int projectId, String outputPath) { - final VirtualFile outFile = myFileSystem.findFileByPath(outputPath); - if (outFile != null) { // not deleted yet - if (myRootBeingDeleted != null && VfsUtil.isAncestor(myRootBeingDeleted, outFile, false)) { - unmarkOutputPathForDeletion(outputPath); - } - else { - final OutputFileInfo outputInfo = loadOutputInfo(outFile); - final String classname = outputInfo != null? outputInfo.getClassName() : null; - markOutputPathForDeletion(projectId, outputPath, classname, mySrcUrl); - } - } - return true; - } - } - - private void markOutputPathForDeletion(final int projectId, final String outputPath, final String classname, final String srcUrl) { - synchronized (myOutputsToDelete) { - Map map = myOutputsToDelete.get(projectId); - if (map == null) { - map = new HashMap(); - myOutputsToDelete.put(projectId, map); - } - map.put(outputPath, new SourceUrlClassNamePair(srcUrl, classname)); - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "ADD path to delete: " + outputPath + "; source: " + srcUrl; - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - } - } - - private void unmarkOutputPathForDeletion(String outputPath) { - synchronized (myOutputsToDelete) { - for (int projectId : myOutputsToDelete.keys()) { - final Map map = myOutputsToDelete.get(projectId); - if (map != null) { - final SourceUrlClassNamePair val = map.remove(outputPath); - if (val != null) { - if (LOG.isDebugEnabled() || ourDebugMode) { - final String message = "REMOVE path to delete: " + outputPath; - LOG.debug(message); - if (ourDebugMode) { - System.out.println(message); - } - } - if (map.isEmpty()) { - myOutputsToDelete.remove(projectId); - } - } - } - } - } - } - - public static final class ProjectRef extends Ref { - static class ProjectClosedException extends RuntimeException { - } - - public ProjectRef() { - } - - public ProjectRef(Project project) { - super(project); - } - - public Project get() { - final Project project = super.get(); - if (project != null && project.isDisposed()) { - throw new ProjectClosedException(); - } - return project; - } - } - -} +/* + * 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.compiler.impl; + +import com.intellij.ProjectTopics; +import com.intellij.compiler.CompilerConfiguration; +import com.intellij.compiler.CompilerIOUtil; +import com.intellij.compiler.make.MakeUtil; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerAdapter; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +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.util.containers.ContainerUtil; +import com.intellij.util.containers.SLRUCache; +import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.IndexInfrastructure; +import com.intellij.util.io.*; +import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.*; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.util.*; + +/** + * @author Eugene Zhuravlev + * Date: Jun 3, 2008 + * + * A source file is scheduled for recompilation if + * 1. its timestamp has changed + * 2. one of its corresponding output files was deleted + * 3. output root of containing module has changed + * + * An output file is scheduled for deletion if: + * 1. corresponding source file has been scheduled for recompilation (see above) + * 2. corresponding source file has been deleted + */ + +public class TranslatingCompilerFilesMonitor implements ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.TranslatingCompilerFilesMonitor"); + public static boolean ourDebugMode = false; + @NonNls + private static final String PATHS_TO_DELETE_FILENAME = "paths_to_delete.dat"; + private static final String OUTPUT_ROOTS_FILENAME = "output_roots.dat"; + private static final FileAttribute ourSourceFileAttribute = new FileAttribute("_make_source_file_info_", 3); + private static final FileAttribute ourOutputFileAttribute = new FileAttribute("_make_output_file_info_", 3); + + private final Object myDataLock = new Object(); + private final TIntObjectHashMap mySourcesToRecompile = new TIntObjectHashMap(); // ProjectId->set of source file paths + private PersistentHashMap>> myOutputRootsStorage; // ProjectId->map[moduleId->Pair(outputDirId, testOutputDirId)] + private final TIntObjectHashMap> myOutputsToDelete = new TIntObjectHashMap>(); // Map: projectId -> Map{output path -> [sourceUrl; classname]} + private final SLRUCache myGeneratedDataPaths = new SLRUCache(8, 8) { + @NotNull + public File createValue(final Project project) { + Disposer.register(project, new Disposable() { + public void dispose() { + myGeneratedDataPaths.remove(project); + } + }); + return CompilerPaths.getGeneratedDataDirectory(project); + } + }; + private final SLRUCache>> myProjectOutputRoots = new SLRUCache>>(2, 2) { + protected void onDropFromCache(Integer key, TIntObjectHashMap> value) { + try { + myOutputRootsStorage.put(key, value); + } + catch (IOException e) { + LOG.info(e); + } + } + + @NotNull + public TIntObjectHashMap> createValue(Integer key) { + TIntObjectHashMap> map = null; + try { + ensureOutputStorageInitialized(); + map = myOutputRootsStorage.get(key); + } + catch (IOException e) { + LOG.info(e); + } + return map != null? map : new TIntObjectHashMap>(); + } + }; + private final ProjectManager myProjectManager; + private final TIntHashSet myInitInProgress = new TIntHashSet(); // projectId fior successfully initialized projects + private final Object myInitializationLock = new Object(); + + public TranslatingCompilerFilesMonitor(VirtualFileManager vfsManager, ProjectManager projectManager, Application application) { + myProjectManager = projectManager; + + projectManager.addProjectManagerListener(new MyProjectManagerListener()); + vfsManager.addVirtualFileListener(new MyVfsListener(), application); + } + + public static TranslatingCompilerFilesMonitor getInstance() { + return ApplicationManager.getApplication().getComponent(TranslatingCompilerFilesMonitor.class); + } + + @Nullable + public static VirtualFile getSourceFileByOutput(VirtualFile outputFile) { + final OutputFileInfo outputFileInfo = loadOutputInfo(outputFile); + if (outputFileInfo != null) { + final String path = outputFileInfo.getSourceFilePath(); + if (path != null) { + return LocalFileSystem.getInstance().findFileByPath(path); + } + } + return null; + } + + public void collectFiles(CompileContext context, final TranslatingCompiler compiler, Iterator scopeSrcIterator, boolean forceCompile, + final boolean isRebuild, + Collection toCompile, + Collection> toDelete) { + final Project project = context.getProject(); + final int projectId = getProjectId(project); + final CompilerConfiguration configuration = CompilerConfiguration.getInstance(project); + final boolean _forceCompile = forceCompile || isRebuild; + final Set selectedForRecompilation = new HashSet(); + synchronized (myDataLock) { + final TIntHashSet pathsToRecompile = mySourcesToRecompile.get(projectId); + if (_forceCompile || pathsToRecompile != null && !pathsToRecompile.isEmpty()) { + if (ourDebugMode) { + System.out.println("Analysing potentially recompilable files for " + compiler.getDescription()); + } + while (scopeSrcIterator.hasNext()) { + final VirtualFile file = scopeSrcIterator.next(); + if (!file.isValid()) { + if (LOG.isDebugEnabled() || ourDebugMode) { + LOG.debug("Skipping invalid file " + file.getPresentableUrl()); + if (ourDebugMode) { + System.out.println("\t SKIPPED(INVALID) " + file.getPresentableUrl()); + } + } + continue; + } + final int fileId = getFileId(file); + if (_forceCompile) { + if (compiler.isCompilableFile(file, context) && !configuration.isExcludedFromCompilation(file)) { + toCompile.add(file); + if (ourDebugMode) { + System.out.println("\t INCLUDED " + file.getPresentableUrl()); + } + selectedForRecompilation.add(file); + if (pathsToRecompile == null || !pathsToRecompile.contains(fileId)) { + addSourceForRecompilation(projectId, file, null); + } + } + else { + if (ourDebugMode) { + System.out.println("\t NOT COMPILABLE OR EXCLUDED " + file.getPresentableUrl()); + } + } + } + else if (pathsToRecompile.contains(fileId)) { + if (compiler.isCompilableFile(file, context) && !configuration.isExcludedFromCompilation(file)) { + toCompile.add(file); + if (ourDebugMode) { + System.out.println("\t INCLUDED " + file.getPresentableUrl()); + } + selectedForRecompilation.add(file); + } + else { + if (ourDebugMode) { + System.out.println("\t NOT COMPILABLE OR EXCLUDED " + file.getPresentableUrl()); + } + } + } + else { + if (ourDebugMode) { + System.out.println("\t NOT INCLUDED " + file.getPresentableUrl()); + } + } + } + } + // it is important that files to delete are collected after the files to compile (see what happens if forceCompile == true) + if (!isRebuild) { + final Map outputsToDelete = myOutputsToDelete.get(projectId); + if (outputsToDelete != null) { + final VirtualFileManager vfm = VirtualFileManager.getInstance(); + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final List zombieEntries = new ArrayList(); + for (String outputPath : outputsToDelete.keySet()) { + final SourceUrlClassNamePair classNamePair = outputsToDelete.get(outputPath); + final String sourceUrl = classNamePair.getSourceUrl(); + final VirtualFile srcFile = vfm.findFileByUrl(sourceUrl); + final boolean sourcePresent = srcFile != null; + if (sourcePresent) { + if (!compiler.isCompilableFile(srcFile, context)) { + continue; // do not collect files that were compiled by another compiler + } + if (!selectedForRecompilation.contains(srcFile)) { + if (!isMarkedForRecompilation(projectId, getFileId(srcFile))) { + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "Found zombie entry (output is marked, but source is present and up-to-date): " + outputPath; + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + zombieEntries.add(outputPath); + } + continue; + } + } + if (lfs.findFileByPath(outputPath) != null) { + //noinspection UnnecessaryBoxing + final File file = new File(outputPath); + toDelete.add(new Trinity(file, classNamePair.getClassName(), Boolean.valueOf(sourcePresent))); + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "Found file to delete: " + file; + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + } + else { + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "Found zombie entry marked for deletion: " + outputPath; + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + // must be gagbage entry, should cleanup + zombieEntries.add(outputPath); + } + } + for (String path : zombieEntries) { + unmarkOutputPathForDeletion(path); + } + } + } + } + } + + private static int getFileId(final VirtualFile file) { + return FileBasedIndex.getFileId(file); + } + + private static VirtualFile findFileById(int id) { + return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id); + } + + public void update(final CompileContext context, final String outputRoot, final Collection successfullyCompiled, final VirtualFile[] filesToRecompile) + throws IOException { + final Project project = context.getProject(); + final int projectId = getProjectId(project); + if (successfullyCompiled.size() > 0) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final IOException[] exceptions = {null}; + // need read action here to ensure that no modifications were made to VFS while updating file attributes + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + final Map compiledSources = new HashMap(); + final Set forceRecompile = new HashSet(); + + for (TranslatingCompiler.OutputItem item : successfullyCompiled) { + final VirtualFile sourceFile = item.getSourceFile(); + final boolean isSourceValid = sourceFile.isValid(); + SourceFileInfo srcInfo = compiledSources.get(sourceFile); + if (isSourceValid && srcInfo == null) { + srcInfo = loadSourceInfo(sourceFile); + if (srcInfo != null) { + srcInfo.clearPaths(projectId); + } + else { + srcInfo = new SourceFileInfo(); + } + compiledSources.put(sourceFile, srcInfo); + } + + final String outputPath = item.getOutputPath(); + if (outputPath != null) { // can be null for packageinfo + final VirtualFile outputFile = lfs.findFileByPath(outputPath); + + //assert outputFile != null : "Virtual file was not found for \"" + outputPath + "\""; + assert outputRoot != null; + + if (outputFile != null) { + if (!sourceFile.equals(outputFile)) { + final String className = MakeUtil.relativeClassPathToQName(outputPath.substring(outputRoot.length()), '/'); + if (isSourceValid) { + srcInfo.addOutputPath(projectId, outputPath); + saveOutputInfo(outputFile, new OutputFileInfo(sourceFile.getPath(), className)); + } + else { + markOutputPathForDeletion(projectId, outputPath, className, sourceFile.getUrl()); + } + } + } + else { // output file was not found + LOG.warn("TranslatingCompilerFilesMonitor.update(): Virtual file was not found for \"" + outputPath + "\""); + if (isSourceValid) { + forceRecompile.add(sourceFile); + } + } + } + } + final long compilationStartStamp = ((CompileContextEx)context).getStartCompilationStamp(); + for (Map.Entry entry : compiledSources.entrySet()) { + final SourceFileInfo info = entry.getValue(); + final VirtualFile file = entry.getKey(); + + final long fileStamp = file.getTimeStamp(); + info.updateTimestamp(projectId, fileStamp); + saveSourceInfo(file, info); + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "Unschedule recompilation (successfully compiled) " + file.getPresentableUrl(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + removeSourceForRecompilation(projectId, Math.abs(getFileId(file))); + if ((fileStamp > compilationStartStamp && !((CompileContextEx)context).isGenerated(file)) || forceRecompile.contains(file)) { + // changes were made during compilation, need to re-schedule compilation + // it is important to invoke removeSourceForRecompilation() before this call to make sure + // the corresponding output paths will be scheduled for deletion + addSourceForRecompilation(projectId, file, info); + } + } + } + catch (IOException e) { + exceptions[0] = e; + } + } + }); + if (exceptions[0] != null) { + throw exceptions[0]; + } + } + + if (filesToRecompile.length > 0) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (VirtualFile file : filesToRecompile) { + if (file.isValid()) { + addSourceForRecompilation(projectId, file, null); + } + } + } + }); + } + } + + public void updateOutputRootsLayout(Project project) { + final TIntObjectHashMap> map = buildOutputRootsLayout(new ProjectRef(project)); + synchronized (myProjectOutputRoots) { + myProjectOutputRoots.put(getProjectId(project), map); + } + } + + @NotNull + public String getComponentName() { + return "TranslatingCompilerFilesMonitor"; + } + + public void initComponent() { + final File file = new File(CompilerPaths.getCompilerSystemDirectory(), PATHS_TO_DELETE_FILENAME); + try { + final DataInputStream is = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); + try { + final int projectsCount = is.readInt(); + synchronized (myDataLock) { + for (int idx = 0; idx < projectsCount; idx++) { + final int projectId = is.readInt(); + final int size = is.readInt(); + if (size > 0) { + final Map map = new HashMap(); + myOutputsToDelete.put(projectId, map); + for (int i = 0; i < size; i++) { + final String outputPath = FileUtil.toSystemIndependentName(CompilerIOUtil.readString(is)); + final String srcUrl = CompilerIOUtil.readString(is); + final String className = CompilerIOUtil.readString(is); + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "INIT path to delete: " + outputPath; + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + map.put(outputPath, new SourceUrlClassNamePair(srcUrl, className)); + } + } + } + } + } + finally { + is.close(); + } + } + catch (FileNotFoundException ignored) { + } + catch (IOException e) { + LOG.info(e); + synchronized (myDataLock) { + myOutputsToDelete.clear(); + } + FileUtil.delete(file); + } + + ensureOutputStorageInitialized(); + } + + private void ensureOutputStorageInitialized() { + if (myOutputRootsStorage != null) { + return; + } + final File rootsFile = new File(CompilerPaths.getCompilerSystemDirectory(), OUTPUT_ROOTS_FILENAME); + try { + initOutputRootsFile(rootsFile); + } + catch (IOException e) { + LOG.info(e); + FileUtil.delete(rootsFile); + try { + initOutputRootsFile(rootsFile); + } + catch (IOException e1) { + LOG.error(e1); + } + } + } + + private TIntObjectHashMap> buildOutputRootsLayout(ProjectRef projRef) { + final TIntObjectHashMap> map = new TIntObjectHashMap>(); + for (Module module : ModuleManager.getInstance(projRef.get()).getModules()) { + final CompilerModuleExtension manager = CompilerModuleExtension.getInstance(module); + if (manager != null) { + final VirtualFile output = manager.getCompilerOutputPath(); + final int first = output != null? Math.abs(getFileId(output)) : -1; + final VirtualFile testsOutput = manager.getCompilerOutputPathForTests(); + final int second = testsOutput != null? Math.abs(getFileId(testsOutput)) : -1; + map.put(getModuleId(module), new Pair(first, second)); + } + } + return map; + } + + private void initOutputRootsFile(File rootsFile) throws IOException { + myOutputRootsStorage = new PersistentHashMap>>(rootsFile, new EnumeratorIntegerDescriptor(), new DataExternalizer>>() { + public void save(DataOutput out, TIntObjectHashMap> value) throws IOException { + for (final TIntObjectIterator> it = value.iterator(); it.hasNext();) { + it.advance(); + DataInputOutputUtil.writeINT(out, it.key()); + final Pair pair = it.value(); + DataInputOutputUtil.writeINT(out, pair.first); + DataInputOutputUtil.writeINT(out, pair.second); + } + } + + public TIntObjectHashMap> read(DataInput in) throws IOException { + final DataInputStream _in = (DataInputStream)in; + final TIntObjectHashMap> map = new TIntObjectHashMap>(); + while (_in.available() > 0) { + final int key = DataInputOutputUtil.readINT(_in); + final int first = DataInputOutputUtil.readINT(_in); + final int second = DataInputOutputUtil.readINT(_in); + map.put(key, new Pair(first, second)); + } + return map; + } + }); + } + + public void disposeComponent() { + final File file = new File(CompilerPaths.getCompilerSystemDirectory(), PATHS_TO_DELETE_FILENAME); + try { + FileUtil.createParentDirs(file); + final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + try { + synchronized (myDataLock) { + final int[] keys = myOutputsToDelete.keys(); + os.writeInt(keys.length); + for (int projectId : keys) { + final Map projectOutputs = myOutputsToDelete.get(projectId); + os.writeInt(projectId); + if (projectOutputs != null) { + os.writeInt(projectOutputs.size()); + for (Map.Entry entry : projectOutputs.entrySet()) { + CompilerIOUtil.writeString(entry.getKey(), os); + final SourceUrlClassNamePair pair = entry.getValue(); + CompilerIOUtil.writeString(pair.getSourceUrl(), os); + CompilerIOUtil.writeString(pair.getClassName(), os); + } + } + else { + os.writeInt(0); + } + } + } + } + finally { + os.close(); + synchronized (myProjectOutputRoots) { + myProjectOutputRoots.clear(); + } + myOutputRootsStorage.close(); + } + } + catch (IOException e) { + LOG.error(e); + } + } + + @Nullable + private static SourceFileInfo loadSourceInfo(final VirtualFile file) { + try { + final DataInputStream is = ourSourceFileAttribute.readAttribute(file); + if (is != null) { + try { + return new SourceFileInfo(is); + } + finally { + is.close(); + } + } + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + LOG.info(e); // ignore IOExceptions + } + else { + throw e; + } + } + catch (IOException ignored) { + LOG.info(ignored); + } + return null; + } + + public static void removeSourceInfo(VirtualFile file) { + saveSourceInfo(file, new SourceFileInfo()); + } + + private static void saveSourceInfo(VirtualFile file, SourceFileInfo descriptor) { + final DataOutputStream out = ourSourceFileAttribute.writeAttribute(file); + try { + try { + descriptor.save(out); + } + finally { + out.close(); + } + } + catch (IOException ignored) { + LOG.info(ignored); + } + } + + @Nullable + private static OutputFileInfo loadOutputInfo(final VirtualFile file) { + try { + final DataInputStream is = ourOutputFileAttribute.readAttribute(file); + if (is != null) { + try { + return new OutputFileInfo(is); + } + finally { + is.close(); + } + } + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + LOG.info(e); // ignore IO exceptions + } + else { + throw e; + } + } + catch (IOException ignored) { + LOG.info(ignored); + } + return null; + } + + private static void saveOutputInfo(VirtualFile file, OutputFileInfo descriptor) { + final DataOutputStream out = ourOutputFileAttribute.writeAttribute(file); + try { + try { + descriptor.save(out); + } + finally { + out.close(); + } + } + catch (IOException ignored) { + LOG.info(ignored); + } + } + + private static int getProjectId(Project project) { + try { + return FSRecords.getNames().enumerate(project.getLocationHash()); + } + catch (IOException e) { + LOG.info(e); + } + return -1; + } + + private static int getModuleId(Module module) { + try { + return FSRecords.getNames().enumerate(module.getName()); + } + catch (IOException e) { + LOG.info(e); + } + return -1; + } + + private static class OutputFileInfo { + private final int mySourcePath; + + private final int myClassName; + + OutputFileInfo(final String sourcePath, @Nullable String className) throws IOException { + final PersistentStringEnumerator symtable = FSRecords.getNames(); + mySourcePath = symtable.enumerate(sourcePath); + myClassName = className != null? symtable.enumerate(className) : -1; + } + + OutputFileInfo(final DataInput in) throws IOException { + mySourcePath = in.readInt(); + myClassName = in.readInt(); + } + + String getSourceFilePath() { + try { + return FSRecords.getNames().valueOf(mySourcePath); + } + catch (IOException e) { + LOG.info(e); + } + return null; + } + + @Nullable + public String getClassName() { + try { + return myClassName < 0? null : FSRecords.getNames().valueOf(myClassName); + } + catch (IOException e) { + LOG.info(e); + } + return null; + } + + public void save(final DataOutput out) throws IOException { + out.writeInt(mySourcePath); + out.writeInt(myClassName); + } + } + + private static class SourceFileInfo { + private TIntLongHashMap myTimestamps; // ProjectId -> last compiled stamp + private TIntObjectHashMap myProjectToOutputPathMap; // ProjectId -> either a single output path or a set of output paths + + private SourceFileInfo() { + } + + private SourceFileInfo(@NotNull DataInput in) throws IOException { + final int projCount = in.readInt(); + for (int idx = 0; idx < projCount; idx++) { + final int projectId = in.readInt(); + final long stamp = in.readLong(); + updateTimestamp(projectId, stamp); + + final int pathsCount = in.readInt(); + for (int i = 0; i < pathsCount; i++) { + final int path = in.readInt(); + addOutputPath(projectId, path); + } + } + } + + public void save(@NotNull final DataOutput out) throws IOException { + final int[] projects = getProjectIds().toArray(); + out.writeInt(projects.length); + for (int projectId : projects) { + out.writeInt(projectId); + out.writeLong(getTimestamp(projectId)); + final Object value = myProjectToOutputPathMap != null? myProjectToOutputPathMap.get(projectId) : null; + if (value instanceof Integer) { + out.writeInt(1); + out.writeInt(((Integer)value).intValue()); + } + else if (value instanceof TIntHashSet) { + final TIntHashSet set = (TIntHashSet)value; + out.writeInt(set.size()); + final IOException[] ex = new IOException[] {null}; + set.forEach(new TIntProcedure() { + public boolean execute(final int value) { + try { + out.writeInt(value); + return true; + } + catch (IOException e) { + ex[0] = e; + return false; + } + } + }); + if (ex[0] != null) { + throw ex[0]; + } + } + else { + out.writeInt(0); + } + } + } + + private void updateTimestamp(final int projectId, final long stamp) { + if (stamp > 0L) { + if (myTimestamps == null) { + myTimestamps = new TIntLongHashMap(1, 0.98f); + } + myTimestamps.put(projectId, stamp); + } + else { + if (myTimestamps != null) { + myTimestamps.remove(projectId); + } + } + } + + TIntHashSet getProjectIds() { + final TIntHashSet result = new TIntHashSet(); + if (myTimestamps != null) { + result.addAll(myTimestamps.keys()); + } + if (myProjectToOutputPathMap != null) { + result.addAll(myProjectToOutputPathMap.keys()); + } + return result; + } + + private void addOutputPath(final int projectId, String outputPath) { + try { + addOutputPath(projectId, FSRecords.getNames().enumerate(outputPath)); + } + catch (IOException e) { + LOG.info(e); + } + } + + private void addOutputPath(final int projectId, final int outputPath) { + if (myProjectToOutputPathMap == null) { + myProjectToOutputPathMap = new TIntObjectHashMap(1, 0.98f); + myProjectToOutputPathMap.put(projectId, outputPath); + } + else { + final Object val = myProjectToOutputPathMap.get(projectId); + if (val == null) { + myProjectToOutputPathMap.put(projectId, outputPath); + } + else { + TIntHashSet set; + if (val instanceof Integer) { + set = new TIntHashSet(); + set.add(((Integer)val).intValue()); + myProjectToOutputPathMap.put(projectId, set); + } + else { + assert val instanceof TIntHashSet; + set = (TIntHashSet)val; + } + set.add(outputPath); + } + } + } + + public void clearPaths(final int projectId){ + if (myProjectToOutputPathMap != null) { + myProjectToOutputPathMap.remove(projectId); + } + } + + long getTimestamp(final int projectId) { + return myTimestamps == null? -1L : myTimestamps.get(projectId); + } + + void processOutputPaths(final int projectId, final Proc proc){ + if (myProjectToOutputPathMap != null) { + try { + final PersistentStringEnumerator symtable = FSRecords.getNames(); + final Object val = myProjectToOutputPathMap.get(projectId); + if (val instanceof Integer) { + proc.execute(projectId, symtable.valueOf(((Integer)val).intValue())); + } + else if (val instanceof TIntHashSet) { + ((TIntHashSet)val).forEach(new TIntProcedure() { + public boolean execute(final int value) { + try { + proc.execute(projectId, symtable.valueOf(value)); + return true; + } + catch (IOException e) { + LOG.info(e); + return false; + } + } + }); + } + } + catch (IOException e) { + LOG.info(e); + } + } + } + + boolean isAssociated(int projectId, String outputPath) { + if (myProjectToOutputPathMap != null) { + try { + final Object val = myProjectToOutputPathMap.get(projectId); + if (val instanceof Integer) { + return FileUtil.pathsEqual(outputPath, FSRecords.getNames().valueOf(((Integer)val).intValue())); + } + if (val instanceof TIntHashSet) { + final int _outputPath = FSRecords.getNames().enumerate(outputPath); + return ((TIntHashSet)val).contains(_outputPath); + } + } + catch (IOException e) { + LOG.info(e); + } + } + return false; + } + } + + + private interface FileProcessor { + void execute(VirtualFile file); + } + + private static void processRecursively(VirtualFile file, boolean dbOnly, final FileProcessor processor) { + if (file.getFileSystem() instanceof LocalFileSystem) { + if (file.isDirectory()) { + if (dbOnly) { + for (VirtualFile child : ((NewVirtualFile)file).iterInDbChildren()) { + processRecursively(child, true, processor); + } + } + else { + for (VirtualFile child : file.getChildren()) { + processRecursively(child, false, processor); + } + } + } + else { + processor.execute(file); + } + } + } + + // made public for tests + public void scanSourceContent(final ProjectRef projRef, final Collection roots, final int totalRootCount, final boolean isNewRoots) { + if (roots.size() == 0) { + return; + } + final int projectId = getProjectId(projRef.get()); + + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(projRef.get()).getFileIndex(); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + int processed = 0; + for (VirtualFile srcRoot : roots) { + if (indicator != null) { + projRef.get(); + indicator.setText2(srcRoot.getPresentableUrl()); + indicator.setFraction(++processed / (double)totalRootCount); + } + if (isNewRoots) { + fileIndex.iterateContentUnderDirectory(srcRoot, new ContentIterator() { + public boolean processFile(final VirtualFile file) { + if (!file.isDirectory()) { + if (!isMarkedForRecompilation(projectId, Math.abs(getFileId(file)))) { + final SourceFileInfo srcInfo = loadSourceInfo(file); + if (srcInfo == null || srcInfo.getTimestamp(projectId) != file.getTimeStamp()) { + addSourceForRecompilation(projectId, file, srcInfo); + } + } + } + else { + projRef.get(); + } + return true; + } + }); + } + else { + final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); + new Object() { + void processFile(VirtualFile file) { + if (fileTypeManager.isFileIgnored(file.getName())) { + return; + } + final int fileId = getFileId(file); + if (fileId > 0 /*file is valid*/) { + if (file.isDirectory()) { + projRef.get(); + for (VirtualFile child : file.getChildren()) { + processFile(child); + } + } + else { + if (!isMarkedForRecompilation(projectId, fileId)) { + final SourceFileInfo srcInfo = loadSourceInfo(file); + if (srcInfo != null) { + addSourceForRecompilation(projectId, file, srcInfo); + } + } + } + } + } + }.processFile(srcRoot); + } + } + } + + public void ensureInitializationCompleted(Project project) { + final int id = getProjectId(project); + synchronized (myInitializationLock) { + while (myInitInProgress.contains(id)) { + if (!project.isOpen() || project.isDisposed()) { + // makes no sense to continue waiting + break; + } + try { + myInitializationLock.wait(); + } + catch (InterruptedException ignored) { + break; + } + } + } + } + + private void markOldOutputRoots(final ProjectRef projRef, final TIntObjectHashMap> currentLayout) { + final int projectId = getProjectId(projRef.get()); + + final TIntHashSet rootsToMark = new TIntHashSet(); + synchronized (myProjectOutputRoots) { + final TIntObjectHashMap> oldLayout = myProjectOutputRoots.get(projectId); + for (final TIntObjectIterator> it = oldLayout.iterator(); it.hasNext();) { + it.advance(); + final Pair currentRoots = currentLayout.get(it.key()); + final Pair oldRoots = it.value(); + if (shouldMark(oldRoots.first, currentRoots != null? currentRoots.first : -1)) { + rootsToMark.add(oldRoots.first); + } + if (shouldMark(oldRoots.second, currentRoots != null? currentRoots.second : -1)) { + rootsToMark.add(oldRoots.second); + } + } + } + + for (TIntIterator it = rootsToMark.iterator(); it.hasNext();) { + final int id = it.next(); + final VirtualFile outputRoot = findFileById(id); + if (outputRoot != null) { + processOldOutputRoot(projectId, outputRoot); + } + } + } + + private boolean shouldMark(Integer oldOutputRoot, Integer currentOutputRoot) { + return oldOutputRoot != null && oldOutputRoot.intValue() > 0 && !Comparing.equal(oldOutputRoot, currentOutputRoot); + } + + private void processOldOutputRoot(int projectId, VirtualFile outputRoot) { + // recursively mark all corresponding sources for recompilation + if (outputRoot.isDirectory()) { + for (VirtualFile child : outputRoot.getChildren()) { + processOldOutputRoot(projectId, child); + } + } + else { + // todo: possible optimization - process only those outputs that are not marked for deletion yet + final OutputFileInfo outputInfo = loadOutputInfo(outputRoot); + if (outputInfo != null) { + final String srcPath = outputInfo.getSourceFilePath(); + final VirtualFile srcFile = srcPath != null? LocalFileSystem.getInstance().findFileByPath(srcPath) : null; + if (srcFile != null) { + addSourceForRecompilation(projectId, srcFile, null); + } + } + } + } + + public void scanSourcesForCompilableFiles(final Project project) { + final int projectId = getProjectId(project); + synchronized (myInitializationLock) { + myInitInProgress.add(projectId); + myInitializationLock.notifyAll(); + } + StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() { + public void run() { + new Task.Backgroundable(project, CompilerBundle.message("compiler.initial.scanning.progress.text"), false) { + public void run(@NotNull final ProgressIndicator indicator) { + final ProjectRef projRef = new ProjectRef(project); + try { + final IntermediateOutputCompiler[] compilers = + CompilerManager.getInstance(projRef.get()).getCompilers(IntermediateOutputCompiler.class); + + final Set intermediateRoots = new HashSet(); + if (compilers.length > 0) { + final Module[] modules = ModuleManager.getInstance(projRef.get()).getModules(); + for (IntermediateOutputCompiler compiler : compilers) { + for (Module module : modules) { + if (module.isDisposed()) { + continue; + } + final VirtualFile outputRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(CompilerPaths.getGenerationOutputPath(compiler, module, false)); + if (outputRoot != null) { + intermediateRoots.add(outputRoot); + } + final VirtualFile testsOutputRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(CompilerPaths.getGenerationOutputPath(compiler, module, true)); + if (testsOutputRoot != null) { + intermediateRoots.add(testsOutputRoot); + } + } + } + } + + final List projectRoots = Arrays.asList(ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots()); + final int totalRootsCount = projectRoots.size() + intermediateRoots.size(); + scanSourceContent(projRef, projectRoots, totalRootsCount, true); + + if (!intermediateRoots.isEmpty()) { + final FileProcessor processor = new FileProcessor() { + public void execute(final VirtualFile file) { + if (!isMarkedForRecompilation(projectId, Math.abs(getFileId(file)))) { + final SourceFileInfo srcInfo = loadSourceInfo(file); + if (srcInfo == null || srcInfo.getTimestamp(projectId) != file.getTimeStamp()) { + addSourceForRecompilation(projectId, file, srcInfo); + } + } + } + }; + int processed = projectRoots.size(); + for (VirtualFile root : intermediateRoots) { + projRef.get(); + indicator.setText2(root.getPresentableUrl()); + indicator.setFraction(++processed / (double)totalRootsCount); + processRecursively(root, false, processor); + } + } + + markOldOutputRoots(projRef, buildOutputRootsLayout(projRef)); + } + catch (ProjectRef.ProjectClosedException swallowed) { + } + finally { + synchronized (myInitializationLock) { + if (myInitInProgress.remove(projectId)) { + myInitializationLock.notifyAll(); + } + } + } + } + }.queue(); + } + }); + } + + private class MyProjectManagerListener extends ProjectManagerAdapter { + + final Map myConnections = new HashMap(); + + public void projectOpened(final Project project) { + final MessageBusConnection conn = project.getMessageBus().connect(); + myConnections.put(project, conn); + final ProjectRef projRef = new ProjectRef(project); + conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + private VirtualFile[] myRootsBefore; + + public void beforeRootsChange(final ModuleRootEvent event) { + try { + myRootsBefore = ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots(); + } + catch (ProjectRef.ProjectClosedException e) { + myRootsBefore = null; + } + } + + public void rootsChanged(final ModuleRootEvent event) { + try { + try { + final VirtualFile[] rootsAfter = ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots(); + + { + final Set newRoots = new HashSet(); + ContainerUtil.addAll(newRoots, rootsAfter); + if (myRootsBefore != null) { + newRoots.removeAll(Arrays.asList(myRootsBefore)); + } + scanSourceContent(projRef, newRoots, newRoots.size(), true); + } + + { + final Set oldRoots = new HashSet(); + if (myRootsBefore != null) { + ContainerUtil.addAll(oldRoots, myRootsBefore); + } + if (!oldRoots.isEmpty()) { + oldRoots.removeAll(Arrays.asList(rootsAfter)); + } + scanSourceContent(projRef, oldRoots, oldRoots.size(), false); + } + } + finally { + myRootsBefore = null; + } + + + markOldOutputRoots(projRef, buildOutputRootsLayout(projRef)); + } + catch (ProjectRef.ProjectClosedException e) { + LOG.info(e); + } + } + }); + + scanSourcesForCompilableFiles(project); + } + + public void projectClosed(final Project project) { + final int projectId = getProjectId(project); + synchronized (myInitializationLock) { + if (myInitInProgress.remove(projectId)) { + myInitializationLock.notifyAll(); + } + } + myConnections.remove(project).disconnect(); + synchronized (myDataLock) { + mySourcesToRecompile.remove(projectId); + } + } + } + + private class MyVfsListener extends VirtualFileAdapter { + public void propertyChanged(final VirtualFilePropertyEvent event) { + if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { + markDirtyIfSource(event.getFile()); + } + } + + public void contentsChanged(final VirtualFileEvent event) { + markDirtyIfSource(event.getFile()); + } + + public void fileCreated(final VirtualFileEvent event) { + processNewFile(event.getFile()); + } + + public void fileCopied(final VirtualFileCopyEvent event) { + processNewFile(event.getFile()); + } + + public void fileMoved(VirtualFileMoveEvent event) { + processNewFile(event.getFile()); + } + + public void beforeFileDeletion(final VirtualFileEvent event) { + final VirtualFile eventFile = event.getFile(); + if ((LOG.isDebugEnabled() && eventFile.isDirectory()) || ourDebugMode) { + final String message = "Processing file deletion: " + eventFile.getPresentableUrl(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + processRecursively(eventFile, true, new FileProcessor() { + public void execute(final VirtualFile file) { + final String filePath = file.getPath(); + try { + final OutputFileInfo outputInfo = loadOutputInfo(file); + if (outputInfo != null) { + final String srcPath = outputInfo.getSourceFilePath(); + final VirtualFile srcFile = srcPath != null? LocalFileSystem.getInstance().findFileByPath(srcPath) : null; + if (srcFile != null) { + final SourceFileInfo srcInfo = loadSourceInfo(srcFile); + if (srcInfo != null) { + final boolean srcWillBeDeleted = VfsUtil.isAncestor(eventFile, srcFile, false); + for (int projectId : srcInfo.getProjectIds().toArray()) { + if (srcInfo.isAssociated(projectId, filePath)) { + if (srcWillBeDeleted) { + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "Unschedule recompilation because of deletion " + srcFile.getPresentableUrl(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + removeSourceForRecompilation(projectId, Math.abs(getFileId(srcFile))); + } + else { + addSourceForRecompilation(projectId, srcFile, srcInfo); + } + } + } + } + } + } + + final SourceFileInfo srcInfo = loadSourceInfo(file); + if (srcInfo != null) { + final TIntHashSet projects = srcInfo.getProjectIds(); + if (!projects.isEmpty()) { + final ScheduleOutputsForDeletionProc deletionProc = new ScheduleOutputsForDeletionProc(file.getUrl()); + deletionProc.setRootBeingDeleted(eventFile); + final int sourceFileId = Math.abs(getFileId(file)); + for (int projectId : projects.toArray()) { + // mark associated outputs for deletion + srcInfo.processOutputPaths(projectId, deletionProc); + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "Unschedule recompilation because of deletion " + file.getPresentableUrl(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + removeSourceForRecompilation(projectId, sourceFileId); + } + } + } + } + finally { + // it is important that update of myOutputsToDelete is done at the end + // otherwise the filePath of the file that is about to be deleted may be re-scheduled for deletion in addSourceForRecompilation() + unmarkOutputPathForDeletion(filePath); + } + } + }); + } + + public void beforeFileMovement(final VirtualFileMoveEvent event) { + markDirtyIfSource(event.getFile()); + } + + private void markDirtyIfSource(final VirtualFile file) { + processRecursively(file, false, new FileProcessor() { + public void execute(final VirtualFile file) { + final SourceFileInfo srcInfo = file.isValid()? loadSourceInfo(file) : null; + if (srcInfo != null) { + for (int projectId : srcInfo.getProjectIds().toArray()) { + addSourceForRecompilation(projectId, file, srcInfo); + } + } + else { + processNewFile(file); + } + } + }); + } + + private void processNewFile(final VirtualFile file) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + // need read action to ensure that the project was not disposed during the iteration over the project list + public void run() { + for (final Project project : myProjectManager.getOpenProjects()) { + if (!project.isInitialized()) { + continue; // the content of this project will be scanned during its post-startup activities + } + final ProjectRootManager rootManager = ProjectRootManager.getInstance(project); + final int projectId = getProjectId(project); + if (rootManager.getFileIndex().isInSourceContent(file)) { + final TranslatingCompiler[] translators = CompilerManager.getInstance(project).getCompilers(TranslatingCompiler.class); + processRecursively(file, false, new FileProcessor() { + public void execute(final VirtualFile file) { + if (isCompilable(file)) { + addSourceForRecompilation(projectId, file, null); + } + } + + boolean isCompilable(VirtualFile file) { + for (TranslatingCompiler translator : translators) { + if (translator.isCompilableFile(file, DummyCompileContext.getInstance())) { + return true; + } + } + return false; + } + }); + } + else { + if (belongsToIntermediateSources(file, project)) { + processRecursively(file, false, new FileProcessor() { + public void execute(final VirtualFile file) { + addSourceForRecompilation(projectId, file, null); + } + }); + } + } + } + } + }); + } + } + + private boolean belongsToIntermediateSources(VirtualFile file, Project project) { + try { + return FileUtil.isAncestor(myGeneratedDataPaths.get(project), new File(file.getPath()), true); + } + catch (IOException e) { + LOG.error(e); // according to javadoc of FileUtil.isAncestor(), this should never happen + } + return false; + } + + private void addSourceForRecompilation(final int projectId, final VirtualFile srcFile, @Nullable final SourceFileInfo preloadedInfo) { + final SourceFileInfo srcInfo = preloadedInfo != null? preloadedInfo : loadSourceInfo(srcFile); + + final boolean alreadyMarked; + synchronized (myDataLock) { + TIntHashSet set = mySourcesToRecompile.get(projectId); + if (set == null) { + set = new TIntHashSet(); + mySourcesToRecompile.put(projectId, set); + } + alreadyMarked = !set.add(Math.abs(getFileId(srcFile))); + if (!alreadyMarked && (LOG.isDebugEnabled() || ourDebugMode)) { + final String message = "Scheduled recompilation " + srcFile.getPresentableUrl(); + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + } + + if (!alreadyMarked && srcInfo != null) { + srcInfo.updateTimestamp(projectId, -1L); + srcInfo.processOutputPaths(projectId, new ScheduleOutputsForDeletionProc(srcFile.getUrl())); + saveSourceInfo(srcFile, srcInfo); + } + } + + private void removeSourceForRecompilation(final int projectId, final int srcId) { + synchronized (myDataLock) { + TIntHashSet set = mySourcesToRecompile.get(projectId); + if (set != null) { + set.remove(srcId); + if (set.isEmpty()) { + mySourcesToRecompile.remove(projectId); + } + } + } + } + + public boolean isMarkedForCompilation(Project project, VirtualFile file) { + return isMarkedForRecompilation(getProjectId(project), getFileId(file)); + } + + private boolean isMarkedForRecompilation(int projectId, final int srcId) { + synchronized (myDataLock) { + final TIntHashSet set = mySourcesToRecompile.get(projectId); + return set != null && set.contains(srcId); + } + } + + private interface Proc { + boolean execute(final int projectId, String outputPath); + } + + private class ScheduleOutputsForDeletionProc implements Proc { + private final String mySrcUrl; + private final LocalFileSystem myFileSystem; + @Nullable + private VirtualFile myRootBeingDeleted; + + private ScheduleOutputsForDeletionProc(final String srcUrl) { + mySrcUrl = srcUrl; + myFileSystem = LocalFileSystem.getInstance(); + } + + public void setRootBeingDeleted(@Nullable VirtualFile rootBeingDeleted) { + myRootBeingDeleted = rootBeingDeleted; + } + + public boolean execute(final int projectId, String outputPath) { + final VirtualFile outFile = myFileSystem.findFileByPath(outputPath); + if (outFile != null) { // not deleted yet + if (myRootBeingDeleted != null && VfsUtil.isAncestor(myRootBeingDeleted, outFile, false)) { + unmarkOutputPathForDeletion(outputPath); + } + else { + final OutputFileInfo outputInfo = loadOutputInfo(outFile); + final String classname = outputInfo != null? outputInfo.getClassName() : null; + markOutputPathForDeletion(projectId, outputPath, classname, mySrcUrl); + } + } + return true; + } + } + + private void markOutputPathForDeletion(final int projectId, final String outputPath, final String classname, final String srcUrl) { + synchronized (myOutputsToDelete) { + Map map = myOutputsToDelete.get(projectId); + if (map == null) { + map = new HashMap(); + myOutputsToDelete.put(projectId, map); + } + map.put(outputPath, new SourceUrlClassNamePair(srcUrl, classname)); + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "ADD path to delete: " + outputPath + "; source: " + srcUrl; + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + } + } + + private void unmarkOutputPathForDeletion(String outputPath) { + synchronized (myOutputsToDelete) { + for (int projectId : myOutputsToDelete.keys()) { + final Map map = myOutputsToDelete.get(projectId); + if (map != null) { + final SourceUrlClassNamePair val = map.remove(outputPath); + if (val != null) { + if (LOG.isDebugEnabled() || ourDebugMode) { + final String message = "REMOVE path to delete: " + outputPath; + LOG.debug(message); + if (ourDebugMode) { + System.out.println(message); + } + } + if (map.isEmpty()) { + myOutputsToDelete.remove(projectId); + } + } + } + } + } + } + + public static final class ProjectRef extends Ref { + static class ProjectClosedException extends RuntimeException { + } + + public ProjectRef() { + } + + public ProjectRef(Project project) { + super(project); + } + + public Project get() { + final Project project = super.get(); + if (project != null && project.isDisposed()) { + throw new ProjectClosedException(); + } + return project; + } + } + +} diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java index 833785759cc6..cb32e9ed21a8 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java @@ -1,967 +1,967 @@ -/* - * 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. - */ - -/* - * @author: Eugene Zhuravlev - * Date: Jan 24, 2003 - * Time: 4:25:47 PM - */ -package com.intellij.compiler.impl.javaCompiler; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.compiler.*; -import com.intellij.compiler.classParsing.AnnotationConstantValue; -import com.intellij.compiler.classParsing.MethodInfo; -import com.intellij.compiler.impl.CompilerUtil; -import com.intellij.compiler.make.Cache; -import com.intellij.compiler.make.CacheCorruptedException; -import com.intellij.compiler.make.DependencyCache; -import com.intellij.compiler.make.MakeUtil; -import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter; -import com.intellij.ide.util.projectWizard.JavaModuleBuilder; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.compiler.*; -import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.module.JavaModuleType; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.JavaSdkType; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiClass; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.util.Chunk; -import com.intellij.util.cls.ClsFormatException; -import gnu.trove.THashMap; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -public class BackendCompilerWrapper { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper"); - - private final BackendCompiler myCompiler; - - private final CompileContextEx myCompileContext; - private final List myFilesToCompile; - private final TranslatingCompiler.OutputSink mySink; - private final Chunk myChunk; - private final Project myProject; - private final Map myModuleToTempDirMap = new THashMap(); - private final ProjectFileIndex myProjectFileIndex; - @NonNls private static final String PACKAGE_ANNOTATION_FILE_NAME = "package-info.java"; - private static final FileObject myStopThreadToken = new FileObject(new File(""), new byte[0]); - public final Map> myFileNameToSourceMap= new THashMap>(); - private final Set myProcessedPackageInfos = new HashSet(); - private final CompileStatistics myStatistics; - private volatile String myModuleName = null; - private boolean myForceCompileTestsSeparately = false; - - public BackendCompilerWrapper(Chunk chunk, @NotNull final Project project, - @NotNull List filesToCompile, - @NotNull CompileContextEx compileContext, - @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) { - myChunk = chunk; - myProject = project; - myCompiler = compiler; - myCompileContext = compileContext; - myFilesToCompile = filesToCompile; - mySink = sink; - myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); - CompileStatistics stat = compileContext.getUserData(CompileStatistics.KEY); - if (stat == null) { - stat = new CompileStatistics(); - compileContext.putUserData(CompileStatistics.KEY, stat); - } - myStatistics = stat; - } - - public void compile() throws CompilerException, CacheCorruptedException { - Application application = ApplicationManager.getApplication(); - try { - if (!myFilesToCompile.isEmpty()) { - if (application.isUnitTestMode()) { - saveTestData(); - } - compileModules(buildModuleToFilesMap(myFilesToCompile)); - } - } - catch (SecurityException e) { - throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e); - } - catch (IllegalArgumentException e) { - throw new CompilerException(e.getMessage(), e); - } - finally { - for (final VirtualFile file : myModuleToTempDirMap.values()) { - if (file != null) { - final File ioFile = new File(file.getPath()); - FileUtil.asyncDelete(ioFile); - } - } - myModuleToTempDirMap.clear(); - } - - if (!myFilesToCompile.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - // package-info.java hack - final List outputs = new ArrayList(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (final VirtualFile file : myFilesToCompile) { - if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName()) && !myProcessedPackageInfos.contains(file)) { - outputs.add(new OutputItemImpl(file)); - } - } - } - }); - if (!outputs.isEmpty()) { - mySink.add(null, outputs, VirtualFile.EMPTY_ARRAY); - } - } - } - - public boolean isForceCompileTestsSeparately() { - return myForceCompileTestsSeparately; - } - - public void setForceCompileTestsSeparately(boolean forceCompileTestsSeparately) { - myForceCompileTestsSeparately = forceCompileTestsSeparately; - } - - private Map> buildModuleToFilesMap(final List filesToCompile) { - if (myChunk.getNodes().size() == 1) { - return Collections.singletonMap(myChunk.getNodes().iterator().next(), Collections.unmodifiableList(filesToCompile)); - } - return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile); - } - - private void compileModules(final Map> moduleToFilesMap) throws CompilerException { - try { - compileChunk(new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap)); - } - catch (IOException e) { - throw new CompilerException(e.getMessage(), e); - } - } - - private void compileChunk(ModuleChunk chunk) throws IOException { - runTransformingCompilers(chunk); - - setPresentableNameFor(chunk); - - final List outs = new ArrayList(); - File fileToDelete = getOutputDirsToCompileTo(chunk, outs); - - try { - for (final OutputDir outputDir : outs) { - chunk.setSourcesFilter(outputDir.getKind()); - doCompile(chunk, outputDir.getPath()); - } - } - finally { - if (fileToDelete != null) { - FileUtil.asyncDelete(fileToDelete); - } - } - } - - - private void setPresentableNameFor(final ModuleChunk chunk) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final Module[] modules = chunk.getModules(); - StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8)); - for (int idx = 0; idx < modules.length; idx++) { - final Module module = modules[idx]; - if (idx > 0) { - moduleName.append(", "); - } - moduleName.append(module.getName()); - if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) { - moduleName.append("..."); - break; - } - } - myModuleName = moduleName.toString(); - } - }); - } - - @Nullable - private File getOutputDirsToCompileTo(ModuleChunk chunk, final List dirs) throws IOException { - File fileToDelete = null; - if (chunk.getModuleCount() == 1) { // optimization - final Module module = chunk.getModules()[0]; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final String sourcesOutputDir = getOutputDir(module); - if (shouldCompileTestsSeparately(module)) { - if (sourcesOutputDir != null) { - dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES)); - } - final String testsOutputDir = getTestsOutputDir(module); - if (testsOutputDir == null) { - LOG.error("Tests output dir is null for module \"" + module.getName() + "\""); - } - else { - dirs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES)); - } - } - else { // both sources and test sources go into the same output - if (sourcesOutputDir == null) { - LOG.error("Sources output dir is null for module \"" + module.getName() + "\""); - } - else { - dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES)); - } - } - } - }); - } - else { // chunk has several modules - final File outputDir = FileUtil.createTempDirectory("compile", "output"); - fileToDelete = outputDir; - dirs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES)); - } - return fileToDelete; - } - - - private boolean shouldCompileTestsSeparately(Module module) { - if (myForceCompileTestsSeparately) { - return true; - } - final String moduleTestOutputDirectory = getTestsOutputDir(module); - if (moduleTestOutputDirectory == null) { - return false; - } - final String moduleOutputDirectory = getOutputDir(module); - return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory); - } - - private void saveTestData() { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (VirtualFile file : myFilesToCompile) { - CompilerManagerImpl.addCompiledPath(file.getPath()); - } - } - }); - } - - private final Object lock = new Object(); - - private class SynchedCompilerParsing extends CompilerParsingThread { - private final ClassParsingThread myClassParsingThread; - - private SynchedCompilerParsing(Process process, - final CompileContext context, - OutputParser outputParser, - ClassParsingThread classParsingThread, - boolean readErrorStream, - boolean trimLines) { - super(process, outputParser, readErrorStream, trimLines,context); - myClassParsingThread = classParsingThread; - } - - public void setProgressText(String text) { - synchronized (lock) { - super.setProgressText(text); - } - } - - public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) { - synchronized (lock) { - super.message(category, message, url, lineNum, columnNum); - } - } - - public void fileProcessed(String path) { - synchronized (lock) { - sourceFileProcessed(); - } - } - - protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException { - synchronized (lock) { - myClassParsingThread.addPath(classFileToProcess); - } - } - } - - private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException { - myCompileContext.getProgressIndicator().checkCanceled(); - - if (ApplicationManager.getApplication().runReadAction(new Computable() { - public Boolean compute() { - return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE; - } - }).booleanValue()) { - return; // should not invoke javac with empty sources list - } - - ModuleType moduleType = chunk.getModules()[0].getModuleType(); - if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) && - !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) { - // TODO - // don't try to compile non-java type module - return; - } - - int exitValue = 0; - try { - Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext); - final long compilationStart = System.currentTimeMillis(); - final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir); - final Future classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread); - - OutputParser errorParser = myCompiler.createErrorParser(outputDir, process); - CompilerParsingThread errorParsingThread = errorParser == null - ? null - : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread, - true, errorParser.isTrimLines()); - Future errorParsingThreadFuture = null; - if (errorParsingThread != null) { - errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread); - } - - OutputParser outputParser = myCompiler.createOutputParser(outputDir); - CompilerParsingThread outputParsingThread = outputParser == null - ? null - : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread, - false, outputParser.isTrimLines()); - Future outputParsingThreadFuture = null; - if (outputParsingThread != null) { - outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread); - } - - try { - exitValue = process.waitFor(); - } - catch (InterruptedException e) { - process.destroy(); - exitValue = process.exitValue(); - } - finally { - if (errorParsingThread != null) { - errorParsingThread.setProcessTerminated(true); - } - if (outputParsingThread != null) { - outputParsingThread.setProcessTerminated(true); - } - joinThread(errorParsingThreadFuture); - joinThread(outputParsingThreadFuture); - classParsingThread.stopParsing(); - joinThread(classParsingThreadFuture); - - registerParsingException(outputParsingThread); - registerParsingException(errorParsingThread); - assert outputParsingThread == null || !outputParsingThread.processing; - assert errorParsingThread == null || !errorParsingThread.processing; - assert classParsingThread == null || !classParsingThread.processing; - } - } - finally { - compileFinished(exitValue, chunk, outputDir); - myModuleName = null; - } - } - - private static void joinThread(final Future threadFuture) { - if (threadFuture != null) { - try { - threadFuture.get(); - } - catch (InterruptedException ignored) { - LOG.info("Thread interrupted", ignored); - } - catch (ExecutionException ignored) { - LOG.info("Thread interrupted", ignored); - } - } - } - - private void registerParsingException(final CompilerParsingThread outputParsingThread) { - Throwable error = outputParsingThread == null ? null : outputParsingThread.getError(); - if (error != null) { - String message = error.getMessage(); - if (error instanceof CacheCorruptedException) { - myCompileContext.requestRebuildNextTime(message); - } - else { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); - } - } - } - - private void runTransformingCompilers(final ModuleChunk chunk) { - final JavaSourceTransformingCompiler[] transformers = - CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class); - if (transformers.length == 0) { - return; - } - if (LOG.isDebugEnabled()) { - LOG.debug("Running transforming compilers..."); - } - final Module[] modules = chunk.getModules(); - for (final JavaSourceTransformingCompiler transformer : transformers) { - final Map originalToCopyFileMap = new HashMap(); - final Application application = ApplicationManager.getApplication(); - application.invokeAndWait(new Runnable() { - public void run() { - for (final Module module : modules) { - for (final VirtualFile file : chunk.getFilesToCompile(module)) { - final VirtualFile untransformed = chunk.getOriginalFile(file); - if (transformer.isTransformable(untransformed)) { - application.runWriteAction(new Runnable() { - public void run() { - try { - // if untransformed != file, the file is already a (possibly transformed) copy of the original 'untransformed' file. - // If this is the case, just use already created copy and do not copy file content once again - final VirtualFile fileCopy = untransformed.equals(file)? createFileCopy(getTempDir(module), file) : file; - originalToCopyFileMap.put(file, fileCopy); - } - catch (IOException e) { - // skip it - } - } - }); - } - } - } - } - }, myCompileContext.getProgressIndicator().getModalityState()); - - // do actual transform - for (final Module module : modules) { - final List filesToCompile = chunk.getFilesToCompile(module); - for (int j = 0; j < filesToCompile.size(); j++) { - final VirtualFile file = filesToCompile.get(j); - final VirtualFile fileCopy = originalToCopyFileMap.get(file); - if (fileCopy != null) { - final boolean ok = transformer.transform(myCompileContext, fileCopy, chunk.getOriginalFile(file)); - if (ok) { - chunk.substituteWithTransformedVersion(module, j, fileCopy); - } - } - } - } - } - } - - private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException { - final String fileName = file.getName(); - if (tempDir.findChild(fileName) != null) { - int idx = 0; - while (true) { - //noinspection HardCodedStringLiteral - final String dirName = "dir" + idx++; - final VirtualFile dir = tempDir.findChild(dirName); - if (dir == null) { - tempDir = tempDir.createChildDirectory(this, dirName); - break; - } - if (dir.findChild(fileName) == null) { - tempDir = dir; - break; - } - } - } - return VfsUtil.copyFile(this, file, tempDir); - } - - private VirtualFile getTempDir(Module module) throws IOException { - VirtualFile tempDir = myModuleToTempDirMap.get(module); - if (tempDir == null) { - final String projectName = myProject.getName(); - final String moduleName = module.getName(); - File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName); - tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory); - if (tempDir == null) { - LOG.error("Cannot locate temp directory " + tempDirectory.getPath()); - } - myModuleToTempDirMap.put(module, tempDir); - } - return tempDir; - } - - private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) { - if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1); - } - - myCompiler.compileFinished(); - final List toRefresh = new ArrayList(); - final Map> results = new HashMap>(); - try { - final FileTypeManager typeManager = FileTypeManager.getInstance(); - final String outputDirPath = outputDir.replace(File.separatorChar, '/'); - try { - for (final Module module : chunk.getModules()) { - for (final VirtualFile root : chunk.getSourceRoots(module)) { - final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root); - if (LOG.isDebugEnabled()) { - LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\""); - } - buildOutputItemsList(outputDirPath, module, root, typeManager, root, packagePrefix, toRefresh, results); - } - } - } - catch (CacheCorruptedException e) { - myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); - if (LOG.isDebugEnabled()) { - LOG.debug(e); - } - } - } - finally { - CompilerUtil.refreshIOFiles(toRefresh); - for (Iterator>> it = results.entrySet().iterator(); it.hasNext();) { - Map.Entry> entry = it.next(); - mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY); - it.remove(); // to free memory - } - } - myFileNameToSourceMap.clear(); // clear the map before the next use - } - - private void buildOutputItemsList(final String outputDir, final Module module, VirtualFile from, - final FileTypeManager typeManager, - final VirtualFile sourceRoot, - final String packagePrefix, final List filesToRefresh, final Map> results) throws CacheCorruptedException { - final Ref exRef = new Ref(null); - final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex(); - final GlobalSearchScope srcRootScope = GlobalSearchScope.moduleScope(module).intersectWith(GlobalSearchScope.directoryScope(myProject, sourceRoot, true)); - - final ContentIterator contentIterator = new ContentIterator() { - public boolean processFile(final VirtualFile child) { - try { - assert child.isValid(); - if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) { - updateOutputItemsList(outputDir, child, sourceRoot, packagePrefix, filesToRefresh, results, srcRootScope); - } - return true; - } - catch (CacheCorruptedException e) { - exRef.set(e); - return false; - } - } - }; - if (fileIndex.isInContent(from)) { - // use file index for iteration to handle 'inner modules' and excludes properly - fileIndex.iterateContentUnderDirectory(from, contentIterator); - } - else { - // seems to be a root for generated sources - new Object() { - void iterateContent(VirtualFile from) { - for (VirtualFile child : from.getChildren()) { - if (child.isDirectory()) { - iterateContent(child); - } - else { - contentIterator.processFile(child); - } - } - } - }.iterateContent(from); - } - final CacheCorruptedException exc = exRef.get(); - if (exc != null) { - throw exc; - } - } - - private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) { - if (LOG.isDebugEnabled()) { - LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource + - "; " + pathToClass + "]"); - } - Set paths = myFileNameToSourceMap.get(sourceFileName); - - if (paths == null) { - paths = new HashSet(); - myFileNameToSourceMap.put(sourceFileName, paths); - } - paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass)); - } - - private void updateOutputItemsList(final String outputDir, final VirtualFile srcFile, - VirtualFile sourceRoot, - final String packagePrefix, final List filesToRefresh, - Map> results, - final GlobalSearchScope srcRootScope) throws CacheCorruptedException { - final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache(); - final Set paths = myFileNameToSourceMap.get(srcFile.getName()); - if (paths == null || paths.isEmpty()) { - return; - } - final String filePath = "/" + calcPackagePath(srcFile, sourceRoot, packagePrefix); - for (final CompiledClass cc : paths) { - myCompileContext.getProgressIndicator().checkCanceled(); - if (LOG.isDebugEnabled()) { - LOG.debug("Checking [pathToClass; relPathToSource] = " + cc); - } - - boolean pathsEquals = FileUtil.pathsEqual(filePath, cc.relativePathToSource); - if (!pathsEquals) { - final String qName = myCompileContext.getDependencyCache().resolve(cc.qName); - if (qName != null) { - pathsEquals = ApplicationManager.getApplication().runReadAction(new Computable() { - public Boolean compute() { - final JavaPsiFacade facade = JavaPsiFacade.getInstance(myProject); - PsiClass psiClass = facade.findClass(qName, srcRootScope); - if (psiClass == null) { - final int dollarIndex = qName.indexOf("$"); - if (dollarIndex >= 0) { - final String topLevelClassName = qName.substring(0, dollarIndex); - psiClass = facade.findClass(topLevelClassName, srcRootScope); - } - } - if (psiClass != null) { - final VirtualFile vFile = psiClass.getContainingFile().getVirtualFile(); - return vFile != null && vFile.equals(srcFile); - } - return false; - } - }); - } - } - - if (pathsEquals) { - final String outputPath = cc.pathToClass.replace(File.separatorChar, '/'); - final Pair realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh); - if (realLocation != null) { - Collection outputs = results.get(realLocation.getFirst()); - if (outputs == null) { - outputs = new ArrayList(); - results.put(realLocation.getFirst(), outputs); - } - outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile)); - if (PACKAGE_ANNOTATION_FILE_NAME.equals(srcFile.getName())) { - myProcessedPackageInfos.add(srcFile); - } - if (CompilerConfiguration.MAKE_ENABLED) { - newCache.setPath(cc.qName, realLocation.getSecond()); - } - if (LOG.isDebugEnabled()) { - LOG.debug("Added output item: [outputDir; outputPath; sourceFile] = [" + realLocation.getFirst() + "; " + - realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]"); - } - } - else { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1); - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir); - } - } - } - } - } - - /** - * - * @param srcFile - * @param sourceRoot - * @param packagePrefix - * @return A 'package'-path to a given src file relative to a specified root. "/" slashes must be used - */ - protected static String calcPackagePath(VirtualFile srcFile, VirtualFile sourceRoot, String packagePrefix) { - final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : ""; - return prefix + VfsUtil.getRelativePath(srcFile, sourceRoot, '/'); - } - - @Nullable - private Pair moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List filesToRefresh) { - final Module module = myCompileContext.getModuleByFile(sourceFile); - if (module == null) { - final String message = - "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass; - LOG.info(message); - myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1); - // do not move: looks like source file has been invalidated, need recompilation - return new Pair(tempOutputDir, pathToClass); - } - final String realOutputDir; - if (myCompileContext.isInTestSourceContent(sourceFile)) { - realOutputDir = getTestsOutputDir(module); - LOG.assertTrue(realOutputDir != null); - } - else { - realOutputDir = getOutputDir(module); - LOG.assertTrue(realOutputDir != null); - } - - if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move - filesToRefresh.add(new File(pathToClass)); - return new Pair(realOutputDir, pathToClass); - } - - final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length()); - final File fromFile = new File(pathToClass); - final File toFile = new File(realPathToClass); - - boolean success = fromFile.renameTo(toFile); - if (!success) { - // assuming cause of the fail: intermediate dirs do not exist - FileUtil.createParentDirs(toFile); - // retry after making non-existent dirs - success = fromFile.renameTo(toFile); - } - if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints. - try { - FileUtil.copy(fromFile, toFile); - FileUtil.delete(fromFile); - success = true; - } - catch (IOException e) { - LOG.info(e); - success = false; - } - } - if (success) { - filesToRefresh.add(toFile); - return new Pair(realOutputDir, realPathToClass); - } - return null; - } - - private final Map myModuleToTestsOutput = new HashMap(); - - private String getTestsOutputDir(final Module module) { - if (myModuleToTestsOutput.containsKey(module)) { - return myModuleToTestsOutput.get(module); - } - final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module); - final String out = outputDirectory != null? outputDirectory.getPath() : null; - myModuleToTestsOutput.put(module, out); - return out; - } - - private final Map myModuleToOutput = new HashMap(); - - private String getOutputDir(final Module module) { - if (myModuleToOutput.containsKey(module)) { - return myModuleToOutput.get(module); - } - final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module); - final String out = outputDirectory != null? outputDirectory.getPath() : null; - myModuleToOutput.put(module, out); - return out; - } - - private void sourceFileProcessed() { - myStatistics.incFilesCount(); - updateStatistics(); - } - - private void updateStatistics() { - final String msg; - String moduleName = myModuleName; - if (moduleName != null) { - msg = CompilerBundle.message("statistics.files.classes.module", myStatistics.getFilesCount(), myStatistics.getClassesCount(), moduleName); - } - else { - msg = CompilerBundle.message("statistics.files.classes", myStatistics.getFilesCount(), myStatistics.getClassesCount()); - } - myCompileContext.getProgressIndicator().setText2(msg); - //myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile); - } - - private class ClassParsingThread implements Runnable { - private final BlockingQueue myPaths = new ArrayBlockingQueue(50000); - private CacheCorruptedException myError = null; - private final boolean myAddNotNullAssertions; - private final boolean myIsJdk16; - private final String myOutputDir; - - private ClassParsingThread(final boolean isJdk16, String outputDir) { - myIsJdk16 = isJdk16; - myOutputDir = FileUtil.toSystemIndependentName(outputDir); - myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL; - } - - private volatile boolean processing; - public void run() { - processing = true; - try { - while (true) { - FileObject path = myPaths.take(); - - if (path == myStopThreadToken) break; - processPath(path); - } - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (CacheCorruptedException e) { - myError = e; - } - finally { - processing = false; - } - } - - public void addPath(FileObject path) throws CacheCorruptedException { - if (myError != null) { - throw myError; - } - myPaths.offer(path); - } - - public void stopParsing() { - myPaths.offer(myStopThreadToken); - } - - private void processPath(FileObject fileObject) throws CacheCorruptedException { - File file = fileObject.getFile(); - final String path = file.getPath(); - try { - if (CompilerConfiguration.MAKE_ENABLED) { - byte[] fileContent = fileObject.getContent(); - // the file is assumed to exist! - final DependencyCache dependencyCache = myCompileContext.getDependencyCache(); - final int newClassQName = dependencyCache.reparseClassFile(file, fileContent); - final Cache newClassesCache = dependencyCache.getNewClassesCache(); - final String sourceFileName = newClassesCache.getSourceFileName(newClassQName); - final String qName = dependencyCache.resolve(newClassQName); - String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName); - putName(sourceFileName, newClassQName, relativePathToSource, path); - boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName); - - if (haveToInstrument) { - try { - ClassReader reader = new ClassReader(fileContent, 0, fileContent.length); - ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16); - - final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer); - reader.accept(instrumenter, 0); - if (instrumenter.isModification()) { - fileObject = new FileObject(file, writer.toByteArray()); - } - } - catch (Exception ignored) { - LOG.info(ignored); - } - } - - fileObject.save(); - } - else { - final String _path = FileUtil.toSystemIndependentName(path); - final int dollarIndex = _path.indexOf('$'); - final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length(); - final int slashIndex = _path.lastIndexOf('/'); - final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java"; - String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java"; - putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path); - } - } - catch (ClsFormatException e) { - final String m = e.getMessage(); - String message = CompilerBundle.message("error.bad.class.file.format", StringUtil.isEmpty(m) ? path : m + "\n" + path); - myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); - LOG.info(e); - } - catch (IOException e) { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1); - LOG.info(e); - } - finally { - myStatistics.incClassesCount(); - updateStatistics(); - } - } - } - - private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className) throws CacheCorruptedException { - for (MethodInfo methodId : cache.getMethods(className)) { - for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) { - if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { - return true; - } - } - final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations(); - for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) { - for (AnnotationConstantValue annotation : _singleParamAnnotations) { - if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { - return true; - } - } - } - } - return false; - } - - private static boolean isJdk6(final Sdk jdk) { - boolean isJDK16 = false; - if (jdk != null) { - final String versionString = jdk.getVersionString(); - if (versionString != null) { - isJDK16 = versionString.contains("1.6") || versionString.contains("6.0"); - } - } - return isJDK16; - } - - private static final class CompileStatistics { - private static final Key KEY = Key.create("_Compile_Statistics_"); - private int myClassesCount; - private int myFilesCount; - - public int getClassesCount() { - return myClassesCount; - } - - public int incClassesCount() { - return ++myClassesCount; - } - - public int getFilesCount() { - return myFilesCount; - } - - public int incFilesCount() { - return ++myFilesCount; - } - } -} +/* + * 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. + */ + +/* + * @author: Eugene Zhuravlev + * Date: Jan 24, 2003 + * Time: 4:25:47 PM + */ +package com.intellij.compiler.impl.javaCompiler; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.compiler.*; +import com.intellij.compiler.classParsing.AnnotationConstantValue; +import com.intellij.compiler.classParsing.MethodInfo; +import com.intellij.compiler.impl.CompilerUtil; +import com.intellij.compiler.make.Cache; +import com.intellij.compiler.make.CacheCorruptedException; +import com.intellij.compiler.make.DependencyCache; +import com.intellij.compiler.make.MakeUtil; +import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter; +import com.intellij.ide.util.projectWizard.JavaModuleBuilder; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.JavaModuleType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.Chunk; +import com.intellij.util.cls.ClsFormatException; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class BackendCompilerWrapper { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper"); + + private final BackendCompiler myCompiler; + + private final CompileContextEx myCompileContext; + private final List myFilesToCompile; + private final TranslatingCompiler.OutputSink mySink; + private final Chunk myChunk; + private final Project myProject; + private final Map myModuleToTempDirMap = new THashMap(); + private final ProjectFileIndex myProjectFileIndex; + @NonNls private static final String PACKAGE_ANNOTATION_FILE_NAME = "package-info.java"; + private static final FileObject myStopThreadToken = new FileObject(new File(""), new byte[0]); + public final Map> myFileNameToSourceMap= new THashMap>(); + private final Set myProcessedPackageInfos = new HashSet(); + private final CompileStatistics myStatistics; + private volatile String myModuleName = null; + private boolean myForceCompileTestsSeparately = false; + + public BackendCompilerWrapper(Chunk chunk, @NotNull final Project project, + @NotNull List filesToCompile, + @NotNull CompileContextEx compileContext, + @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) { + myChunk = chunk; + myProject = project; + myCompiler = compiler; + myCompileContext = compileContext; + myFilesToCompile = filesToCompile; + mySink = sink; + myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); + CompileStatistics stat = compileContext.getUserData(CompileStatistics.KEY); + if (stat == null) { + stat = new CompileStatistics(); + compileContext.putUserData(CompileStatistics.KEY, stat); + } + myStatistics = stat; + } + + public void compile() throws CompilerException, CacheCorruptedException { + Application application = ApplicationManager.getApplication(); + try { + if (!myFilesToCompile.isEmpty()) { + if (application.isUnitTestMode()) { + saveTestData(); + } + compileModules(buildModuleToFilesMap(myFilesToCompile)); + } + } + catch (SecurityException e) { + throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e); + } + catch (IllegalArgumentException e) { + throw new CompilerException(e.getMessage(), e); + } + finally { + for (final VirtualFile file : myModuleToTempDirMap.values()) { + if (file != null) { + final File ioFile = new File(file.getPath()); + FileUtil.asyncDelete(ioFile); + } + } + myModuleToTempDirMap.clear(); + } + + if (!myFilesToCompile.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + // package-info.java hack + final List outputs = new ArrayList(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (final VirtualFile file : myFilesToCompile) { + if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName()) && !myProcessedPackageInfos.contains(file)) { + outputs.add(new OutputItemImpl(file)); + } + } + } + }); + if (!outputs.isEmpty()) { + mySink.add(null, outputs, VirtualFile.EMPTY_ARRAY); + } + } + } + + public boolean isForceCompileTestsSeparately() { + return myForceCompileTestsSeparately; + } + + public void setForceCompileTestsSeparately(boolean forceCompileTestsSeparately) { + myForceCompileTestsSeparately = forceCompileTestsSeparately; + } + + private Map> buildModuleToFilesMap(final List filesToCompile) { + if (myChunk.getNodes().size() == 1) { + return Collections.singletonMap(myChunk.getNodes().iterator().next(), Collections.unmodifiableList(filesToCompile)); + } + return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile); + } + + private void compileModules(final Map> moduleToFilesMap) throws CompilerException { + try { + compileChunk(new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap)); + } + catch (IOException e) { + throw new CompilerException(e.getMessage(), e); + } + } + + private void compileChunk(ModuleChunk chunk) throws IOException { + runTransformingCompilers(chunk); + + setPresentableNameFor(chunk); + + final List outs = new ArrayList(); + File fileToDelete = getOutputDirsToCompileTo(chunk, outs); + + try { + for (final OutputDir outputDir : outs) { + chunk.setSourcesFilter(outputDir.getKind()); + doCompile(chunk, outputDir.getPath()); + } + } + finally { + if (fileToDelete != null) { + FileUtil.asyncDelete(fileToDelete); + } + } + } + + + private void setPresentableNameFor(final ModuleChunk chunk) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final Module[] modules = chunk.getModules(); + StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8)); + for (int idx = 0; idx < modules.length; idx++) { + final Module module = modules[idx]; + if (idx > 0) { + moduleName.append(", "); + } + moduleName.append(module.getName()); + if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) { + moduleName.append("..."); + break; + } + } + myModuleName = moduleName.toString(); + } + }); + } + + @Nullable + private File getOutputDirsToCompileTo(ModuleChunk chunk, final List dirs) throws IOException { + File fileToDelete = null; + if (chunk.getModuleCount() == 1) { // optimization + final Module module = chunk.getModules()[0]; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final String sourcesOutputDir = getOutputDir(module); + if (shouldCompileTestsSeparately(module)) { + if (sourcesOutputDir != null) { + dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES)); + } + final String testsOutputDir = getTestsOutputDir(module); + if (testsOutputDir == null) { + LOG.error("Tests output dir is null for module \"" + module.getName() + "\""); + } + else { + dirs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES)); + } + } + else { // both sources and test sources go into the same output + if (sourcesOutputDir == null) { + LOG.error("Sources output dir is null for module \"" + module.getName() + "\""); + } + else { + dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES)); + } + } + } + }); + } + else { // chunk has several modules + final File outputDir = FileUtil.createTempDirectory("compile", "output"); + fileToDelete = outputDir; + dirs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES)); + } + return fileToDelete; + } + + + private boolean shouldCompileTestsSeparately(Module module) { + if (myForceCompileTestsSeparately) { + return true; + } + final String moduleTestOutputDirectory = getTestsOutputDir(module); + if (moduleTestOutputDirectory == null) { + return false; + } + final String moduleOutputDirectory = getOutputDir(module); + return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory); + } + + private void saveTestData() { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (VirtualFile file : myFilesToCompile) { + CompilerManagerImpl.addCompiledPath(file.getPath()); + } + } + }); + } + + private final Object lock = new Object(); + + private class SynchedCompilerParsing extends CompilerParsingThread { + private final ClassParsingThread myClassParsingThread; + + private SynchedCompilerParsing(Process process, + final CompileContext context, + OutputParser outputParser, + ClassParsingThread classParsingThread, + boolean readErrorStream, + boolean trimLines) { + super(process, outputParser, readErrorStream, trimLines,context); + myClassParsingThread = classParsingThread; + } + + public void setProgressText(String text) { + synchronized (lock) { + super.setProgressText(text); + } + } + + public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) { + synchronized (lock) { + super.message(category, message, url, lineNum, columnNum); + } + } + + public void fileProcessed(String path) { + synchronized (lock) { + sourceFileProcessed(); + } + } + + protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException { + synchronized (lock) { + myClassParsingThread.addPath(classFileToProcess); + } + } + } + + private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException { + myCompileContext.getProgressIndicator().checkCanceled(); + + if (ApplicationManager.getApplication().runReadAction(new Computable() { + public Boolean compute() { + return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE; + } + }).booleanValue()) { + return; // should not invoke javac with empty sources list + } + + ModuleType moduleType = chunk.getModules()[0].getModuleType(); + if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) && + !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) { + // TODO + // don't try to compile non-java type module + return; + } + + int exitValue = 0; + try { + Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext); + final long compilationStart = System.currentTimeMillis(); + final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir); + final Future classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread); + + OutputParser errorParser = myCompiler.createErrorParser(outputDir, process); + CompilerParsingThread errorParsingThread = errorParser == null + ? null + : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread, + true, errorParser.isTrimLines()); + Future errorParsingThreadFuture = null; + if (errorParsingThread != null) { + errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread); + } + + OutputParser outputParser = myCompiler.createOutputParser(outputDir); + CompilerParsingThread outputParsingThread = outputParser == null + ? null + : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread, + false, outputParser.isTrimLines()); + Future outputParsingThreadFuture = null; + if (outputParsingThread != null) { + outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread); + } + + try { + exitValue = process.waitFor(); + } + catch (InterruptedException e) { + process.destroy(); + exitValue = process.exitValue(); + } + finally { + if (errorParsingThread != null) { + errorParsingThread.setProcessTerminated(true); + } + if (outputParsingThread != null) { + outputParsingThread.setProcessTerminated(true); + } + joinThread(errorParsingThreadFuture); + joinThread(outputParsingThreadFuture); + classParsingThread.stopParsing(); + joinThread(classParsingThreadFuture); + + registerParsingException(outputParsingThread); + registerParsingException(errorParsingThread); + assert outputParsingThread == null || !outputParsingThread.processing; + assert errorParsingThread == null || !errorParsingThread.processing; + assert classParsingThread == null || !classParsingThread.processing; + } + } + finally { + compileFinished(exitValue, chunk, outputDir); + myModuleName = null; + } + } + + private static void joinThread(final Future threadFuture) { + if (threadFuture != null) { + try { + threadFuture.get(); + } + catch (InterruptedException ignored) { + LOG.info("Thread interrupted", ignored); + } + catch (ExecutionException ignored) { + LOG.info("Thread interrupted", ignored); + } + } + } + + private void registerParsingException(final CompilerParsingThread outputParsingThread) { + Throwable error = outputParsingThread == null ? null : outputParsingThread.getError(); + if (error != null) { + String message = error.getMessage(); + if (error instanceof CacheCorruptedException) { + myCompileContext.requestRebuildNextTime(message); + } + else { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); + } + } + } + + private void runTransformingCompilers(final ModuleChunk chunk) { + final JavaSourceTransformingCompiler[] transformers = + CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class); + if (transformers.length == 0) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Running transforming compilers..."); + } + final Module[] modules = chunk.getModules(); + for (final JavaSourceTransformingCompiler transformer : transformers) { + final Map originalToCopyFileMap = new HashMap(); + final Application application = ApplicationManager.getApplication(); + application.invokeAndWait(new Runnable() { + public void run() { + for (final Module module : modules) { + for (final VirtualFile file : chunk.getFilesToCompile(module)) { + final VirtualFile untransformed = chunk.getOriginalFile(file); + if (transformer.isTransformable(untransformed)) { + application.runWriteAction(new Runnable() { + public void run() { + try { + // if untransformed != file, the file is already a (possibly transformed) copy of the original 'untransformed' file. + // If this is the case, just use already created copy and do not copy file content once again + final VirtualFile fileCopy = untransformed.equals(file)? createFileCopy(getTempDir(module), file) : file; + originalToCopyFileMap.put(file, fileCopy); + } + catch (IOException e) { + // skip it + } + } + }); + } + } + } + } + }, myCompileContext.getProgressIndicator().getModalityState()); + + // do actual transform + for (final Module module : modules) { + final List filesToCompile = chunk.getFilesToCompile(module); + for (int j = 0; j < filesToCompile.size(); j++) { + final VirtualFile file = filesToCompile.get(j); + final VirtualFile fileCopy = originalToCopyFileMap.get(file); + if (fileCopy != null) { + final boolean ok = transformer.transform(myCompileContext, fileCopy, chunk.getOriginalFile(file)); + if (ok) { + chunk.substituteWithTransformedVersion(module, j, fileCopy); + } + } + } + } + } + } + + private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException { + final String fileName = file.getName(); + if (tempDir.findChild(fileName) != null) { + int idx = 0; + while (true) { + //noinspection HardCodedStringLiteral + final String dirName = "dir" + idx++; + final VirtualFile dir = tempDir.findChild(dirName); + if (dir == null) { + tempDir = tempDir.createChildDirectory(this, dirName); + break; + } + if (dir.findChild(fileName) == null) { + tempDir = dir; + break; + } + } + } + return VfsUtil.copyFile(this, file, tempDir); + } + + private VirtualFile getTempDir(Module module) throws IOException { + VirtualFile tempDir = myModuleToTempDirMap.get(module); + if (tempDir == null) { + final String projectName = myProject.getName(); + final String moduleName = module.getName(); + File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName); + tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory); + if (tempDir == null) { + LOG.error("Cannot locate temp directory " + tempDirectory.getPath()); + } + myModuleToTempDirMap.put(module, tempDir); + } + return tempDir; + } + + private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) { + if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1); + } + + myCompiler.compileFinished(); + final List toRefresh = new ArrayList(); + final Map> results = new HashMap>(); + try { + final FileTypeManager typeManager = FileTypeManager.getInstance(); + final String outputDirPath = outputDir.replace(File.separatorChar, '/'); + try { + for (final Module module : chunk.getModules()) { + for (final VirtualFile root : chunk.getSourceRoots(module)) { + final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root); + if (LOG.isDebugEnabled()) { + LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\""); + } + buildOutputItemsList(outputDirPath, module, root, typeManager, root, packagePrefix, toRefresh, results); + } + } + } + catch (CacheCorruptedException e) { + myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); + if (LOG.isDebugEnabled()) { + LOG.debug(e); + } + } + } + finally { + CompilerUtil.refreshIOFiles(toRefresh); + for (Iterator>> it = results.entrySet().iterator(); it.hasNext();) { + Map.Entry> entry = it.next(); + mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY); + it.remove(); // to free memory + } + } + myFileNameToSourceMap.clear(); // clear the map before the next use + } + + private void buildOutputItemsList(final String outputDir, final Module module, VirtualFile from, + final FileTypeManager typeManager, + final VirtualFile sourceRoot, + final String packagePrefix, final List filesToRefresh, final Map> results) throws CacheCorruptedException { + final Ref exRef = new Ref(null); + final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex(); + final GlobalSearchScope srcRootScope = GlobalSearchScope.moduleScope(module).intersectWith(GlobalSearchScope.directoryScope(myProject, sourceRoot, true)); + + final ContentIterator contentIterator = new ContentIterator() { + public boolean processFile(final VirtualFile child) { + try { + assert child.isValid(); + if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) { + updateOutputItemsList(outputDir, child, sourceRoot, packagePrefix, filesToRefresh, results, srcRootScope); + } + return true; + } + catch (CacheCorruptedException e) { + exRef.set(e); + return false; + } + } + }; + if (fileIndex.isInContent(from)) { + // use file index for iteration to handle 'inner modules' and excludes properly + fileIndex.iterateContentUnderDirectory(from, contentIterator); + } + else { + // seems to be a root for generated sources + new Object() { + void iterateContent(VirtualFile from) { + for (VirtualFile child : from.getChildren()) { + if (child.isDirectory()) { + iterateContent(child); + } + else { + contentIterator.processFile(child); + } + } + } + }.iterateContent(from); + } + final CacheCorruptedException exc = exRef.get(); + if (exc != null) { + throw exc; + } + } + + private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) { + if (LOG.isDebugEnabled()) { + LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource + + "; " + pathToClass + "]"); + } + Set paths = myFileNameToSourceMap.get(sourceFileName); + + if (paths == null) { + paths = new HashSet(); + myFileNameToSourceMap.put(sourceFileName, paths); + } + paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass)); + } + + private void updateOutputItemsList(final String outputDir, final VirtualFile srcFile, + VirtualFile sourceRoot, + final String packagePrefix, final List filesToRefresh, + Map> results, + final GlobalSearchScope srcRootScope) throws CacheCorruptedException { + final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache(); + final Set paths = myFileNameToSourceMap.get(srcFile.getName()); + if (paths == null || paths.isEmpty()) { + return; + } + final String filePath = "/" + calcPackagePath(srcFile, sourceRoot, packagePrefix); + for (final CompiledClass cc : paths) { + myCompileContext.getProgressIndicator().checkCanceled(); + if (LOG.isDebugEnabled()) { + LOG.debug("Checking [pathToClass; relPathToSource] = " + cc); + } + + boolean pathsEquals = FileUtil.pathsEqual(filePath, cc.relativePathToSource); + if (!pathsEquals) { + final String qName = myCompileContext.getDependencyCache().resolve(cc.qName); + if (qName != null) { + pathsEquals = ApplicationManager.getApplication().runReadAction(new Computable() { + public Boolean compute() { + final JavaPsiFacade facade = JavaPsiFacade.getInstance(myProject); + PsiClass psiClass = facade.findClass(qName, srcRootScope); + if (psiClass == null) { + final int dollarIndex = qName.indexOf("$"); + if (dollarIndex >= 0) { + final String topLevelClassName = qName.substring(0, dollarIndex); + psiClass = facade.findClass(topLevelClassName, srcRootScope); + } + } + if (psiClass != null) { + final VirtualFile vFile = psiClass.getContainingFile().getVirtualFile(); + return vFile != null && vFile.equals(srcFile); + } + return false; + } + }); + } + } + + if (pathsEquals) { + final String outputPath = cc.pathToClass.replace(File.separatorChar, '/'); + final Pair realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh); + if (realLocation != null) { + Collection outputs = results.get(realLocation.getFirst()); + if (outputs == null) { + outputs = new ArrayList(); + results.put(realLocation.getFirst(), outputs); + } + outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile)); + if (PACKAGE_ANNOTATION_FILE_NAME.equals(srcFile.getName())) { + myProcessedPackageInfos.add(srcFile); + } + if (CompilerConfiguration.MAKE_ENABLED) { + newCache.setPath(cc.qName, realLocation.getSecond()); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Added output item: [outputDir; outputPath; sourceFile] = [" + realLocation.getFirst() + "; " + + realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]"); + } + } + else { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1); + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir); + } + } + } + } + } + + /** + * + * @param srcFile + * @param sourceRoot + * @param packagePrefix + * @return A 'package'-path to a given src file relative to a specified root. "/" slashes must be used + */ + protected static String calcPackagePath(VirtualFile srcFile, VirtualFile sourceRoot, String packagePrefix) { + final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : ""; + return prefix + VfsUtil.getRelativePath(srcFile, sourceRoot, '/'); + } + + @Nullable + private Pair moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List filesToRefresh) { + final Module module = myCompileContext.getModuleByFile(sourceFile); + if (module == null) { + final String message = + "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass; + LOG.info(message); + myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1); + // do not move: looks like source file has been invalidated, need recompilation + return new Pair(tempOutputDir, pathToClass); + } + final String realOutputDir; + if (myCompileContext.isInTestSourceContent(sourceFile)) { + realOutputDir = getTestsOutputDir(module); + LOG.assertTrue(realOutputDir != null); + } + else { + realOutputDir = getOutputDir(module); + LOG.assertTrue(realOutputDir != null); + } + + if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move + filesToRefresh.add(new File(pathToClass)); + return new Pair(realOutputDir, pathToClass); + } + + final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length()); + final File fromFile = new File(pathToClass); + final File toFile = new File(realPathToClass); + + boolean success = fromFile.renameTo(toFile); + if (!success) { + // assuming cause of the fail: intermediate dirs do not exist + FileUtil.createParentDirs(toFile); + // retry after making non-existent dirs + success = fromFile.renameTo(toFile); + } + if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints. + try { + FileUtil.copy(fromFile, toFile); + FileUtil.delete(fromFile); + success = true; + } + catch (IOException e) { + LOG.info(e); + success = false; + } + } + if (success) { + filesToRefresh.add(toFile); + return new Pair(realOutputDir, realPathToClass); + } + return null; + } + + private final Map myModuleToTestsOutput = new HashMap(); + + private String getTestsOutputDir(final Module module) { + if (myModuleToTestsOutput.containsKey(module)) { + return myModuleToTestsOutput.get(module); + } + final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module); + final String out = outputDirectory != null? outputDirectory.getPath() : null; + myModuleToTestsOutput.put(module, out); + return out; + } + + private final Map myModuleToOutput = new HashMap(); + + private String getOutputDir(final Module module) { + if (myModuleToOutput.containsKey(module)) { + return myModuleToOutput.get(module); + } + final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module); + final String out = outputDirectory != null? outputDirectory.getPath() : null; + myModuleToOutput.put(module, out); + return out; + } + + private void sourceFileProcessed() { + myStatistics.incFilesCount(); + updateStatistics(); + } + + private void updateStatistics() { + final String msg; + String moduleName = myModuleName; + if (moduleName != null) { + msg = CompilerBundle.message("statistics.files.classes.module", myStatistics.getFilesCount(), myStatistics.getClassesCount(), moduleName); + } + else { + msg = CompilerBundle.message("statistics.files.classes", myStatistics.getFilesCount(), myStatistics.getClassesCount()); + } + myCompileContext.getProgressIndicator().setText2(msg); + //myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile); + } + + private class ClassParsingThread implements Runnable { + private final BlockingQueue myPaths = new ArrayBlockingQueue(50000); + private CacheCorruptedException myError = null; + private final boolean myAddNotNullAssertions; + private final boolean myIsJdk16; + private final String myOutputDir; + + private ClassParsingThread(final boolean isJdk16, String outputDir) { + myIsJdk16 = isJdk16; + myOutputDir = FileUtil.toSystemIndependentName(outputDir); + myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL; + } + + private volatile boolean processing; + public void run() { + processing = true; + try { + while (true) { + FileObject path = myPaths.take(); + + if (path == myStopThreadToken) break; + processPath(path); + } + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (CacheCorruptedException e) { + myError = e; + } + finally { + processing = false; + } + } + + public void addPath(FileObject path) throws CacheCorruptedException { + if (myError != null) { + throw myError; + } + myPaths.offer(path); + } + + public void stopParsing() { + myPaths.offer(myStopThreadToken); + } + + private void processPath(FileObject fileObject) throws CacheCorruptedException { + File file = fileObject.getFile(); + final String path = file.getPath(); + try { + if (CompilerConfiguration.MAKE_ENABLED) { + byte[] fileContent = fileObject.getContent(); + // the file is assumed to exist! + final DependencyCache dependencyCache = myCompileContext.getDependencyCache(); + final int newClassQName = dependencyCache.reparseClassFile(file, fileContent); + final Cache newClassesCache = dependencyCache.getNewClassesCache(); + final String sourceFileName = newClassesCache.getSourceFileName(newClassQName); + final String qName = dependencyCache.resolve(newClassQName); + String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName); + putName(sourceFileName, newClassQName, relativePathToSource, path); + boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName); + + if (haveToInstrument) { + try { + ClassReader reader = new ClassReader(fileContent, 0, fileContent.length); + ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16); + + final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer); + reader.accept(instrumenter, 0); + if (instrumenter.isModification()) { + fileObject = new FileObject(file, writer.toByteArray()); + } + } + catch (Exception ignored) { + LOG.info(ignored); + } + } + + fileObject.save(); + } + else { + final String _path = FileUtil.toSystemIndependentName(path); + final int dollarIndex = _path.indexOf('$'); + final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length(); + final int slashIndex = _path.lastIndexOf('/'); + final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java"; + String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java"; + putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path); + } + } + catch (ClsFormatException e) { + final String m = e.getMessage(); + String message = CompilerBundle.message("error.bad.class.file.format", StringUtil.isEmpty(m) ? path : m + "\n" + path); + myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); + LOG.info(e); + } + catch (IOException e) { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1); + LOG.info(e); + } + finally { + myStatistics.incClassesCount(); + updateStatistics(); + } + } + } + + private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className) throws CacheCorruptedException { + for (MethodInfo methodId : cache.getMethods(className)) { + for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) { + if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { + return true; + } + } + final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations(); + for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) { + for (AnnotationConstantValue annotation : _singleParamAnnotations) { + if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { + return true; + } + } + } + } + return false; + } + + private static boolean isJdk6(final Sdk jdk) { + boolean isJDK16 = false; + if (jdk != null) { + final String versionString = jdk.getVersionString(); + if (versionString != null) { + isJDK16 = versionString.contains("1.6") || versionString.contains("6.0"); + } + } + return isJDK16; + } + + private static final class CompileStatistics { + private static final Key KEY = Key.create("_Compile_Statistics_"); + private int myClassesCount; + private int myFilesCount; + + public int getClassesCount() { + return myClassesCount; + } + + public int incClassesCount() { + return ++myClassesCount; + } + + public int getFilesCount() { + return myFilesCount; + } + + public int incFilesCount() { + return ++myFilesCount; + } + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java index bc1bc6483e0b..8f609405b3cc 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java @@ -1,237 +1,237 @@ -/* - * 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.compiler.impl.javaCompiler.jikes; - -import com.intellij.compiler.CompilerConfiguration; -import com.intellij.compiler.CompilerConfigurationImpl; -import com.intellij.compiler.OutputParser; -import com.intellij.compiler.impl.CompilerUtil; -import com.intellij.compiler.impl.javaCompiler.BackendCompiler; -import com.intellij.compiler.impl.javaCompiler.ExternalCompiler; -import com.intellij.compiler.impl.javaCompiler.ModuleChunk; -import com.intellij.compiler.options.JavaCompilersTab; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.compiler.CompileContext; -import com.intellij.openapi.compiler.CompileScope; -import com.intellij.openapi.compiler.CompilerBundle; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ShowSettingsUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.StringTokenizer; - -public class JikesCompiler extends ExternalCompiler { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.JikesHandler"); - private final Project myProject; - private File myTempFile; - - public JikesCompiler(Project project) { - myProject = project; - } - - public boolean checkCompiler(final CompileScope scope) { - String compilerPath = getCompilerPath(); - if (StringUtil.isEmpty(compilerPath)) { - Messages.showMessageDialog( - myProject, - CompilerBundle.message("jikes.error.path.to.compiler.unspecified"), CompilerBundle.message("compiler.jikes.name"), - Messages.getErrorIcon() - ); - openConfigurationDialog(); - compilerPath = getCompilerPath(); // update path - if (StringUtil.isEmpty(compilerPath)) { - return false; - } - } - - File file = new File(compilerPath); - if (!file.exists()) { - Messages.showMessageDialog( - myProject, - CompilerBundle.message("jikes.error.path.to.compiler.missing", compilerPath), - CompilerBundle.message("compiler.jikes.name"), - Messages.getErrorIcon() - ); - openConfigurationDialog(); - compilerPath = getCompilerPath(); // update path - if (StringUtil.isEmpty(compilerPath)) { - return false; - } - if (!new File(compilerPath).exists()) { - return false; - } - } - - return true; - } - - private void openConfigurationDialog() { - final CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject); - final Collection compilers = configuration.getRegisteredJavaCompilers(); - final BackendCompiler defaultCompiler = configuration.getDefaultCompiler(); - final JavaCompilersTab compilersTab = new JavaCompilersTab(myProject, compilers, defaultCompiler); - - ShowSettingsUtil.getInstance().editConfigurable(myProject, compilersTab); - } - - private String getCompilerPath() { - return JikesConfiguration.getSettings(myProject).JIKES_PATH.replace('/', File.separatorChar); - } - - @NotNull - @NonNls - public String getId() // used for externalization - { - return "Jikes"; - } - - @NotNull - public String getPresentableName() { - return CompilerBundle.message("compiler.jikes.name"); - } - - @NotNull - public Configurable createConfigurable() { - return new JikesConfigurable(JikesConfiguration.getSettings(myProject)); - } - - public OutputParser createErrorParser(@NotNull final String outputDir, Process process) { - return new JikesOutputParser(myProject); - } - - @Nullable - public OutputParser createOutputParser(@NotNull final String outputDir) { - return null; - } - - @NotNull - public String[] createStartupCommand(final ModuleChunk chunk, final CompileContext context, final String outputPath) - throws IOException { - - final ArrayList commandLine = new ArrayList(); - final IOException[] ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - _createStartupCommand(chunk, commandLine, outputPath); - } - catch (IOException e) { - ex[0] = e; - } - } - }); - if (ex[0] != null) { - throw ex[0]; - } - return ArrayUtil.toStringArray(commandLine); - } - - private void _createStartupCommand(final ModuleChunk chunk, final ArrayList commandLine, @NotNull final String outputPath) throws IOException { - - myTempFile = FileUtil.createTempFile("jikes", ".tmp"); - myTempFile.deleteOnExit(); - - final List files = chunk.getFilesToCompile(); - PrintWriter writer = new PrintWriter(new FileWriter(myTempFile)); - try { - for (VirtualFile file : files) { - writer.println(file.getPath()); - } - } - finally { - writer.close(); - } - - String compilerPath = getCompilerPath(); - LOG.assertTrue(compilerPath != null, "No path to compiler configured"); - - commandLine.add(compilerPath); - - //noinspection HardCodedStringLiteral - commandLine.add("-verbose"); - //noinspection HardCodedStringLiteral - commandLine.add("-classpath"); - - // must include output path to classpath, otherwise javac will compile all dependent files no matter were they compiled before or not - commandLine.add(chunk.getCompilationBootClasspath() + File.pathSeparator + chunk.getCompilationClasspath()); - - setupSourceVersion(chunk, commandLine); - //noinspection HardCodedStringLiteral - commandLine.add("-sourcepath"); - String sourcePath = chunk.getSourcePath(); - if (sourcePath.length() > 0) { - commandLine.add(sourcePath); - } - else { - commandLine.add("\"\""); - } - - //noinspection HardCodedStringLiteral - commandLine.add("-d"); - commandLine.add(outputPath.replace('/', File.separatorChar)); - - JikesSettings jikesSettings = JikesConfiguration.getSettings(myProject); - StringTokenizer tokenizer = new StringTokenizer(jikesSettings.getOptionsString(), " "); - while (tokenizer.hasMoreTokens()) { - commandLine.add(tokenizer.nextToken()); - } - commandLine.add("@" + myTempFile.getAbsolutePath()); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private static void setupSourceVersion(final ModuleChunk chunk, final ArrayList commandLine) { - final Sdk jdk = chunk.getJdk(); - final String versionString = jdk.getVersionString(); - - final LanguageLevel applicableLanguageLevel = CompilerUtil.getApplicableLanguageLevel(versionString, chunk.getLanguageLevel()); - if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_5)) { - commandLine.add("-source"); - commandLine.add("1.4"); // -source 1.5 not supported yet by jikes, so use the highest possible version - } - else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_4)) { - commandLine.add("-source"); - commandLine.add("1.4"); - } - } - - - public void compileFinished() { - if (myTempFile != null) { - FileUtil.delete(myTempFile); - myTempFile = null; - } - } - -} - +/* + * 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.compiler.impl.javaCompiler.jikes; + +import com.intellij.compiler.CompilerConfiguration; +import com.intellij.compiler.CompilerConfigurationImpl; +import com.intellij.compiler.OutputParser; +import com.intellij.compiler.impl.CompilerUtil; +import com.intellij.compiler.impl.javaCompiler.BackendCompiler; +import com.intellij.compiler.impl.javaCompiler.ExternalCompiler; +import com.intellij.compiler.impl.javaCompiler.ModuleChunk; +import com.intellij.compiler.options.JavaCompilersTab; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompileScope; +import com.intellij.openapi.compiler.CompilerBundle; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.StringTokenizer; + +public class JikesCompiler extends ExternalCompiler { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.JikesHandler"); + private final Project myProject; + private File myTempFile; + + public JikesCompiler(Project project) { + myProject = project; + } + + public boolean checkCompiler(final CompileScope scope) { + String compilerPath = getCompilerPath(); + if (StringUtil.isEmpty(compilerPath)) { + Messages.showMessageDialog( + myProject, + CompilerBundle.message("jikes.error.path.to.compiler.unspecified"), CompilerBundle.message("compiler.jikes.name"), + Messages.getErrorIcon() + ); + openConfigurationDialog(); + compilerPath = getCompilerPath(); // update path + if (StringUtil.isEmpty(compilerPath)) { + return false; + } + } + + File file = new File(compilerPath); + if (!file.exists()) { + Messages.showMessageDialog( + myProject, + CompilerBundle.message("jikes.error.path.to.compiler.missing", compilerPath), + CompilerBundle.message("compiler.jikes.name"), + Messages.getErrorIcon() + ); + openConfigurationDialog(); + compilerPath = getCompilerPath(); // update path + if (StringUtil.isEmpty(compilerPath)) { + return false; + } + if (!new File(compilerPath).exists()) { + return false; + } + } + + return true; + } + + private void openConfigurationDialog() { + final CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject); + final Collection compilers = configuration.getRegisteredJavaCompilers(); + final BackendCompiler defaultCompiler = configuration.getDefaultCompiler(); + final JavaCompilersTab compilersTab = new JavaCompilersTab(myProject, compilers, defaultCompiler); + + ShowSettingsUtil.getInstance().editConfigurable(myProject, compilersTab); + } + + private String getCompilerPath() { + return JikesConfiguration.getSettings(myProject).JIKES_PATH.replace('/', File.separatorChar); + } + + @NotNull + @NonNls + public String getId() // used for externalization + { + return "Jikes"; + } + + @NotNull + public String getPresentableName() { + return CompilerBundle.message("compiler.jikes.name"); + } + + @NotNull + public Configurable createConfigurable() { + return new JikesConfigurable(JikesConfiguration.getSettings(myProject)); + } + + public OutputParser createErrorParser(@NotNull final String outputDir, Process process) { + return new JikesOutputParser(myProject); + } + + @Nullable + public OutputParser createOutputParser(@NotNull final String outputDir) { + return null; + } + + @NotNull + public String[] createStartupCommand(final ModuleChunk chunk, final CompileContext context, final String outputPath) + throws IOException { + + final ArrayList commandLine = new ArrayList(); + final IOException[] ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + _createStartupCommand(chunk, commandLine, outputPath); + } + catch (IOException e) { + ex[0] = e; + } + } + }); + if (ex[0] != null) { + throw ex[0]; + } + return ArrayUtil.toStringArray(commandLine); + } + + private void _createStartupCommand(final ModuleChunk chunk, final ArrayList commandLine, @NotNull final String outputPath) throws IOException { + + myTempFile = FileUtil.createTempFile("jikes", ".tmp"); + myTempFile.deleteOnExit(); + + final List files = chunk.getFilesToCompile(); + PrintWriter writer = new PrintWriter(new FileWriter(myTempFile)); + try { + for (VirtualFile file : files) { + writer.println(file.getPath()); + } + } + finally { + writer.close(); + } + + String compilerPath = getCompilerPath(); + LOG.assertTrue(compilerPath != null, "No path to compiler configured"); + + commandLine.add(compilerPath); + + //noinspection HardCodedStringLiteral + commandLine.add("-verbose"); + //noinspection HardCodedStringLiteral + commandLine.add("-classpath"); + + // must include output path to classpath, otherwise javac will compile all dependent files no matter were they compiled before or not + commandLine.add(chunk.getCompilationBootClasspath() + File.pathSeparator + chunk.getCompilationClasspath()); + + setupSourceVersion(chunk, commandLine); + //noinspection HardCodedStringLiteral + commandLine.add("-sourcepath"); + String sourcePath = chunk.getSourcePath(); + if (sourcePath.length() > 0) { + commandLine.add(sourcePath); + } + else { + commandLine.add("\"\""); + } + + //noinspection HardCodedStringLiteral + commandLine.add("-d"); + commandLine.add(outputPath.replace('/', File.separatorChar)); + + JikesSettings jikesSettings = JikesConfiguration.getSettings(myProject); + StringTokenizer tokenizer = new StringTokenizer(jikesSettings.getOptionsString(), " "); + while (tokenizer.hasMoreTokens()) { + commandLine.add(tokenizer.nextToken()); + } + commandLine.add("@" + myTempFile.getAbsolutePath()); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static void setupSourceVersion(final ModuleChunk chunk, final ArrayList commandLine) { + final Sdk jdk = chunk.getJdk(); + final String versionString = jdk.getVersionString(); + + final LanguageLevel applicableLanguageLevel = CompilerUtil.getApplicableLanguageLevel(versionString, chunk.getLanguageLevel()); + if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_5)) { + commandLine.add("-source"); + commandLine.add("1.4"); // -source 1.5 not supported yet by jikes, so use the highest possible version + } + else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_4)) { + commandLine.add("-source"); + commandLine.add("1.4"); + } + } + + + public void compileFinished() { + if (myTempFile != null) { + FileUtil.delete(myTempFile); + myTempFile = null; + } + } + +} + diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java index 2fbadc240c80..446dd2e37b28 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java @@ -1,72 +1,72 @@ -/* - * 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. - */ - -/** - * @author cdr - */ -package com.intellij.compiler.impl.packagingCompiler; - -import com.intellij.openapi.compiler.make.BuildInstruction; -import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.openapi.util.io.FileUtil; -import gnu.trove.THashSet; -import org.jetbrains.annotations.NonNls; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; - -public abstract class BuildInstructionBase extends UserDataHolderBase implements BuildInstruction, Cloneable { - private final String myOutputRelativePath; - private Collection myFilesToDelete; - - protected BuildInstructionBase(String outputRelativePath) { - myOutputRelativePath = outputRelativePath; - } - - public String getOutputRelativePath() { - return myOutputRelativePath; - } - - public BuildInstructionBase clone() { - return (BuildInstructionBase)super.clone(); - } - - public void addFileToDelete(File file) { - if (myFilesToDelete == null) { - myFilesToDelete = new THashSet(); - } - myFilesToDelete.add(file); - } - - public void collectFilesToDelete(Collection filesToDelete) { - if (myFilesToDelete != null) { - filesToDelete.addAll(myFilesToDelete); - } - myFilesToDelete = null; - } - - @NonNls - public String toString() { - return super.toString(); - } - - protected File createTempFile(final String prefix, final String suffix) throws IOException { - final File tempFile = FileUtil.createTempFile(prefix + "___", suffix); - addFileToDelete(tempFile); - return tempFile; - } -} +/* + * 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. + */ + +/** + * @author cdr + */ +package com.intellij.compiler.impl.packagingCompiler; + +import com.intellij.openapi.compiler.make.BuildInstruction; +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.util.io.FileUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NonNls; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +public abstract class BuildInstructionBase extends UserDataHolderBase implements BuildInstruction, Cloneable { + private final String myOutputRelativePath; + private Collection myFilesToDelete; + + protected BuildInstructionBase(String outputRelativePath) { + myOutputRelativePath = outputRelativePath; + } + + public String getOutputRelativePath() { + return myOutputRelativePath; + } + + public BuildInstructionBase clone() { + return (BuildInstructionBase)super.clone(); + } + + public void addFileToDelete(File file) { + if (myFilesToDelete == null) { + myFilesToDelete = new THashSet(); + } + myFilesToDelete.add(file); + } + + public void collectFilesToDelete(Collection filesToDelete) { + if (myFilesToDelete != null) { + filesToDelete.addAll(myFilesToDelete); + } + myFilesToDelete = null; + } + + @NonNls + public String toString() { + return super.toString(); + } + + protected File createTempFile(final String prefix, final String suffix) throws IOException { + final File tempFile = FileUtil.createTempFile(prefix + "___", suffix); + addFileToDelete(tempFile); + return tempFile; + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java b/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java index 362bef8e6db0..0ba8dbf392ce 100644 --- a/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java +++ b/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java @@ -1,658 +1,658 @@ -/* - * 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. - */ - -/* - * @author: Eugene Zhuravlev - * Date: Jan 22, 2003 - * Time: 2:25:31 PM - */ -package com.intellij.compiler.progress; - -import com.intellij.compiler.CompilerManagerImpl; -import com.intellij.compiler.CompilerMessageImpl; -import com.intellij.compiler.impl.CompilerErrorTreeView; -import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel; -import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.compiler.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressFunComponentProvider; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.project.DumbModeAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ProjectManagerListener; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.*; -import com.intellij.openapi.wm.ex.ProgressIndicatorEx; -import com.intellij.peer.PeerFactory; -import com.intellij.pom.Navigatable; -import com.intellij.problems.WolfTheProblemSolver; -import com.intellij.ui.AppIcon; -import com.intellij.ui.content.*; -import com.intellij.util.Alarm; -import com.intellij.util.ArrayUtil; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.MessageCategory; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.StringTokenizer; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class CompilerTask extends Task.Backgroundable { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.progress.CompilerProgressIndicator"); - private static final boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode(); - private static final int UPDATE_INTERVAL = 50; //msec. 20 frames per second. - private static final Key> CONTENT_ID_KEY = Key.create("CONTENT_ID"); - private static final String APP_ICON_ID = "compiler"; - private Key> myContentIdKey = CONTENT_ID_KEY; - private final Key> myContentId = Key.create("compile_content"); - private CompilerProgressDialog myDialog; - private NewErrorTreeViewPanel myErrorTreeView; - private final Object myMessageViewLock = new Object(); - private final String myContentName; - private final boolean myHeadlessMode; - private boolean myIsBackgroundMode; - private int myErrorCount = 0; - private int myWarningCount = 0; - private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); - private boolean myMessagesAutoActivated = false; - - private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator(); - private Runnable myCompileWork; - private final AtomicBoolean myMessageViewWasPrepared = new AtomicBoolean(false); - private Runnable myRestartWork; - private IdeFrame myIdeFrame; - - public CompilerTask(@NotNull Project project, boolean compileInBackground, String contentName, final boolean headlessMode) { - super(project, contentName); - myIsBackgroundMode = compileInBackground; - myContentName = contentName; - myHeadlessMode = headlessMode; - myIdeFrame = (IdeFrame)WindowManager.getInstance().getFrame(myProject); - } - - public void setContentIdKey(Key> contentIdKey) { - myContentIdKey = contentIdKey != null? contentIdKey : CONTENT_ID_KEY; - } - - public String getProcessId() { - return ProgressFunComponentProvider.COMPILATION_ID; - } - - @Override - public DumbModeAction getDumbModeAction() { - return DumbModeAction.WAIT; - } - - public boolean shouldStartInBackground() { - return myIsBackgroundMode; - } - - public ProgressIndicator getIndicator() { - return myIndicator; - } - - @Nullable - public NotificationInfo getNotificationInfo() { - return new NotificationInfo("Compiler", "Compilation Finished", myErrorCount + " Errors, " + myWarningCount + " Warnings", true); - } - - private CloseListener myCloseListener; - - public void run(@NotNull final ProgressIndicator indicator) { - myIndicator = indicator; - - final ProjectManager projectManager = ProjectManager.getInstance(); - projectManager.addProjectManagerListener(myProject, myCloseListener = new CloseListener()); - - final Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(myProject)).getCompilationSemaphore(); - try { - while (!semaphore.tryAcquire(500, TimeUnit.MILLISECONDS)) { - if (indicator.isCanceled()) { - // give up obtaining the semaphore, - // let compile work begin in order to stop gracefuly on cancel event - break; - } - } - } - catch (InterruptedException ignored) { - } - try { - if (!isHeadless()) { - addIndicatorDelegate(); - } - myCompileWork.run(); - } - finally { - indicator.stop(); - projectManager.removeProjectManagerListener(myProject, myCloseListener); - semaphore.release(); - } - } - - private void prepareMessageView() { - if (!myIndicator.isRunning()) { - return; - } - if (myMessageViewWasPrepared.getAndSet(true)) { - return; - } - - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (myProject.isDisposed()) return; - //if (myIsBackgroundMode) { - // openMessageView(); - //} - //else { - synchronized (myMessageViewLock) { - // clear messages from the previous compilation - if (myErrorTreeView == null) { - // if message view != null, the contents has already been cleared - removeAllContents(myProject, null); - } - } - //} - } - }); - } - - private void addIndicatorDelegate() { - ProgressIndicator indicator = myIndicator; - if (!(indicator instanceof ProgressIndicatorEx)) return; - ((ProgressIndicatorEx)indicator).addStateDelegate(new ProgressIndicatorBase() { - - public void cancel() { - super.cancel(); - closeUI(); - stopAppIconProgress(); - } - - public void stop() { - super.stop(); - if (!isCanceled()) { - closeUI(); - } - stopAppIconProgress(); - } - - private void stopAppIconProgress() { - UIUtil.invokeLaterIfNeeded(new Runnable() { - public void run() { - AppIcon appIcon = AppIcon.getInstance(); - if (appIcon.hideProgress(APP_ICON_ID)) { - if (myErrorCount > 0) { - appIcon.setErrorBadge(String.valueOf(myErrorCount)); - appIcon.requestAttention(true); - } else { - appIcon.setOkBadge(true); - appIcon.requestAttention(false); - } - } - } - }); - } - - public void setText(final String text) { - super.setText(text); - updateProgressText(); - } - - public void setText2(final String text) { - super.setText2(text); - updateProgressText(); - } - - public void setFraction(final double fraction) { - super.setFraction(fraction); - updateProgressText(); - UIUtil.invokeLaterIfNeeded(new Runnable() { - public void run() { - AppIcon.getInstance().setProgress(APP_ICON_ID, AppIconScheme.Progress.BUILD, fraction, true); - } - }); - } - - protected void onProgressChange() { - prepareMessageView(); - } - }); - } - - public void cancel() { - if (!myIndicator.isCanceled()) { - myIndicator.cancel(); - } - } - - public void addMessage(final CompilerMessage message) { - prepareMessageView(); - - final CompilerMessageCategory messageCategory = message.getCategory(); - if (CompilerMessageCategory.WARNING.equals(messageCategory)) { - myWarningCount += 1; - } - else if (CompilerMessageCategory.ERROR.equals(messageCategory)) { - myErrorCount += 1; - informWolf(message); - } - - if (ApplicationManager.getApplication().isDispatchThread()) { - openMessageView(); - doAddMessage(message); - } - else { - final Window window = getWindow(); - final ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL; - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (!myProject.isDisposed()) { - openMessageView(); - doAddMessage(message); - } - } - }, modalityState); - } - } - - private void informWolf(final CompilerMessage message) { - WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(myProject); - VirtualFile file = getVirtualFile(message); - wolf.queue(file); - } - - private void doAddMessage(final CompilerMessage message) { - synchronized (myMessageViewLock) { - if (myErrorTreeView != null) { - final Navigatable navigatable = message.getNavigatable(); - final VirtualFile file = message.getVirtualFile(); - final CompilerMessageCategory category = message.getCategory(); - final int type = translateCategory(category); - final String[] text = convertMessage(message); - if (navigatable != null) { - final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText(); - myErrorTreeView.addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile()); - } - else { - myErrorTreeView.addMessage(type, text, file, -1, -1, message.getVirtualFile()); - } - - final boolean shouldAutoActivate = !myMessagesAutoActivated && - (CompilerMessageCategory.ERROR.equals(category) || - CompilerMessageCategory.WARNING.equals(category) && - !ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()); - if (shouldAutoActivate) { - myMessagesAutoActivated = true; - activateMessageView(); - } - } - } - } - - private static String[] convertMessage(final CompilerMessage message) { - String text = message.getMessage(); - if (!text.contains("\n")) { - return new String[]{text}; - } - ArrayList lines = new ArrayList(); - StringTokenizer tokenizer = new StringTokenizer(text, "\n", false); - while (tokenizer.hasMoreTokens()) { - lines.add(tokenizer.nextToken()); - } - return ArrayUtil.toStringArray(lines); - } - - public static int translateCategory(CompilerMessageCategory category) { - if (CompilerMessageCategory.ERROR.equals(category)) { - return MessageCategory.ERROR; - } - if (CompilerMessageCategory.WARNING.equals(category)) { - return MessageCategory.WARNING; - } - if (CompilerMessageCategory.STATISTICS.equals(category)) { - return MessageCategory.STATISTICS; - } - if (CompilerMessageCategory.INFORMATION.equals(category)) { - return MessageCategory.INFORMATION; - } - LOG.error("Unknown message category: " + category); - return 0; - } - - public void start(Runnable compileWork, Runnable restartWork) { - myCompileWork = compileWork; - myRestartWork = restartWork; - queue(); - } - - private void updateProgressText() { - if (isHeadlessMode()) { - return; - } - if (myAlarm.getActiveRequestCount() == 0) { - myAlarm.addRequest(myRepaintRequest, UPDATE_INTERVAL); - } - } - - private final Runnable myRepaintRequest = new Runnable() { - public void run() { - SwingUtilities.invokeLater(myRepaintRunnable); - } - private final Runnable myRepaintRunnable = new Runnable() { - public void run() { - String s = myIndicator.getText(); - if (myIndicator.getFraction() > 0) { - s += " " + (int)(myIndicator.getFraction() * 100 + 0.5) + "%"; - } - - synchronized (myMessageViewLock) { - if (myIsBackgroundMode) { - if (myErrorTreeView != null) { - //myErrorTreeView.setProgressText(s); - myErrorTreeView.setProgressStatistics(""); - } - } - else { - if (myDialog != null) { - myDialog.setStatusText(s); - myDialog.setStatisticsText(""); - } - } - } - } - }; - }; - - public void sendToBackground() { - myIsBackgroundMode = true; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - closeProgressDialog(); - } - }); - } - - - // error tree view initialization must be invoked from event dispatch thread - private void openMessageView() { - if (isHeadlessMode()) { - return; - } - if (myIndicator.isCanceled()) { - return; - } - - final JComponent component; - synchronized (myMessageViewLock) { - if (myErrorTreeView != null) { - return; - } - myErrorTreeView = new CompilerErrorTreeView( - myProject, - myRestartWork - ); - - myErrorTreeView.setProcessController(new NewErrorTreeViewPanel.ProcessController() { - public void stopProcess() { - cancel(); - } - - public boolean isProcessStopped() { - return !myIndicator.isRunning(); - } - }); - component = myErrorTreeView.getComponent(); - } - - final MessageView messageView = MessageView.SERVICE.getInstance(myProject); - final Content content = PeerFactory.getInstance().getContentFactory().createContent(component, myContentName, true); - content.putUserData(myContentIdKey, myContentId); - messageView.getContentManager().addContent(content); - myCloseListener.setContent(content, messageView.getContentManager()); - removeAllContents(myProject, content); - messageView.getContentManager().setSelectedContent(content); - updateProgressText(); - } - - public void showCompilerContent() { - synchronized (myMessageViewLock) { - if (myErrorTreeView != null) { - final MessageView messageView = MessageView.SERVICE.getInstance(myProject); - Content[] contents = messageView.getContentManager().getContents(); - for (Content content : contents) { - if (content.getUserData(myContentIdKey) != null) { - messageView.getContentManager().setSelectedContent(content); - return; - } - } - } - } - } - - private void removeAllContents(Project project, Content notRemove) { - MessageView messageView = MessageView.SERVICE.getInstance(project); - Content[] contents = messageView.getContentManager().getContents(); - for (Content content : contents) { - if (content.isPinned()) continue; - if (content == notRemove) continue; - if (content.getUserData(myContentIdKey) != null) { // the content was added by me - messageView.getContentManager().removeContent(content, true); - } - } - } - - private void activateMessageView() { - synchronized (myMessageViewLock) { - if (myErrorTreeView != null) { - ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW).activate(null); - } - } - } - - private void closeUI() { - if (isHeadlessMode()) { - return; - } - Window window = getWindow(); - ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL; - final Application application = ApplicationManager.getApplication(); - application.invokeLater(new Runnable() { - public void run() { - closeProgressDialog(); - synchronized (myMessageViewLock) { - if (myErrorTreeView != null) { - final boolean shouldRetainView = myErrorCount > 0 || myWarningCount > 0 && !myErrorTreeView.isHideWarnings(); - if (shouldRetainView) { - addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, - CompilerBundle.message("statistics.error.count", myErrorCount), null, -1, -1, null)); - addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, - CompilerBundle.message("statistics.warnings.count", myWarningCount), null, -1, -1, null)); - //activateMessageView(); - myErrorTreeView.selectFirstMessage(); - } - else { - removeAllContents(myProject, null); - } - } - } - } - }, modalityState); - } - - public Window getWindow(){ - if (!myIsBackgroundMode && myDialog != null) { - final Container pane = myDialog.getContentPane(); - return pane != null? SwingUtilities.windowForComponent(pane) : null; - } - return null; - } - - private void closeProgressDialog() { - synchronized (myMessageViewLock) { - if (myDialog != null) { - myDialog.close(DialogWrapper.CANCEL_EXIT_CODE); - myDialog = null; - } - } - } - - public boolean isHeadless() { - return myHeadlessMode; - } - - private boolean isHeadlessMode() { - return myHeadlessMode; - } - - private static VirtualFile getVirtualFile(final CompilerMessage message) { - VirtualFile virtualFile = message.getVirtualFile(); - if (virtualFile == null) { - Navigatable navigatable = message.getNavigatable(); - if (navigatable instanceof OpenFileDescriptor) { - virtualFile = ((OpenFileDescriptor)navigatable).getFile(); - } - } - return virtualFile; - } - - public static TextRange getTextRange(final CompilerMessage message) { - Navigatable navigatable = message.getNavigatable(); - if (navigatable instanceof OpenFileDescriptor) { - int offset = ((OpenFileDescriptor)navigatable).getOffset(); - return new TextRange(offset, offset); - } - return new TextRange(0, 0); - } - - private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener { - private Content myContent; - private ContentManager myContentManager; - private boolean myIsApplicationExitingOrProjectClosing = false; - private boolean myUserAcceptedCancel = false; - - public boolean canCloseProject(final Project project) { - assert project != null; - if (!project.equals(myProject)) { - return true; - } - if (shouldAskUser()) { - int result = Messages.showOkCancelDialog( - myProject, - CompilerBundle.message("warning.compiler.running.on.project.close"), - CompilerBundle.message("compiler.running.dialog.title"), - Messages.getQuestionIcon() - ); - if (result != 0) { - return false; // veto closing - } - myUserAcceptedCancel = true; - - final MessageBusConnection connection = project.getMessageBus().connect(); - connection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() { - public void compilationFinished(boolean aborted, int errors, int warnings, final CompileContext compileContext) { - connection.disconnect(); - ProjectUtil.closeAndDispose(project); - } - }); - cancel(); - return false; // cancel compiler and let it finish, after compilation close the project, but currently - veto closing - } - return !myIndicator.isRunning(); - } - - public void setContent(Content content, ContentManager contentManager) { - myContent = content; - myContentManager = contentManager; - contentManager.addContentManagerListener(this); - } - - public void contentRemoved(ContentManagerEvent event) { - if (event.getContent() == myContent) { - synchronized (myMessageViewLock) { - if (myErrorTreeView != null) { - myErrorTreeView.dispose(); - myErrorTreeView = null; - if (myIndicator.isRunning()) { - cancel(); - } - if (AppIcon.getInstance().hideProgress("compiler")) { - AppIcon.getInstance().setErrorBadge(null); - } - } - } - myContentManager.removeContentManagerListener(this); - myContent.release(); - myContent = null; - } - } - - public void contentRemoveQuery(ContentManagerEvent event) { - if (event.getContent() == myContent) { - if (!myIndicator.isCanceled() && shouldAskUser()) { - int result = Messages.showOkCancelDialog( - myProject, - CompilerBundle.message("warning.compiler.running.on.toolwindow.close"), - CompilerBundle.message("compiler.running.dialog.title"), - Messages.getQuestionIcon() - ); - if (result != 0) { - event.consume(); // veto closing - } - myUserAcceptedCancel = true; - } - } - } - - private boolean shouldAskUser() { - // do not ask second time if user already accepted closing - return !myUserAcceptedCancel && !myIsApplicationExitingOrProjectClosing && myIndicator.isRunning(); - } - - public void projectOpened(Project project) { - } - - public void projectClosed(Project project) { - if (project.equals(myProject) && myContent != null) { - myContentManager.removeContent(myContent, true); - } - } - - public void projectClosing(Project project) { - if (project.equals(myProject)) { - myIsApplicationExitingOrProjectClosing = true; - } - } - } -} +/* + * 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. + */ + +/* + * @author: Eugene Zhuravlev + * Date: Jan 22, 2003 + * Time: 2:25:31 PM + */ +package com.intellij.compiler.progress; + +import com.intellij.compiler.CompilerManagerImpl; +import com.intellij.compiler.CompilerMessageImpl; +import com.intellij.compiler.impl.CompilerErrorTreeView; +import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel; +import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressFunComponentProvider; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.project.DumbModeAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerListener; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.*; +import com.intellij.openapi.wm.ex.ProgressIndicatorEx; +import com.intellij.peer.PeerFactory; +import com.intellij.pom.Navigatable; +import com.intellij.problems.WolfTheProblemSolver; +import com.intellij.ui.AppIcon; +import com.intellij.ui.content.*; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.MessageCategory; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class CompilerTask extends Task.Backgroundable { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.progress.CompilerProgressIndicator"); + private static final boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode(); + private static final int UPDATE_INTERVAL = 50; //msec. 20 frames per second. + private static final Key> CONTENT_ID_KEY = Key.create("CONTENT_ID"); + private static final String APP_ICON_ID = "compiler"; + private Key> myContentIdKey = CONTENT_ID_KEY; + private final Key> myContentId = Key.create("compile_content"); + private CompilerProgressDialog myDialog; + private NewErrorTreeViewPanel myErrorTreeView; + private final Object myMessageViewLock = new Object(); + private final String myContentName; + private final boolean myHeadlessMode; + private boolean myIsBackgroundMode; + private int myErrorCount = 0; + private int myWarningCount = 0; + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + private boolean myMessagesAutoActivated = false; + + private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator(); + private Runnable myCompileWork; + private final AtomicBoolean myMessageViewWasPrepared = new AtomicBoolean(false); + private Runnable myRestartWork; + private IdeFrame myIdeFrame; + + public CompilerTask(@NotNull Project project, boolean compileInBackground, String contentName, final boolean headlessMode) { + super(project, contentName); + myIsBackgroundMode = compileInBackground; + myContentName = contentName; + myHeadlessMode = headlessMode; + myIdeFrame = (IdeFrame)WindowManager.getInstance().getFrame(myProject); + } + + public void setContentIdKey(Key> contentIdKey) { + myContentIdKey = contentIdKey != null? contentIdKey : CONTENT_ID_KEY; + } + + public String getProcessId() { + return ProgressFunComponentProvider.COMPILATION_ID; + } + + @Override + public DumbModeAction getDumbModeAction() { + return DumbModeAction.WAIT; + } + + public boolean shouldStartInBackground() { + return myIsBackgroundMode; + } + + public ProgressIndicator getIndicator() { + return myIndicator; + } + + @Nullable + public NotificationInfo getNotificationInfo() { + return new NotificationInfo("Compiler", "Compilation Finished", myErrorCount + " Errors, " + myWarningCount + " Warnings", true); + } + + private CloseListener myCloseListener; + + public void run(@NotNull final ProgressIndicator indicator) { + myIndicator = indicator; + + final ProjectManager projectManager = ProjectManager.getInstance(); + projectManager.addProjectManagerListener(myProject, myCloseListener = new CloseListener()); + + final Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(myProject)).getCompilationSemaphore(); + try { + while (!semaphore.tryAcquire(500, TimeUnit.MILLISECONDS)) { + if (indicator.isCanceled()) { + // give up obtaining the semaphore, + // let compile work begin in order to stop gracefuly on cancel event + break; + } + } + } + catch (InterruptedException ignored) { + } + try { + if (!isHeadless()) { + addIndicatorDelegate(); + } + myCompileWork.run(); + } + finally { + indicator.stop(); + projectManager.removeProjectManagerListener(myProject, myCloseListener); + semaphore.release(); + } + } + + private void prepareMessageView() { + if (!myIndicator.isRunning()) { + return; + } + if (myMessageViewWasPrepared.getAndSet(true)) { + return; + } + + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (myProject.isDisposed()) return; + //if (myIsBackgroundMode) { + // openMessageView(); + //} + //else { + synchronized (myMessageViewLock) { + // clear messages from the previous compilation + if (myErrorTreeView == null) { + // if message view != null, the contents has already been cleared + removeAllContents(myProject, null); + } + } + //} + } + }); + } + + private void addIndicatorDelegate() { + ProgressIndicator indicator = myIndicator; + if (!(indicator instanceof ProgressIndicatorEx)) return; + ((ProgressIndicatorEx)indicator).addStateDelegate(new ProgressIndicatorBase() { + + public void cancel() { + super.cancel(); + closeUI(); + stopAppIconProgress(); + } + + public void stop() { + super.stop(); + if (!isCanceled()) { + closeUI(); + } + stopAppIconProgress(); + } + + private void stopAppIconProgress() { + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + AppIcon appIcon = AppIcon.getInstance(); + if (appIcon.hideProgress(APP_ICON_ID)) { + if (myErrorCount > 0) { + appIcon.setErrorBadge(String.valueOf(myErrorCount)); + appIcon.requestAttention(true); + } else { + appIcon.setOkBadge(true); + appIcon.requestAttention(false); + } + } + } + }); + } + + public void setText(final String text) { + super.setText(text); + updateProgressText(); + } + + public void setText2(final String text) { + super.setText2(text); + updateProgressText(); + } + + public void setFraction(final double fraction) { + super.setFraction(fraction); + updateProgressText(); + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + AppIcon.getInstance().setProgress(APP_ICON_ID, AppIconScheme.Progress.BUILD, fraction, true); + } + }); + } + + protected void onProgressChange() { + prepareMessageView(); + } + }); + } + + public void cancel() { + if (!myIndicator.isCanceled()) { + myIndicator.cancel(); + } + } + + public void addMessage(final CompilerMessage message) { + prepareMessageView(); + + final CompilerMessageCategory messageCategory = message.getCategory(); + if (CompilerMessageCategory.WARNING.equals(messageCategory)) { + myWarningCount += 1; + } + else if (CompilerMessageCategory.ERROR.equals(messageCategory)) { + myErrorCount += 1; + informWolf(message); + } + + if (ApplicationManager.getApplication().isDispatchThread()) { + openMessageView(); + doAddMessage(message); + } + else { + final Window window = getWindow(); + final ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL; + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (!myProject.isDisposed()) { + openMessageView(); + doAddMessage(message); + } + } + }, modalityState); + } + } + + private void informWolf(final CompilerMessage message) { + WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(myProject); + VirtualFile file = getVirtualFile(message); + wolf.queue(file); + } + + private void doAddMessage(final CompilerMessage message) { + synchronized (myMessageViewLock) { + if (myErrorTreeView != null) { + final Navigatable navigatable = message.getNavigatable(); + final VirtualFile file = message.getVirtualFile(); + final CompilerMessageCategory category = message.getCategory(); + final int type = translateCategory(category); + final String[] text = convertMessage(message); + if (navigatable != null) { + final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText(); + myErrorTreeView.addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile()); + } + else { + myErrorTreeView.addMessage(type, text, file, -1, -1, message.getVirtualFile()); + } + + final boolean shouldAutoActivate = !myMessagesAutoActivated && + (CompilerMessageCategory.ERROR.equals(category) || + CompilerMessageCategory.WARNING.equals(category) && + !ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()); + if (shouldAutoActivate) { + myMessagesAutoActivated = true; + activateMessageView(); + } + } + } + } + + private static String[] convertMessage(final CompilerMessage message) { + String text = message.getMessage(); + if (!text.contains("\n")) { + return new String[]{text}; + } + ArrayList lines = new ArrayList(); + StringTokenizer tokenizer = new StringTokenizer(text, "\n", false); + while (tokenizer.hasMoreTokens()) { + lines.add(tokenizer.nextToken()); + } + return ArrayUtil.toStringArray(lines); + } + + public static int translateCategory(CompilerMessageCategory category) { + if (CompilerMessageCategory.ERROR.equals(category)) { + return MessageCategory.ERROR; + } + if (CompilerMessageCategory.WARNING.equals(category)) { + return MessageCategory.WARNING; + } + if (CompilerMessageCategory.STATISTICS.equals(category)) { + return MessageCategory.STATISTICS; + } + if (CompilerMessageCategory.INFORMATION.equals(category)) { + return MessageCategory.INFORMATION; + } + LOG.error("Unknown message category: " + category); + return 0; + } + + public void start(Runnable compileWork, Runnable restartWork) { + myCompileWork = compileWork; + myRestartWork = restartWork; + queue(); + } + + private void updateProgressText() { + if (isHeadlessMode()) { + return; + } + if (myAlarm.getActiveRequestCount() == 0) { + myAlarm.addRequest(myRepaintRequest, UPDATE_INTERVAL); + } + } + + private final Runnable myRepaintRequest = new Runnable() { + public void run() { + SwingUtilities.invokeLater(myRepaintRunnable); + } + private final Runnable myRepaintRunnable = new Runnable() { + public void run() { + String s = myIndicator.getText(); + if (myIndicator.getFraction() > 0) { + s += " " + (int)(myIndicator.getFraction() * 100 + 0.5) + "%"; + } + + synchronized (myMessageViewLock) { + if (myIsBackgroundMode) { + if (myErrorTreeView != null) { + //myErrorTreeView.setProgressText(s); + myErrorTreeView.setProgressStatistics(""); + } + } + else { + if (myDialog != null) { + myDialog.setStatusText(s); + myDialog.setStatisticsText(""); + } + } + } + } + }; + }; + + public void sendToBackground() { + myIsBackgroundMode = true; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + closeProgressDialog(); + } + }); + } + + + // error tree view initialization must be invoked from event dispatch thread + private void openMessageView() { + if (isHeadlessMode()) { + return; + } + if (myIndicator.isCanceled()) { + return; + } + + final JComponent component; + synchronized (myMessageViewLock) { + if (myErrorTreeView != null) { + return; + } + myErrorTreeView = new CompilerErrorTreeView( + myProject, + myRestartWork + ); + + myErrorTreeView.setProcessController(new NewErrorTreeViewPanel.ProcessController() { + public void stopProcess() { + cancel(); + } + + public boolean isProcessStopped() { + return !myIndicator.isRunning(); + } + }); + component = myErrorTreeView.getComponent(); + } + + final MessageView messageView = MessageView.SERVICE.getInstance(myProject); + final Content content = PeerFactory.getInstance().getContentFactory().createContent(component, myContentName, true); + content.putUserData(myContentIdKey, myContentId); + messageView.getContentManager().addContent(content); + myCloseListener.setContent(content, messageView.getContentManager()); + removeAllContents(myProject, content); + messageView.getContentManager().setSelectedContent(content); + updateProgressText(); + } + + public void showCompilerContent() { + synchronized (myMessageViewLock) { + if (myErrorTreeView != null) { + final MessageView messageView = MessageView.SERVICE.getInstance(myProject); + Content[] contents = messageView.getContentManager().getContents(); + for (Content content : contents) { + if (content.getUserData(myContentIdKey) != null) { + messageView.getContentManager().setSelectedContent(content); + return; + } + } + } + } + } + + private void removeAllContents(Project project, Content notRemove) { + MessageView messageView = MessageView.SERVICE.getInstance(project); + Content[] contents = messageView.getContentManager().getContents(); + for (Content content : contents) { + if (content.isPinned()) continue; + if (content == notRemove) continue; + if (content.getUserData(myContentIdKey) != null) { // the content was added by me + messageView.getContentManager().removeContent(content, true); + } + } + } + + private void activateMessageView() { + synchronized (myMessageViewLock) { + if (myErrorTreeView != null) { + ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW).activate(null); + } + } + } + + private void closeUI() { + if (isHeadlessMode()) { + return; + } + Window window = getWindow(); + ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL; + final Application application = ApplicationManager.getApplication(); + application.invokeLater(new Runnable() { + public void run() { + closeProgressDialog(); + synchronized (myMessageViewLock) { + if (myErrorTreeView != null) { + final boolean shouldRetainView = myErrorCount > 0 || myWarningCount > 0 && !myErrorTreeView.isHideWarnings(); + if (shouldRetainView) { + addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, + CompilerBundle.message("statistics.error.count", myErrorCount), null, -1, -1, null)); + addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, + CompilerBundle.message("statistics.warnings.count", myWarningCount), null, -1, -1, null)); + //activateMessageView(); + myErrorTreeView.selectFirstMessage(); + } + else { + removeAllContents(myProject, null); + } + } + } + } + }, modalityState); + } + + public Window getWindow(){ + if (!myIsBackgroundMode && myDialog != null) { + final Container pane = myDialog.getContentPane(); + return pane != null? SwingUtilities.windowForComponent(pane) : null; + } + return null; + } + + private void closeProgressDialog() { + synchronized (myMessageViewLock) { + if (myDialog != null) { + myDialog.close(DialogWrapper.CANCEL_EXIT_CODE); + myDialog = null; + } + } + } + + public boolean isHeadless() { + return myHeadlessMode; + } + + private boolean isHeadlessMode() { + return myHeadlessMode; + } + + private static VirtualFile getVirtualFile(final CompilerMessage message) { + VirtualFile virtualFile = message.getVirtualFile(); + if (virtualFile == null) { + Navigatable navigatable = message.getNavigatable(); + if (navigatable instanceof OpenFileDescriptor) { + virtualFile = ((OpenFileDescriptor)navigatable).getFile(); + } + } + return virtualFile; + } + + public static TextRange getTextRange(final CompilerMessage message) { + Navigatable navigatable = message.getNavigatable(); + if (navigatable instanceof OpenFileDescriptor) { + int offset = ((OpenFileDescriptor)navigatable).getOffset(); + return new TextRange(offset, offset); + } + return new TextRange(0, 0); + } + + private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener { + private Content myContent; + private ContentManager myContentManager; + private boolean myIsApplicationExitingOrProjectClosing = false; + private boolean myUserAcceptedCancel = false; + + public boolean canCloseProject(final Project project) { + assert project != null; + if (!project.equals(myProject)) { + return true; + } + if (shouldAskUser()) { + int result = Messages.showOkCancelDialog( + myProject, + CompilerBundle.message("warning.compiler.running.on.project.close"), + CompilerBundle.message("compiler.running.dialog.title"), + Messages.getQuestionIcon() + ); + if (result != 0) { + return false; // veto closing + } + myUserAcceptedCancel = true; + + final MessageBusConnection connection = project.getMessageBus().connect(); + connection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() { + public void compilationFinished(boolean aborted, int errors, int warnings, final CompileContext compileContext) { + connection.disconnect(); + ProjectUtil.closeAndDispose(project); + } + }); + cancel(); + return false; // cancel compiler and let it finish, after compilation close the project, but currently - veto closing + } + return !myIndicator.isRunning(); + } + + public void setContent(Content content, ContentManager contentManager) { + myContent = content; + myContentManager = contentManager; + contentManager.addContentManagerListener(this); + } + + public void contentRemoved(ContentManagerEvent event) { + if (event.getContent() == myContent) { + synchronized (myMessageViewLock) { + if (myErrorTreeView != null) { + myErrorTreeView.dispose(); + myErrorTreeView = null; + if (myIndicator.isRunning()) { + cancel(); + } + if (AppIcon.getInstance().hideProgress("compiler")) { + AppIcon.getInstance().setErrorBadge(null); + } + } + } + myContentManager.removeContentManagerListener(this); + myContent.release(); + myContent = null; + } + } + + public void contentRemoveQuery(ContentManagerEvent event) { + if (event.getContent() == myContent) { + if (!myIndicator.isCanceled() && shouldAskUser()) { + int result = Messages.showOkCancelDialog( + myProject, + CompilerBundle.message("warning.compiler.running.on.toolwindow.close"), + CompilerBundle.message("compiler.running.dialog.title"), + Messages.getQuestionIcon() + ); + if (result != 0) { + event.consume(); // veto closing + } + myUserAcceptedCancel = true; + } + } + } + + private boolean shouldAskUser() { + // do not ask second time if user already accepted closing + return !myUserAcceptedCancel && !myIsApplicationExitingOrProjectClosing && myIndicator.isRunning(); + } + + public void projectOpened(Project project) { + } + + public void projectClosed(Project project) { + if (project.equals(myProject) && myContent != null) { + myContentManager.removeContent(myContent, true); + } + } + + public void projectClosing(Project project) { + if (project.equals(myProject)) { + myIsApplicationExitingOrProjectClosing = true; + } + } + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/SmartStepIntoActionHandler.java b/java/debugger/impl/src/com/intellij/debugger/actions/SmartStepIntoActionHandler.java index 961324e0e74b..3786d50a9f8c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/SmartStepIntoActionHandler.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/SmartStepIntoActionHandler.java @@ -1,156 +1,156 @@ -/* - * 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.debugger.actions; - -import com.intellij.debugger.DebuggerManagerEx; -import com.intellij.debugger.SourcePosition; -import com.intellij.debugger.engine.RequestHint; -import com.intellij.debugger.engine.SuspendContextImpl; -import com.intellij.debugger.impl.DebuggerContextImpl; -import com.intellij.debugger.impl.DebuggerSession; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.ListPopup; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.util.containers.OrderedSet; -import com.intellij.util.text.CharArrayUtil; -import com.intellij.xdebugger.impl.actions.DebuggerActionHandler; -import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; -import gnu.trove.TObjectHashingStrategy; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -public class SmartStepIntoActionHandler extends DebuggerActionHandler { - public void perform(@NotNull final Project project, final AnActionEvent event) { - final DebuggerContextImpl debuggerContext = (DebuggerManagerEx.getInstanceEx(project)).getContext(); - doStep(project, debuggerContext.getSourcePosition(), debuggerContext.getDebuggerSession()); - } - - - private static void doStep(final @NotNull Project project, final @Nullable SourcePosition position, final @NotNull DebuggerSession session) { - final VirtualFile file = position != null ? position.getFile().getVirtualFile() : null; - final FileEditor fileEditor = file != null? FileEditorManager.getInstance(project).getSelectedEditor(file) : null; - if (fileEditor instanceof TextEditor) { - final List methods = findReferencedMethods(position); - if (methods.size() > 0) { - if (methods.size() == 1) { - session.stepInto(true, createSmartStepFilter(methods.get(0))); - } - else { - final PsiMethodListPopupStep popupStep = new PsiMethodListPopupStep(methods, new PsiMethodListPopupStep.OnChooseRunnable() { - public void execute(PsiMethod chosenMethod) { - session.stepInto(true, createSmartStepFilter(chosenMethod)); - } - }); - final ListPopup popup = JBPopupFactory.getInstance().createListPopup(popupStep); - final RelativePoint point = DebuggerUIUtil.calcPopupLocation(((TextEditor)fileEditor).getEditor(), position.getLine()); - popup.show(point); - } - return; - } - } - session.stepInto(true, null); - } - - @Nullable - private static RequestHint.SmartStepFilter createSmartStepFilter(final PsiMethod method) { - return new RequestHint.SmartStepFilter(method); - } - - - private static List findReferencedMethods(final SourcePosition position) { - final int line = position.getLine(); - if (line < 0) { - return Collections.emptyList(); // the document has been changed - } - - final PsiFile file = position.getFile(); - final VirtualFile vFile = file.getVirtualFile(); - if (vFile == null) { - // the file is not physical - return Collections.emptyList(); - } - - final Document doc = FileDocumentManager.getInstance().getDocument(vFile); - if (line >= doc.getLineCount()) { - return Collections.emptyList(); // the document has been changed - } - final int startOffset = doc.getLineStartOffset(line); - final TextRange lineRange = new TextRange(startOffset, doc.getLineEndOffset(line)); - final int offset = CharArrayUtil.shiftForward(doc.getCharsSequence(), startOffset, " \t"); - PsiElement element = file.findElementAt(offset); - if (element != null && !(element instanceof PsiCompiledElement)) { - do { - final PsiElement parent = element.getParent(); - if (parent == null || (parent.getTextOffset() < lineRange.getStartOffset())) { - break; - } - element = parent; - } - while(true); - - //noinspection unchecked - final List methods = new OrderedSet(TObjectHashingStrategy.CANONICAL); - final PsiElementVisitor methodCollector = new JavaRecursiveElementWalkingVisitor() { - @Override public void visitAnonymousClass(PsiAnonymousClass aClass) { /*skip annonymous classes*/ } - - @Override public void visitStatement(PsiStatement statement) { - if (lineRange.intersects(statement.getTextRange())) { - super.visitStatement(statement); - } - } - - @Override public void visitCallExpression(final PsiCallExpression expression) { - final PsiMethod psiMethod = expression.resolveMethod(); - if (psiMethod != null) { - methods.add(psiMethod); - } - super.visitCallExpression(expression); - } - }; - element.accept(methodCollector); - for (PsiElement sibling = element.getNextSibling(); sibling != null; sibling = sibling.getNextSibling()) { - if (!lineRange.intersects(sibling.getTextRange())) { - break; - } - sibling.accept(methodCollector); - } - return methods; - } - return Collections.emptyList(); - } - - public boolean isEnabled(@NotNull final Project project, final AnActionEvent event) { - final DebuggerContextImpl context = (DebuggerManagerEx.getInstanceEx(project)).getContext(); - DebuggerSession debuggerSession = context.getDebuggerSession(); - final boolean isPaused = debuggerSession != null && debuggerSession.isPaused(); - final SuspendContextImpl suspendContext = context.getSuspendContext(); - final boolean hasCurrentThread = suspendContext != null && suspendContext.getThread() != null; - return isPaused && hasCurrentThread; - } -} +/* + * 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.debugger.actions; + +import com.intellij.debugger.DebuggerManagerEx; +import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.RequestHint; +import com.intellij.debugger.engine.SuspendContextImpl; +import com.intellij.debugger.impl.DebuggerContextImpl; +import com.intellij.debugger.impl.DebuggerSession; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.containers.OrderedSet; +import com.intellij.util.text.CharArrayUtil; +import com.intellij.xdebugger.impl.actions.DebuggerActionHandler; +import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; +import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class SmartStepIntoActionHandler extends DebuggerActionHandler { + public void perform(@NotNull final Project project, final AnActionEvent event) { + final DebuggerContextImpl debuggerContext = (DebuggerManagerEx.getInstanceEx(project)).getContext(); + doStep(project, debuggerContext.getSourcePosition(), debuggerContext.getDebuggerSession()); + } + + + private static void doStep(final @NotNull Project project, final @Nullable SourcePosition position, final @NotNull DebuggerSession session) { + final VirtualFile file = position != null ? position.getFile().getVirtualFile() : null; + final FileEditor fileEditor = file != null? FileEditorManager.getInstance(project).getSelectedEditor(file) : null; + if (fileEditor instanceof TextEditor) { + final List methods = findReferencedMethods(position); + if (methods.size() > 0) { + if (methods.size() == 1) { + session.stepInto(true, createSmartStepFilter(methods.get(0))); + } + else { + final PsiMethodListPopupStep popupStep = new PsiMethodListPopupStep(methods, new PsiMethodListPopupStep.OnChooseRunnable() { + public void execute(PsiMethod chosenMethod) { + session.stepInto(true, createSmartStepFilter(chosenMethod)); + } + }); + final ListPopup popup = JBPopupFactory.getInstance().createListPopup(popupStep); + final RelativePoint point = DebuggerUIUtil.calcPopupLocation(((TextEditor)fileEditor).getEditor(), position.getLine()); + popup.show(point); + } + return; + } + } + session.stepInto(true, null); + } + + @Nullable + private static RequestHint.SmartStepFilter createSmartStepFilter(final PsiMethod method) { + return new RequestHint.SmartStepFilter(method); + } + + + private static List findReferencedMethods(final SourcePosition position) { + final int line = position.getLine(); + if (line < 0) { + return Collections.emptyList(); // the document has been changed + } + + final PsiFile file = position.getFile(); + final VirtualFile vFile = file.getVirtualFile(); + if (vFile == null) { + // the file is not physical + return Collections.emptyList(); + } + + final Document doc = FileDocumentManager.getInstance().getDocument(vFile); + if (line >= doc.getLineCount()) { + return Collections.emptyList(); // the document has been changed + } + final int startOffset = doc.getLineStartOffset(line); + final TextRange lineRange = new TextRange(startOffset, doc.getLineEndOffset(line)); + final int offset = CharArrayUtil.shiftForward(doc.getCharsSequence(), startOffset, " \t"); + PsiElement element = file.findElementAt(offset); + if (element != null && !(element instanceof PsiCompiledElement)) { + do { + final PsiElement parent = element.getParent(); + if (parent == null || (parent.getTextOffset() < lineRange.getStartOffset())) { + break; + } + element = parent; + } + while(true); + + //noinspection unchecked + final List methods = new OrderedSet(TObjectHashingStrategy.CANONICAL); + final PsiElementVisitor methodCollector = new JavaRecursiveElementWalkingVisitor() { + @Override public void visitAnonymousClass(PsiAnonymousClass aClass) { /*skip annonymous classes*/ } + + @Override public void visitStatement(PsiStatement statement) { + if (lineRange.intersects(statement.getTextRange())) { + super.visitStatement(statement); + } + } + + @Override public void visitCallExpression(final PsiCallExpression expression) { + final PsiMethod psiMethod = expression.resolveMethod(); + if (psiMethod != null) { + methods.add(psiMethod); + } + super.visitCallExpression(expression); + } + }; + element.accept(methodCollector); + for (PsiElement sibling = element.getNextSibling(); sibling != null; sibling = sibling.getNextSibling()) { + if (!lineRange.intersects(sibling.getTextRange())) { + break; + } + sibling.accept(methodCollector); + } + return methods; + } + return Collections.emptyList(); + } + + public boolean isEnabled(@NotNull final Project project, final AnActionEvent event) { + final DebuggerContextImpl context = (DebuggerManagerEx.getInstanceEx(project)).getContext(); + DebuggerSession debuggerSession = context.getDebuggerSession(); + final boolean isPaused = debuggerSession != null && debuggerSession.isPaused(); + final SuspendContextImpl suspendContext = context.getSuspendContext(); + final boolean hasCurrentThread = suspendContext != null && suspendContext.getThread() != null; + return isPaused && hasCurrentThread; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java b/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java index e8637897a69b..7172f4f0bf6a 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java @@ -1,144 +1,144 @@ -/* - * 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.debugger.ui; - -import com.intellij.debugger.impl.DebuggerSession; -import com.intellij.debugger.settings.DebuggerSettings; -import com.intellij.debugger.DebuggerBundle; -import com.intellij.ide.util.ElementsChooser; -import com.intellij.openapi.project.Project; -import com.intellij.ui.IdeBorderFactory; -import com.intellij.util.ui.OptionsDialog; -import com.intellij.util.ui.UIUtil; -import com.intellij.CommonBundle; - -import javax.swing.*; -import java.awt.*; -import java.util.*; -import java.util.List; - -/** - * User: lex - * Date: Oct 6, 2003 - * Time: 5:58:17 PM - */ - - - -public class RunHotswapDialog extends OptionsDialog { - private final JPanel myPanel; - private final ElementsChooser myElementsChooser; - - public RunHotswapDialog(Project project, java.util.List sessions) { - super(project); - myPanel = new JPanel(new BorderLayout()); - final List items = new ArrayList(sessions.size()); - for (DebuggerSession session : sessions) { - items.add(new SessionItem(session)); - } - Collections.sort(items, new Comparator() { - public int compare(SessionItem debuggerSession, SessionItem debuggerSession1) { - return debuggerSession.getSession().getSessionName().compareTo(debuggerSession1.getSession().getSessionName()); - } - }); - myElementsChooser = new ElementsChooser(items, true); - myPanel.setBorder(IdeBorderFactory.createEmptyBorder(10, 0, 5, 0)); - //myElementsChooser.setBorder(IdeBorderFactory.createEmptyBorder(5, 0, 0, 0)); - if (sessions.size() > 0) { - myElementsChooser.selectElements(items.subList(0, 1)); - } - myPanel.add(myElementsChooser, BorderLayout.CENTER); - //myPanel.add(new JLabel("Choose debug sessions to reload classes:"), BorderLayout.NORTH); - if(sessions.size() == 1) { - setTitle(DebuggerBundle.message("hotswap.dialog.title.with.session", sessions.get(0).getSessionName())); - myPanel.setVisible(false); - } - else { - setTitle(DebuggerBundle.message("hotswap.dialog.title")); - } - setButtonsAlignment(SwingUtilities.CENTER); - this.init(); - } - - protected boolean isToBeShown() { - return DebuggerSettings.RUN_HOTSWAP_ASK.equals(DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE); - } - - protected void setToBeShown(boolean value, boolean onOk) { - if (value) { - DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE = DebuggerSettings.RUN_HOTSWAP_ASK; - } - else { - if (onOk) { - DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE = DebuggerSettings.RUN_HOTSWAP_ALWAYS; - } - else { - DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE = DebuggerSettings.RUN_HOTSWAP_NEVER; - } - } - } - - protected boolean shouldSaveOptionsOnCancel() { - return true; - } - - protected Action[] createActions(){ - setOKButtonText(CommonBundle.getYesButtonText()); - setCancelButtonText(CommonBundle.getNoButtonText()); - return new Action[]{getOKAction(), getCancelAction()}; - } - - protected JComponent createNorthPanel() { - JLabel label = new JLabel(DebuggerBundle.message("hotswap.dialog.run.prompt")); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(label, BorderLayout.CENTER); - Icon icon = UIUtil.getQuestionIcon(); - if (icon != null) { - label.setIcon(icon); - label.setIconTextGap(7); - } - return panel; - } - - protected JComponent createCenterPanel() { - return myPanel; - } - - public Collection getSessionsToReload() { - final List markedElements = myElementsChooser.getMarkedElements(); - final List sessions = new ArrayList(markedElements.size()); - for (SessionItem item : markedElements) { - sessions.add(item.getSession()); - } - return sessions; - } - - private static class SessionItem { - private final DebuggerSession mySession; - - public SessionItem(DebuggerSession session) { - mySession = session; - } - - public DebuggerSession getSession() { - return mySession; - } - - public String toString() { - return mySession.getSessionName(); - } - } -} +/* + * 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.debugger.ui; + +import com.intellij.debugger.impl.DebuggerSession; +import com.intellij.debugger.settings.DebuggerSettings; +import com.intellij.debugger.DebuggerBundle; +import com.intellij.ide.util.ElementsChooser; +import com.intellij.openapi.project.Project; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.OptionsDialog; +import com.intellij.util.ui.UIUtil; +import com.intellij.CommonBundle; + +import javax.swing.*; +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * User: lex + * Date: Oct 6, 2003 + * Time: 5:58:17 PM + */ + + + +public class RunHotswapDialog extends OptionsDialog { + private final JPanel myPanel; + private final ElementsChooser myElementsChooser; + + public RunHotswapDialog(Project project, java.util.List sessions) { + super(project); + myPanel = new JPanel(new BorderLayout()); + final List items = new ArrayList(sessions.size()); + for (DebuggerSession session : sessions) { + items.add(new SessionItem(session)); + } + Collections.sort(items, new Comparator() { + public int compare(SessionItem debuggerSession, SessionItem debuggerSession1) { + return debuggerSession.getSession().getSessionName().compareTo(debuggerSession1.getSession().getSessionName()); + } + }); + myElementsChooser = new ElementsChooser(items, true); + myPanel.setBorder(IdeBorderFactory.createEmptyBorder(10, 0, 5, 0)); + //myElementsChooser.setBorder(IdeBorderFactory.createEmptyBorder(5, 0, 0, 0)); + if (sessions.size() > 0) { + myElementsChooser.selectElements(items.subList(0, 1)); + } + myPanel.add(myElementsChooser, BorderLayout.CENTER); + //myPanel.add(new JLabel("Choose debug sessions to reload classes:"), BorderLayout.NORTH); + if(sessions.size() == 1) { + setTitle(DebuggerBundle.message("hotswap.dialog.title.with.session", sessions.get(0).getSessionName())); + myPanel.setVisible(false); + } + else { + setTitle(DebuggerBundle.message("hotswap.dialog.title")); + } + setButtonsAlignment(SwingUtilities.CENTER); + this.init(); + } + + protected boolean isToBeShown() { + return DebuggerSettings.RUN_HOTSWAP_ASK.equals(DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE); + } + + protected void setToBeShown(boolean value, boolean onOk) { + if (value) { + DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE = DebuggerSettings.RUN_HOTSWAP_ASK; + } + else { + if (onOk) { + DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE = DebuggerSettings.RUN_HOTSWAP_ALWAYS; + } + else { + DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE = DebuggerSettings.RUN_HOTSWAP_NEVER; + } + } + } + + protected boolean shouldSaveOptionsOnCancel() { + return true; + } + + protected Action[] createActions(){ + setOKButtonText(CommonBundle.getYesButtonText()); + setCancelButtonText(CommonBundle.getNoButtonText()); + return new Action[]{getOKAction(), getCancelAction()}; + } + + protected JComponent createNorthPanel() { + JLabel label = new JLabel(DebuggerBundle.message("hotswap.dialog.run.prompt")); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(label, BorderLayout.CENTER); + Icon icon = UIUtil.getQuestionIcon(); + if (icon != null) { + label.setIcon(icon); + label.setIconTextGap(7); + } + return panel; + } + + protected JComponent createCenterPanel() { + return myPanel; + } + + public Collection getSessionsToReload() { + final List markedElements = myElementsChooser.getMarkedElements(); + final List sessions = new ArrayList(markedElements.size()); + for (SessionItem item : markedElements) { + sessions.add(item.getSession()); + } + return sessions; + } + + private static class SessionItem { + private final DebuggerSession mySession; + + public SessionItem(DebuggerSession session) { + mySession = session; + } + + public DebuggerSession getSession() { + return mySession; + } + + public String toString() { + return mySession.getSessionName(); + } + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/UpdatableDebuggerView.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/UpdatableDebuggerView.java index 224983ba91d9..c6b912883391 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/UpdatableDebuggerView.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/UpdatableDebuggerView.java @@ -1,137 +1,137 @@ -/* - * 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.debugger.ui.impl; - -import com.intellij.debugger.impl.DebuggerContextImpl; -import com.intellij.debugger.impl.DebuggerContextListener; -import com.intellij.debugger.impl.DebuggerStateManager; -import com.intellij.debugger.ui.DebuggerView; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.ShortcutSet; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import com.intellij.util.Alarm; -import com.sun.jdi.VMDisconnectedException; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; - -public abstract class UpdatableDebuggerView extends JPanel implements DebuggerView { - private final Project myProject; - private final DebuggerStateManager myStateManager; - protected final Alarm myRebuildAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - protected volatile boolean myRefreshNeeded = true; - protected final java.util.List myDisposables = new ArrayList(); - private boolean myUpdateEnabled; - - protected UpdatableDebuggerView(final Project project, final DebuggerStateManager stateManager) { - setLayout(new BorderLayout()); - myProject = project; - myStateManager = stateManager; - - final DebuggerContextListener contextListener = new DebuggerContextListener() { - public void changeEvent(DebuggerContextImpl newContext, int event) { - UpdatableDebuggerView.this.changeEvent(newContext, event); - } - }; - myStateManager.addListener(contextListener); - - registerDisposable(new Disposable() { - public void dispose() { - myStateManager.removeListener(contextListener); - } - }); - - } - - protected void changeEvent(final DebuggerContextImpl newContext, final int event) { - if (newContext.getDebuggerSession() != null) { - rebuildIfVisible(event); - } - } - - protected final boolean isUpdateEnabled() { - return myUpdateEnabled; - } - - public final void setUpdateEnabled(final boolean enabled) { - myUpdateEnabled = enabled; - } - - public final boolean isRefreshNeeded() { - return myRefreshNeeded; - } - - public final void rebuildIfVisible(final int event) { - if(isUpdateEnabled()) { - myRefreshNeeded = false; - myRebuildAlarm.cancelAllRequests(); - myRebuildAlarm.addRequest(new Runnable() { - public void run() { - try { - rebuild(event); - } - catch (VMDisconnectedException e) { - // ignored - } - } - }, 100, ModalityState.NON_MODAL); - } - else { - myRefreshNeeded = true; - } - } - - protected abstract void rebuild(int event); - - protected final void registerDisposable(Disposable disposable) { - myDisposables.add(disposable); - } - - public DebuggerContextImpl getContext() { - return myStateManager.getContext(); - } - - protected final Project getProject() { - return myProject; - } - - public DebuggerStateManager getContextManager() { - return myStateManager; - } - - public void dispose() { - Disposer.dispose(myRebuildAlarm); - for (Disposable disposable : myDisposables) { - Disposer.dispose(disposable); - } - myDisposables.clear(); - } - - protected void overrideShortcut(final JComponent forComponent, final String actionId, final ShortcutSet shortcutSet) { - final AnAction action = ActionManager.getInstance().getAction(actionId); - action.registerCustomShortcutSet(shortcutSet, forComponent); - registerDisposable(new Disposable() { - public void dispose() { - action.unregisterCustomShortcutSet(forComponent); - } - }); - } -} +/* + * 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.debugger.ui.impl; + +import com.intellij.debugger.impl.DebuggerContextImpl; +import com.intellij.debugger.impl.DebuggerContextListener; +import com.intellij.debugger.impl.DebuggerStateManager; +import com.intellij.debugger.ui.DebuggerView; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.ShortcutSet; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.Alarm; +import com.sun.jdi.VMDisconnectedException; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; + +public abstract class UpdatableDebuggerView extends JPanel implements DebuggerView { + private final Project myProject; + private final DebuggerStateManager myStateManager; + protected final Alarm myRebuildAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + protected volatile boolean myRefreshNeeded = true; + protected final java.util.List myDisposables = new ArrayList(); + private boolean myUpdateEnabled; + + protected UpdatableDebuggerView(final Project project, final DebuggerStateManager stateManager) { + setLayout(new BorderLayout()); + myProject = project; + myStateManager = stateManager; + + final DebuggerContextListener contextListener = new DebuggerContextListener() { + public void changeEvent(DebuggerContextImpl newContext, int event) { + UpdatableDebuggerView.this.changeEvent(newContext, event); + } + }; + myStateManager.addListener(contextListener); + + registerDisposable(new Disposable() { + public void dispose() { + myStateManager.removeListener(contextListener); + } + }); + + } + + protected void changeEvent(final DebuggerContextImpl newContext, final int event) { + if (newContext.getDebuggerSession() != null) { + rebuildIfVisible(event); + } + } + + protected final boolean isUpdateEnabled() { + return myUpdateEnabled; + } + + public final void setUpdateEnabled(final boolean enabled) { + myUpdateEnabled = enabled; + } + + public final boolean isRefreshNeeded() { + return myRefreshNeeded; + } + + public final void rebuildIfVisible(final int event) { + if(isUpdateEnabled()) { + myRefreshNeeded = false; + myRebuildAlarm.cancelAllRequests(); + myRebuildAlarm.addRequest(new Runnable() { + public void run() { + try { + rebuild(event); + } + catch (VMDisconnectedException e) { + // ignored + } + } + }, 100, ModalityState.NON_MODAL); + } + else { + myRefreshNeeded = true; + } + } + + protected abstract void rebuild(int event); + + protected final void registerDisposable(Disposable disposable) { + myDisposables.add(disposable); + } + + public DebuggerContextImpl getContext() { + return myStateManager.getContext(); + } + + protected final Project getProject() { + return myProject; + } + + public DebuggerStateManager getContextManager() { + return myStateManager; + } + + public void dispose() { + Disposer.dispose(myRebuildAlarm); + for (Disposable disposable : myDisposables) { + Disposer.dispose(disposable); + } + myDisposables.clear(); + } + + protected void overrideShortcut(final JComponent forComponent, final String actionId, final ShortcutSet shortcutSet) { + final AnAction action = ActionManager.getInstance().getAction(actionId); + action.registerCustomShortcutSet(shortcutSet, forComponent); + registerDisposable(new Disposable() { + public void dispose() { + action.unregisterCustomShortcutSet(forComponent); + } + }); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/DebuggerTree.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/DebuggerTree.java index 7542c2ed1065..1e3048069631 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/DebuggerTree.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/DebuggerTree.java @@ -1,742 +1,742 @@ -/* - * 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. - */ - -/* - * Class DebuggerTree - * @author Jeka - */ -package com.intellij.debugger.ui.impl.watch; - -import com.intellij.debugger.DebuggerBundle; -import com.intellij.debugger.DebuggerInvocationUtil; -import com.intellij.debugger.engine.DebugProcessImpl; -import com.intellij.debugger.engine.SuspendContextImpl; -import com.intellij.debugger.engine.evaluation.EvaluateException; -import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; -import com.intellij.debugger.engine.events.DebuggerCommandImpl; -import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; -import com.intellij.debugger.engine.events.SuspendContextCommandImpl; -import com.intellij.debugger.impl.DebuggerContextImpl; -import com.intellij.debugger.impl.DebuggerSession; -import com.intellij.debugger.jdi.LocalVariableProxyImpl; -import com.intellij.debugger.jdi.StackFrameProxyImpl; -import com.intellij.debugger.jdi.ThreadGroupReferenceProxyImpl; -import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; -import com.intellij.debugger.settings.NodeRendererSettings; -import com.intellij.debugger.settings.ThreadsViewSettings; -import com.intellij.debugger.ui.impl.DebuggerTreeBase; -import com.intellij.debugger.ui.impl.tree.TreeBuilder; -import com.intellij.debugger.ui.impl.tree.TreeBuilderNode; -import com.intellij.debugger.ui.tree.DebuggerTreeNode; -import com.intellij.debugger.ui.tree.NodeDescriptor; -import com.intellij.debugger.ui.tree.render.ChildrenBuilder; -import com.intellij.debugger.ui.tree.render.NodeRenderer; -import com.intellij.debugger.ui.tree.render.NodeRendererSettingsListener; -import com.intellij.openapi.actionSystem.DataKey; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.SpeedSearchBase; -import com.intellij.ui.TreeSpeedSearch; -import com.sun.jdi.*; - -import javax.swing.*; -import javax.swing.event.TreeModelEvent; -import javax.swing.event.TreeModelListener; -import javax.swing.tree.TreePath; -import java.awt.*; -import java.awt.event.MouseEvent; -import java.util.*; -import java.util.List; - -public abstract class DebuggerTree extends DebuggerTreeBase implements DataProvider { - private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.impl.watch.DebuggerTree"); - protected static final Key VISIBLE_RECT = Key.create("VISIBLE_RECT"); - - public static final DataKey DATA_KEY = DataKey.create("DebuggerTree"); - - private final Project myProject; - protected final NodeManagerImpl myNodeManager; - - private NodeRendererSettingsListener mySettingsListener; - private DebuggerContextImpl myDebuggerContext = DebuggerContextImpl.EMPTY_CONTEXT; - - private DebuggerTreeNodeImpl myEditedNode; - - public DebuggerTree(Project project) { - super(null, project); - setScrollsOnExpand(false); - myNodeManager = createNodeManager(project); - - final TreeBuilder model = new TreeBuilder(this) { - public void buildChildren(TreeBuilderNode node) { - final DebuggerTreeNodeImpl debuggerTreeNode = (DebuggerTreeNodeImpl)node; - if (debuggerTreeNode.getDescriptor() instanceof DefaultNodeDescriptor) { - return; - } - buildNode(debuggerTreeNode); - } - - public boolean isExpandable(TreeBuilderNode builderNode) { - return DebuggerTree.this.isExpandable((DebuggerTreeNodeImpl)builderNode); - } - }; - model.setRoot(getNodeFactory().getDefaultNode()); - model.addTreeModelListener(new TreeModelListener() { - public void treeNodesChanged(TreeModelEvent event) { - hideTooltip(); - } - - public void treeNodesInserted(TreeModelEvent event) { - hideTooltip(); - } - - public void treeNodesRemoved(TreeModelEvent event) { - hideTooltip(); - } - - public void treeStructureChanged(TreeModelEvent event) { - hideTooltip(); - } - }); - - setModel(model); - - myProject = project; - final TreeSpeedSearch search = new TreeSpeedSearch(this); - search.setComparator(new SpeedSearchBase.SpeedSearchComparator(false)); - } - - protected NodeManagerImpl createNodeManager(Project project) { - return new NodeManagerImpl(project, this); - } - - public void dispose() { - myNodeManager.dispose(); - myDebuggerContext = DebuggerContextImpl.EMPTY_CONTEXT; - super.dispose(); - } - - private void installSettingsListener() { - if (mySettingsListener != null) { - return; - } - mySettingsListener = new NodeRendererSettingsListener() { - private void rendererSettingsChanged(DebuggerTreeNodeImpl node) { - final NodeDescriptorImpl nodeDescriptor = node.getDescriptor(); - if (nodeDescriptor instanceof ValueDescriptorImpl || nodeDescriptor instanceof StackFrameDescriptorImpl) { - node.calcRepresentation(); - } - - final Enumeration e = node.rawChildren(); - while (e.hasMoreElements()) { - DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); - rendererSettingsChanged(child); - } - } - - public void renderersChanged() { - DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getModel().getRoot(); - if (root != null) { - rendererSettingsChanged(root); - } - - } - }; - NodeRendererSettings.getInstance().addListener(mySettingsListener); - } - - private void uninstallSettingsListener() { - if (mySettingsListener == null) { - return; - } - NodeRendererSettings.getInstance().removeListener(mySettingsListener); - mySettingsListener = null; - } - - public void removeNotify() { - uninstallSettingsListener(); - super.removeNotify(); - } - - public void addNotify() { - super.addNotify(); - installSettingsListener(); - } - - protected boolean isExpandable(DebuggerTreeNodeImpl node) { - NodeDescriptorImpl descriptor = node.getDescriptor(); - return descriptor.isExpandable(); - } - - public Object getData(String dataId) { - if (DebuggerTree.DATA_KEY.is(dataId)) { - return this; - } - return null; - } - - - private void buildNode(final DebuggerTreeNodeImpl node) { - if (node == null || node.getDescriptor() == null) { - return; - } - final DebugProcessImpl debugProcess = getDebuggerContext().getDebugProcess(); - if (debugProcess != null) { - DebuggerCommandImpl command = getBuildNodeCommand(node); - if (command != null) { - node.add(myNodeManager.createMessageNode(MessageDescriptor.EVALUATING)); - debugProcess.getManagerThread().schedule(command); - } - } - } - - // todo: convert "if" into instance method call - protected DebuggerCommandImpl getBuildNodeCommand(final DebuggerTreeNodeImpl node) { - if (node.getDescriptor() instanceof StackFrameDescriptorImpl) { - return new BuildStackFrameCommand(node); - } - else if (node.getDescriptor() instanceof ValueDescriptorImpl) { - return new BuildValueNodeCommand(node); - } - else if (node.getDescriptor() instanceof StaticDescriptorImpl) { - return new BuildStaticNodeCommand(node); - } - else if (node.getDescriptor() instanceof ThreadDescriptorImpl) { - return new BuildThreadCommand(node); - } - else if (node.getDescriptor() instanceof ThreadGroupDescriptorImpl) { - return new BuildThreadGroupCommand(node); - } - LOG.assertTrue(false); - return null; - } - - public void saveState(DebuggerTreeNodeImpl node) { - if (node.getDescriptor() != null) { - TreePath path = new TreePath(node.getPath()); - node.getDescriptor().myIsExpanded = isExpanded(path); - node.getDescriptor().myIsSelected = getSelectionModel().isPathSelected(path); - Rectangle rowBounds = getRowBounds(getRowForPath(path)); - if (rowBounds != null && getVisibleRect().contains(rowBounds)) { - node.getDescriptor().putUserData(VISIBLE_RECT, getVisibleRect()); - node.getDescriptor().myIsVisible = true; - } - else { - node.getDescriptor().putUserData(VISIBLE_RECT, null); - node.getDescriptor().myIsVisible = false; - } - } - - for (Enumeration e = node.rawChildren(); e.hasMoreElements();) { - DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); - saveState(child); - } - } - - public void restoreState(DebuggerTreeNodeImpl node) { - restoreStateImpl(node); - scrollToVisible(node); - } - - protected final void scrollToVisible(DebuggerTreeNodeImpl scopeNode) { - final TreePath rootPath = new TreePath(scopeNode.getPath()); - final int rowCount = getRowCount(); - for (int idx = rowCount - 1; idx >= 0; idx--) { - final TreePath treePath = getPathForRow(idx); - if (treePath != null) { - if (!rootPath.isDescendant(treePath)) { - continue; - } - final DebuggerTreeNodeImpl pathNode = (DebuggerTreeNodeImpl)treePath.getLastPathComponent(); - final NodeDescriptorImpl descriptor = pathNode.getDescriptor(); - - if (descriptor != null && descriptor.myIsVisible) { - final Rectangle visibleRect = descriptor.getUserData(VISIBLE_RECT); - if (visibleRect != null) { - // prefer visible rect - scrollRectToVisible(visibleRect); - } - else { - scrollPathToVisible(treePath); - } - break; - } - } - } - } - - public void scrollRectToVisible(Rectangle aRect) { - // see IDEADEV-432 - aRect.width += aRect.x; - aRect.x = 0; - super.scrollRectToVisible(aRect); - } - - private void restoreStateImpl(DebuggerTreeNodeImpl node) { - restoreNodeState(node); - if (node.getDescriptor().myIsExpanded) { - for (Enumeration e = node.rawChildren(); e.hasMoreElements();) { - DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); - restoreStateImpl(child); - } - } - } - - public void restoreState() { - clearSelection(); - DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getModel().getRoot(); - if (root != null) { - restoreState(root); - } - } - - protected void restoreNodeState(DebuggerTreeNodeImpl node) { - final NodeDescriptorImpl descriptor = node.getDescriptor(); - if (descriptor != null) { - if (node.getParent() == null) { - descriptor.myIsExpanded = true; - } - - TreePath path = new TreePath(node.getPath()); - if (descriptor.myIsExpanded) { - expandPath(path); - } - if (descriptor.myIsSelected) { - addSelectionPath(path); - } - } - } - - public NodeManagerImpl getNodeFactory() { - return myNodeManager; - } - - public TreeBuilder getMutableModel() { - return (TreeBuilder)getModel(); - } - - public void removeAllChildren() { - DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getModel().getRoot(); - root.removeAllChildren(); - treeChanged(); - } - - public void showMessage(MessageDescriptor messageDesc) { - DebuggerTreeNodeImpl root = getNodeFactory().getDefaultNode(); - getMutableModel().setRoot(root); - DebuggerTreeNodeImpl message = root.add(messageDesc); - treeChanged(); - expandPath(new TreePath(message.getPath())); - } - - public void showMessage(String messageText) { - showMessage(new MessageDescriptor(messageText)); - } - - public final void treeChanged() { - DebuggerTreeNodeImpl node = (DebuggerTreeNodeImpl)getModel().getRoot(); - if (node != null) { - getMutableModel().nodeStructureChanged(node); - restoreState(); - } - } - - public Project getProject() { - return myProject; - } - - protected abstract void build(DebuggerContextImpl context); - - protected final void buildWhenPaused(DebuggerContextImpl context, RefreshDebuggerTreeCommand command) { - DebuggerSession debuggerSession = context.getDebuggerSession(); - - if (ApplicationManager.getApplication().isUnitTestMode() || debuggerSession.getState() == DebuggerSession.STATE_PAUSED) { - showMessage(MessageDescriptor.EVALUATING); - context.getDebugProcess().getManagerThread().schedule(command); - } - else { - showMessage(context.getDebuggerSession().getStateDescription()); - } - } - - public void rebuild(final DebuggerContextImpl context) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final DebugProcessImpl process = context.getDebugProcess(); - if (process == null) { - return; // empty context, no process available yet - } - myDebuggerContext = context; - saveState(); - process.getManagerThread().schedule(new DebuggerCommandImpl() { - protected void action() throws Exception { - getNodeFactory().setHistoryByContext(context); - } - public Priority getPriority() { - return Priority.NORMAL; - } - }); - - build(context); - } - - public void saveState() { - saveState((DebuggerTreeNodeImpl)getModel().getRoot()); - } - - public void onEditorShown(DebuggerTreeNodeImpl node) { - myEditedNode = node; - hideTooltip(); - } - - public void onEditorHidden(DebuggerTreeNodeImpl node) { - if (myEditedNode != null) { - assert myEditedNode == node; - myEditedNode = null; - } - } - - @Override - public JComponent createToolTip(MouseEvent e) { - return myEditedNode != null ? null : super.createToolTip(e); - } - - protected abstract static class RefreshDebuggerTreeCommand extends SuspendContextCommandImpl { - private final DebuggerContextImpl myDebuggerContext; - - public Priority getPriority() { - return Priority.NORMAL; - } - - public RefreshDebuggerTreeCommand(DebuggerContextImpl context) { - super(context.getSuspendContext()); - myDebuggerContext = context; - } - - public final DebuggerContextImpl getDebuggerContext() { - return myDebuggerContext; - } - } - - public DebuggerContextImpl getDebuggerContext() { - return myDebuggerContext; - } - - public abstract class BuildNodeCommand extends DebuggerContextCommandImpl { - private final DebuggerTreeNodeImpl myNode; - - protected final List myChildren = new LinkedList(); - - protected BuildNodeCommand(DebuggerTreeNodeImpl node) { - super(DebuggerTree.this.getDebuggerContext()); - myNode = node; - } - - public Priority getPriority() { - return Priority.NORMAL; - } - - public DebuggerTreeNodeImpl getNode() { - return myNode; - } - - protected void updateUI(final boolean scrollToVisible) { - DebuggerInvocationUtil.swingInvokeLater(getProject(), new Runnable() { - public void run() { - myNode.removeAllChildren(); - for (DebuggerTreeNode debuggerTreeNode : myChildren) { - myNode.add(debuggerTreeNode); - } - myNode.childrenChanged(scrollToVisible); - } - }); - } - } - - protected class BuildStackFrameCommand extends BuildNodeCommand { - public BuildStackFrameCommand(DebuggerTreeNodeImpl stackNode) { - super(stackNode); - } - - public final void threadAction() { - try { - final StackFrameDescriptorImpl stackDescriptor = (StackFrameDescriptorImpl)getNode().getDescriptor(); - final StackFrameProxyImpl frame = stackDescriptor.getFrameProxy(); - if (frame == null) { - return; - } - final Location location = frame.location(); - - final ObjectReference thisObjectReference = frame.thisObject(); - - final DebuggerContextImpl debuggerContext = getDebuggerContext(); - final EvaluationContextImpl evaluationContext = debuggerContext.createEvaluationContext(); - - if (!debuggerContext.isEvaluationPossible()) { - myChildren.add(myNodeManager.createNode(MessageDescriptor.EVALUATION_NOT_POSSIBLE, evaluationContext)); - } - - final NodeDescriptor descriptor; - if (thisObjectReference != null) { - descriptor = myNodeManager.getThisDescriptor(stackDescriptor, thisObjectReference); - } - else { - final ReferenceType type = location.method().declaringType(); - descriptor = myNodeManager.getStaticDescriptor(stackDescriptor, type); - } - myChildren.add(myNodeManager.createNode(descriptor, evaluationContext)); - - if (thisObjectReference != null && evaluationContext.getDebugProcess().getVirtualMachineProxy().canGetSyntheticAttribute()) { - final ReferenceType thisRefType = thisObjectReference.referenceType(); - if (thisRefType instanceof ClassType && thisRefType.equals(location.declaringType()) && thisRefType.name().contains("$")) { // makes sense for nested classes only - final ClassType clsType = (ClassType)thisRefType; - for (Field field : clsType.fields()) { - if (field.isSynthetic() && StringUtil.startsWith(field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) { - final FieldDescriptorImpl fieldDescriptor = myNodeManager.getFieldDescriptor(stackDescriptor, thisObjectReference, field); - myChildren.add(myNodeManager.createNode(fieldDescriptor, evaluationContext)); - } - } - } - } - - try { - buildVariables(stackDescriptor, evaluationContext); - if (NodeRendererSettings.getInstance().getClassRenderer().SORT_ASCENDING) { - Collections.sort(myChildren, NodeManagerImpl.getNodeComparator()); - } - } - catch (EvaluateException e) { - myChildren.add(myNodeManager.createMessageNode(new MessageDescriptor(e.getMessage()))); - } - // add last method return value if any - final Pair methodValuePair = debuggerContext.getDebugProcess().getLastExecutedMethod(); - if (methodValuePair != null) { - final ValueDescriptorImpl returnValueDescriptor = - myNodeManager.getMethodReturnValueDescriptor(stackDescriptor, methodValuePair.getFirst(), methodValuePair.getSecond()); - final DebuggerTreeNodeImpl methodReturnValueNode = myNodeManager.createNode(returnValueDescriptor, evaluationContext); - myChildren.add(1, methodReturnValueNode); - } - } - catch (EvaluateException e) { - myChildren.clear(); - myChildren.add(myNodeManager.createMessageNode(new MessageDescriptor(e.getMessage()))); - } - catch (InvalidStackFrameException e) { - LOG.info(e); - myChildren.clear(); - notifyCancelled(); - } - catch (InternalException e) { - if (e.errorCode() == 35) { - myChildren.add( - myNodeManager.createMessageNode(new MessageDescriptor(DebuggerBundle.message("error.corrupt.debug.info", e.getMessage())))); - } - else { - throw e; - } - } - - updateUI(true); - } - - protected void buildVariables(final StackFrameDescriptorImpl stackDescriptor, final EvaluationContextImpl evaluationContext) throws EvaluateException { - final StackFrameProxyImpl frame = stackDescriptor.getFrameProxy(); - if (frame != null) { - for (final LocalVariableProxyImpl local : frame.visibleVariables()) { - final LocalVariableDescriptorImpl localVariableDescriptor = myNodeManager.getLocalVariableDescriptor(stackDescriptor, local); - final DebuggerTreeNodeImpl variableNode = myNodeManager.createNode(localVariableDescriptor, evaluationContext); - myChildren.add(variableNode); - } - } - } - } - - private class BuildValueNodeCommand extends BuildNodeCommand implements ChildrenBuilder { - public BuildValueNodeCommand(DebuggerTreeNodeImpl node) { - super(node); - } - - public void threadAction() { - ValueDescriptorImpl descriptor = (ValueDescriptorImpl)getNode().getDescriptor(); - try { - final NodeRenderer renderer = descriptor.getRenderer(getSuspendContext().getDebugProcess()); - renderer.buildChildren(descriptor.getValue(), this, getDebuggerContext().createEvaluationContext()); - } - catch (ObjectCollectedException e) { - getNode().removeAllChildren(); - getNode().add(getNodeFactory().createMessageNode( - new MessageDescriptor(DebuggerBundle.message("error.cannot.build.node.children.object.collected", e.getMessage())))); - getNode().childrenChanged(false); - } - } - - public NodeManagerImpl getNodeManager() { - - return myNodeManager; - } - - public NodeManagerImpl getDescriptorManager() { - return myNodeManager; - } - - public ValueDescriptorImpl getParentDescriptor() { - return (ValueDescriptorImpl)getNode().getDescriptor(); - } - - public void setChildren(final List children) { - myChildren.addAll(children); - updateUI(false); - } - } - - private class BuildStaticNodeCommand extends BuildNodeCommand { - public BuildStaticNodeCommand(DebuggerTreeNodeImpl node) { - super(node); - } - - public void threadAction() { - final StaticDescriptorImpl sd = (StaticDescriptorImpl)getNode().getDescriptor(); - final ReferenceType refType = sd.getType(); - List fields = refType.allFields(); - for (Field field : fields) { - if (field.isStatic()) { - final FieldDescriptorImpl fieldDescriptor = myNodeManager.getFieldDescriptor(sd, null, field); - final EvaluationContextImpl evaluationContext = getDebuggerContext().createEvaluationContext(); - final DebuggerTreeNodeImpl node = myNodeManager.createNode(fieldDescriptor, evaluationContext); - myChildren.add(node); - } - } - - updateUI(true); - } - } - - private class BuildThreadCommand extends BuildNodeCommand { - public BuildThreadCommand(DebuggerTreeNodeImpl threadNode) { - super(threadNode); - } - - public void threadAction() { - ThreadDescriptorImpl threadDescriptor = ((ThreadDescriptorImpl)getNode().getDescriptor()); - ThreadReferenceProxyImpl threadProxy = threadDescriptor.getThreadReference(); - if (!threadProxy.isCollected() && getDebuggerContext().getDebugProcess().getSuspendManager().isSuspended(threadProxy)) { - int status = threadProxy.status(); - if (!(status == ThreadReference.THREAD_STATUS_UNKNOWN) && - !(status == ThreadReference.THREAD_STATUS_NOT_STARTED) && - !(status == ThreadReference.THREAD_STATUS_ZOMBIE)) { - try { - for (StackFrameProxyImpl stackFrame : threadProxy.frames()) { - //Method method = stackFrame.location().method(); - //ToDo :check whether is synthetic if (shouldDisplay(method)) { - myChildren.add(myNodeManager.createNode(myNodeManager.getStackFrameDescriptor(threadDescriptor, stackFrame), - getDebuggerContext().createEvaluationContext())); - } - } - catch (EvaluateException e) { - myChildren.clear(); - myChildren.add(myNodeManager.createMessageNode(e.getMessage())); - LOG.debug(e); - //LOG.assertTrue(false); - // if we pause during evaluation of this method the exception is thrown - // private static void longMethod(){ - // try { - // Thread.sleep(100000); - // } catch (InterruptedException e) { - // e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - // } - // } - } - } - } - updateUI(true); - } - } - - private class BuildThreadGroupCommand extends DebuggerCommandImpl { - private final DebuggerTreeNodeImpl myNode; - protected final List myChildren = new LinkedList(); - - public BuildThreadGroupCommand(DebuggerTreeNodeImpl node) { - myNode = node; - } - - protected void action() throws Exception { - ThreadGroupDescriptorImpl groupDescriptor = (ThreadGroupDescriptorImpl)myNode.getDescriptor(); - ThreadGroupReferenceProxyImpl threadGroup = groupDescriptor.getThreadGroupReference(); - - List threads = new ArrayList(threadGroup.threads()); - Collections.sort(threads, ThreadReferenceProxyImpl.ourComparator); - - final DebuggerContextImpl debuggerContext = getDebuggerContext(); - final SuspendContextImpl suspendContext = debuggerContext.getSuspendContext(); - final EvaluationContextImpl evaluationContext = suspendContext != null? debuggerContext.createEvaluationContext() : null; - - boolean showCurrent = ThreadsViewSettings.getInstance().SHOW_CURRENT_THREAD; - - for (final ThreadGroupReferenceProxyImpl group : threadGroup.threadGroups()) { - if (group != null) { - DebuggerTreeNodeImpl threadNode = - myNodeManager.createNode(myNodeManager.getThreadGroupDescriptor(groupDescriptor, group), evaluationContext); - - if (showCurrent && ((ThreadGroupDescriptorImpl)threadNode.getDescriptor()).isCurrent()) { - myChildren.add(0, threadNode); - } - else { - myChildren.add(threadNode); - } - } - } - - ArrayList threadNodes = new ArrayList(); - - for (ThreadReferenceProxyImpl thread : threads) { - if (thread != null) { - final DebuggerTreeNodeImpl threadNode = myNodeManager.createNode(myNodeManager.getThreadDescriptor(groupDescriptor, thread), evaluationContext); - if (showCurrent && ((ThreadDescriptorImpl)threadNode.getDescriptor()).isCurrent()) { - threadNodes.add(0, threadNode); - } - else { - threadNodes.add(threadNode); - } - } - } - - myChildren.addAll(threadNodes); - - updateUI(true); - } - - protected void updateUI(final boolean scrollToVisible) { - DebuggerInvocationUtil.swingInvokeLater(getProject(), new Runnable() { - public void run() { - myNode.removeAllChildren(); - for (DebuggerTreeNode debuggerTreeNode : myChildren) { - myNode.add(debuggerTreeNode); - } - myNode.childrenChanged(scrollToVisible); - } - }); - } - } - - public void hideTooltip() { - myTipManager.hideTooltip(); - } -} +/* + * 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. + */ + +/* + * Class DebuggerTree + * @author Jeka + */ +package com.intellij.debugger.ui.impl.watch; + +import com.intellij.debugger.DebuggerBundle; +import com.intellij.debugger.DebuggerInvocationUtil; +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.SuspendContextImpl; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.debugger.engine.events.DebuggerCommandImpl; +import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; +import com.intellij.debugger.engine.events.SuspendContextCommandImpl; +import com.intellij.debugger.impl.DebuggerContextImpl; +import com.intellij.debugger.impl.DebuggerSession; +import com.intellij.debugger.jdi.LocalVariableProxyImpl; +import com.intellij.debugger.jdi.StackFrameProxyImpl; +import com.intellij.debugger.jdi.ThreadGroupReferenceProxyImpl; +import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; +import com.intellij.debugger.settings.NodeRendererSettings; +import com.intellij.debugger.settings.ThreadsViewSettings; +import com.intellij.debugger.ui.impl.DebuggerTreeBase; +import com.intellij.debugger.ui.impl.tree.TreeBuilder; +import com.intellij.debugger.ui.impl.tree.TreeBuilderNode; +import com.intellij.debugger.ui.tree.DebuggerTreeNode; +import com.intellij.debugger.ui.tree.NodeDescriptor; +import com.intellij.debugger.ui.tree.render.ChildrenBuilder; +import com.intellij.debugger.ui.tree.render.NodeRenderer; +import com.intellij.debugger.ui.tree.render.NodeRendererSettingsListener; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.SpeedSearchBase; +import com.intellij.ui.TreeSpeedSearch; +import com.sun.jdi.*; + +import javax.swing.*; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; + +public abstract class DebuggerTree extends DebuggerTreeBase implements DataProvider { + private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.impl.watch.DebuggerTree"); + protected static final Key VISIBLE_RECT = Key.create("VISIBLE_RECT"); + + public static final DataKey DATA_KEY = DataKey.create("DebuggerTree"); + + private final Project myProject; + protected final NodeManagerImpl myNodeManager; + + private NodeRendererSettingsListener mySettingsListener; + private DebuggerContextImpl myDebuggerContext = DebuggerContextImpl.EMPTY_CONTEXT; + + private DebuggerTreeNodeImpl myEditedNode; + + public DebuggerTree(Project project) { + super(null, project); + setScrollsOnExpand(false); + myNodeManager = createNodeManager(project); + + final TreeBuilder model = new TreeBuilder(this) { + public void buildChildren(TreeBuilderNode node) { + final DebuggerTreeNodeImpl debuggerTreeNode = (DebuggerTreeNodeImpl)node; + if (debuggerTreeNode.getDescriptor() instanceof DefaultNodeDescriptor) { + return; + } + buildNode(debuggerTreeNode); + } + + public boolean isExpandable(TreeBuilderNode builderNode) { + return DebuggerTree.this.isExpandable((DebuggerTreeNodeImpl)builderNode); + } + }; + model.setRoot(getNodeFactory().getDefaultNode()); + model.addTreeModelListener(new TreeModelListener() { + public void treeNodesChanged(TreeModelEvent event) { + hideTooltip(); + } + + public void treeNodesInserted(TreeModelEvent event) { + hideTooltip(); + } + + public void treeNodesRemoved(TreeModelEvent event) { + hideTooltip(); + } + + public void treeStructureChanged(TreeModelEvent event) { + hideTooltip(); + } + }); + + setModel(model); + + myProject = project; + final TreeSpeedSearch search = new TreeSpeedSearch(this); + search.setComparator(new SpeedSearchBase.SpeedSearchComparator(false)); + } + + protected NodeManagerImpl createNodeManager(Project project) { + return new NodeManagerImpl(project, this); + } + + public void dispose() { + myNodeManager.dispose(); + myDebuggerContext = DebuggerContextImpl.EMPTY_CONTEXT; + super.dispose(); + } + + private void installSettingsListener() { + if (mySettingsListener != null) { + return; + } + mySettingsListener = new NodeRendererSettingsListener() { + private void rendererSettingsChanged(DebuggerTreeNodeImpl node) { + final NodeDescriptorImpl nodeDescriptor = node.getDescriptor(); + if (nodeDescriptor instanceof ValueDescriptorImpl || nodeDescriptor instanceof StackFrameDescriptorImpl) { + node.calcRepresentation(); + } + + final Enumeration e = node.rawChildren(); + while (e.hasMoreElements()) { + DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); + rendererSettingsChanged(child); + } + } + + public void renderersChanged() { + DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getModel().getRoot(); + if (root != null) { + rendererSettingsChanged(root); + } + + } + }; + NodeRendererSettings.getInstance().addListener(mySettingsListener); + } + + private void uninstallSettingsListener() { + if (mySettingsListener == null) { + return; + } + NodeRendererSettings.getInstance().removeListener(mySettingsListener); + mySettingsListener = null; + } + + public void removeNotify() { + uninstallSettingsListener(); + super.removeNotify(); + } + + public void addNotify() { + super.addNotify(); + installSettingsListener(); + } + + protected boolean isExpandable(DebuggerTreeNodeImpl node) { + NodeDescriptorImpl descriptor = node.getDescriptor(); + return descriptor.isExpandable(); + } + + public Object getData(String dataId) { + if (DebuggerTree.DATA_KEY.is(dataId)) { + return this; + } + return null; + } + + + private void buildNode(final DebuggerTreeNodeImpl node) { + if (node == null || node.getDescriptor() == null) { + return; + } + final DebugProcessImpl debugProcess = getDebuggerContext().getDebugProcess(); + if (debugProcess != null) { + DebuggerCommandImpl command = getBuildNodeCommand(node); + if (command != null) { + node.add(myNodeManager.createMessageNode(MessageDescriptor.EVALUATING)); + debugProcess.getManagerThread().schedule(command); + } + } + } + + // todo: convert "if" into instance method call + protected DebuggerCommandImpl getBuildNodeCommand(final DebuggerTreeNodeImpl node) { + if (node.getDescriptor() instanceof StackFrameDescriptorImpl) { + return new BuildStackFrameCommand(node); + } + else if (node.getDescriptor() instanceof ValueDescriptorImpl) { + return new BuildValueNodeCommand(node); + } + else if (node.getDescriptor() instanceof StaticDescriptorImpl) { + return new BuildStaticNodeCommand(node); + } + else if (node.getDescriptor() instanceof ThreadDescriptorImpl) { + return new BuildThreadCommand(node); + } + else if (node.getDescriptor() instanceof ThreadGroupDescriptorImpl) { + return new BuildThreadGroupCommand(node); + } + LOG.assertTrue(false); + return null; + } + + public void saveState(DebuggerTreeNodeImpl node) { + if (node.getDescriptor() != null) { + TreePath path = new TreePath(node.getPath()); + node.getDescriptor().myIsExpanded = isExpanded(path); + node.getDescriptor().myIsSelected = getSelectionModel().isPathSelected(path); + Rectangle rowBounds = getRowBounds(getRowForPath(path)); + if (rowBounds != null && getVisibleRect().contains(rowBounds)) { + node.getDescriptor().putUserData(VISIBLE_RECT, getVisibleRect()); + node.getDescriptor().myIsVisible = true; + } + else { + node.getDescriptor().putUserData(VISIBLE_RECT, null); + node.getDescriptor().myIsVisible = false; + } + } + + for (Enumeration e = node.rawChildren(); e.hasMoreElements();) { + DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); + saveState(child); + } + } + + public void restoreState(DebuggerTreeNodeImpl node) { + restoreStateImpl(node); + scrollToVisible(node); + } + + protected final void scrollToVisible(DebuggerTreeNodeImpl scopeNode) { + final TreePath rootPath = new TreePath(scopeNode.getPath()); + final int rowCount = getRowCount(); + for (int idx = rowCount - 1; idx >= 0; idx--) { + final TreePath treePath = getPathForRow(idx); + if (treePath != null) { + if (!rootPath.isDescendant(treePath)) { + continue; + } + final DebuggerTreeNodeImpl pathNode = (DebuggerTreeNodeImpl)treePath.getLastPathComponent(); + final NodeDescriptorImpl descriptor = pathNode.getDescriptor(); + + if (descriptor != null && descriptor.myIsVisible) { + final Rectangle visibleRect = descriptor.getUserData(VISIBLE_RECT); + if (visibleRect != null) { + // prefer visible rect + scrollRectToVisible(visibleRect); + } + else { + scrollPathToVisible(treePath); + } + break; + } + } + } + } + + public void scrollRectToVisible(Rectangle aRect) { + // see IDEADEV-432 + aRect.width += aRect.x; + aRect.x = 0; + super.scrollRectToVisible(aRect); + } + + private void restoreStateImpl(DebuggerTreeNodeImpl node) { + restoreNodeState(node); + if (node.getDescriptor().myIsExpanded) { + for (Enumeration e = node.rawChildren(); e.hasMoreElements();) { + DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); + restoreStateImpl(child); + } + } + } + + public void restoreState() { + clearSelection(); + DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getModel().getRoot(); + if (root != null) { + restoreState(root); + } + } + + protected void restoreNodeState(DebuggerTreeNodeImpl node) { + final NodeDescriptorImpl descriptor = node.getDescriptor(); + if (descriptor != null) { + if (node.getParent() == null) { + descriptor.myIsExpanded = true; + } + + TreePath path = new TreePath(node.getPath()); + if (descriptor.myIsExpanded) { + expandPath(path); + } + if (descriptor.myIsSelected) { + addSelectionPath(path); + } + } + } + + public NodeManagerImpl getNodeFactory() { + return myNodeManager; + } + + public TreeBuilder getMutableModel() { + return (TreeBuilder)getModel(); + } + + public void removeAllChildren() { + DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getModel().getRoot(); + root.removeAllChildren(); + treeChanged(); + } + + public void showMessage(MessageDescriptor messageDesc) { + DebuggerTreeNodeImpl root = getNodeFactory().getDefaultNode(); + getMutableModel().setRoot(root); + DebuggerTreeNodeImpl message = root.add(messageDesc); + treeChanged(); + expandPath(new TreePath(message.getPath())); + } + + public void showMessage(String messageText) { + showMessage(new MessageDescriptor(messageText)); + } + + public final void treeChanged() { + DebuggerTreeNodeImpl node = (DebuggerTreeNodeImpl)getModel().getRoot(); + if (node != null) { + getMutableModel().nodeStructureChanged(node); + restoreState(); + } + } + + public Project getProject() { + return myProject; + } + + protected abstract void build(DebuggerContextImpl context); + + protected final void buildWhenPaused(DebuggerContextImpl context, RefreshDebuggerTreeCommand command) { + DebuggerSession debuggerSession = context.getDebuggerSession(); + + if (ApplicationManager.getApplication().isUnitTestMode() || debuggerSession.getState() == DebuggerSession.STATE_PAUSED) { + showMessage(MessageDescriptor.EVALUATING); + context.getDebugProcess().getManagerThread().schedule(command); + } + else { + showMessage(context.getDebuggerSession().getStateDescription()); + } + } + + public void rebuild(final DebuggerContextImpl context) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final DebugProcessImpl process = context.getDebugProcess(); + if (process == null) { + return; // empty context, no process available yet + } + myDebuggerContext = context; + saveState(); + process.getManagerThread().schedule(new DebuggerCommandImpl() { + protected void action() throws Exception { + getNodeFactory().setHistoryByContext(context); + } + public Priority getPriority() { + return Priority.NORMAL; + } + }); + + build(context); + } + + public void saveState() { + saveState((DebuggerTreeNodeImpl)getModel().getRoot()); + } + + public void onEditorShown(DebuggerTreeNodeImpl node) { + myEditedNode = node; + hideTooltip(); + } + + public void onEditorHidden(DebuggerTreeNodeImpl node) { + if (myEditedNode != null) { + assert myEditedNode == node; + myEditedNode = null; + } + } + + @Override + public JComponent createToolTip(MouseEvent e) { + return myEditedNode != null ? null : super.createToolTip(e); + } + + protected abstract static class RefreshDebuggerTreeCommand extends SuspendContextCommandImpl { + private final DebuggerContextImpl myDebuggerContext; + + public Priority getPriority() { + return Priority.NORMAL; + } + + public RefreshDebuggerTreeCommand(DebuggerContextImpl context) { + super(context.getSuspendContext()); + myDebuggerContext = context; + } + + public final DebuggerContextImpl getDebuggerContext() { + return myDebuggerContext; + } + } + + public DebuggerContextImpl getDebuggerContext() { + return myDebuggerContext; + } + + public abstract class BuildNodeCommand extends DebuggerContextCommandImpl { + private final DebuggerTreeNodeImpl myNode; + + protected final List myChildren = new LinkedList(); + + protected BuildNodeCommand(DebuggerTreeNodeImpl node) { + super(DebuggerTree.this.getDebuggerContext()); + myNode = node; + } + + public Priority getPriority() { + return Priority.NORMAL; + } + + public DebuggerTreeNodeImpl getNode() { + return myNode; + } + + protected void updateUI(final boolean scrollToVisible) { + DebuggerInvocationUtil.swingInvokeLater(getProject(), new Runnable() { + public void run() { + myNode.removeAllChildren(); + for (DebuggerTreeNode debuggerTreeNode : myChildren) { + myNode.add(debuggerTreeNode); + } + myNode.childrenChanged(scrollToVisible); + } + }); + } + } + + protected class BuildStackFrameCommand extends BuildNodeCommand { + public BuildStackFrameCommand(DebuggerTreeNodeImpl stackNode) { + super(stackNode); + } + + public final void threadAction() { + try { + final StackFrameDescriptorImpl stackDescriptor = (StackFrameDescriptorImpl)getNode().getDescriptor(); + final StackFrameProxyImpl frame = stackDescriptor.getFrameProxy(); + if (frame == null) { + return; + } + final Location location = frame.location(); + + final ObjectReference thisObjectReference = frame.thisObject(); + + final DebuggerContextImpl debuggerContext = getDebuggerContext(); + final EvaluationContextImpl evaluationContext = debuggerContext.createEvaluationContext(); + + if (!debuggerContext.isEvaluationPossible()) { + myChildren.add(myNodeManager.createNode(MessageDescriptor.EVALUATION_NOT_POSSIBLE, evaluationContext)); + } + + final NodeDescriptor descriptor; + if (thisObjectReference != null) { + descriptor = myNodeManager.getThisDescriptor(stackDescriptor, thisObjectReference); + } + else { + final ReferenceType type = location.method().declaringType(); + descriptor = myNodeManager.getStaticDescriptor(stackDescriptor, type); + } + myChildren.add(myNodeManager.createNode(descriptor, evaluationContext)); + + if (thisObjectReference != null && evaluationContext.getDebugProcess().getVirtualMachineProxy().canGetSyntheticAttribute()) { + final ReferenceType thisRefType = thisObjectReference.referenceType(); + if (thisRefType instanceof ClassType && thisRefType.equals(location.declaringType()) && thisRefType.name().contains("$")) { // makes sense for nested classes only + final ClassType clsType = (ClassType)thisRefType; + for (Field field : clsType.fields()) { + if (field.isSynthetic() && StringUtil.startsWith(field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) { + final FieldDescriptorImpl fieldDescriptor = myNodeManager.getFieldDescriptor(stackDescriptor, thisObjectReference, field); + myChildren.add(myNodeManager.createNode(fieldDescriptor, evaluationContext)); + } + } + } + } + + try { + buildVariables(stackDescriptor, evaluationContext); + if (NodeRendererSettings.getInstance().getClassRenderer().SORT_ASCENDING) { + Collections.sort(myChildren, NodeManagerImpl.getNodeComparator()); + } + } + catch (EvaluateException e) { + myChildren.add(myNodeManager.createMessageNode(new MessageDescriptor(e.getMessage()))); + } + // add last method return value if any + final Pair methodValuePair = debuggerContext.getDebugProcess().getLastExecutedMethod(); + if (methodValuePair != null) { + final ValueDescriptorImpl returnValueDescriptor = + myNodeManager.getMethodReturnValueDescriptor(stackDescriptor, methodValuePair.getFirst(), methodValuePair.getSecond()); + final DebuggerTreeNodeImpl methodReturnValueNode = myNodeManager.createNode(returnValueDescriptor, evaluationContext); + myChildren.add(1, methodReturnValueNode); + } + } + catch (EvaluateException e) { + myChildren.clear(); + myChildren.add(myNodeManager.createMessageNode(new MessageDescriptor(e.getMessage()))); + } + catch (InvalidStackFrameException e) { + LOG.info(e); + myChildren.clear(); + notifyCancelled(); + } + catch (InternalException e) { + if (e.errorCode() == 35) { + myChildren.add( + myNodeManager.createMessageNode(new MessageDescriptor(DebuggerBundle.message("error.corrupt.debug.info", e.getMessage())))); + } + else { + throw e; + } + } + + updateUI(true); + } + + protected void buildVariables(final StackFrameDescriptorImpl stackDescriptor, final EvaluationContextImpl evaluationContext) throws EvaluateException { + final StackFrameProxyImpl frame = stackDescriptor.getFrameProxy(); + if (frame != null) { + for (final LocalVariableProxyImpl local : frame.visibleVariables()) { + final LocalVariableDescriptorImpl localVariableDescriptor = myNodeManager.getLocalVariableDescriptor(stackDescriptor, local); + final DebuggerTreeNodeImpl variableNode = myNodeManager.createNode(localVariableDescriptor, evaluationContext); + myChildren.add(variableNode); + } + } + } + } + + private class BuildValueNodeCommand extends BuildNodeCommand implements ChildrenBuilder { + public BuildValueNodeCommand(DebuggerTreeNodeImpl node) { + super(node); + } + + public void threadAction() { + ValueDescriptorImpl descriptor = (ValueDescriptorImpl)getNode().getDescriptor(); + try { + final NodeRenderer renderer = descriptor.getRenderer(getSuspendContext().getDebugProcess()); + renderer.buildChildren(descriptor.getValue(), this, getDebuggerContext().createEvaluationContext()); + } + catch (ObjectCollectedException e) { + getNode().removeAllChildren(); + getNode().add(getNodeFactory().createMessageNode( + new MessageDescriptor(DebuggerBundle.message("error.cannot.build.node.children.object.collected", e.getMessage())))); + getNode().childrenChanged(false); + } + } + + public NodeManagerImpl getNodeManager() { + + return myNodeManager; + } + + public NodeManagerImpl getDescriptorManager() { + return myNodeManager; + } + + public ValueDescriptorImpl getParentDescriptor() { + return (ValueDescriptorImpl)getNode().getDescriptor(); + } + + public void setChildren(final List children) { + myChildren.addAll(children); + updateUI(false); + } + } + + private class BuildStaticNodeCommand extends BuildNodeCommand { + public BuildStaticNodeCommand(DebuggerTreeNodeImpl node) { + super(node); + } + + public void threadAction() { + final StaticDescriptorImpl sd = (StaticDescriptorImpl)getNode().getDescriptor(); + final ReferenceType refType = sd.getType(); + List fields = refType.allFields(); + for (Field field : fields) { + if (field.isStatic()) { + final FieldDescriptorImpl fieldDescriptor = myNodeManager.getFieldDescriptor(sd, null, field); + final EvaluationContextImpl evaluationContext = getDebuggerContext().createEvaluationContext(); + final DebuggerTreeNodeImpl node = myNodeManager.createNode(fieldDescriptor, evaluationContext); + myChildren.add(node); + } + } + + updateUI(true); + } + } + + private class BuildThreadCommand extends BuildNodeCommand { + public BuildThreadCommand(DebuggerTreeNodeImpl threadNode) { + super(threadNode); + } + + public void threadAction() { + ThreadDescriptorImpl threadDescriptor = ((ThreadDescriptorImpl)getNode().getDescriptor()); + ThreadReferenceProxyImpl threadProxy = threadDescriptor.getThreadReference(); + if (!threadProxy.isCollected() && getDebuggerContext().getDebugProcess().getSuspendManager().isSuspended(threadProxy)) { + int status = threadProxy.status(); + if (!(status == ThreadReference.THREAD_STATUS_UNKNOWN) && + !(status == ThreadReference.THREAD_STATUS_NOT_STARTED) && + !(status == ThreadReference.THREAD_STATUS_ZOMBIE)) { + try { + for (StackFrameProxyImpl stackFrame : threadProxy.frames()) { + //Method method = stackFrame.location().method(); + //ToDo :check whether is synthetic if (shouldDisplay(method)) { + myChildren.add(myNodeManager.createNode(myNodeManager.getStackFrameDescriptor(threadDescriptor, stackFrame), + getDebuggerContext().createEvaluationContext())); + } + } + catch (EvaluateException e) { + myChildren.clear(); + myChildren.add(myNodeManager.createMessageNode(e.getMessage())); + LOG.debug(e); + //LOG.assertTrue(false); + // if we pause during evaluation of this method the exception is thrown + // private static void longMethod(){ + // try { + // Thread.sleep(100000); + // } catch (InterruptedException e) { + // e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + // } + // } + } + } + } + updateUI(true); + } + } + + private class BuildThreadGroupCommand extends DebuggerCommandImpl { + private final DebuggerTreeNodeImpl myNode; + protected final List myChildren = new LinkedList(); + + public BuildThreadGroupCommand(DebuggerTreeNodeImpl node) { + myNode = node; + } + + protected void action() throws Exception { + ThreadGroupDescriptorImpl groupDescriptor = (ThreadGroupDescriptorImpl)myNode.getDescriptor(); + ThreadGroupReferenceProxyImpl threadGroup = groupDescriptor.getThreadGroupReference(); + + List threads = new ArrayList(threadGroup.threads()); + Collections.sort(threads, ThreadReferenceProxyImpl.ourComparator); + + final DebuggerContextImpl debuggerContext = getDebuggerContext(); + final SuspendContextImpl suspendContext = debuggerContext.getSuspendContext(); + final EvaluationContextImpl evaluationContext = suspendContext != null? debuggerContext.createEvaluationContext() : null; + + boolean showCurrent = ThreadsViewSettings.getInstance().SHOW_CURRENT_THREAD; + + for (final ThreadGroupReferenceProxyImpl group : threadGroup.threadGroups()) { + if (group != null) { + DebuggerTreeNodeImpl threadNode = + myNodeManager.createNode(myNodeManager.getThreadGroupDescriptor(groupDescriptor, group), evaluationContext); + + if (showCurrent && ((ThreadGroupDescriptorImpl)threadNode.getDescriptor()).isCurrent()) { + myChildren.add(0, threadNode); + } + else { + myChildren.add(threadNode); + } + } + } + + ArrayList threadNodes = new ArrayList(); + + for (ThreadReferenceProxyImpl thread : threads) { + if (thread != null) { + final DebuggerTreeNodeImpl threadNode = myNodeManager.createNode(myNodeManager.getThreadDescriptor(groupDescriptor, thread), evaluationContext); + if (showCurrent && ((ThreadDescriptorImpl)threadNode.getDescriptor()).isCurrent()) { + threadNodes.add(0, threadNode); + } + else { + threadNodes.add(threadNode); + } + } + } + + myChildren.addAll(threadNodes); + + updateUI(true); + } + + protected void updateUI(final boolean scrollToVisible) { + DebuggerInvocationUtil.swingInvokeLater(getProject(), new Runnable() { + public void run() { + myNode.removeAllChildren(); + for (DebuggerTreeNode debuggerTreeNode : myChildren) { + myNode.add(debuggerTreeNode); + } + myNode.childrenChanged(scrollToVisible); + } + }); + } + } + + public void hideTooltip() { + myTipManager.hideTooltip(); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java index 6d613e3027e0..201d63fd3038 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java @@ -1,279 +1,279 @@ -/* - * 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.debugger.ui.tree.render; - -import com.intellij.debugger.DebuggerBundle; -import com.intellij.debugger.DebuggerContext; -import com.intellij.debugger.engine.DebuggerManagerThreadImpl; -import com.intellij.debugger.engine.DebuggerUtils; -import com.intellij.debugger.engine.evaluation.EvaluateException; -import com.intellij.debugger.engine.evaluation.EvaluationContext; -import com.intellij.debugger.engine.jdi.StackFrameProxy; -import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl; -import com.intellij.debugger.ui.impl.watch.MessageDescriptor; -import com.intellij.debugger.ui.impl.watch.NodeManagerImpl; -import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl; -import com.intellij.debugger.ui.tree.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.DefaultJDOMExternalizer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiElementFactory; -import com.intellij.psi.PsiExpression; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.StringBuilderSpinAllocator; -import com.sun.jdi.*; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * User: lex - * Date: Sep 17, 2003 - * Time: 2:04:00 PM - */ -public class ClassRenderer extends NodeRendererImpl{ - private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.tree.render.ClassRenderer"); - - public static final @NonNls String UNIQUE_ID = "ClassRenderer"; - - public boolean SORT_ASCENDING = false; - public boolean SHOW_SYNTHETICS = true; - public boolean SHOW_STATIC = false; - public boolean SHOW_STATIC_FINAL = false; - - public boolean SHOW_DECLARED_TYPE = false; - public boolean SHOW_OBJECT_ID = true; - - public ClassRenderer() { - myProperties.setEnabled(true); - } - - public String getUniqueId() { - return UNIQUE_ID; - } - - public boolean isEnabled() { - return myProperties.isEnabled(); - } - - public void setEnabled(boolean enabled) { - myProperties.setEnabled(enabled); - } - - public ClassRenderer clone() { - return (ClassRenderer) super.clone(); - } - - public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener labelListener) throws EvaluateException { - return calcLabel(descriptor); - } - - protected static String calcLabel(ValueDescriptor descriptor) { - final ValueDescriptorImpl valueDescriptor = (ValueDescriptorImpl)descriptor; - final Value value = valueDescriptor.getValue(); - if (value instanceof ObjectReference) { - final StringBuilder buf = StringBuilderSpinAllocator.alloc(); - try { - if (value instanceof StringReference) { - buf.append('\"'); - buf.append(((StringReference)value).value()); - buf.append('\"'); - } - else if (value instanceof ClassObjectReference) { - ReferenceType type = ((ClassObjectReference)value).reflectedType(); - buf.append((type != null)?type.name():"{...}"); - } - else { - final ObjectReference objRef = (ObjectReference)value; - final Type type = objRef.type(); - if (type instanceof ClassType && ((ClassType)type).isEnum()) { - final String name = getEnumConstantName(objRef, (ClassType)type); - if (name != null) { - buf.append(name); - } - else { - buf.append(type.name()); - } - } - else { - buf.append(ValueDescriptorImpl.getIdLabel(objRef)); - } - } - return buf.toString(); - } - finally { - StringBuilderSpinAllocator.dispose(buf); - } - } - else if(value == null) { - //noinspection HardCodedStringLiteral - return "null"; - } - else { - return DebuggerBundle.message("label.undefined"); - } - } - - public void buildChildren(final Value value, final ChildrenBuilder builder, final EvaluationContext evaluationContext) { - DebuggerManagerThreadImpl.assertIsManagerThread(); - final ValueDescriptorImpl parentDescriptor = (ValueDescriptorImpl)builder.getParentDescriptor(); - final NodeManager nodeManager = builder.getNodeManager(); - final NodeDescriptorFactory nodeDescriptorFactory = builder.getDescriptorManager(); - - List children = new ArrayList(); - if (value instanceof ObjectReference) { - final ObjectReference objRef = (ObjectReference)value; - final ReferenceType refType = objRef.referenceType(); - // default ObjectReference processing - final List fields = refType.allFields(); - if (fields.size() > 0) { - for (final Field field : fields) { - if (!shouldDisplay(evaluationContext, objRef, field)) { - continue; - } - children.add(nodeManager.createNode(nodeDescriptorFactory.getFieldDescriptor(parentDescriptor, objRef, field), evaluationContext)); - } - - if(SORT_ASCENDING) { - Collections.sort(children, NodeManagerImpl.getNodeComparator()); - } - } - else { - children.add(nodeManager.createMessageNode(MessageDescriptor.CLASS_HAS_NO_FIELDS.getLabel())); - } - } - builder.setChildren(children); - } - - private boolean shouldDisplay(EvaluationContext context, @NotNull ObjectReference objInstance, @NotNull Field field) { - final boolean isSynthetic = DebuggerUtils.isSynthetic(field); - if (!SHOW_SYNTHETICS && isSynthetic) { - return false; - } - if (isSynthetic) { - try { - final StackFrameProxy frameProxy = context.getFrameProxy(); - if (frameProxy != null) { - final Location location = frameProxy.location(); - if (location != null && objInstance.equals(context.getThisObject()) && Comparing.equal(objInstance.referenceType(), location.declaringType()) && StringUtil.startsWith(field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) { - return false; - } - } - } - catch (EvaluateException ignored) { - } - } - if(!SHOW_STATIC && field.isStatic()) { - return false; - } - - if(!SHOW_STATIC_FINAL && field.isStatic() && field.isFinal()) { - return false; - } - - return true; - } - - public void readExternal(Element element) throws InvalidDataException { - super.readExternal(element); - DefaultJDOMExternalizer.readExternal(this, element); - } - - public void writeExternal(Element element) throws WriteExternalException { - super.writeExternal(element); - DefaultJDOMExternalizer.writeExternal(this, element); - } - - public PsiExpression getChildValueExpression(DebuggerTreeNode node, DebuggerContext context) throws EvaluateException { - FieldDescriptor fieldDescriptor = (FieldDescriptor)node.getDescriptor(); - - PsiElementFactory elementFactory = JavaPsiFacade.getInstance(node.getProject()).getElementFactory(); - try { - return elementFactory.createExpressionFromText(fieldDescriptor.getField().name(), DebuggerUtils.findClass( - fieldDescriptor.getObject().referenceType().name(), context.getProject(), context.getDebugProcess().getSearchScope()) - ); - } - catch (IncorrectOperationException e) { - throw new EvaluateException(DebuggerBundle.message("error.invalid.field.name", fieldDescriptor.getField().name()), null); - } - } - - private static boolean valueExpandable(Value value) { - try { - if(value instanceof ArrayReference) { - return ((ArrayReference)value).length() > 0; - } - else if(value instanceof ObjectReference) { - return ((ObjectReference)value).referenceType().allFields().size() > 0; - } - } - catch (ObjectCollectedException e) { - return true; - } - - return false; - } - - public boolean isExpandable(Value value, EvaluationContext evaluationContext, NodeDescriptor parentDescriptor) { - DebuggerManagerThreadImpl.assertIsManagerThread(); - return valueExpandable(value); - } - - public boolean isApplicable(Type type) { - return type instanceof ReferenceType && !(type instanceof ArrayType); - } - - public @NonNls String getName() { - return "Object"; - } - - public void setName(String text) { - LOG.assertTrue(false); - } - - @Nullable - public static String getEnumConstantName(final ObjectReference objRef, ClassType classType) { - do { - if (!classType.isPrepared()) { - return null; - } - classType = classType.superclass(); - if (classType == null) { - return null; - } - } - while (!("java.lang.Enum".equals(classType.name()))); - //noinspection HardCodedStringLiteral - final Field field = classType.fieldByName("name"); - if (field == null) { - return null; - } - final Value value = objRef.getValue(field); - if (!(value instanceof StringReference)) { - return null; - } - return ((StringReference)value).value(); - } -} +/* + * 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.debugger.ui.tree.render; + +import com.intellij.debugger.DebuggerBundle; +import com.intellij.debugger.DebuggerContext; +import com.intellij.debugger.engine.DebuggerManagerThreadImpl; +import com.intellij.debugger.engine.DebuggerUtils; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContext; +import com.intellij.debugger.engine.jdi.StackFrameProxy; +import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl; +import com.intellij.debugger.ui.impl.watch.MessageDescriptor; +import com.intellij.debugger.ui.impl.watch.NodeManagerImpl; +import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl; +import com.intellij.debugger.ui.tree.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.DefaultJDOMExternalizer; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiElementFactory; +import com.intellij.psi.PsiExpression; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.StringBuilderSpinAllocator; +import com.sun.jdi.*; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * User: lex + * Date: Sep 17, 2003 + * Time: 2:04:00 PM + */ +public class ClassRenderer extends NodeRendererImpl{ + private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.tree.render.ClassRenderer"); + + public static final @NonNls String UNIQUE_ID = "ClassRenderer"; + + public boolean SORT_ASCENDING = false; + public boolean SHOW_SYNTHETICS = true; + public boolean SHOW_STATIC = false; + public boolean SHOW_STATIC_FINAL = false; + + public boolean SHOW_DECLARED_TYPE = false; + public boolean SHOW_OBJECT_ID = true; + + public ClassRenderer() { + myProperties.setEnabled(true); + } + + public String getUniqueId() { + return UNIQUE_ID; + } + + public boolean isEnabled() { + return myProperties.isEnabled(); + } + + public void setEnabled(boolean enabled) { + myProperties.setEnabled(enabled); + } + + public ClassRenderer clone() { + return (ClassRenderer) super.clone(); + } + + public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener labelListener) throws EvaluateException { + return calcLabel(descriptor); + } + + protected static String calcLabel(ValueDescriptor descriptor) { + final ValueDescriptorImpl valueDescriptor = (ValueDescriptorImpl)descriptor; + final Value value = valueDescriptor.getValue(); + if (value instanceof ObjectReference) { + final StringBuilder buf = StringBuilderSpinAllocator.alloc(); + try { + if (value instanceof StringReference) { + buf.append('\"'); + buf.append(((StringReference)value).value()); + buf.append('\"'); + } + else if (value instanceof ClassObjectReference) { + ReferenceType type = ((ClassObjectReference)value).reflectedType(); + buf.append((type != null)?type.name():"{...}"); + } + else { + final ObjectReference objRef = (ObjectReference)value; + final Type type = objRef.type(); + if (type instanceof ClassType && ((ClassType)type).isEnum()) { + final String name = getEnumConstantName(objRef, (ClassType)type); + if (name != null) { + buf.append(name); + } + else { + buf.append(type.name()); + } + } + else { + buf.append(ValueDescriptorImpl.getIdLabel(objRef)); + } + } + return buf.toString(); + } + finally { + StringBuilderSpinAllocator.dispose(buf); + } + } + else if(value == null) { + //noinspection HardCodedStringLiteral + return "null"; + } + else { + return DebuggerBundle.message("label.undefined"); + } + } + + public void buildChildren(final Value value, final ChildrenBuilder builder, final EvaluationContext evaluationContext) { + DebuggerManagerThreadImpl.assertIsManagerThread(); + final ValueDescriptorImpl parentDescriptor = (ValueDescriptorImpl)builder.getParentDescriptor(); + final NodeManager nodeManager = builder.getNodeManager(); + final NodeDescriptorFactory nodeDescriptorFactory = builder.getDescriptorManager(); + + List children = new ArrayList(); + if (value instanceof ObjectReference) { + final ObjectReference objRef = (ObjectReference)value; + final ReferenceType refType = objRef.referenceType(); + // default ObjectReference processing + final List fields = refType.allFields(); + if (fields.size() > 0) { + for (final Field field : fields) { + if (!shouldDisplay(evaluationContext, objRef, field)) { + continue; + } + children.add(nodeManager.createNode(nodeDescriptorFactory.getFieldDescriptor(parentDescriptor, objRef, field), evaluationContext)); + } + + if(SORT_ASCENDING) { + Collections.sort(children, NodeManagerImpl.getNodeComparator()); + } + } + else { + children.add(nodeManager.createMessageNode(MessageDescriptor.CLASS_HAS_NO_FIELDS.getLabel())); + } + } + builder.setChildren(children); + } + + private boolean shouldDisplay(EvaluationContext context, @NotNull ObjectReference objInstance, @NotNull Field field) { + final boolean isSynthetic = DebuggerUtils.isSynthetic(field); + if (!SHOW_SYNTHETICS && isSynthetic) { + return false; + } + if (isSynthetic) { + try { + final StackFrameProxy frameProxy = context.getFrameProxy(); + if (frameProxy != null) { + final Location location = frameProxy.location(); + if (location != null && objInstance.equals(context.getThisObject()) && Comparing.equal(objInstance.referenceType(), location.declaringType()) && StringUtil.startsWith(field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) { + return false; + } + } + } + catch (EvaluateException ignored) { + } + } + if(!SHOW_STATIC && field.isStatic()) { + return false; + } + + if(!SHOW_STATIC_FINAL && field.isStatic() && field.isFinal()) { + return false; + } + + return true; + } + + public void readExternal(Element element) throws InvalidDataException { + super.readExternal(element); + DefaultJDOMExternalizer.readExternal(this, element); + } + + public void writeExternal(Element element) throws WriteExternalException { + super.writeExternal(element); + DefaultJDOMExternalizer.writeExternal(this, element); + } + + public PsiExpression getChildValueExpression(DebuggerTreeNode node, DebuggerContext context) throws EvaluateException { + FieldDescriptor fieldDescriptor = (FieldDescriptor)node.getDescriptor(); + + PsiElementFactory elementFactory = JavaPsiFacade.getInstance(node.getProject()).getElementFactory(); + try { + return elementFactory.createExpressionFromText(fieldDescriptor.getField().name(), DebuggerUtils.findClass( + fieldDescriptor.getObject().referenceType().name(), context.getProject(), context.getDebugProcess().getSearchScope()) + ); + } + catch (IncorrectOperationException e) { + throw new EvaluateException(DebuggerBundle.message("error.invalid.field.name", fieldDescriptor.getField().name()), null); + } + } + + private static boolean valueExpandable(Value value) { + try { + if(value instanceof ArrayReference) { + return ((ArrayReference)value).length() > 0; + } + else if(value instanceof ObjectReference) { + return ((ObjectReference)value).referenceType().allFields().size() > 0; + } + } + catch (ObjectCollectedException e) { + return true; + } + + return false; + } + + public boolean isExpandable(Value value, EvaluationContext evaluationContext, NodeDescriptor parentDescriptor) { + DebuggerManagerThreadImpl.assertIsManagerThread(); + return valueExpandable(value); + } + + public boolean isApplicable(Type type) { + return type instanceof ReferenceType && !(type instanceof ArrayType); + } + + public @NonNls String getName() { + return "Object"; + } + + public void setName(String text) { + LOG.assertTrue(false); + } + + @Nullable + public static String getEnumConstantName(final ObjectReference objRef, ClassType classType) { + do { + if (!classType.isPrepared()) { + return null; + } + classType = classType.superclass(); + if (classType == null) { + return null; + } + } + while (!("java.lang.Enum".equals(classType.name()))); + //noinspection HardCodedStringLiteral + final Field field = classType.fieldByName("name"); + if (field == null) { + return null; + } + final Value value = objRef.getValue(field); + if (!(value instanceof StringReference)) { + return null; + } + return ((StringReference)value).value(); + } +} diff --git a/java/debugger/openapi/src/com/intellij/debugger/engine/evaluation/EvaluateException.java b/java/debugger/openapi/src/com/intellij/debugger/engine/evaluation/EvaluateException.java index 5da4bd09a6db..30dac08cc738 100644 --- a/java/debugger/openapi/src/com/intellij/debugger/engine/evaluation/EvaluateException.java +++ b/java/debugger/openapi/src/com/intellij/debugger/engine/evaluation/EvaluateException.java @@ -1,69 +1,69 @@ -/* - * 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.debugger.engine.evaluation; - -import com.intellij.openapi.diagnostic.Logger; -import com.sun.jdi.InvocationException; -import com.sun.jdi.ObjectReference; -import org.jetbrains.annotations.Nullable; - -public class EvaluateException extends Exception { - private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.evaluation.EvaluateException"); - private ObjectReference myTargetException; - - public EvaluateException(final String message) { - super(message); - if (LOG.isDebugEnabled()) { - LOG.debug(message); - } - } - - public EvaluateException(String msg, Throwable th) { - super(msg, th); - if (th instanceof EvaluateException) { - myTargetException = ((EvaluateException)th).getExceptionFromTargetVM(); - } - else if(th instanceof InvocationException){ - InvocationException invocationException = (InvocationException) th; - myTargetException = invocationException.exception(); - } - if (LOG.isDebugEnabled()) { - LOG.debug(msg); - } - } - - @Nullable - public ObjectReference getExceptionFromTargetVM() { - return myTargetException; - } - - public void setTargetException(final ObjectReference targetException) { - myTargetException = targetException; - } - - public String getMessage() { - final String errorMessage = super.getMessage(); - if (errorMessage != null) { - return errorMessage; - } - final Throwable cause = getCause(); - final String causeMessage = cause != null? cause.getMessage() : null; - if (causeMessage != null) { - return causeMessage; - } - return "unknown error"; - } +/* + * 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.debugger.engine.evaluation; + +import com.intellij.openapi.diagnostic.Logger; +import com.sun.jdi.InvocationException; +import com.sun.jdi.ObjectReference; +import org.jetbrains.annotations.Nullable; + +public class EvaluateException extends Exception { + private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.evaluation.EvaluateException"); + private ObjectReference myTargetException; + + public EvaluateException(final String message) { + super(message); + if (LOG.isDebugEnabled()) { + LOG.debug(message); + } + } + + public EvaluateException(String msg, Throwable th) { + super(msg, th); + if (th instanceof EvaluateException) { + myTargetException = ((EvaluateException)th).getExceptionFromTargetVM(); + } + else if(th instanceof InvocationException){ + InvocationException invocationException = (InvocationException) th; + myTargetException = invocationException.exception(); + } + if (LOG.isDebugEnabled()) { + LOG.debug(msg); + } + } + + @Nullable + public ObjectReference getExceptionFromTargetVM() { + return myTargetException; + } + + public void setTargetException(final ObjectReference targetException) { + myTargetException = targetException; + } + + public String getMessage() { + final String errorMessage = super.getMessage(); + if (errorMessage != null) { + return errorMessage; + } + final Throwable cause = getCause(); + final String causeMessage = cause != null? cause.getMessage() : null; + if (causeMessage != null) { + return causeMessage; + } + return "unknown error"; + } } \ No newline at end of file diff --git a/java/execution/impl/src/com/intellij/execution/applet/AppletConfiguration.java b/java/execution/impl/src/com/intellij/execution/applet/AppletConfiguration.java index e18f887ea0cf..d6921529547e 100644 --- a/java/execution/impl/src/com/intellij/execution/applet/AppletConfiguration.java +++ b/java/execution/impl/src/com/intellij/execution/applet/AppletConfiguration.java @@ -1,369 +1,369 @@ -/* - * 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.execution.applet; - -import com.intellij.execution.*; -import com.intellij.execution.configurations.*; -import com.intellij.execution.filters.TextConsoleBuilderFactory; -import com.intellij.execution.junit.RefactoringListeners; -import com.intellij.execution.process.OSProcessHandler; -import com.intellij.execution.process.ProcessAdapter; -import com.intellij.execution.process.ProcessEvent; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.util.JavaParametersUtil; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.options.SettingsEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.DefaultJDOMExternalizer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.refactoring.listeners.RefactoringElementListener; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -public class AppletConfiguration extends ModuleBasedConfiguration implements SingleClassConfiguration, RefactoringListenerProvider { - - public String MAIN_CLASS_NAME; - public String HTML_FILE_NAME; - public boolean HTML_USED; - public int WIDTH; - public int HEIGHT; - public String POLICY_FILE; - public String VM_PARAMETERS; - private AppletParameter[] myAppletParameters; - public boolean ALTERNATIVE_JRE_PATH_ENABLED; - public String ALTERNATIVE_JRE_PATH; - @NonNls - protected static final String NAME_ATTR = "name"; - @NonNls - protected static final String VALUE_ATTR = "value"; - @NonNls - protected static final String PARAMETER_ELEMENT_NAME = "parameter"; - - public AppletConfiguration(final String name, final Project project, ConfigurationFactory factory) { - super(name, new JavaRunConfigurationModule(project, false), factory); - } - - public void setMainClass(final PsiClass psiClass) { - final Module originalModule = getConfigurationModule().getModule(); - setMainClassName(JavaExecutionUtil.getRuntimeQualifiedName(psiClass)); - setModule(JavaExecutionUtil.findModule(psiClass)); - restoreOriginalModule(originalModule); - } - - public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException { - final JavaCommandLineState state = new JavaCommandLineState(env) { - private AppletHtmlFile myHtmlURL = null; - - protected JavaParameters createJavaParameters() throws ExecutionException { - final JavaParameters params = new JavaParameters(); - myHtmlURL = getHtmlURL(); - if (myHtmlURL != null) { - final int classPathType = myHtmlURL.isHttp() ? JavaParameters.JDK_ONLY : JavaParameters.JDK_AND_CLASSES; - final RunConfigurationModule runConfigurationModule = getConfigurationModule(); - JavaParametersUtil.configureModule(runConfigurationModule, params, classPathType, ALTERNATIVE_JRE_PATH_ENABLED ? ALTERNATIVE_JRE_PATH : null); - final String policyFileParameter = getPolicyFileParameter(); - if (policyFileParameter != null) { - params.getVMParametersList().add(policyFileParameter); - } - params.getVMParametersList().addParametersString(VM_PARAMETERS); - params.setMainClass("sun.applet.AppletViewer"); - params.getProgramParametersList().add(myHtmlURL.getUrl()); - } - return params; - } - - protected OSProcessHandler startProcess() throws ExecutionException { - final OSProcessHandler handler = super.startProcess(); - final AppletHtmlFile htmlUrl = myHtmlURL; - if (htmlUrl != null) { - handler.addProcessListener(new ProcessAdapter() { - public void processTerminated(ProcessEvent event) { - htmlUrl.deleteFile(); - } - }); - } - return handler; - } - }; - state.setConsoleBuilder(TextConsoleBuilderFactory.getInstance().createBuilder(getProject())); - return state; - } - - public SettingsEditor getConfigurationEditor() { - return new AppletConfigurable(getProject()); - } - - @NonNls private String getPolicyFileParameter() { - if (POLICY_FILE != null && POLICY_FILE.length() > 0) { - return "-Djava.security.policy=" + getPolicyFile(); - } - return null; - } - - public void setPolicyFile(final String localPath) { - POLICY_FILE = ExternalizablePath.urlValue(localPath); - } - - public String getPolicyFile() { - return ExternalizablePath.localPathValue(POLICY_FILE); - } - - public static class AppletParameter { - public String myName; - public String myValue; - - public AppletParameter(@NonNls final String name, final String value) { - myName = name; - myValue = value; - } - - public String getName() { - return myName; - } - - public void setName(final String name) { - myName = name; - } - - public String getValue() { - return myValue; - } - - public void setValue(final String value) { - myValue = value; - } - - public boolean equals(final Object obj) { - if (!(obj instanceof AppletParameter)) return false; - final AppletParameter second = (AppletParameter)obj; - return Comparing.equal(myName, second.myName) && Comparing.equal(myValue, second.myValue); - } - - public int hashCode() { - return Comparing.hashcode(myName, myValue); - } - } - - public Collection getValidModules() { - return JavaRunConfigurationModule.getModulesForClass(getProject(), MAIN_CLASS_NAME); - } - - public void readExternal(final Element parentNode) throws InvalidDataException { - DefaultJDOMExternalizer.readExternal(this, parentNode); - readModule(parentNode); - final ArrayList parameters = new ArrayList(); - for ( - Iterator iterator = parentNode.getChildren(PARAMETER_ELEMENT_NAME).iterator(); iterator.hasNext();) { - final Element element = (Element)iterator.next(); - final String name = element.getAttributeValue(NAME_ATTR); - final String value = element.getAttributeValue(VALUE_ATTR); - parameters.add(new AppletParameter(name, value)); - } - myAppletParameters = parameters.toArray(new AppletParameter[parameters.size()]); - } - - public void writeExternal(final Element parentNode) throws WriteExternalException { - writeModule(parentNode); - DefaultJDOMExternalizer.writeExternal(this, parentNode); - if (myAppletParameters != null) { - for (int i = 0; i < myAppletParameters.length; i++) { - final Element element = new Element(PARAMETER_ELEMENT_NAME); - parentNode.addContent(element); - element.setAttribute(NAME_ATTR, myAppletParameters[i].getName()); - element.setAttribute(VALUE_ATTR, myAppletParameters[i].getValue()); - } - } - } - - protected ModuleBasedConfiguration createInstance() { - return new AppletConfiguration(getName(), getProject(), AppletConfigurationType.getInstance().getConfigurationFactories()[0]); - } - - public String getGeneratedName() { - if (MAIN_CLASS_NAME == null) return null; - return JavaExecutionUtil.getPresentableClassName(MAIN_CLASS_NAME, getConfigurationModule()); - } - - public RefactoringElementListener getRefactoringElementListener(final PsiElement element) { - if (HTML_USED) return null; - return RefactoringListeners.getClassOrPackageListener(element, new RefactoringListeners.SingleClassConfigurationAccessor(this)); - } - - public PsiClass getMainClass() { - return getConfigurationModule().findClass(MAIN_CLASS_NAME); - } - - public void setGeneratedName() { - setName(getGeneratedName()); - } - - public boolean isGeneratedName() { - return Comparing.equal(getName(), getGeneratedName()); - } - - public String suggestedName() { - return ProgramRunnerUtil.shortenName(JavaExecutionUtil.getShortClassName(MAIN_CLASS_NAME), 0); - } - - public void setMainClassName(final String qualifiedName) { - final boolean generatedName = isGeneratedName(); - MAIN_CLASS_NAME = qualifiedName; - if (generatedName) setGeneratedName(); - } - - public void checkConfiguration() throws RuntimeConfigurationException { - if (ALTERNATIVE_JRE_PATH_ENABLED){ - if (ALTERNATIVE_JRE_PATH == null || - ALTERNATIVE_JRE_PATH.length() == 0 || - !JavaSdkImpl.checkForJre(ALTERNATIVE_JRE_PATH)){ - throw new RuntimeConfigurationWarning(ExecutionBundle.message("jre.not.valid.error.message", ALTERNATIVE_JRE_PATH)); - } - } - getConfigurationModule().checkForWarning(); - if (HTML_USED) { - if (HTML_FILE_NAME == null || HTML_FILE_NAME.length() == 0) { - throw new RuntimeConfigurationWarning(ExecutionBundle.message("html.file.not.specified.error.message")); - } - try { - new URL(HTML_FILE_NAME); - } - catch (MalformedURLException ex) { - throw new RuntimeConfigurationWarning("URL " + HTML_FILE_NAME + " is not valid: " + ex.getLocalizedMessage()); - } - } - else { - getConfigurationModule().checkClassName(MAIN_CLASS_NAME, ExecutionBundle.message("no.applet.class.specified.error.message")); - } - } - - public AppletParameter[] getAppletParameters() { - return myAppletParameters; - } - - public void setAppletParameters(final AppletParameter[] appletParameters) { - myAppletParameters = appletParameters; - } - - public void setAppletParameters(final List parameters) { - setAppletParameters(parameters.toArray(new AppletParameter[parameters.size()])); - } - - private AppletHtmlFile getHtmlURL() throws CantRunException { - if (HTML_USED) { - if (HTML_FILE_NAME == null || HTML_FILE_NAME.length() == 0) { - throw new CantRunException(ExecutionBundle.message("html.file.not.specified.error.message")); - } - return new AppletHtmlFile(HTML_FILE_NAME, null); - } - else { - if (MAIN_CLASS_NAME == null || MAIN_CLASS_NAME.length() == 0) { - throw new CantRunException(ExecutionBundle.message("class.not.specified.error.message")); - } - - // generate html - try { - return generateAppletTempPage(); - } - catch (IOException e) { - throw new CantRunException(ExecutionBundle.message("failed.to.generate.wrapper.error.message")); - } - } - } - - private AppletHtmlFile generateAppletTempPage() throws IOException { - final File tempFile = FileUtil.createTempFile("AppletPage", ".html"); - @NonNls final FileWriter writer = new FileWriter(tempFile); - try { - writer.write("\n" + - "\n" + - "" + MAIN_CLASS_NAME + "\n" + - "\n" + - "\n"); - final AppletParameter[] appletParameters = getAppletParameters(); - if (appletParameters != null) { - for (final AppletParameter parameter : appletParameters) { - writer.write("\n"); - } - } - writer.write("\n\n\n"); - } - finally { - writer.close(); - } - final String htmlFile = tempFile.getAbsolutePath(); - return new AppletHtmlFile(htmlFile, tempFile); - } - - private static class AppletHtmlFile { - private final String myHtmlFile; - private final File myFileToDelete; - @NonNls - protected static final String FILE_PREFIX = "file:/"; - @NonNls - protected static final String HTTP_PREFIX = "http:/"; - @NonNls - protected static final String HTTPS_PREFIX = "https:/"; - - protected AppletHtmlFile(final String htmlFile, final File fileToDelete) { - myHtmlFile = htmlFile; - myFileToDelete = fileToDelete; - } - - public String getUrl() { - if (!StringUtil.startsWithIgnoreCase(myHtmlFile, FILE_PREFIX) && !isHttp()) { - try { - return new File(myHtmlFile).toURL().toString(); - } - catch (MalformedURLException ex) { - } - } - return myHtmlFile; - } - - public boolean isHttp() { - return StringUtil.startsWithIgnoreCase(myHtmlFile, HTTP_PREFIX) || StringUtil.startsWithIgnoreCase(myHtmlFile, HTTPS_PREFIX); - } - - public void deleteFile() { - if (myFileToDelete != null) { - myFileToDelete.delete(); - } - } - } -} +/* + * 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.execution.applet; + +import com.intellij.execution.*; +import com.intellij.execution.configurations.*; +import com.intellij.execution.filters.TextConsoleBuilderFactory; +import com.intellij.execution.junit.RefactoringListeners; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.util.JavaParametersUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.DefaultJDOMExternalizer; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.listeners.RefactoringElementListener; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class AppletConfiguration extends ModuleBasedConfiguration implements SingleClassConfiguration, RefactoringListenerProvider { + + public String MAIN_CLASS_NAME; + public String HTML_FILE_NAME; + public boolean HTML_USED; + public int WIDTH; + public int HEIGHT; + public String POLICY_FILE; + public String VM_PARAMETERS; + private AppletParameter[] myAppletParameters; + public boolean ALTERNATIVE_JRE_PATH_ENABLED; + public String ALTERNATIVE_JRE_PATH; + @NonNls + protected static final String NAME_ATTR = "name"; + @NonNls + protected static final String VALUE_ATTR = "value"; + @NonNls + protected static final String PARAMETER_ELEMENT_NAME = "parameter"; + + public AppletConfiguration(final String name, final Project project, ConfigurationFactory factory) { + super(name, new JavaRunConfigurationModule(project, false), factory); + } + + public void setMainClass(final PsiClass psiClass) { + final Module originalModule = getConfigurationModule().getModule(); + setMainClassName(JavaExecutionUtil.getRuntimeQualifiedName(psiClass)); + setModule(JavaExecutionUtil.findModule(psiClass)); + restoreOriginalModule(originalModule); + } + + public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException { + final JavaCommandLineState state = new JavaCommandLineState(env) { + private AppletHtmlFile myHtmlURL = null; + + protected JavaParameters createJavaParameters() throws ExecutionException { + final JavaParameters params = new JavaParameters(); + myHtmlURL = getHtmlURL(); + if (myHtmlURL != null) { + final int classPathType = myHtmlURL.isHttp() ? JavaParameters.JDK_ONLY : JavaParameters.JDK_AND_CLASSES; + final RunConfigurationModule runConfigurationModule = getConfigurationModule(); + JavaParametersUtil.configureModule(runConfigurationModule, params, classPathType, ALTERNATIVE_JRE_PATH_ENABLED ? ALTERNATIVE_JRE_PATH : null); + final String policyFileParameter = getPolicyFileParameter(); + if (policyFileParameter != null) { + params.getVMParametersList().add(policyFileParameter); + } + params.getVMParametersList().addParametersString(VM_PARAMETERS); + params.setMainClass("sun.applet.AppletViewer"); + params.getProgramParametersList().add(myHtmlURL.getUrl()); + } + return params; + } + + protected OSProcessHandler startProcess() throws ExecutionException { + final OSProcessHandler handler = super.startProcess(); + final AppletHtmlFile htmlUrl = myHtmlURL; + if (htmlUrl != null) { + handler.addProcessListener(new ProcessAdapter() { + public void processTerminated(ProcessEvent event) { + htmlUrl.deleteFile(); + } + }); + } + return handler; + } + }; + state.setConsoleBuilder(TextConsoleBuilderFactory.getInstance().createBuilder(getProject())); + return state; + } + + public SettingsEditor getConfigurationEditor() { + return new AppletConfigurable(getProject()); + } + + @NonNls private String getPolicyFileParameter() { + if (POLICY_FILE != null && POLICY_FILE.length() > 0) { + return "-Djava.security.policy=" + getPolicyFile(); + } + return null; + } + + public void setPolicyFile(final String localPath) { + POLICY_FILE = ExternalizablePath.urlValue(localPath); + } + + public String getPolicyFile() { + return ExternalizablePath.localPathValue(POLICY_FILE); + } + + public static class AppletParameter { + public String myName; + public String myValue; + + public AppletParameter(@NonNls final String name, final String value) { + myName = name; + myValue = value; + } + + public String getName() { + return myName; + } + + public void setName(final String name) { + myName = name; + } + + public String getValue() { + return myValue; + } + + public void setValue(final String value) { + myValue = value; + } + + public boolean equals(final Object obj) { + if (!(obj instanceof AppletParameter)) return false; + final AppletParameter second = (AppletParameter)obj; + return Comparing.equal(myName, second.myName) && Comparing.equal(myValue, second.myValue); + } + + public int hashCode() { + return Comparing.hashcode(myName, myValue); + } + } + + public Collection getValidModules() { + return JavaRunConfigurationModule.getModulesForClass(getProject(), MAIN_CLASS_NAME); + } + + public void readExternal(final Element parentNode) throws InvalidDataException { + DefaultJDOMExternalizer.readExternal(this, parentNode); + readModule(parentNode); + final ArrayList parameters = new ArrayList(); + for ( + Iterator iterator = parentNode.getChildren(PARAMETER_ELEMENT_NAME).iterator(); iterator.hasNext();) { + final Element element = (Element)iterator.next(); + final String name = element.getAttributeValue(NAME_ATTR); + final String value = element.getAttributeValue(VALUE_ATTR); + parameters.add(new AppletParameter(name, value)); + } + myAppletParameters = parameters.toArray(new AppletParameter[parameters.size()]); + } + + public void writeExternal(final Element parentNode) throws WriteExternalException { + writeModule(parentNode); + DefaultJDOMExternalizer.writeExternal(this, parentNode); + if (myAppletParameters != null) { + for (int i = 0; i < myAppletParameters.length; i++) { + final Element element = new Element(PARAMETER_ELEMENT_NAME); + parentNode.addContent(element); + element.setAttribute(NAME_ATTR, myAppletParameters[i].getName()); + element.setAttribute(VALUE_ATTR, myAppletParameters[i].getValue()); + } + } + } + + protected ModuleBasedConfiguration createInstance() { + return new AppletConfiguration(getName(), getProject(), AppletConfigurationType.getInstance().getConfigurationFactories()[0]); + } + + public String getGeneratedName() { + if (MAIN_CLASS_NAME == null) return null; + return JavaExecutionUtil.getPresentableClassName(MAIN_CLASS_NAME, getConfigurationModule()); + } + + public RefactoringElementListener getRefactoringElementListener(final PsiElement element) { + if (HTML_USED) return null; + return RefactoringListeners.getClassOrPackageListener(element, new RefactoringListeners.SingleClassConfigurationAccessor(this)); + } + + public PsiClass getMainClass() { + return getConfigurationModule().findClass(MAIN_CLASS_NAME); + } + + public void setGeneratedName() { + setName(getGeneratedName()); + } + + public boolean isGeneratedName() { + return Comparing.equal(getName(), getGeneratedName()); + } + + public String suggestedName() { + return ProgramRunnerUtil.shortenName(JavaExecutionUtil.getShortClassName(MAIN_CLASS_NAME), 0); + } + + public void setMainClassName(final String qualifiedName) { + final boolean generatedName = isGeneratedName(); + MAIN_CLASS_NAME = qualifiedName; + if (generatedName) setGeneratedName(); + } + + public void checkConfiguration() throws RuntimeConfigurationException { + if (ALTERNATIVE_JRE_PATH_ENABLED){ + if (ALTERNATIVE_JRE_PATH == null || + ALTERNATIVE_JRE_PATH.length() == 0 || + !JavaSdkImpl.checkForJre(ALTERNATIVE_JRE_PATH)){ + throw new RuntimeConfigurationWarning(ExecutionBundle.message("jre.not.valid.error.message", ALTERNATIVE_JRE_PATH)); + } + } + getConfigurationModule().checkForWarning(); + if (HTML_USED) { + if (HTML_FILE_NAME == null || HTML_FILE_NAME.length() == 0) { + throw new RuntimeConfigurationWarning(ExecutionBundle.message("html.file.not.specified.error.message")); + } + try { + new URL(HTML_FILE_NAME); + } + catch (MalformedURLException ex) { + throw new RuntimeConfigurationWarning("URL " + HTML_FILE_NAME + " is not valid: " + ex.getLocalizedMessage()); + } + } + else { + getConfigurationModule().checkClassName(MAIN_CLASS_NAME, ExecutionBundle.message("no.applet.class.specified.error.message")); + } + } + + public AppletParameter[] getAppletParameters() { + return myAppletParameters; + } + + public void setAppletParameters(final AppletParameter[] appletParameters) { + myAppletParameters = appletParameters; + } + + public void setAppletParameters(final List parameters) { + setAppletParameters(parameters.toArray(new AppletParameter[parameters.size()])); + } + + private AppletHtmlFile getHtmlURL() throws CantRunException { + if (HTML_USED) { + if (HTML_FILE_NAME == null || HTML_FILE_NAME.length() == 0) { + throw new CantRunException(ExecutionBundle.message("html.file.not.specified.error.message")); + } + return new AppletHtmlFile(HTML_FILE_NAME, null); + } + else { + if (MAIN_CLASS_NAME == null || MAIN_CLASS_NAME.length() == 0) { + throw new CantRunException(ExecutionBundle.message("class.not.specified.error.message")); + } + + // generate html + try { + return generateAppletTempPage(); + } + catch (IOException e) { + throw new CantRunException(ExecutionBundle.message("failed.to.generate.wrapper.error.message")); + } + } + } + + private AppletHtmlFile generateAppletTempPage() throws IOException { + final File tempFile = FileUtil.createTempFile("AppletPage", ".html"); + @NonNls final FileWriter writer = new FileWriter(tempFile); + try { + writer.write("\n" + + "\n" + + "" + MAIN_CLASS_NAME + "\n" + + "\n" + + "\n"); + final AppletParameter[] appletParameters = getAppletParameters(); + if (appletParameters != null) { + for (final AppletParameter parameter : appletParameters) { + writer.write("\n"); + } + } + writer.write("\n\n\n"); + } + finally { + writer.close(); + } + final String htmlFile = tempFile.getAbsolutePath(); + return new AppletHtmlFile(htmlFile, tempFile); + } + + private static class AppletHtmlFile { + private final String myHtmlFile; + private final File myFileToDelete; + @NonNls + protected static final String FILE_PREFIX = "file:/"; + @NonNls + protected static final String HTTP_PREFIX = "http:/"; + @NonNls + protected static final String HTTPS_PREFIX = "https:/"; + + protected AppletHtmlFile(final String htmlFile, final File fileToDelete) { + myHtmlFile = htmlFile; + myFileToDelete = fileToDelete; + } + + public String getUrl() { + if (!StringUtil.startsWithIgnoreCase(myHtmlFile, FILE_PREFIX) && !isHttp()) { + try { + return new File(myHtmlFile).toURL().toString(); + } + catch (MalformedURLException ex) { + } + } + return myHtmlFile; + } + + public boolean isHttp() { + return StringUtil.startsWithIgnoreCase(myHtmlFile, HTTP_PREFIX) || StringUtil.startsWithIgnoreCase(myHtmlFile, HTTPS_PREFIX); + } + + public void deleteFile() { + if (myFileToDelete != null) { + myFileToDelete.delete(); + } + } + } +} diff --git a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java index d873f3547621..157402578f4b 100644 --- a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java +++ b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java @@ -1,143 +1,143 @@ -/* - * 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.execution.filters; - -import com.intellij.openapi.editor.colors.CodeInsightColors; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.Trinity; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; - -public class ExceptionFilter implements Filter, DumbAware { - private final Project myProject; - @NonNls private static final String AT = "at"; - private static final String AT_PREFIX = AT + " "; - private static final String STANDALONE_AT = " " + AT + " "; - private static final TextAttributes HYPERLINK_ATTRIBUTES = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES); - private final GlobalSearchScope mySearchScope; - - public ExceptionFilter(@NotNull final Project project) { - myProject = project; - mySearchScope = GlobalSearchScope.allScope(myProject); - } - - public ExceptionFilter(@NotNull final GlobalSearchScope scope) { - myProject = scope.getProject(); - mySearchScope = scope; - } - - @Nullable - static Trinity parseExceptionLine(final String line) { - int atIndex; - if (line.startsWith(AT_PREFIX)){ - atIndex = 0; - } - else{ - atIndex = line.indexOf(STANDALONE_AT); - if (atIndex < 0) { - atIndex = line.indexOf(AT_PREFIX); - } - if (atIndex < 0) return null; - } - - final int lparenthIndex = line.indexOf('(', atIndex); - if (lparenthIndex < 0) return null; - final int lastDotIndex = line.lastIndexOf('.', lparenthIndex); - if (lastDotIndex < 0 || lastDotIndex < atIndex) return null; - String className = line.substring(atIndex + AT.length() + 1, lastDotIndex).trim(); - - String methodName = line.substring(lastDotIndex + 1, lparenthIndex).trim(); - - final int rparenthIndex = line.indexOf(')', lparenthIndex); - if (rparenthIndex < 0) return null; - - return Trinity.create(className, methodName, new TextRange(lparenthIndex, rparenthIndex)); - } - - public Result applyFilter(final String line, final int textEndOffset) { - final Trinity info = parseExceptionLine(line); - if (info == null) { - return null; - } - - String className = info.first; - final int dollarIndex = className.indexOf('$'); - if (dollarIndex >= 0){ - className = className.substring(0, dollarIndex); - } - - final int lparenthIndex = info.third.getStartOffset(); - final int rparenthIndex = info.third.getEndOffset(); - final String fileAndLine = line.substring(lparenthIndex + 1, rparenthIndex).trim(); - - final int colonIndex = fileAndLine.lastIndexOf(':'); - if (colonIndex < 0) return null; - - final String lineString = fileAndLine.substring(colonIndex + 1); - try{ - final int lineNumber = Integer.parseInt(lineString); - final PsiManager manager = PsiManager.getInstance(myProject); - PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(className, mySearchScope); - if (aClass == null) { - aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(className, GlobalSearchScope.allScope(myProject)); - if (aClass == null) return null; - } - final PsiFile file = (PsiFile) aClass.getContainingFile().getNavigationElement(); - if (file == null) return null; - - /* - IDEADEV-4976: Some scramblers put something like SourceFile mock instead of real class name. - final String filePath = fileAndLine.substring(0, colonIndex).replace('/', File.separatorChar); - final int slashIndex = filePath.lastIndexOf(File.separatorChar); - final String shortFileName = slashIndex < 0 ? filePath : filePath.substring(slashIndex + 1); - if (!file.getName().equalsIgnoreCase(shortFileName)) return null; - */ - - final int textStartOffset = textEndOffset - line.length(); - - final int highlightStartOffset = textStartOffset + lparenthIndex + 1; - final int highlightEndOffset = textStartOffset + rparenthIndex; - VirtualFile virtualFile = file.getVirtualFile(); - final OpenFileHyperlinkInfo linkInfo = new OpenFileHyperlinkInfo(myProject, virtualFile, lineNumber - 1); - TextAttributes attributes = HYPERLINK_ATTRIBUTES.clone(); - if (!ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(virtualFile)) { - Color color = UIUtil.getInactiveTextColor(); - attributes.setForegroundColor(color); - attributes.setEffectColor(color); - } - return new Result(highlightStartOffset, highlightEndOffset, linkInfo, attributes); - } - catch(NumberFormatException e){ - return null; - } - } - +/* + * 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.execution.filters; + +import com.intellij.openapi.editor.colors.CodeInsightColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.Trinity; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +public class ExceptionFilter implements Filter, DumbAware { + private final Project myProject; + @NonNls private static final String AT = "at"; + private static final String AT_PREFIX = AT + " "; + private static final String STANDALONE_AT = " " + AT + " "; + private static final TextAttributes HYPERLINK_ATTRIBUTES = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES); + private final GlobalSearchScope mySearchScope; + + public ExceptionFilter(@NotNull final Project project) { + myProject = project; + mySearchScope = GlobalSearchScope.allScope(myProject); + } + + public ExceptionFilter(@NotNull final GlobalSearchScope scope) { + myProject = scope.getProject(); + mySearchScope = scope; + } + + @Nullable + static Trinity parseExceptionLine(final String line) { + int atIndex; + if (line.startsWith(AT_PREFIX)){ + atIndex = 0; + } + else{ + atIndex = line.indexOf(STANDALONE_AT); + if (atIndex < 0) { + atIndex = line.indexOf(AT_PREFIX); + } + if (atIndex < 0) return null; + } + + final int lparenthIndex = line.indexOf('(', atIndex); + if (lparenthIndex < 0) return null; + final int lastDotIndex = line.lastIndexOf('.', lparenthIndex); + if (lastDotIndex < 0 || lastDotIndex < atIndex) return null; + String className = line.substring(atIndex + AT.length() + 1, lastDotIndex).trim(); + + String methodName = line.substring(lastDotIndex + 1, lparenthIndex).trim(); + + final int rparenthIndex = line.indexOf(')', lparenthIndex); + if (rparenthIndex < 0) return null; + + return Trinity.create(className, methodName, new TextRange(lparenthIndex, rparenthIndex)); + } + + public Result applyFilter(final String line, final int textEndOffset) { + final Trinity info = parseExceptionLine(line); + if (info == null) { + return null; + } + + String className = info.first; + final int dollarIndex = className.indexOf('$'); + if (dollarIndex >= 0){ + className = className.substring(0, dollarIndex); + } + + final int lparenthIndex = info.third.getStartOffset(); + final int rparenthIndex = info.third.getEndOffset(); + final String fileAndLine = line.substring(lparenthIndex + 1, rparenthIndex).trim(); + + final int colonIndex = fileAndLine.lastIndexOf(':'); + if (colonIndex < 0) return null; + + final String lineString = fileAndLine.substring(colonIndex + 1); + try{ + final int lineNumber = Integer.parseInt(lineString); + final PsiManager manager = PsiManager.getInstance(myProject); + PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(className, mySearchScope); + if (aClass == null) { + aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(className, GlobalSearchScope.allScope(myProject)); + if (aClass == null) return null; + } + final PsiFile file = (PsiFile) aClass.getContainingFile().getNavigationElement(); + if (file == null) return null; + + /* + IDEADEV-4976: Some scramblers put something like SourceFile mock instead of real class name. + final String filePath = fileAndLine.substring(0, colonIndex).replace('/', File.separatorChar); + final int slashIndex = filePath.lastIndexOf(File.separatorChar); + final String shortFileName = slashIndex < 0 ? filePath : filePath.substring(slashIndex + 1); + if (!file.getName().equalsIgnoreCase(shortFileName)) return null; + */ + + final int textStartOffset = textEndOffset - line.length(); + + final int highlightStartOffset = textStartOffset + lparenthIndex + 1; + final int highlightEndOffset = textStartOffset + rparenthIndex; + VirtualFile virtualFile = file.getVirtualFile(); + final OpenFileHyperlinkInfo linkInfo = new OpenFileHyperlinkInfo(myProject, virtualFile, lineNumber - 1); + TextAttributes attributes = HYPERLINK_ATTRIBUTES.clone(); + if (!ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(virtualFile)) { + Color color = UIUtil.getInactiveTextColor(); + attributes.setForegroundColor(color); + attributes.setEffectColor(color); + } + return new Result(highlightStartOffset, highlightEndOffset, linkInfo, attributes); + } + catch(NumberFormatException e){ + return null; + } + } + } \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/ide/impl/NewProjectUtil.java b/java/idea-ui/src/com/intellij/ide/impl/NewProjectUtil.java index c6db9b30c660..46b163c9c780 100644 --- a/java/idea-ui/src/com/intellij/ide/impl/NewProjectUtil.java +++ b/java/idea-ui/src/com/intellij/ide/impl/NewProjectUtil.java @@ -1,204 +1,204 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.ide.impl; - -import com.intellij.ide.GeneralSettings; -import com.intellij.ide.IdeBundle; -import com.intellij.ide.util.newProjectWizard.AddModuleWizard; -import com.intellij.ide.util.projectWizard.ProjectBuilder; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.components.StorageScheme; -import com.intellij.openapi.module.LanguageLevelUtil; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.CompilerProjectExtension; -import com.intellij.openapi.roots.LanguageLevelProjectExtension; -import com.intellij.openapi.roots.ex.ProjectRootManagerEx; -import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator; -import com.intellij.openapi.roots.ui.configuration.ModulesProvider; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.pom.java.LanguageLevel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.File; -import java.io.IOException; - -public class NewProjectUtil { - private NewProjectUtil() { - } - - public static void createNewProject(Project projectToClose, @Nullable final String defaultPath) { - final boolean proceed = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { - public void run() { - ProjectManager.getInstance().getDefaultProject(); //warm up components - } - }, ProjectBundle.message("project.new.wizard.progress.title"), true, null); - if (!proceed) return; - AddModuleWizard dialog = new AddModuleWizard(null, ModulesProvider.EMPTY_MODULES_PROVIDER, defaultPath); - dialog.show(); - if (!dialog.isOK()) { - return; - } - - final ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); - final String projectFilePath = dialog.getNewProjectFilePath(); - final ProjectBuilder projectBuilder = dialog.getProjectBuilder(); - - try { - if (StorageScheme.DIRECTORY_BASED == dialog.getStorageScheme()) { - final File ideaDir = new File(projectFilePath + File.separator + ".idea"); - if (!ideaDir.exists() && !ideaDir.mkdirs()) { - Messages.showErrorDialog("Unable to create '.idea' directory at: " + projectFilePath, "Project initialization failed"); - return; - } - } - - final Project newProject = - projectBuilder == null || !projectBuilder.isUpdate() ? projectManager.newProject(dialog.getProjectName(), projectFilePath, true, false) : projectToClose; - - if (newProject == null) return; - - final Sdk jdk = dialog.getNewProjectJdk(); - if (jdk != null) { - CommandProcessor.getInstance().executeCommand(newProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - applyJdkToProject(newProject, jdk); - } - }); - } - }, null, null); - } - - final String compileOutput = dialog.getNewCompileOutput(); - CommandProcessor.getInstance().executeCommand(newProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - String canonicalPath = compileOutput; - try { - canonicalPath = FileUtil.resolveShortWindowsName(compileOutput); - } - catch (IOException e) { - //file doesn't exist - } - canonicalPath = FileUtil.toSystemIndependentName(canonicalPath); - CompilerProjectExtension.getInstance(newProject).setCompilerOutputUrl(VfsUtil.pathToUrl(canonicalPath)); - } - }); - } - }, null, null); - - newProject.save(); - - - if (projectBuilder != null && !projectBuilder.validate(projectToClose, newProject)) { - return; - } - - if (newProject != projectToClose) { - closePreviousProject(projectToClose); - } - - if (projectBuilder != null) { - projectBuilder.commit(newProject, null, ModulesProvider.EMPTY_MODULES_PROVIDER); - } - - final boolean need2OpenProjectStructure = projectBuilder == null || projectBuilder.isOpenProjectSettingsAfter(); - StartupManager.getInstance(newProject).registerPostStartupActivity(new Runnable() { - public void run() { - // ensure the dialog is shown after all startup activities are done - SwingUtilities.invokeLater(new Runnable() { - public void run() { - if (newProject.isDisposed()) return; - if (need2OpenProjectStructure) { - ModulesConfigurator.showDialog(newProject, null, null, true); - } - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (newProject.isDisposed()) return; - final ToolWindow toolWindow = ToolWindowManager.getInstance(newProject).getToolWindow(ToolWindowId.PROJECT_VIEW); - if (toolWindow != null) { - toolWindow.activate(null); - } - } - }, ModalityState.NON_MODAL); - } - }); - } - }); - - if (newProject != projectToClose) { - ProjectUtil.updateLastProjectLocation(projectFilePath); - - projectManager.openProject(newProject); - } - } - finally { - if (projectBuilder != null) { - projectBuilder.cleanup(); - } - } - } - - public static void applyJdkToProject(@NotNull Project project, @NotNull Sdk jdk) { - String versionString = jdk.getVersionString(); - if (versionString == null) return; - - ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(project); - rootManager.setProjectSdk(jdk); - LanguageLevel level = LanguageLevelUtil.getDefaultLanguageLevel(versionString); - LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project); - if (level.compareTo(ext.getLanguageLevel()) < 0) { - ext.setLanguageLevel(level); - } - } - - public static void closePreviousProject(final Project projectToClose) { - Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); - if (openProjects.length > 0) { - final GeneralSettings settings = GeneralSettings.getInstance(); - int exitCode = settings.getConfirmOpenNewProject(); - if (exitCode == GeneralSettings.OPEN_PROJECT_ASK) { - exitCode = Messages.showDialog(IdeBundle.message("prompt.open.project.in.new.frame"), IdeBundle.message("title.new.project"), - new String[]{IdeBundle.message("button.newframe"), IdeBundle.message("button.existingframe")}, 1, 0, - Messages.getQuestionIcon(), new ProjectNewWindowDoNotAskOption()); - } - if (exitCode == 1) { // "No" option - ProjectUtil.closeAndDispose(projectToClose != null ? projectToClose : openProjects[openProjects.length - 1]); - } - } - } -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.ide.impl; + +import com.intellij.ide.GeneralSettings; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.util.newProjectWizard.AddModuleWizard; +import com.intellij.ide.util.projectWizard.ProjectBuilder; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.components.StorageScheme; +import com.intellij.openapi.module.LanguageLevelUtil; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.CompilerProjectExtension; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator; +import com.intellij.openapi.roots.ui.configuration.ModulesProvider; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; + +public class NewProjectUtil { + private NewProjectUtil() { + } + + public static void createNewProject(Project projectToClose, @Nullable final String defaultPath) { + final boolean proceed = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + ProjectManager.getInstance().getDefaultProject(); //warm up components + } + }, ProjectBundle.message("project.new.wizard.progress.title"), true, null); + if (!proceed) return; + AddModuleWizard dialog = new AddModuleWizard(null, ModulesProvider.EMPTY_MODULES_PROVIDER, defaultPath); + dialog.show(); + if (!dialog.isOK()) { + return; + } + + final ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); + final String projectFilePath = dialog.getNewProjectFilePath(); + final ProjectBuilder projectBuilder = dialog.getProjectBuilder(); + + try { + if (StorageScheme.DIRECTORY_BASED == dialog.getStorageScheme()) { + final File ideaDir = new File(projectFilePath + File.separator + ".idea"); + if (!ideaDir.exists() && !ideaDir.mkdirs()) { + Messages.showErrorDialog("Unable to create '.idea' directory at: " + projectFilePath, "Project initialization failed"); + return; + } + } + + final Project newProject = + projectBuilder == null || !projectBuilder.isUpdate() ? projectManager.newProject(dialog.getProjectName(), projectFilePath, true, false) : projectToClose; + + if (newProject == null) return; + + final Sdk jdk = dialog.getNewProjectJdk(); + if (jdk != null) { + CommandProcessor.getInstance().executeCommand(newProject, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + applyJdkToProject(newProject, jdk); + } + }); + } + }, null, null); + } + + final String compileOutput = dialog.getNewCompileOutput(); + CommandProcessor.getInstance().executeCommand(newProject, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + String canonicalPath = compileOutput; + try { + canonicalPath = FileUtil.resolveShortWindowsName(compileOutput); + } + catch (IOException e) { + //file doesn't exist + } + canonicalPath = FileUtil.toSystemIndependentName(canonicalPath); + CompilerProjectExtension.getInstance(newProject).setCompilerOutputUrl(VfsUtil.pathToUrl(canonicalPath)); + } + }); + } + }, null, null); + + newProject.save(); + + + if (projectBuilder != null && !projectBuilder.validate(projectToClose, newProject)) { + return; + } + + if (newProject != projectToClose) { + closePreviousProject(projectToClose); + } + + if (projectBuilder != null) { + projectBuilder.commit(newProject, null, ModulesProvider.EMPTY_MODULES_PROVIDER); + } + + final boolean need2OpenProjectStructure = projectBuilder == null || projectBuilder.isOpenProjectSettingsAfter(); + StartupManager.getInstance(newProject).registerPostStartupActivity(new Runnable() { + public void run() { + // ensure the dialog is shown after all startup activities are done + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (newProject.isDisposed()) return; + if (need2OpenProjectStructure) { + ModulesConfigurator.showDialog(newProject, null, null, true); + } + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (newProject.isDisposed()) return; + final ToolWindow toolWindow = ToolWindowManager.getInstance(newProject).getToolWindow(ToolWindowId.PROJECT_VIEW); + if (toolWindow != null) { + toolWindow.activate(null); + } + } + }, ModalityState.NON_MODAL); + } + }); + } + }); + + if (newProject != projectToClose) { + ProjectUtil.updateLastProjectLocation(projectFilePath); + + projectManager.openProject(newProject); + } + } + finally { + if (projectBuilder != null) { + projectBuilder.cleanup(); + } + } + } + + public static void applyJdkToProject(@NotNull Project project, @NotNull Sdk jdk) { + String versionString = jdk.getVersionString(); + if (versionString == null) return; + + ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(project); + rootManager.setProjectSdk(jdk); + LanguageLevel level = LanguageLevelUtil.getDefaultLanguageLevel(versionString); + LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project); + if (level.compareTo(ext.getLanguageLevel()) < 0) { + ext.setLanguageLevel(level); + } + } + + public static void closePreviousProject(final Project projectToClose) { + Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + if (openProjects.length > 0) { + final GeneralSettings settings = GeneralSettings.getInstance(); + int exitCode = settings.getConfirmOpenNewProject(); + if (exitCode == GeneralSettings.OPEN_PROJECT_ASK) { + exitCode = Messages.showDialog(IdeBundle.message("prompt.open.project.in.new.frame"), IdeBundle.message("title.new.project"), + new String[]{IdeBundle.message("button.newframe"), IdeBundle.message("button.existingframe")}, 1, 0, + Messages.getQuestionIcon(), new ProjectNewWindowDoNotAskOption()); + } + if (exitCode == 1) { // "No" option + ProjectUtil.closeAndDispose(projectToClose != null ? projectToClose : openProjects[openProjects.length - 1]); + } + } + } +} diff --git a/java/idea-ui/src/com/intellij/ide/util/projectWizard/AbstractStepWithProgress.java b/java/idea-ui/src/com/intellij/ide/util/projectWizard/AbstractStepWithProgress.java index 269e64dd7ab8..dfe7601da4f9 100644 --- a/java/idea-ui/src/com/intellij/ide/util/projectWizard/AbstractStepWithProgress.java +++ b/java/idea-ui/src/com/intellij/ide/util/projectWizard/AbstractStepWithProgress.java @@ -1,192 +1,192 @@ -/* - * 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.ide.util.projectWizard; - -import com.intellij.ide.IdeBundle; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Ref; -import com.intellij.util.concurrency.SwingWorker; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -/** - * @author nik - */ -public abstract class AbstractStepWithProgress extends ModuleWizardStep { - - @NonNls private static final String PROGRESS_PANEL = "progress_panel"; - @NonNls private static final String RESULTS_PANEL = "results_panel"; - private JPanel myPanel; - - private JLabel myTitleLabel; - private JLabel myProgressLabel; - private JLabel myProgressLabel2; - private ProgressIndicator myProgressIndicator = null; - private final String myPromptStopSearch; - - public AbstractStepWithProgress(final String promptStopSearching) { - myPromptStopSearch = promptStopSearching; - } - - public final JComponent getComponent() { - if (myPanel == null) { - myPanel = new JPanel(new CardLayout()); - myPanel.setBorder(BorderFactory.createEtchedBorder()); - - myPanel.add(createProgressPanel(), PROGRESS_PANEL); - myPanel.add(createResultsPanel(), RESULTS_PANEL); - } - return myPanel; - } - - protected abstract JComponent createResultsPanel(); - - protected abstract String getProgressText(); - - protected abstract boolean shouldRunProgress(); - - protected abstract Result calculate(); - - protected abstract void onFinished(Result result, boolean canceled); - - private JPanel createProgressPanel() { - final JPanel progressPanel = new JPanel(new GridBagLayout()); - myTitleLabel = new JLabel(); - myTitleLabel.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD)); - progressPanel.add(myTitleLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 5, 10), 0, 0)); - - myProgressLabel = new JLabel(); - progressPanel.add(myProgressLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 0, 10), 0, 0)); - - myProgressLabel2 = new JLabel(); - progressPanel.add(myProgressLabel2, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 0, 10), 0, 0)); - - JButton stopButton = new JButton(IdeBundle.message("button.stop.searching")); - stopButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - cancelSearch(); - } - }); - progressPanel.add(stopButton, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 2, 0.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(10, 0, 0, 10), 0, 0)); - return progressPanel; - } - - private void cancelSearch() { - if (myProgressIndicator != null) { - myProgressIndicator.cancel(); - } - } - - private synchronized boolean isProgressRunning() { - return myProgressIndicator != null && myProgressIndicator.isRunning(); - } - - - public void updateStep() { - if (shouldRunProgress()) { - runProgress(); - } - else { - showCard(RESULTS_PANEL); - } - } - - protected void runProgress() { - final MyProgressIndicator progress = new MyProgressIndicator(); - progress.setModalityProgress(null); - final String title = getProgressText(); - if (title != null) { - myTitleLabel.setText(title); - } - showCard(PROGRESS_PANEL); - myProgressIndicator = progress; - new SwingWorker() { - public Object construct() { - final Ref result = Ref.create(null); - ProgressManager.getInstance().runProcess(new Runnable() { - public void run() { - result.set(calculate()); - } - }, progress); - return result.get(); - } - - public void finished() { - myProgressIndicator = null; - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - final Result result = (Result)get(); - onFinished(result, progress.isCanceled()); - showCard(RESULTS_PANEL); - } - }); - } - }.start(); - } - - private void showCard(final String id) { - ((CardLayout)myPanel.getLayout()).show(myPanel, id); - myPanel.revalidate(); - } - - public boolean validate() { - if (isProgressRunning()) { - final int answer = Messages.showDialog(getComponent(), myPromptStopSearch, - IdeBundle.message("title.question"), new String[] {IdeBundle.message("action.continue.searching"), IdeBundle.message("action.stop.searching")}, 0, Messages.getWarningIcon()); - if (answer == 1) { // terminate - cancelSearch(); - } - return false; - } - return true; - } - - public void onStepLeaving() { - if (isProgressRunning()) { - cancelSearch(); - } - } - - protected class MyProgressIndicator extends ProgressIndicatorBase { - public void setText(String text) { - updateLabel(myProgressLabel, text); - super.setText(text); - } - - public void setText2(String text) { - updateLabel(myProgressLabel2, text); - super.setText2(text); - } - - private void updateLabel(final JLabel label, final String text) { - UIUtil.invokeLaterIfNeeded(new Runnable() { - public void run() { - label.setText(text); - } - }); - } - } -} +/* + * 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.ide.util.projectWizard; + +import com.intellij.ide.IdeBundle; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.util.concurrency.SwingWorker; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * @author nik + */ +public abstract class AbstractStepWithProgress extends ModuleWizardStep { + + @NonNls private static final String PROGRESS_PANEL = "progress_panel"; + @NonNls private static final String RESULTS_PANEL = "results_panel"; + private JPanel myPanel; + + private JLabel myTitleLabel; + private JLabel myProgressLabel; + private JLabel myProgressLabel2; + private ProgressIndicator myProgressIndicator = null; + private final String myPromptStopSearch; + + public AbstractStepWithProgress(final String promptStopSearching) { + myPromptStopSearch = promptStopSearching; + } + + public final JComponent getComponent() { + if (myPanel == null) { + myPanel = new JPanel(new CardLayout()); + myPanel.setBorder(BorderFactory.createEtchedBorder()); + + myPanel.add(createProgressPanel(), PROGRESS_PANEL); + myPanel.add(createResultsPanel(), RESULTS_PANEL); + } + return myPanel; + } + + protected abstract JComponent createResultsPanel(); + + protected abstract String getProgressText(); + + protected abstract boolean shouldRunProgress(); + + protected abstract Result calculate(); + + protected abstract void onFinished(Result result, boolean canceled); + + private JPanel createProgressPanel() { + final JPanel progressPanel = new JPanel(new GridBagLayout()); + myTitleLabel = new JLabel(); + myTitleLabel.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD)); + progressPanel.add(myTitleLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 5, 10), 0, 0)); + + myProgressLabel = new JLabel(); + progressPanel.add(myProgressLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 0, 10), 0, 0)); + + myProgressLabel2 = new JLabel(); + progressPanel.add(myProgressLabel2, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 0, 10), 0, 0)); + + JButton stopButton = new JButton(IdeBundle.message("button.stop.searching")); + stopButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + cancelSearch(); + } + }); + progressPanel.add(stopButton, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 2, 0.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(10, 0, 0, 10), 0, 0)); + return progressPanel; + } + + private void cancelSearch() { + if (myProgressIndicator != null) { + myProgressIndicator.cancel(); + } + } + + private synchronized boolean isProgressRunning() { + return myProgressIndicator != null && myProgressIndicator.isRunning(); + } + + + public void updateStep() { + if (shouldRunProgress()) { + runProgress(); + } + else { + showCard(RESULTS_PANEL); + } + } + + protected void runProgress() { + final MyProgressIndicator progress = new MyProgressIndicator(); + progress.setModalityProgress(null); + final String title = getProgressText(); + if (title != null) { + myTitleLabel.setText(title); + } + showCard(PROGRESS_PANEL); + myProgressIndicator = progress; + new SwingWorker() { + public Object construct() { + final Ref result = Ref.create(null); + ProgressManager.getInstance().runProcess(new Runnable() { + public void run() { + result.set(calculate()); + } + }, progress); + return result.get(); + } + + public void finished() { + myProgressIndicator = null; + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final Result result = (Result)get(); + onFinished(result, progress.isCanceled()); + showCard(RESULTS_PANEL); + } + }); + } + }.start(); + } + + private void showCard(final String id) { + ((CardLayout)myPanel.getLayout()).show(myPanel, id); + myPanel.revalidate(); + } + + public boolean validate() { + if (isProgressRunning()) { + final int answer = Messages.showDialog(getComponent(), myPromptStopSearch, + IdeBundle.message("title.question"), new String[] {IdeBundle.message("action.continue.searching"), IdeBundle.message("action.stop.searching")}, 0, Messages.getWarningIcon()); + if (answer == 1) { // terminate + cancelSearch(); + } + return false; + } + return true; + } + + public void onStepLeaving() { + if (isProgressRunning()) { + cancelSearch(); + } + } + + protected class MyProgressIndicator extends ProgressIndicatorBase { + public void setText(String text) { + updateLabel(myProgressLabel, text); + super.setText(text); + } + + public void setText2(String text) { + updateLabel(myProgressLabel2, text); + super.setText2(text); + } + + private void updateLabel(final JLabel label, final String text) { + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + label.setText(text); + } + }); + } + } +} diff --git a/java/idea-ui/src/com/intellij/ide/util/projectWizard/JdkChooserPanel.java b/java/idea-ui/src/com/intellij/ide/util/projectWizard/JdkChooserPanel.java index b9de0745402f..5ec15435cb92 100644 --- a/java/idea-ui/src/com/intellij/ide/util/projectWizard/JdkChooserPanel.java +++ b/java/idea-ui/src/com/intellij/ide/util/projectWizard/JdkChooserPanel.java @@ -1,291 +1,291 @@ -/* - * 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.ide.util.projectWizard; - -import com.intellij.ide.IdeBundle; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.projectRoots.ProjectJdkTable; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.SdkType; -import com.intellij.openapi.projectRoots.ui.ProjectJdksEditor; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.components.JBList; -import com.intellij.util.ArrayUtil; -import gnu.trove.TIntArrayList; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.*; - -public class JdkChooserPanel extends JPanel { - private JList myList = null; - private DefaultListModel myListModel = null; - private Sdk myCurrentJdk; - private final Project myProject; - private SdkType[] myAllowedJdkTypes = null; - - public JdkChooserPanel(Project project) { - super(new BorderLayout()); - myProject = project; - myListModel = new DefaultListModel(); - myList = new JBList(myListModel); - myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - myList.setCellRenderer(new ProjectJdkListRenderer(myList.getCellRenderer())); - //noinspection HardCodedStringLiteral - myList.setPrototypeCellValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); - - myList.addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - myCurrentJdk = (Sdk)myList.getSelectedValue(); - } - }); - myList.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2 && myProject == null) { - editJdkTable(); - } - } - }); - - JPanel panel = new JPanel(new BorderLayout()); - panel.add(ScrollPaneFactory.createScrollPane(myList), BorderLayout.CENTER); - add(panel, BorderLayout.CENTER); - if (myListModel.getSize() > 0) { - myList.setSelectedIndex(0); - } - } - - /** - * Sets the JDK types which may be shown in the panel. - * - * @param allowedJdkTypes the array of JDK types which may be shown, or null if all JDK types are allowed. - * @since 7.0.3 - */ - public void setAllowedJdkTypes(@Nullable final SdkType[] allowedJdkTypes) { - myAllowedJdkTypes = allowedJdkTypes; - } - - public Sdk getChosenJdk() { - return myCurrentJdk; - } - - public void editJdkTable() { - ProjectJdksEditor editor = new ProjectJdksEditor((Sdk)myList.getSelectedValue(), - myProject != null ? myProject : ProjectManager.getInstance().getDefaultProject(), - myList); - editor.show(); - if (editor.isOK()) { - Sdk selectedJdk = editor.getSelectedJdk(); - updateList(selectedJdk, null); - } - } - - public void updateList(final Sdk selectedJdk, final SdkType type) { - final int[] selectedIndices = myList.getSelectedIndices(); - fillList(type); - // restore selection - if (selectedJdk != null) { - TIntArrayList list = new TIntArrayList(); - for (int i = 0; i < myListModel.size(); i++) { - final Sdk jdk = (Sdk)myListModel.getElementAt(i); - if (Comparing.strEqual(jdk.getName(), selectedJdk.getName())){ - list.add(i); - } - } - final int[] indicesToSelect = list.toNativeArray(); - if (indicesToSelect.length > 0) { - myList.setSelectedIndices(indicesToSelect); - } - else if (myList.getModel().getSize() > 0) { - myList.setSelectedIndex(0); - } - } - else { - if (selectedIndices.length > 0) { - myList.setSelectedIndices(selectedIndices); - } - else { - myList.setSelectedIndex(0); - } - } - - myCurrentJdk = (Sdk)myList.getSelectedValue(); - } - - public JList getPreferredFocusedComponent() { - return myList; - } - - public void fillList(final SdkType type) { - myListModel.clear(); - final Sdk[] jdks; - if (myProject == null) { - final Sdk[] allJdks = ProjectJdkTable.getInstance().getAllJdks(); - jdks = getCompatibleJdks(type, Arrays.asList(allJdks)); - } - else { - final ProjectSdksModel projectJdksModel = ProjectStructureConfigurable.getInstance(myProject).getProjectJdksModel(); - if (!projectJdksModel.isInitialized()){ //should be initialized - projectJdksModel.reset(myProject); - } - final Collection collection = projectJdksModel.getProjectSdks().values(); - jdks = getCompatibleJdks(type, collection); - } - Arrays.sort(jdks, new Comparator() { - public int compare(final Sdk o1, final Sdk o2) { - return o1.getName().compareToIgnoreCase(o2.getName()); - } - }); - for (Sdk jdk : jdks) { - myListModel.addElement(jdk); - } - } - - private Sdk[] getCompatibleJdks(final SdkType type, final Collection collection) { - final Set compatibleJdks = new HashSet(); - for (Sdk projectJdk : collection) { - if (isCompatibleJdk(projectJdk, type)) { - compatibleJdks.add(projectJdk); - } - } - return compatibleJdks.toArray(new Sdk[compatibleJdks.size()]); - } - - private boolean isCompatibleJdk(final Sdk projectJdk, final SdkType type) { - if (type != null) { - return projectJdk.getSdkType() == type; - } - if (myAllowedJdkTypes != null) { - return ArrayUtil.indexOf(myAllowedJdkTypes, projectJdk.getSdkType()) >= 0; - } - return true; - } - - public JComponent getDefaultFocusedComponent() { - return myList; - } - - public void selectJdk(Sdk defaultJdk) { - final int index = myListModel.indexOf(defaultJdk); - if (index >= 0) { - myList.setSelectedIndex(index); - } - } - - private static Sdk showDialog(final Project project, String title, final Component parent, Sdk jdkToSelect) { - final JdkChooserPanel jdkChooserPanel = new JdkChooserPanel(project); - jdkChooserPanel.fillList(null); - final MyDialog dialog = jdkChooserPanel.new MyDialog(parent); - if (title != null) { - dialog.setTitle(title); - } - if (jdkToSelect != null) { - jdkChooserPanel.selectJdk(jdkToSelect); - } - jdkChooserPanel.myList.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { - dialog.clickDefaultButton(); - } - } - }); - dialog.show(); - return dialog.isOK() ? jdkChooserPanel.getChosenJdk() : null; - } - - public static Sdk chooseAndSetJDK(final Project project) { - final Sdk projectJdk = ProjectRootManager.getInstance(project).getProjectSdk(); - final Sdk jdk = showDialog(project, ProjectBundle.message("module.libraries.target.jdk.select.title"), WindowManagerEx.getInstanceEx().getFrame(project), projectJdk); - if (jdk == null) { - return null; - } - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - ProjectRootManager.getInstance(project).setProjectSdk(jdk); - } - }); - return jdk; - } - - public class MyDialog extends DialogWrapper implements ListSelectionListener { - - public MyDialog(Component parent) { - super(parent, true); - setTitle(IdeBundle.message("title.select.jdk")); - init(); - myList.addListSelectionListener(this); - updateOkButton(); - } - - protected String getDimensionServiceKey() { - return "#com.intellij.ide.util.projectWizard.JdkChooserPanel.MyDialog"; - } - - public void valueChanged(ListSelectionEvent e) { - updateOkButton(); - } - - private void updateOkButton() { - setOKActionEnabled(myList.getSelectedValue() != null); - } - - public void dispose() { - myList.removeListSelectionListener(this); - super.dispose(); - } - - protected JComponent createCenterPanel() { - return JdkChooserPanel.this; - } - - protected Action[] createActions() { - return new Action[]{new ConfigureAction(), getOKAction(), getCancelAction()}; - } - - public JComponent getPreferredFocusedComponent() { - return myList; - } - - private final class ConfigureAction extends AbstractAction { - public ConfigureAction() { - super(IdeBundle.message("button.configure.e")); - putValue(Action.MNEMONIC_KEY, new Integer('E')); - } - - public void actionPerformed(ActionEvent e) { - editJdkTable(); - } - } - } - - -} +/* + * 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.ide.util.projectWizard; + +import com.intellij.ide.IdeBundle; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkType; +import com.intellij.openapi.projectRoots.ui.ProjectJdksEditor; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.components.JBList; +import com.intellij.util.ArrayUtil; +import gnu.trove.TIntArrayList; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; + +public class JdkChooserPanel extends JPanel { + private JList myList = null; + private DefaultListModel myListModel = null; + private Sdk myCurrentJdk; + private final Project myProject; + private SdkType[] myAllowedJdkTypes = null; + + public JdkChooserPanel(Project project) { + super(new BorderLayout()); + myProject = project; + myListModel = new DefaultListModel(); + myList = new JBList(myListModel); + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myList.setCellRenderer(new ProjectJdkListRenderer(myList.getCellRenderer())); + //noinspection HardCodedStringLiteral + myList.setPrototypeCellValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + + myList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + myCurrentJdk = (Sdk)myList.getSelectedValue(); + } + }); + myList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && myProject == null) { + editJdkTable(); + } + } + }); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(ScrollPaneFactory.createScrollPane(myList), BorderLayout.CENTER); + add(panel, BorderLayout.CENTER); + if (myListModel.getSize() > 0) { + myList.setSelectedIndex(0); + } + } + + /** + * Sets the JDK types which may be shown in the panel. + * + * @param allowedJdkTypes the array of JDK types which may be shown, or null if all JDK types are allowed. + * @since 7.0.3 + */ + public void setAllowedJdkTypes(@Nullable final SdkType[] allowedJdkTypes) { + myAllowedJdkTypes = allowedJdkTypes; + } + + public Sdk getChosenJdk() { + return myCurrentJdk; + } + + public void editJdkTable() { + ProjectJdksEditor editor = new ProjectJdksEditor((Sdk)myList.getSelectedValue(), + myProject != null ? myProject : ProjectManager.getInstance().getDefaultProject(), + myList); + editor.show(); + if (editor.isOK()) { + Sdk selectedJdk = editor.getSelectedJdk(); + updateList(selectedJdk, null); + } + } + + public void updateList(final Sdk selectedJdk, final SdkType type) { + final int[] selectedIndices = myList.getSelectedIndices(); + fillList(type); + // restore selection + if (selectedJdk != null) { + TIntArrayList list = new TIntArrayList(); + for (int i = 0; i < myListModel.size(); i++) { + final Sdk jdk = (Sdk)myListModel.getElementAt(i); + if (Comparing.strEqual(jdk.getName(), selectedJdk.getName())){ + list.add(i); + } + } + final int[] indicesToSelect = list.toNativeArray(); + if (indicesToSelect.length > 0) { + myList.setSelectedIndices(indicesToSelect); + } + else if (myList.getModel().getSize() > 0) { + myList.setSelectedIndex(0); + } + } + else { + if (selectedIndices.length > 0) { + myList.setSelectedIndices(selectedIndices); + } + else { + myList.setSelectedIndex(0); + } + } + + myCurrentJdk = (Sdk)myList.getSelectedValue(); + } + + public JList getPreferredFocusedComponent() { + return myList; + } + + public void fillList(final SdkType type) { + myListModel.clear(); + final Sdk[] jdks; + if (myProject == null) { + final Sdk[] allJdks = ProjectJdkTable.getInstance().getAllJdks(); + jdks = getCompatibleJdks(type, Arrays.asList(allJdks)); + } + else { + final ProjectSdksModel projectJdksModel = ProjectStructureConfigurable.getInstance(myProject).getProjectJdksModel(); + if (!projectJdksModel.isInitialized()){ //should be initialized + projectJdksModel.reset(myProject); + } + final Collection collection = projectJdksModel.getProjectSdks().values(); + jdks = getCompatibleJdks(type, collection); + } + Arrays.sort(jdks, new Comparator() { + public int compare(final Sdk o1, final Sdk o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }); + for (Sdk jdk : jdks) { + myListModel.addElement(jdk); + } + } + + private Sdk[] getCompatibleJdks(final SdkType type, final Collection collection) { + final Set compatibleJdks = new HashSet(); + for (Sdk projectJdk : collection) { + if (isCompatibleJdk(projectJdk, type)) { + compatibleJdks.add(projectJdk); + } + } + return compatibleJdks.toArray(new Sdk[compatibleJdks.size()]); + } + + private boolean isCompatibleJdk(final Sdk projectJdk, final SdkType type) { + if (type != null) { + return projectJdk.getSdkType() == type; + } + if (myAllowedJdkTypes != null) { + return ArrayUtil.indexOf(myAllowedJdkTypes, projectJdk.getSdkType()) >= 0; + } + return true; + } + + public JComponent getDefaultFocusedComponent() { + return myList; + } + + public void selectJdk(Sdk defaultJdk) { + final int index = myListModel.indexOf(defaultJdk); + if (index >= 0) { + myList.setSelectedIndex(index); + } + } + + private static Sdk showDialog(final Project project, String title, final Component parent, Sdk jdkToSelect) { + final JdkChooserPanel jdkChooserPanel = new JdkChooserPanel(project); + jdkChooserPanel.fillList(null); + final MyDialog dialog = jdkChooserPanel.new MyDialog(parent); + if (title != null) { + dialog.setTitle(title); + } + if (jdkToSelect != null) { + jdkChooserPanel.selectJdk(jdkToSelect); + } + jdkChooserPanel.myList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { + dialog.clickDefaultButton(); + } + } + }); + dialog.show(); + return dialog.isOK() ? jdkChooserPanel.getChosenJdk() : null; + } + + public static Sdk chooseAndSetJDK(final Project project) { + final Sdk projectJdk = ProjectRootManager.getInstance(project).getProjectSdk(); + final Sdk jdk = showDialog(project, ProjectBundle.message("module.libraries.target.jdk.select.title"), WindowManagerEx.getInstanceEx().getFrame(project), projectJdk); + if (jdk == null) { + return null; + } + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ProjectRootManager.getInstance(project).setProjectSdk(jdk); + } + }); + return jdk; + } + + public class MyDialog extends DialogWrapper implements ListSelectionListener { + + public MyDialog(Component parent) { + super(parent, true); + setTitle(IdeBundle.message("title.select.jdk")); + init(); + myList.addListSelectionListener(this); + updateOkButton(); + } + + protected String getDimensionServiceKey() { + return "#com.intellij.ide.util.projectWizard.JdkChooserPanel.MyDialog"; + } + + public void valueChanged(ListSelectionEvent e) { + updateOkButton(); + } + + private void updateOkButton() { + setOKActionEnabled(myList.getSelectedValue() != null); + } + + public void dispose() { + myList.removeListSelectionListener(this); + super.dispose(); + } + + protected JComponent createCenterPanel() { + return JdkChooserPanel.this; + } + + protected Action[] createActions() { + return new Action[]{new ConfigureAction(), getOKAction(), getCancelAction()}; + } + + public JComponent getPreferredFocusedComponent() { + return myList; + } + + private final class ConfigureAction extends AbstractAction { + public ConfigureAction() { + super(IdeBundle.message("button.configure.e")); + putValue(Action.MNEMONIC_KEY, new Integer('E')); + } + + public void actionPerformed(ActionEvent e) { + editJdkTable(); + } + } + } + + +} diff --git a/java/idea-ui/src/com/intellij/ide/util/projectWizard/NameLocationStep.java b/java/idea-ui/src/com/intellij/ide/util/projectWizard/NameLocationStep.java index 193fb980ff19..3fd71a17784c 100644 --- a/java/idea-ui/src/com/intellij/ide/util/projectWizard/NameLocationStep.java +++ b/java/idea-ui/src/com/intellij/ide/util/projectWizard/NameLocationStep.java @@ -1,290 +1,290 @@ -/* - * 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.ide.util.projectWizard; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.highlighter.ModuleFileType; -import com.intellij.ide.util.BrowseFilesListener; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.roots.ModuleRootModel; -import com.intellij.openapi.roots.ui.configuration.ModulesProvider; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.MultiLineLabelUI; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.FieldPanel; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.io.File; - -/** - * @author Eugene Zhuravlev - * Date: Dec 29, 2003 - */ -public class NameLocationStep extends ModuleWizardStep { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.projectWizard.NameLocationStep"); - private final JPanel myPanel; - private final NamePathComponent myNamePathComponent; - private final WizardContext myWizardContext; - private final JavaModuleBuilder myBuilder; - private final ModulesProvider myModulesProvider; - private final Icon myIcon; - private final String myHelpId; - private boolean myModuleFileDirectoryChangedByUser = false; - private final JTextField myTfModuleFilePath; - private boolean myFirstTimeInitializationDone = false; - - public NameLocationStep(WizardContext wizardContext, JavaModuleBuilder builder, - ModulesProvider modulesProvider, - Icon icon, - @NonNls String helpId) { - myWizardContext = wizardContext; - myBuilder = builder; - myModulesProvider = modulesProvider; - myIcon = icon; - myHelpId = helpId; - myPanel = new JPanel(new GridBagLayout()); - myPanel.setBorder(BorderFactory.createEtchedBorder()); - - final String text = IdeBundle.message("prompt.please.specify.module.name.and.content.root"); - final JLabel textLabel = new JLabel(text); - textLabel.setUI(new MultiLineLabelUI()); - myPanel.add(textLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(8, 10, 8, 10), 0, 0)); - - myNamePathComponent = new NamePathComponent(IdeBundle.message("label.module.name"), IdeBundle.message("label.module.content.root"), 'M', 'r', - IdeBundle.message("title.select.module.content.root"), ""); - //if (ModuleType.J2EE_APPLICATION.equals(moduleType)) { - // myNamePathComponent.setPathComponentVisible(false); - //} - - myPanel.add(myNamePathComponent, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 0, 10), 0, 0)); - - final JLabel label = new JLabel(IdeBundle.message("label.module.file.will.be.saved.in")); - myPanel.add(label, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 1.0, GridBagConstraints.SOUTHWEST, GridBagConstraints.HORIZONTAL, new Insets(30, 10, 0, 10), 0, 0)); - - myTfModuleFilePath = new JTextField(); - myTfModuleFilePath.setEditable(false); - final Insets borderInsets = myTfModuleFilePath.getBorder().getBorderInsets(myTfModuleFilePath); - myTfModuleFilePath.setBorder(BorderFactory.createEmptyBorder(borderInsets.top, borderInsets.left, borderInsets.bottom, borderInsets.right)); - final FieldPanel fieldPanel = createFieldPanel(myTfModuleFilePath, null, null); - myPanel.add(fieldPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 10, 10, 0), 0, 0)); - - JButton browseButton = new JButton(IdeBundle.message("button.change.directory")); - browseButton.addActionListener(new BrowseModuleFileDirectoryListener(myTfModuleFilePath)); - myPanel.add(browseButton, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 10, 10), 0, 0)); - - final DocumentListener documentListener = new DocumentAdapter() { - protected void textChanged(DocumentEvent e) { - updateModuleFilePathField(); - } - }; - myNamePathComponent.getNameComponent().getDocument().addDocumentListener(documentListener); - myNamePathComponent.getPathComponent().getDocument().addDocumentListener(documentListener); - myNamePathComponent.getPathComponent().getDocument().addDocumentListener(new DocumentAdapter() { - public void textChanged(DocumentEvent event) { - myWizardContext.requestWizardButtonsUpdate(); - } - }); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private String suggestModuleName(ModuleType moduleType) { - return "untitled"; - } - - public void updateStep() { - super.updateStep(); - - // initial UI settings - if (!myFirstTimeInitializationDone) { - if (myWizardContext.isCreatingNewProject()) { - setSyncEnabled(false); - } - else { // project already exists - final VirtualFile baseDir = myWizardContext.getProject().getBaseDir(); - final String projectFileDirectory = baseDir != null ? VfsUtil.virtualToIoFile(baseDir).getPath() : null; - if (projectFileDirectory != null) { - final String name = ProjectWizardUtil.findNonExistingFileName(projectFileDirectory, suggestModuleName(myBuilder.getModuleType()), ""); - setModuleName(name); - setContentEntryPath(projectFileDirectory + File.separatorChar + name); - } - } - } - - if (myWizardContext.isCreatingNewProject()) { // creating new project - // in this mode we depend on the settings of the "project name" step - if (!isPathChangedByUser()) { - setContentEntryPath(myWizardContext.getProjectFileDirectory().replace('/', File.separatorChar)); - } - if (!isNameChangedByUser()) { - setModuleName(myWizardContext.getProjectName()); - } - } - - if (!myFirstTimeInitializationDone) { - myNamePathComponent.getNameComponent().selectAll(); - } - myFirstTimeInitializationDone = true; - } - - public JComponent getComponent() { - return myPanel; - } - - public String getModuleName() { - return myNamePathComponent.getNameValue(); - } - - public void setModuleName(String name) { - myNamePathComponent.setNameValue(name); - } - - public String getContentEntryPath() { - final String path = myNamePathComponent.getPath(); - return path.length() == 0? null : path; - } - - public void setContentEntryPath(String path) { - myNamePathComponent.setPath(path); - } - - public String getModuleFilePath() { - return getModuleFileDirectory() + "/" + myNamePathComponent.getNameValue() + ModuleFileType.DOT_DEFAULT_EXTENSION; - } - - public boolean isSyncEnabled() { - return myNamePathComponent.isSyncEnabled(); - } - - public void setSyncEnabled(boolean isSyncEnabled) { - myNamePathComponent.setSyncEnabled(isSyncEnabled); - } - - public String getModuleFileDirectory() { - return myTfModuleFilePath.getText().trim().replace(File.separatorChar, '/'); - } - - public boolean validate() { - final String moduleName = getModuleName(); - if (moduleName.length() == 0) { - Messages.showErrorDialog(myNamePathComponent.getNameComponent(), IdeBundle.message("prompt.please.specify.module.name"), - IdeBundle.message("title.module.name.not.specified")); - return false; - } - if (isAlreadyExists(moduleName)) { - Messages.showErrorDialog(IdeBundle.message("error.module.with.name.already.exists", moduleName), IdeBundle.message("title.module.already.exists")); - return false; - } - final String moduleLocation = getModuleFileDirectory(); - if (moduleLocation.length() == 0) { - Messages.showErrorDialog(myNamePathComponent.getPathComponent(), IdeBundle.message("error.please.specify.module.file.location"), - IdeBundle.message("title.module.file.location.not.specified")); - return false; - } - - final String contentEntryPath = getContentEntryPath(); - if (contentEntryPath != null) { - // the check makes sence only for non-null module root - Module[] modules = myModulesProvider.getModules(); - for (final Module module : modules) { - ModuleRootModel rootModel = myModulesProvider.getRootModel(module); - LOG.assertTrue(rootModel != null); - final VirtualFile[] moduleContentRoots = rootModel.getContentRoots(); - final String moduleContentRoot = contentEntryPath.replace(File.separatorChar, '/'); - for (final VirtualFile root : moduleContentRoots) { - if (moduleContentRoot.equals(root.getPath())) { - Messages.showErrorDialog(myNamePathComponent.getPathComponent(), - IdeBundle.message("error.content.root.already.defined.for.module", contentEntryPath, module.getName()), - IdeBundle.message("title.module.content.root.already.exists")); - return false; - } - } - } - if (!ProjectWizardUtil.createDirectoryIfNotExists(IdeBundle.message("directory.module.content.root"), contentEntryPath, myNamePathComponent.isPathChangedByUser())) { - return false; - } - } - final String moduleFileDirectory = getModuleFileDirectory(); - return ProjectWizardUtil.createDirectoryIfNotExists(IdeBundle.message("directory.module.file"), moduleFileDirectory, myModuleFileDirectoryChangedByUser); - } - - public void updateDataModel() { - myBuilder.setName(getModuleName()); - myBuilder.setModuleFilePath(getModuleFilePath()); - myBuilder.setContentEntryPath(getContentEntryPath()); - } - - public JComponent getPreferredFocusedComponent() { - return myNamePathComponent.getNameComponent(); - } - - private boolean isAlreadyExists(String moduleName) { - final Module[] modules = myModulesProvider.getModules(); - for (Module module : modules) { - if (moduleName.equals(module.getName())) { - return true; - } - } - return false; - } - - private void updateModuleFilePathField() { - if (!myModuleFileDirectoryChangedByUser) { - final String dir = myNamePathComponent.getPath().replace('/', File.separatorChar); - myTfModuleFilePath.setText(dir); - } - } - - public boolean isNameChangedByUser() { - return myNamePathComponent.isNameChangedByUser(); - } - - public boolean isPathChangedByUser() { - return myNamePathComponent.isPathChangedByUser(); - } - - private class BrowseModuleFileDirectoryListener extends BrowseFilesListener { - private BrowseModuleFileDirectoryListener(final JTextField textField) { - super(textField, IdeBundle.message("title.select.module.file.location"), - IdeBundle.message("description.select.module.file.location"), SINGLE_DIRECTORY_DESCRIPTOR); - } - - public void actionPerformed(ActionEvent e) { - final String pathBefore = myTfModuleFilePath.getText().trim(); - super.actionPerformed(e); - final String path = myTfModuleFilePath.getText().trim(); - if (!path.equals(pathBefore)) { - myModuleFileDirectoryChangedByUser = true; - } - } - } - - public Icon getIcon() { - return myIcon; - } - - public String getHelpId() { - return myHelpId; - } -} +/* + * 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.ide.util.projectWizard; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.highlighter.ModuleFileType; +import com.intellij.ide.util.BrowseFilesListener; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.roots.ModuleRootModel; +import com.intellij.openapi.roots.ui.configuration.ModulesProvider; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.MultiLineLabelUI; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.FieldPanel; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.File; + +/** + * @author Eugene Zhuravlev + * Date: Dec 29, 2003 + */ +public class NameLocationStep extends ModuleWizardStep { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.projectWizard.NameLocationStep"); + private final JPanel myPanel; + private final NamePathComponent myNamePathComponent; + private final WizardContext myWizardContext; + private final JavaModuleBuilder myBuilder; + private final ModulesProvider myModulesProvider; + private final Icon myIcon; + private final String myHelpId; + private boolean myModuleFileDirectoryChangedByUser = false; + private final JTextField myTfModuleFilePath; + private boolean myFirstTimeInitializationDone = false; + + public NameLocationStep(WizardContext wizardContext, JavaModuleBuilder builder, + ModulesProvider modulesProvider, + Icon icon, + @NonNls String helpId) { + myWizardContext = wizardContext; + myBuilder = builder; + myModulesProvider = modulesProvider; + myIcon = icon; + myHelpId = helpId; + myPanel = new JPanel(new GridBagLayout()); + myPanel.setBorder(BorderFactory.createEtchedBorder()); + + final String text = IdeBundle.message("prompt.please.specify.module.name.and.content.root"); + final JLabel textLabel = new JLabel(text); + textLabel.setUI(new MultiLineLabelUI()); + myPanel.add(textLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(8, 10, 8, 10), 0, 0)); + + myNamePathComponent = new NamePathComponent(IdeBundle.message("label.module.name"), IdeBundle.message("label.module.content.root"), 'M', 'r', + IdeBundle.message("title.select.module.content.root"), ""); + //if (ModuleType.J2EE_APPLICATION.equals(moduleType)) { + // myNamePathComponent.setPathComponentVisible(false); + //} + + myPanel.add(myNamePathComponent, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(8, 10, 0, 10), 0, 0)); + + final JLabel label = new JLabel(IdeBundle.message("label.module.file.will.be.saved.in")); + myPanel.add(label, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 1.0, GridBagConstraints.SOUTHWEST, GridBagConstraints.HORIZONTAL, new Insets(30, 10, 0, 10), 0, 0)); + + myTfModuleFilePath = new JTextField(); + myTfModuleFilePath.setEditable(false); + final Insets borderInsets = myTfModuleFilePath.getBorder().getBorderInsets(myTfModuleFilePath); + myTfModuleFilePath.setBorder(BorderFactory.createEmptyBorder(borderInsets.top, borderInsets.left, borderInsets.bottom, borderInsets.right)); + final FieldPanel fieldPanel = createFieldPanel(myTfModuleFilePath, null, null); + myPanel.add(fieldPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 10, 10, 0), 0, 0)); + + JButton browseButton = new JButton(IdeBundle.message("button.change.directory")); + browseButton.addActionListener(new BrowseModuleFileDirectoryListener(myTfModuleFilePath)); + myPanel.add(browseButton, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 10, 10), 0, 0)); + + final DocumentListener documentListener = new DocumentAdapter() { + protected void textChanged(DocumentEvent e) { + updateModuleFilePathField(); + } + }; + myNamePathComponent.getNameComponent().getDocument().addDocumentListener(documentListener); + myNamePathComponent.getPathComponent().getDocument().addDocumentListener(documentListener); + myNamePathComponent.getPathComponent().getDocument().addDocumentListener(new DocumentAdapter() { + public void textChanged(DocumentEvent event) { + myWizardContext.requestWizardButtonsUpdate(); + } + }); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private String suggestModuleName(ModuleType moduleType) { + return "untitled"; + } + + public void updateStep() { + super.updateStep(); + + // initial UI settings + if (!myFirstTimeInitializationDone) { + if (myWizardContext.isCreatingNewProject()) { + setSyncEnabled(false); + } + else { // project already exists + final VirtualFile baseDir = myWizardContext.getProject().getBaseDir(); + final String projectFileDirectory = baseDir != null ? VfsUtil.virtualToIoFile(baseDir).getPath() : null; + if (projectFileDirectory != null) { + final String name = ProjectWizardUtil.findNonExistingFileName(projectFileDirectory, suggestModuleName(myBuilder.getModuleType()), ""); + setModuleName(name); + setContentEntryPath(projectFileDirectory + File.separatorChar + name); + } + } + } + + if (myWizardContext.isCreatingNewProject()) { // creating new project + // in this mode we depend on the settings of the "project name" step + if (!isPathChangedByUser()) { + setContentEntryPath(myWizardContext.getProjectFileDirectory().replace('/', File.separatorChar)); + } + if (!isNameChangedByUser()) { + setModuleName(myWizardContext.getProjectName()); + } + } + + if (!myFirstTimeInitializationDone) { + myNamePathComponent.getNameComponent().selectAll(); + } + myFirstTimeInitializationDone = true; + } + + public JComponent getComponent() { + return myPanel; + } + + public String getModuleName() { + return myNamePathComponent.getNameValue(); + } + + public void setModuleName(String name) { + myNamePathComponent.setNameValue(name); + } + + public String getContentEntryPath() { + final String path = myNamePathComponent.getPath(); + return path.length() == 0? null : path; + } + + public void setContentEntryPath(String path) { + myNamePathComponent.setPath(path); + } + + public String getModuleFilePath() { + return getModuleFileDirectory() + "/" + myNamePathComponent.getNameValue() + ModuleFileType.DOT_DEFAULT_EXTENSION; + } + + public boolean isSyncEnabled() { + return myNamePathComponent.isSyncEnabled(); + } + + public void setSyncEnabled(boolean isSyncEnabled) { + myNamePathComponent.setSyncEnabled(isSyncEnabled); + } + + public String getModuleFileDirectory() { + return myTfModuleFilePath.getText().trim().replace(File.separatorChar, '/'); + } + + public boolean validate() { + final String moduleName = getModuleName(); + if (moduleName.length() == 0) { + Messages.showErrorDialog(myNamePathComponent.getNameComponent(), IdeBundle.message("prompt.please.specify.module.name"), + IdeBundle.message("title.module.name.not.specified")); + return false; + } + if (isAlreadyExists(moduleName)) { + Messages.showErrorDialog(IdeBundle.message("error.module.with.name.already.exists", moduleName), IdeBundle.message("title.module.already.exists")); + return false; + } + final String moduleLocation = getModuleFileDirectory(); + if (moduleLocation.length() == 0) { + Messages.showErrorDialog(myNamePathComponent.getPathComponent(), IdeBundle.message("error.please.specify.module.file.location"), + IdeBundle.message("title.module.file.location.not.specified")); + return false; + } + + final String contentEntryPath = getContentEntryPath(); + if (contentEntryPath != null) { + // the check makes sence only for non-null module root + Module[] modules = myModulesProvider.getModules(); + for (final Module module : modules) { + ModuleRootModel rootModel = myModulesProvider.getRootModel(module); + LOG.assertTrue(rootModel != null); + final VirtualFile[] moduleContentRoots = rootModel.getContentRoots(); + final String moduleContentRoot = contentEntryPath.replace(File.separatorChar, '/'); + for (final VirtualFile root : moduleContentRoots) { + if (moduleContentRoot.equals(root.getPath())) { + Messages.showErrorDialog(myNamePathComponent.getPathComponent(), + IdeBundle.message("error.content.root.already.defined.for.module", contentEntryPath, module.getName()), + IdeBundle.message("title.module.content.root.already.exists")); + return false; + } + } + } + if (!ProjectWizardUtil.createDirectoryIfNotExists(IdeBundle.message("directory.module.content.root"), contentEntryPath, myNamePathComponent.isPathChangedByUser())) { + return false; + } + } + final String moduleFileDirectory = getModuleFileDirectory(); + return ProjectWizardUtil.createDirectoryIfNotExists(IdeBundle.message("directory.module.file"), moduleFileDirectory, myModuleFileDirectoryChangedByUser); + } + + public void updateDataModel() { + myBuilder.setName(getModuleName()); + myBuilder.setModuleFilePath(getModuleFilePath()); + myBuilder.setContentEntryPath(getContentEntryPath()); + } + + public JComponent getPreferredFocusedComponent() { + return myNamePathComponent.getNameComponent(); + } + + private boolean isAlreadyExists(String moduleName) { + final Module[] modules = myModulesProvider.getModules(); + for (Module module : modules) { + if (moduleName.equals(module.getName())) { + return true; + } + } + return false; + } + + private void updateModuleFilePathField() { + if (!myModuleFileDirectoryChangedByUser) { + final String dir = myNamePathComponent.getPath().replace('/', File.separatorChar); + myTfModuleFilePath.setText(dir); + } + } + + public boolean isNameChangedByUser() { + return myNamePathComponent.isNameChangedByUser(); + } + + public boolean isPathChangedByUser() { + return myNamePathComponent.isPathChangedByUser(); + } + + private class BrowseModuleFileDirectoryListener extends BrowseFilesListener { + private BrowseModuleFileDirectoryListener(final JTextField textField) { + super(textField, IdeBundle.message("title.select.module.file.location"), + IdeBundle.message("description.select.module.file.location"), SINGLE_DIRECTORY_DESCRIPTOR); + } + + public void actionPerformed(ActionEvent e) { + final String pathBefore = myTfModuleFilePath.getText().trim(); + super.actionPerformed(e); + final String path = myTfModuleFilePath.getText().trim(); + if (!path.equals(pathBefore)) { + myModuleFileDirectoryChangedByUser = true; + } + } + } + + public Icon getIcon() { + return myIcon; + } + + public String getHelpId() { + return myHelpId; + } +} diff --git a/java/idea-ui/src/com/intellij/ide/util/projectWizard/ProjectJdkListRenderer.java b/java/idea-ui/src/com/intellij/ide/util/projectWizard/ProjectJdkListRenderer.java index 8dd02b633679..443b60e89159 100644 --- a/java/idea-ui/src/com/intellij/ide/util/projectWizard/ProjectJdkListRenderer.java +++ b/java/idea-ui/src/com/intellij/ide/util/projectWizard/ProjectJdkListRenderer.java @@ -1,46 +1,46 @@ -/* - * 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.ide.util.projectWizard; - -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.ui.util.OrderEntryCellAppearanceUtils; -import com.intellij.ui.HtmlListCellRenderer; -import com.intellij.ui.SimpleTextAttributes; - -import javax.swing.*; - -/** - * @author Eugene Zhuravlev - * Date: May 18, 2005 - */ -public class ProjectJdkListRenderer extends HtmlListCellRenderer { - public ProjectJdkListRenderer(final ListCellRenderer listCellRenderer) { - super(listCellRenderer); - } - - @Override - public void doCustomize(JList list, Object value, int index, boolean selected, boolean hasFocus) { - if (value == null || value instanceof Sdk) { - render(OrderEntryCellAppearanceUtils.forJdk((Sdk)value, false, selected, index != -1)); - } - else { - final String str = value.toString(); - if (str != null) { - append(str, selected ? SimpleTextAttributes.SELECTED_SIMPLE_CELL_ATTRIBUTES : SimpleTextAttributes.SIMPLE_CELL_ATTRIBUTES); - } - } - } -} +/* + * 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.ide.util.projectWizard; + +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.ui.util.OrderEntryCellAppearanceUtils; +import com.intellij.ui.HtmlListCellRenderer; +import com.intellij.ui.SimpleTextAttributes; + +import javax.swing.*; + +/** + * @author Eugene Zhuravlev + * Date: May 18, 2005 + */ +public class ProjectJdkListRenderer extends HtmlListCellRenderer { + public ProjectJdkListRenderer(final ListCellRenderer listCellRenderer) { + super(listCellRenderer); + } + + @Override + public void doCustomize(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (value == null || value instanceof Sdk) { + render(OrderEntryCellAppearanceUtils.forJdk((Sdk)value, false, selected, index != -1)); + } + else { + final String str = value.toString(); + if (str != null) { + append(str, selected ? SimpleTextAttributes.SELECTED_SIMPLE_CELL_ATTRIBUTES : SimpleTextAttributes.SIMPLE_CELL_ATTRIBUTES); + } + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/project/impl/IdeaProjectManagerImpl.java b/java/idea-ui/src/com/intellij/openapi/project/impl/IdeaProjectManagerImpl.java index a296a27d903e..57df3e44d5bc 100644 --- a/java/idea-ui/src/com/intellij/openapi/project/impl/IdeaProjectManagerImpl.java +++ b/java/idea-ui/src/com/intellij/openapi/project/impl/IdeaProjectManagerImpl.java @@ -1,77 +1,77 @@ -/* - * 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.project.impl; - -import com.intellij.conversion.ConversionResult; -import com.intellij.conversion.ConversionService; -import com.intellij.conversion.impl.ConversionResultImpl; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; - -/** - * @author mike - */ -public class IdeaProjectManagerImpl extends ProjectManagerImpl { - public IdeaProjectManagerImpl(VirtualFileManagerEx virtualFileManagerEx, ProgressManager progressManager) { - super(virtualFileManagerEx, progressManager); - } - - @NotNull - private static ConversionResult convertProject(final String filePath) throws ProcessCanceledException { - final String fp = canonicalize(filePath); - - final File f = new File(fp); - if (fp != null && f.exists() && !ApplicationManager.getApplication().isHeadlessEnvironment()) { - return ConversionService.getInstance().convert(fp); - } - return ConversionResultImpl.CONVERSION_NOT_NEEDED; - } - - @Nullable - protected Project convertAndLoadProject(String filePath, boolean convert) throws IOException { - final ConversionResult conversionResult; - if (convert) { - conversionResult = convertProject(filePath); - if (conversionResult.openingIsCanceled()) { - return null; - } - } - else { - conversionResult = null; - } - - final Project project = loadProjectWithProgress(filePath); - if (project == null) return null; - - if (conversionResult != null && !conversionResult.conversionNotNeeded()) { - StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() { - public void run() { - conversionResult.postStartupActivity(project); - } - }); - } - return project; - } -} +/* + * 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.project.impl; + +import com.intellij.conversion.ConversionResult; +import com.intellij.conversion.ConversionService; +import com.intellij.conversion.impl.ConversionResultImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; + +/** + * @author mike + */ +public class IdeaProjectManagerImpl extends ProjectManagerImpl { + public IdeaProjectManagerImpl(VirtualFileManagerEx virtualFileManagerEx, ProgressManager progressManager) { + super(virtualFileManagerEx, progressManager); + } + + @NotNull + private static ConversionResult convertProject(final String filePath) throws ProcessCanceledException { + final String fp = canonicalize(filePath); + + final File f = new File(fp); + if (fp != null && f.exists() && !ApplicationManager.getApplication().isHeadlessEnvironment()) { + return ConversionService.getInstance().convert(fp); + } + return ConversionResultImpl.CONVERSION_NOT_NEEDED; + } + + @Nullable + protected Project convertAndLoadProject(String filePath, boolean convert) throws IOException { + final ConversionResult conversionResult; + if (convert) { + conversionResult = convertProject(filePath); + if (conversionResult.openingIsCanceled()) { + return null; + } + } + else { + conversionResult = null; + } + + final Project project = loadProjectWithProgress(filePath); + if (project == null) return null; + + if (conversionResult != null && !conversionResult.conversionNotNeeded()) { + StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() { + public void run() { + conversionResult.postStartupActivity(project); + } + }); + } + return project; + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationError.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationError.java index 88366a11e0c1..66a8de47a85a 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationError.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationError.java @@ -1,91 +1,91 @@ -/* - * 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.roots.ui.configuration; - -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -/** - * User: spLeaner - */ -public abstract class ConfigurationError implements Comparable { - protected String myPlainTextTitle; - protected String myDescription; - private boolean myIgnored; - - protected ConfigurationError(final String plainTextTitle, final String description) { - this(plainTextTitle, description, false); - } - - protected ConfigurationError(final String plainTextTitle, final String description, final boolean ignored) { - myPlainTextTitle = plainTextTitle; - myDescription = description; - myIgnored = ignored; - } - - @NotNull - public String getPlainTextTitle() { - return myPlainTextTitle; - } - - @NotNull - public String getDescription() { - return myDescription; - } - - /** - * Called when user invokes "Ignore" action - * @param "true" if user invokes "Ignore", "false" if user wish to not ignore this error anymore - */ - public void ignore(final boolean b) { - if (b != myIgnored) { - myIgnored = b; - } - } - - /** - * @return "true" if this error is ignored - */ - public boolean isIgnored() { - return myIgnored; - } - - /** - * Called when user invokes "Fix" action - */ - public void fix(JComponent contextComponent) { - } - - public boolean canBeFixed() { - return true; - } - - public abstract void navigate(); - - @Override - public int compareTo(final ConfigurationError o) { - if (myIgnored != o.isIgnored()) return -1; - - final int titleResult = getPlainTextTitle().compareTo(o.getPlainTextTitle()); - if (titleResult != 0) return titleResult; - - final int descriptionResult = getDescription().compareTo(o.getDescription()); - if (descriptionResult != 0) return descriptionResult; - - return 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.roots.ui.configuration; + +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * User: spLeaner + */ +public abstract class ConfigurationError implements Comparable { + protected String myPlainTextTitle; + protected String myDescription; + private boolean myIgnored; + + protected ConfigurationError(final String plainTextTitle, final String description) { + this(plainTextTitle, description, false); + } + + protected ConfigurationError(final String plainTextTitle, final String description, final boolean ignored) { + myPlainTextTitle = plainTextTitle; + myDescription = description; + myIgnored = ignored; + } + + @NotNull + public String getPlainTextTitle() { + return myPlainTextTitle; + } + + @NotNull + public String getDescription() { + return myDescription; + } + + /** + * Called when user invokes "Ignore" action + * @param "true" if user invokes "Ignore", "false" if user wish to not ignore this error anymore + */ + public void ignore(final boolean b) { + if (b != myIgnored) { + myIgnored = b; + } + } + + /** + * @return "true" if this error is ignored + */ + public boolean isIgnored() { + return myIgnored; + } + + /** + * Called when user invokes "Fix" action + */ + public void fix(JComponent contextComponent) { + } + + public boolean canBeFixed() { + return true; + } + + public abstract void navigate(); + + @Override + public int compareTo(final ConfigurationError o) { + if (myIgnored != o.isIgnored()) return -1; + + final int titleResult = getPlainTextTitle().compareTo(o.getPlainTextTitle()); + if (titleResult != 0) return titleResult; + + final int descriptionResult = getDescription().compareTo(o.getDescription()); + if (descriptionResult != 0) return descriptionResult; + + return 0; + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationErrorsComponent.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationErrorsComponent.java index a0035a9a9637..341ca35c2761 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationErrorsComponent.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ConfigurationErrorsComponent.java @@ -1,722 +1,722 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.wm.impl.content.GraphicsConfig; -import com.intellij.ui.components.JBList; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.ui.components.labels.LinkLabel; -import com.intellij.ui.components.labels.LinkListener; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.BaseButtonBehavior; -import com.intellij.util.ui.TimedDeadzone; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; -import java.awt.*; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.geom.RoundRectangle2D; -import java.util.ArrayList; -import java.util.List; - -/** - * User: spLeaner - */ -public class ConfigurationErrorsComponent extends JPanel implements Disposable, ListDataListener { - private static final boolean ONE_LINE = true; - private static final boolean MULTI_LINE = false; - - private static final Icon EXPAND = IconLoader.getIcon("/actions/expandall.png"); - private static final Icon COLLAPSE = IconLoader.getIcon("/actions/collapseall.png"); - private static final Icon FIX = IconLoader.findIcon("/actions/quickfixBulb.png"); - private static final Icon IGNORE = IconLoader.findIcon("/toolbar/unknown.png"); - private static final Icon NAVIGATE = IconLoader.findIcon("/general/autoscrollToSource.png"); - - @NonNls private static final String FIX_ACTION_NAME = "FIX"; - @NonNls private static final String NAVIGATE_ACTION_NAME = "NAVIGATE"; - - private ConfigurationErrorsListModel myConfigurationErrorsListModel; - private ErrorView myCurrentView; - - private OneLineErrorComponent myOneLineErrorComponent; - private MultiLineErrorComponent myMultiLineErrorComponent; - - public ConfigurationErrorsComponent(@NotNull final Project project) { - setLayout(new BorderLayout()); - myConfigurationErrorsListModel = new ConfigurationErrorsListModel(project); - myConfigurationErrorsListModel.addListDataListener(this); - - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(final ComponentEvent e) { - revalidate(); - repaint(); - } - }); - - ensureCurrentViewIs(ONE_LINE); - Disposer.register(this, myConfigurationErrorsListModel); - } - - @Override - public void dispose() { - if (myConfigurationErrorsListModel != null) { - myConfigurationErrorsListModel.removeListDataListener(this); - myConfigurationErrorsListModel = null; - } - } - - private void ensureCurrentViewIs(final boolean oneLine) { - if (oneLine) { - if (myCurrentView instanceof OneLineErrorComponent) return; - if (myOneLineErrorComponent == null) { - myOneLineErrorComponent = new OneLineErrorComponent(myConfigurationErrorsListModel) { - @Override - public void onViewChange() { - ensureCurrentViewIs(MULTI_LINE); - } - }; - } - - if (myCurrentView != null) { - remove(myCurrentView.self()); - } - - myCurrentView = myOneLineErrorComponent; - } else { - if (myCurrentView instanceof MultiLineErrorComponent) return; - if (myMultiLineErrorComponent == null) { - myMultiLineErrorComponent = new MultiLineErrorComponent(myConfigurationErrorsListModel) { - @Override - public void onViewChange() { - ensureCurrentViewIs(ONE_LINE); - } - }; - } - - if (myCurrentView != null) { - remove(myCurrentView.self()); - } - - myCurrentView = myMultiLineErrorComponent; - } - - add(myCurrentView.self(), BorderLayout.CENTER); - myCurrentView.updateView(); - revalidate(); - repaint(); - } - - @Override - public void intervalAdded(final ListDataEvent e) { - updateCurrentView(); - } - - @Override - public void intervalRemoved(final ListDataEvent e) { - updateCurrentView(); - } - - @Override - public void contentsChanged(final ListDataEvent e) { - updateCurrentView(); - } - - private void updateCurrentView() { - if (myCurrentView instanceof MultiLineErrorComponent && myConfigurationErrorsListModel.getSize() == 0) { - ensureCurrentViewIs(ONE_LINE); - } - - myCurrentView.updateView(); - } - - private interface ErrorView { - void updateView(); - void onViewChange(); - JComponent self(); - } - - private abstract static class MultiLineErrorComponent extends JPanel implements ErrorView { - private ConfigurationErrorsListModel myModel; - private JList myList = new JBList(); - - protected MultiLineErrorComponent(@NotNull final ConfigurationErrorsListModel model) { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); - - myModel = model; - myList.setModel(model); - myList.setCellRenderer(new ErrorListRenderer(myList)); - myList.setBackground(UIUtil.getPanelBackground()); - - myList.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - if (!e.isPopupTrigger()) { - processListMouseEvent(e, true); - } - } - }); - - myList.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - myList.setCellRenderer(new ErrorListRenderer(myList)); // request cell renderer size invalidation - updatePreferredSize(); - } - }); - - add(new JBScrollPane(myList), BorderLayout.CENTER); - add(buildToolbar(), BorderLayout.WEST); - } - - private void processListMouseEvent(final MouseEvent e, final boolean click) { - final int index = myList.locationToIndex(e.getPoint()); - if (index > -1) { - final Object value = myList.getModel().getElementAt(index); - if (value != null && value instanceof ConfigurationError) { - final ConfigurationError error = (ConfigurationError)value; - final Component renderer = myList.getCellRenderer().getListCellRendererComponent(myList, value, index, false, false); - if (renderer instanceof ErrorListRenderer) { - final Rectangle bounds = myList.getCellBounds(index, index); - renderer.setBounds(bounds); - renderer.doLayout(); - - final Point point = e.getPoint(); - point.translate(-bounds.x, -bounds.y); - - final Component deepestComponentAt = SwingUtilities.getDeepestComponentAt(renderer, point.x, point.y); - if (deepestComponentAt instanceof ToolbarAlikeButton) { - final String name = ((ToolbarAlikeButton)deepestComponentAt).getButtonName(); - if (FIX_ACTION_NAME.equals(name)) { - onClickFix(error, (JComponent)deepestComponentAt); - } - else if (NAVIGATE_ACTION_NAME.equals(name)) { - error.navigate(); - } - else { - onClickIgnore(error); - } - } - } - } - } - } - - private void onClickIgnore(@NotNull final ConfigurationError error) { - error.ignore(!error.isIgnored()); - final ListModel model = myList.getModel(); - if (model instanceof ConfigurationErrorsListModel) { - ((ConfigurationErrorsListModel)model).update(error); - } - } - - private void onClickFix(@NotNull final ConfigurationError error, JComponent component) { - error.fix(component); - } - - @Override - public void addNotify() { - super.addNotify(); - updatePreferredSize(); - } - - private void updatePreferredSize() { - final Window window = SwingUtilities.getWindowAncestor(this); - if (window != null) { - final Dimension d = window.getSize(); - final Dimension preferredSize = getPreferredSize(); - setPreferredSize(new Dimension(preferredSize.width, d.height / 4)); - setMinimumSize(new Dimension(preferredSize.width, 100)); - } - } - - private JComponent buildToolbar() { - final JPanel result = new JPanel(); - result.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); - result.setLayout(new BorderLayout()); - result.add(new ToolbarAlikeButton(COLLAPSE) { - { - setToolTipText("Collapse"); - } - - @Override - public void onClick() { - onViewChange(); - } - }, BorderLayout.NORTH); - - return result; - } - - @Override - public void updateView() { - } - - @Override - public JComponent self() { - return this; - } - - public abstract void onViewChange(); - } - - private abstract static class ToolbarAlikeButton extends JComponent { - private BaseButtonBehavior myBehavior; - private Icon myIcon; - private String myName; - - private ToolbarAlikeButton(@NotNull final Icon icon, @NotNull final String name) { - this(icon); - myName = name; - } - - private ToolbarAlikeButton(@NotNull final Icon icon) { - myIcon = icon; - - myBehavior = new BaseButtonBehavior(this, TimedDeadzone.NULL) { - @Override - protected void execute(MouseEvent e) { - onClick(); - } - }; - - setOpaque(false); - } - - public String getButtonName() { - return myName; - } - - public void onClick() {} - - @Override - public Insets getInsets() { - return new Insets(2, 2, 2, 2); - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() { - final Insets insets = getInsets(); - return new Dimension(myIcon.getIconWidth() + insets.left + insets.right, myIcon.getIconHeight() + insets.top + insets.bottom); - } - - @Override - public void paint(final Graphics g) { - final Insets insets = getInsets(); - final Dimension d = getSize(); - - int x = (d.width - myIcon.getIconWidth() - insets.left - insets.right) / 2; - int y = (d.height - myIcon.getIconHeight() - insets.top - insets.bottom) / 2; - - if (myBehavior.isHovered()) { - // todo - } - - if (myBehavior.isPressedByMouse()) { - x += 1; - y += 1; - } - - myIcon.paintIcon(this, g, x + insets.left, y + insets.top); - } - } - - private static class ErrorListRenderer extends JComponent implements ListCellRenderer { - private boolean mySelected; - private boolean myHasFocus; - private JTextPane myText; - private JTextPane myFakeTextPane; - private JViewport myFakeViewport; - private JList myList; - private JPanel myButtonsPanel; - private JPanel myFixGroup; - - private ErrorListRenderer(@NotNull final JList list) { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - setOpaque(false); - - myList = list; - - myText = new JTextPane() { - @Override - public void setCursor(Cursor cursor) { - super.setCursor(cursor); - //onCursorChanged(cursor); - } - }; - - myButtonsPanel = new JPanel(new BorderLayout()); - myButtonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 3, 5, 3)); - myButtonsPanel.setOpaque(false); - final JPanel buttons = new JPanel(); - buttons.setOpaque(false); - buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); - myButtonsPanel.add(buttons, BorderLayout.NORTH); - add(myButtonsPanel, BorderLayout.EAST); - - myFixGroup = new JPanel(); - myFixGroup.setOpaque(false); - myFixGroup.setLayout(new BoxLayout(myFixGroup, BoxLayout.Y_AXIS)); - - myFixGroup.add(new ToolbarAlikeButton(FIX, FIX_ACTION_NAME) {}); - myFixGroup.add(Box.createHorizontalStrut(3)); - buttons.add(myFixGroup); - - buttons.add(new ToolbarAlikeButton(NAVIGATE, NAVIGATE_ACTION_NAME) {}); - buttons.add(Box.createHorizontalStrut(3)); - - buttons.add(new ToolbarAlikeButton(IGNORE, "IGNORE") {}); - - myFakeTextPane = new JTextPane(); - myText.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); - myFakeTextPane.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); - myText.setOpaque(false); - if (UIUtil.isUnderNimbusLookAndFeel()) { - myText.setBackground(new Color(0, 0, 0, 0)); - } - - myText.setEditable(false); - myFakeTextPane.setEditable(false); - myText.setEditorKit(UIUtil.getHTMLEditorKit()); - myFakeTextPane.setEditorKit(UIUtil.getHTMLEditorKit()); - - myFakeViewport = new JViewport(); - myFakeViewport.setView(myFakeTextPane); - - add(myText, BorderLayout.CENTER); - } - - @Override - public Dimension getPreferredSize() { - final Container parent = myList.getParent(); - if (parent != null) { - myFakeTextPane.setText(myText.getText()); - final Dimension size = parent.getSize(); - myFakeViewport.setSize(size); - final Dimension preferredSize = myFakeTextPane.getPreferredSize(); - - final Dimension buttonsPrefSize = myButtonsPanel.getPreferredSize(); - final int maxHeight = Math.max(buttonsPrefSize.height, preferredSize.height); - - final Insets insets = getInsets(); - return new Dimension(Math.min(size.width - 20, preferredSize.width), maxHeight + insets.top + insets.bottom); - } - - return super.getPreferredSize(); - } - - @Override - public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { - final ConfigurationError error = (ConfigurationError)value; - - myList = list; - - mySelected = isSelected; - myHasFocus = cellHasFocus; - - myFixGroup.setVisible(error.canBeFixed()); - - myText.setText(error.getDescription()); - - setBackground(error.isIgnored() ? MessageType.WARNING.getPopupBackground() : MessageType.ERROR.getPopupBackground()); - return this; - } - - @Override - protected void paintComponent(Graphics g) { - final Graphics2D g2d = (Graphics2D)g; - - final Rectangle bounds = getBounds(); - final Insets insets = getInsets(); - - final GraphicsConfig cfg = new GraphicsConfig(g); - cfg.setAntialiasing(true); - - final Shape shape = new RoundRectangle2D.Double(insets.left, insets.top, bounds.width - 1 - insets.left - insets.right, - bounds.height - 1 - insets.top - insets.bottom, 6, 6); - - if (mySelected) { - g2d.setColor(UIUtil.getListSelectionBackground()); - g2d.fillRect(0, 0, bounds.width, bounds.height); - } - - g2d.setColor(Color.WHITE); - g2d.fill(shape); - - - Color bgColor = getBackground(); - - g2d.setColor(bgColor); - g2d.fill(shape); - - g2d.setColor(myHasFocus || mySelected ? getBackground().darker().darker() : getBackground().darker()); - g2d.draw(shape); - cfg.restore(); - - super.paintComponent(g); - } - } - - private abstract static class OneLineErrorComponent extends JComponent implements ErrorView, LinkListener { - private LinkLabel myErrorsLabel = new LinkLabel(null, null); - private LinkLabel myIgnoredErrorsLabel = new LinkLabel(null, null); - private JLabel mySingleErrorLabel = new JLabel(); - - private ConfigurationErrorsListModel myModel; - - private OneLineErrorComponent(@NotNull final ConfigurationErrorsListModel model) { - myModel = model; - - setLayout(new BorderLayout()); - setOpaque(true); - - updateLabel(myErrorsLabel, MessageType.ERROR.getPopupBackground(), this); - updateLabel(mySingleErrorLabel, MessageType.ERROR.getPopupBackground(), null); - updateLabel(myIgnoredErrorsLabel, MessageType.WARNING.getPopupBackground(), this); - } - - private static void updateLabel(@NotNull final JLabel label, @NotNull final Color bgColor, @Nullable final LinkListener listener) { - label.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5)); - label.setOpaque(true); - label.setBackground(bgColor); - if (label instanceof LinkLabel) { - ((LinkLabel)label).setListener(listener, null); - } - } - - @Override - public void updateView() { - if (myModel.getSize() == 0) { - setBorder(null); - } else { - if (getBorder() == null) setBorder( - BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(5, 0, 5, 0, UIUtil.getPanelBackground()), - BorderFactory.createLineBorder(UIUtil.getPanelBackground().darker()))); - } - - final List errors = myModel.getErrors(); - if (errors.size() > 0) { - if (errors.size() == 1) { - mySingleErrorLabel.setText(myModel.getErrors().get(0).getPlainTextTitle()); - } else { - myErrorsLabel.setText(String.format("%s errors found", errors.size())); - } - } - - final List ignoredErrors = myModel.getIgnoredErrors(); - if (ignoredErrors.size() > 0) { - myIgnoredErrorsLabel.setText(String.format("%s ignored error%s", ignoredErrors.size(), ignoredErrors.size() == 1 ? "" : "s")); - } - - removeAll(); - if (errors.size() > 0) { - if (errors.size() == 1) { - add(wrapLabel(mySingleErrorLabel, errors.get(0)), BorderLayout.CENTER); - mySingleErrorLabel.setToolTipText(errors.get(0).getDescription()); - } else { - add(myErrorsLabel, BorderLayout.CENTER); - } - } - - if (ignoredErrors.size() > 0) { - add(myIgnoredErrorsLabel, errors.size() > 0 ? BorderLayout.EAST : BorderLayout.CENTER); - } - - revalidate(); - repaint(); - } - - private JComponent wrapLabel(@NotNull final JLabel label, @NotNull final ConfigurationError configurationError) { - final JPanel result = new JPanel(new BorderLayout()); - result.setBackground(label.getBackground()); - result.add(label, BorderLayout.CENTER); - - final JPanel buttonsPanel = new JPanel(); - buttonsPanel.setOpaque(false); - buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS)); - - if (configurationError.canBeFixed()) { - buttonsPanel.add(new ToolbarAlikeButton(FIX) { - { - setToolTipText("Fix error"); - } - - @Override - public void onClick() { - final Object o = myModel.getElementAt(0); - if (o instanceof ConfigurationError) { - ((ConfigurationError)o).fix(this); - updateView(); - final Container ancestor = SwingUtilities.getAncestorOfClass(ConfigurationErrorsComponent.class, this); - if (ancestor != null && ancestor instanceof JComponent) { - ((JComponent)ancestor).revalidate(); - ancestor.repaint(); - } - } - } - }); - - buttonsPanel.add(Box.createHorizontalStrut(3)); - } - - buttonsPanel.add(new ToolbarAlikeButton(NAVIGATE) { - { - setToolTipText("Navigate to error"); - } - - @Override - public void onClick() { - final Object o = myModel.getElementAt(0); - if (o instanceof ConfigurationError) { - ((ConfigurationError)o).navigate(); - } - } - }); - - buttonsPanel.add(Box.createHorizontalStrut(3)); - - buttonsPanel.add(new ToolbarAlikeButton(IGNORE) { - { - setToolTipText("Ignore error"); - } - - @Override - public void onClick() { - final Object o = myModel.getElementAt(0); - if (o instanceof ConfigurationError) { - ((ConfigurationError)o).ignore(!((ConfigurationError)o).isIgnored()); - updateView(); - } - } - }); - buttonsPanel.add(Box.createHorizontalStrut(5)); - - result.add(buttonsPanel, BorderLayout.EAST); - - return result; - } - - @Override - public JComponent self() { - return this; - } - - public abstract void onViewChange(); - - @Override - public void linkSelected(LinkLabel aSource, Object aLinkData) { - onViewChange(); - } - } - - private static class ConfigurationErrorsListModel extends AbstractListModel implements ConfigurationErrors, Disposable { - private MessageBusConnection myConnection; - private List myErrorsList = new ArrayList(); - - private ConfigurationErrorsListModel(@NotNull final Project project) { - myConnection = project.getMessageBus().connect(); - myConnection.subscribe(TOPIC, this); - } - - @Override - public int getSize() { - return myErrorsList.size(); - } - - @Override - public Object getElementAt(int index) { - return myErrorsList.get(index); - } - - @Override - public void addError(@NotNull ConfigurationError error) { - if (!myErrorsList.contains(error)) { - int ndx = 0; - if (error.isIgnored()) { - ndx = myErrorsList.size(); - } - - myErrorsList.add(ndx, error); - fireIntervalAdded(this, ndx, ndx); - } - } - - @Override - public void removeError(@NotNull ConfigurationError error) { - if (myErrorsList.contains(error)) { - final int ndx = myErrorsList.indexOf(error); - myErrorsList.remove(ndx); - fireIntervalRemoved(this, ndx, ndx); - } - } - - public List getErrors() { - return ContainerUtil.filter(myErrorsList, new Condition() { - @Override - public boolean value(final ConfigurationError error) { - return !error.isIgnored(); - } - }); - } - - public List getIgnoredErrors() { - return ContainerUtil.filter(myErrorsList, new Condition() { - @Override - public boolean value(final ConfigurationError error) { - return error.isIgnored(); - } - }); - } - - @Override - public void dispose() { - if (myConnection != null) { - myConnection.disconnect(); - myConnection = null; - } - } - - public void update(final ConfigurationError error) { - final int ndx = myErrorsList.indexOf(error); - if (ndx >= 0) { - fireContentsChanged(this, ndx, ndx); - } - } - } -} - +/* + * 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.roots.ui.configuration; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.wm.impl.content.GraphicsConfig; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.components.labels.LinkLabel; +import com.intellij.ui.components.labels.LinkListener; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.BaseButtonBehavior; +import com.intellij.util.ui.TimedDeadzone; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.RoundRectangle2D; +import java.util.ArrayList; +import java.util.List; + +/** + * User: spLeaner + */ +public class ConfigurationErrorsComponent extends JPanel implements Disposable, ListDataListener { + private static final boolean ONE_LINE = true; + private static final boolean MULTI_LINE = false; + + private static final Icon EXPAND = IconLoader.getIcon("/actions/expandall.png"); + private static final Icon COLLAPSE = IconLoader.getIcon("/actions/collapseall.png"); + private static final Icon FIX = IconLoader.findIcon("/actions/quickfixBulb.png"); + private static final Icon IGNORE = IconLoader.findIcon("/toolbar/unknown.png"); + private static final Icon NAVIGATE = IconLoader.findIcon("/general/autoscrollToSource.png"); + + @NonNls private static final String FIX_ACTION_NAME = "FIX"; + @NonNls private static final String NAVIGATE_ACTION_NAME = "NAVIGATE"; + + private ConfigurationErrorsListModel myConfigurationErrorsListModel; + private ErrorView myCurrentView; + + private OneLineErrorComponent myOneLineErrorComponent; + private MultiLineErrorComponent myMultiLineErrorComponent; + + public ConfigurationErrorsComponent(@NotNull final Project project) { + setLayout(new BorderLayout()); + myConfigurationErrorsListModel = new ConfigurationErrorsListModel(project); + myConfigurationErrorsListModel.addListDataListener(this); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(final ComponentEvent e) { + revalidate(); + repaint(); + } + }); + + ensureCurrentViewIs(ONE_LINE); + Disposer.register(this, myConfigurationErrorsListModel); + } + + @Override + public void dispose() { + if (myConfigurationErrorsListModel != null) { + myConfigurationErrorsListModel.removeListDataListener(this); + myConfigurationErrorsListModel = null; + } + } + + private void ensureCurrentViewIs(final boolean oneLine) { + if (oneLine) { + if (myCurrentView instanceof OneLineErrorComponent) return; + if (myOneLineErrorComponent == null) { + myOneLineErrorComponent = new OneLineErrorComponent(myConfigurationErrorsListModel) { + @Override + public void onViewChange() { + ensureCurrentViewIs(MULTI_LINE); + } + }; + } + + if (myCurrentView != null) { + remove(myCurrentView.self()); + } + + myCurrentView = myOneLineErrorComponent; + } else { + if (myCurrentView instanceof MultiLineErrorComponent) return; + if (myMultiLineErrorComponent == null) { + myMultiLineErrorComponent = new MultiLineErrorComponent(myConfigurationErrorsListModel) { + @Override + public void onViewChange() { + ensureCurrentViewIs(ONE_LINE); + } + }; + } + + if (myCurrentView != null) { + remove(myCurrentView.self()); + } + + myCurrentView = myMultiLineErrorComponent; + } + + add(myCurrentView.self(), BorderLayout.CENTER); + myCurrentView.updateView(); + revalidate(); + repaint(); + } + + @Override + public void intervalAdded(final ListDataEvent e) { + updateCurrentView(); + } + + @Override + public void intervalRemoved(final ListDataEvent e) { + updateCurrentView(); + } + + @Override + public void contentsChanged(final ListDataEvent e) { + updateCurrentView(); + } + + private void updateCurrentView() { + if (myCurrentView instanceof MultiLineErrorComponent && myConfigurationErrorsListModel.getSize() == 0) { + ensureCurrentViewIs(ONE_LINE); + } + + myCurrentView.updateView(); + } + + private interface ErrorView { + void updateView(); + void onViewChange(); + JComponent self(); + } + + private abstract static class MultiLineErrorComponent extends JPanel implements ErrorView { + private ConfigurationErrorsListModel myModel; + private JList myList = new JBList(); + + protected MultiLineErrorComponent(@NotNull final ConfigurationErrorsListModel model) { + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + + myModel = model; + myList.setModel(model); + myList.setCellRenderer(new ErrorListRenderer(myList)); + myList.setBackground(UIUtil.getPanelBackground()); + + myList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + if (!e.isPopupTrigger()) { + processListMouseEvent(e, true); + } + } + }); + + myList.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + myList.setCellRenderer(new ErrorListRenderer(myList)); // request cell renderer size invalidation + updatePreferredSize(); + } + }); + + add(new JBScrollPane(myList), BorderLayout.CENTER); + add(buildToolbar(), BorderLayout.WEST); + } + + private void processListMouseEvent(final MouseEvent e, final boolean click) { + final int index = myList.locationToIndex(e.getPoint()); + if (index > -1) { + final Object value = myList.getModel().getElementAt(index); + if (value != null && value instanceof ConfigurationError) { + final ConfigurationError error = (ConfigurationError)value; + final Component renderer = myList.getCellRenderer().getListCellRendererComponent(myList, value, index, false, false); + if (renderer instanceof ErrorListRenderer) { + final Rectangle bounds = myList.getCellBounds(index, index); + renderer.setBounds(bounds); + renderer.doLayout(); + + final Point point = e.getPoint(); + point.translate(-bounds.x, -bounds.y); + + final Component deepestComponentAt = SwingUtilities.getDeepestComponentAt(renderer, point.x, point.y); + if (deepestComponentAt instanceof ToolbarAlikeButton) { + final String name = ((ToolbarAlikeButton)deepestComponentAt).getButtonName(); + if (FIX_ACTION_NAME.equals(name)) { + onClickFix(error, (JComponent)deepestComponentAt); + } + else if (NAVIGATE_ACTION_NAME.equals(name)) { + error.navigate(); + } + else { + onClickIgnore(error); + } + } + } + } + } + } + + private void onClickIgnore(@NotNull final ConfigurationError error) { + error.ignore(!error.isIgnored()); + final ListModel model = myList.getModel(); + if (model instanceof ConfigurationErrorsListModel) { + ((ConfigurationErrorsListModel)model).update(error); + } + } + + private void onClickFix(@NotNull final ConfigurationError error, JComponent component) { + error.fix(component); + } + + @Override + public void addNotify() { + super.addNotify(); + updatePreferredSize(); + } + + private void updatePreferredSize() { + final Window window = SwingUtilities.getWindowAncestor(this); + if (window != null) { + final Dimension d = window.getSize(); + final Dimension preferredSize = getPreferredSize(); + setPreferredSize(new Dimension(preferredSize.width, d.height / 4)); + setMinimumSize(new Dimension(preferredSize.width, 100)); + } + } + + private JComponent buildToolbar() { + final JPanel result = new JPanel(); + result.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); + result.setLayout(new BorderLayout()); + result.add(new ToolbarAlikeButton(COLLAPSE) { + { + setToolTipText("Collapse"); + } + + @Override + public void onClick() { + onViewChange(); + } + }, BorderLayout.NORTH); + + return result; + } + + @Override + public void updateView() { + } + + @Override + public JComponent self() { + return this; + } + + public abstract void onViewChange(); + } + + private abstract static class ToolbarAlikeButton extends JComponent { + private BaseButtonBehavior myBehavior; + private Icon myIcon; + private String myName; + + private ToolbarAlikeButton(@NotNull final Icon icon, @NotNull final String name) { + this(icon); + myName = name; + } + + private ToolbarAlikeButton(@NotNull final Icon icon) { + myIcon = icon; + + myBehavior = new BaseButtonBehavior(this, TimedDeadzone.NULL) { + @Override + protected void execute(MouseEvent e) { + onClick(); + } + }; + + setOpaque(false); + } + + public String getButtonName() { + return myName; + } + + public void onClick() {} + + @Override + public Insets getInsets() { + return new Insets(2, 2, 2, 2); + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public Dimension getMinimumSize() { + final Insets insets = getInsets(); + return new Dimension(myIcon.getIconWidth() + insets.left + insets.right, myIcon.getIconHeight() + insets.top + insets.bottom); + } + + @Override + public void paint(final Graphics g) { + final Insets insets = getInsets(); + final Dimension d = getSize(); + + int x = (d.width - myIcon.getIconWidth() - insets.left - insets.right) / 2; + int y = (d.height - myIcon.getIconHeight() - insets.top - insets.bottom) / 2; + + if (myBehavior.isHovered()) { + // todo + } + + if (myBehavior.isPressedByMouse()) { + x += 1; + y += 1; + } + + myIcon.paintIcon(this, g, x + insets.left, y + insets.top); + } + } + + private static class ErrorListRenderer extends JComponent implements ListCellRenderer { + private boolean mySelected; + private boolean myHasFocus; + private JTextPane myText; + private JTextPane myFakeTextPane; + private JViewport myFakeViewport; + private JList myList; + private JPanel myButtonsPanel; + private JPanel myFixGroup; + + private ErrorListRenderer(@NotNull final JList list) { + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + setOpaque(false); + + myList = list; + + myText = new JTextPane() { + @Override + public void setCursor(Cursor cursor) { + super.setCursor(cursor); + //onCursorChanged(cursor); + } + }; + + myButtonsPanel = new JPanel(new BorderLayout()); + myButtonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 3, 5, 3)); + myButtonsPanel.setOpaque(false); + final JPanel buttons = new JPanel(); + buttons.setOpaque(false); + buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); + myButtonsPanel.add(buttons, BorderLayout.NORTH); + add(myButtonsPanel, BorderLayout.EAST); + + myFixGroup = new JPanel(); + myFixGroup.setOpaque(false); + myFixGroup.setLayout(new BoxLayout(myFixGroup, BoxLayout.Y_AXIS)); + + myFixGroup.add(new ToolbarAlikeButton(FIX, FIX_ACTION_NAME) {}); + myFixGroup.add(Box.createHorizontalStrut(3)); + buttons.add(myFixGroup); + + buttons.add(new ToolbarAlikeButton(NAVIGATE, NAVIGATE_ACTION_NAME) {}); + buttons.add(Box.createHorizontalStrut(3)); + + buttons.add(new ToolbarAlikeButton(IGNORE, "IGNORE") {}); + + myFakeTextPane = new JTextPane(); + myText.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + myFakeTextPane.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + myText.setOpaque(false); + if (UIUtil.isUnderNimbusLookAndFeel()) { + myText.setBackground(new Color(0, 0, 0, 0)); + } + + myText.setEditable(false); + myFakeTextPane.setEditable(false); + myText.setEditorKit(UIUtil.getHTMLEditorKit()); + myFakeTextPane.setEditorKit(UIUtil.getHTMLEditorKit()); + + myFakeViewport = new JViewport(); + myFakeViewport.setView(myFakeTextPane); + + add(myText, BorderLayout.CENTER); + } + + @Override + public Dimension getPreferredSize() { + final Container parent = myList.getParent(); + if (parent != null) { + myFakeTextPane.setText(myText.getText()); + final Dimension size = parent.getSize(); + myFakeViewport.setSize(size); + final Dimension preferredSize = myFakeTextPane.getPreferredSize(); + + final Dimension buttonsPrefSize = myButtonsPanel.getPreferredSize(); + final int maxHeight = Math.max(buttonsPrefSize.height, preferredSize.height); + + final Insets insets = getInsets(); + return new Dimension(Math.min(size.width - 20, preferredSize.width), maxHeight + insets.top + insets.bottom); + } + + return super.getPreferredSize(); + } + + @Override + public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { + final ConfigurationError error = (ConfigurationError)value; + + myList = list; + + mySelected = isSelected; + myHasFocus = cellHasFocus; + + myFixGroup.setVisible(error.canBeFixed()); + + myText.setText(error.getDescription()); + + setBackground(error.isIgnored() ? MessageType.WARNING.getPopupBackground() : MessageType.ERROR.getPopupBackground()); + return this; + } + + @Override + protected void paintComponent(Graphics g) { + final Graphics2D g2d = (Graphics2D)g; + + final Rectangle bounds = getBounds(); + final Insets insets = getInsets(); + + final GraphicsConfig cfg = new GraphicsConfig(g); + cfg.setAntialiasing(true); + + final Shape shape = new RoundRectangle2D.Double(insets.left, insets.top, bounds.width - 1 - insets.left - insets.right, + bounds.height - 1 - insets.top - insets.bottom, 6, 6); + + if (mySelected) { + g2d.setColor(UIUtil.getListSelectionBackground()); + g2d.fillRect(0, 0, bounds.width, bounds.height); + } + + g2d.setColor(Color.WHITE); + g2d.fill(shape); + + + Color bgColor = getBackground(); + + g2d.setColor(bgColor); + g2d.fill(shape); + + g2d.setColor(myHasFocus || mySelected ? getBackground().darker().darker() : getBackground().darker()); + g2d.draw(shape); + cfg.restore(); + + super.paintComponent(g); + } + } + + private abstract static class OneLineErrorComponent extends JComponent implements ErrorView, LinkListener { + private LinkLabel myErrorsLabel = new LinkLabel(null, null); + private LinkLabel myIgnoredErrorsLabel = new LinkLabel(null, null); + private JLabel mySingleErrorLabel = new JLabel(); + + private ConfigurationErrorsListModel myModel; + + private OneLineErrorComponent(@NotNull final ConfigurationErrorsListModel model) { + myModel = model; + + setLayout(new BorderLayout()); + setOpaque(true); + + updateLabel(myErrorsLabel, MessageType.ERROR.getPopupBackground(), this); + updateLabel(mySingleErrorLabel, MessageType.ERROR.getPopupBackground(), null); + updateLabel(myIgnoredErrorsLabel, MessageType.WARNING.getPopupBackground(), this); + } + + private static void updateLabel(@NotNull final JLabel label, @NotNull final Color bgColor, @Nullable final LinkListener listener) { + label.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5)); + label.setOpaque(true); + label.setBackground(bgColor); + if (label instanceof LinkLabel) { + ((LinkLabel)label).setListener(listener, null); + } + } + + @Override + public void updateView() { + if (myModel.getSize() == 0) { + setBorder(null); + } else { + if (getBorder() == null) setBorder( + BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(5, 0, 5, 0, UIUtil.getPanelBackground()), + BorderFactory.createLineBorder(UIUtil.getPanelBackground().darker()))); + } + + final List errors = myModel.getErrors(); + if (errors.size() > 0) { + if (errors.size() == 1) { + mySingleErrorLabel.setText(myModel.getErrors().get(0).getPlainTextTitle()); + } else { + myErrorsLabel.setText(String.format("%s errors found", errors.size())); + } + } + + final List ignoredErrors = myModel.getIgnoredErrors(); + if (ignoredErrors.size() > 0) { + myIgnoredErrorsLabel.setText(String.format("%s ignored error%s", ignoredErrors.size(), ignoredErrors.size() == 1 ? "" : "s")); + } + + removeAll(); + if (errors.size() > 0) { + if (errors.size() == 1) { + add(wrapLabel(mySingleErrorLabel, errors.get(0)), BorderLayout.CENTER); + mySingleErrorLabel.setToolTipText(errors.get(0).getDescription()); + } else { + add(myErrorsLabel, BorderLayout.CENTER); + } + } + + if (ignoredErrors.size() > 0) { + add(myIgnoredErrorsLabel, errors.size() > 0 ? BorderLayout.EAST : BorderLayout.CENTER); + } + + revalidate(); + repaint(); + } + + private JComponent wrapLabel(@NotNull final JLabel label, @NotNull final ConfigurationError configurationError) { + final JPanel result = new JPanel(new BorderLayout()); + result.setBackground(label.getBackground()); + result.add(label, BorderLayout.CENTER); + + final JPanel buttonsPanel = new JPanel(); + buttonsPanel.setOpaque(false); + buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS)); + + if (configurationError.canBeFixed()) { + buttonsPanel.add(new ToolbarAlikeButton(FIX) { + { + setToolTipText("Fix error"); + } + + @Override + public void onClick() { + final Object o = myModel.getElementAt(0); + if (o instanceof ConfigurationError) { + ((ConfigurationError)o).fix(this); + updateView(); + final Container ancestor = SwingUtilities.getAncestorOfClass(ConfigurationErrorsComponent.class, this); + if (ancestor != null && ancestor instanceof JComponent) { + ((JComponent)ancestor).revalidate(); + ancestor.repaint(); + } + } + } + }); + + buttonsPanel.add(Box.createHorizontalStrut(3)); + } + + buttonsPanel.add(new ToolbarAlikeButton(NAVIGATE) { + { + setToolTipText("Navigate to error"); + } + + @Override + public void onClick() { + final Object o = myModel.getElementAt(0); + if (o instanceof ConfigurationError) { + ((ConfigurationError)o).navigate(); + } + } + }); + + buttonsPanel.add(Box.createHorizontalStrut(3)); + + buttonsPanel.add(new ToolbarAlikeButton(IGNORE) { + { + setToolTipText("Ignore error"); + } + + @Override + public void onClick() { + final Object o = myModel.getElementAt(0); + if (o instanceof ConfigurationError) { + ((ConfigurationError)o).ignore(!((ConfigurationError)o).isIgnored()); + updateView(); + } + } + }); + buttonsPanel.add(Box.createHorizontalStrut(5)); + + result.add(buttonsPanel, BorderLayout.EAST); + + return result; + } + + @Override + public JComponent self() { + return this; + } + + public abstract void onViewChange(); + + @Override + public void linkSelected(LinkLabel aSource, Object aLinkData) { + onViewChange(); + } + } + + private static class ConfigurationErrorsListModel extends AbstractListModel implements ConfigurationErrors, Disposable { + private MessageBusConnection myConnection; + private List myErrorsList = new ArrayList(); + + private ConfigurationErrorsListModel(@NotNull final Project project) { + myConnection = project.getMessageBus().connect(); + myConnection.subscribe(TOPIC, this); + } + + @Override + public int getSize() { + return myErrorsList.size(); + } + + @Override + public Object getElementAt(int index) { + return myErrorsList.get(index); + } + + @Override + public void addError(@NotNull ConfigurationError error) { + if (!myErrorsList.contains(error)) { + int ndx = 0; + if (error.isIgnored()) { + ndx = myErrorsList.size(); + } + + myErrorsList.add(ndx, error); + fireIntervalAdded(this, ndx, ndx); + } + } + + @Override + public void removeError(@NotNull ConfigurationError error) { + if (myErrorsList.contains(error)) { + final int ndx = myErrorsList.indexOf(error); + myErrorsList.remove(ndx); + fireIntervalRemoved(this, ndx, ndx); + } + } + + public List getErrors() { + return ContainerUtil.filter(myErrorsList, new Condition() { + @Override + public boolean value(final ConfigurationError error) { + return !error.isIgnored(); + } + }); + } + + public List getIgnoredErrors() { + return ContainerUtil.filter(myErrorsList, new Condition() { + @Override + public boolean value(final ConfigurationError error) { + return error.isIgnored(); + } + }); + } + + @Override + public void dispose() { + if (myConnection != null) { + myConnection.disconnect(); + myConnection = null; + } + } + + public void update(final ConfigurationError error) { + final int ndx = myErrorsList.indexOf(error); + if (ndx >= 0) { + fireContentsChanged(this, ndx, ndx); + } + } + } +} + diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java index df3512992bb8..7c4763a50b30 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java @@ -1,295 +1,295 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.ide.DataManager; -import com.intellij.ide.util.projectWizard.ProjectJdkListRenderer; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.application.ApplicationBundle; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.ui.configuration.projectRoot.JdkListConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; -import com.intellij.openapi.ui.ComboBoxWithWidePopup; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Condition; -import com.intellij.ui.ScreenUtil; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.Consumer; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; - -/** - * @author Eugene Zhuravlev - * Date: May 18, 2005 - */ -class JdkComboBox extends ComboBoxWithWidePopup { - private final JButton myEditButton = new JButton(ApplicationBundle.message("button.edit")); - - public JdkComboBox(final ProjectSdksModel jdksModel) { - super(new JdkComboBoxModel(jdksModel)); - setRenderer(new ProjectJdkListRenderer(getRenderer()) { - @Override - public void doCustomize(JList list, Object value, int index, boolean selected, boolean hasFocus) { - if (JdkComboBox.this.isEnabled()) { - if (value instanceof InvalidJdkComboBoxItem) { - final String str = value.toString(); - append(str, SimpleTextAttributes.ERROR_ATTRIBUTES); - } - else if (value instanceof ProjectJdkComboBoxItem){ - final Sdk jdk = jdksModel.getProjectSdk(); - if (jdk != null){ - setIcon(jdk.getSdkType().getIcon()); - append(ProjectBundle.message("project.roots.project.jdk.inherited"), SimpleTextAttributes.REGULAR_ATTRIBUTES); - append(" (" + jdk.getName() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } else { - final String str = value.toString(); - append(str, SimpleTextAttributes.ERROR_ATTRIBUTES); - } - } - else { - super.doCustomize(list, value != null ? ((JdkComboBoxItem)value).getJdk() : new NoneJdkComboBoxItem(), index, selected, - hasFocus); - } - } - } - }); - } - - @Override - public Dimension getPreferredSize() { - final Rectangle rec = ScreenUtil.getScreenRectangle(0, 0); - final Dimension size = super.getPreferredSize(); - final int maxWidth = rec.width / 4; - if (size.width > maxWidth) { - size.width = maxWidth; - } - return size; - } - - @Override - public Dimension getMinimumSize() { - final Dimension minSize = super.getMinimumSize(); - final Dimension prefSize = getPreferredSize(); - if (minSize.width > prefSize.width) { - minSize.width = prefSize.width; - } - return minSize; - } - - public JButton createSetupButton(final Project project, final ProjectSdksModel jdksModel, final JdkComboBoxItem firstItem) { - return createSetupButton(project, jdksModel, firstItem, null, false); - } - - - public JButton createSetupButton(final Project project, - final ProjectSdksModel jdksModel, - final JdkComboBoxItem firstItem, - final Condition additionalSetup, - final boolean moduleJdkSetup) { - final JButton setUpButton = new JButton(ApplicationBundle.message("button.new")); - setUpButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final JdkListConfigurable configurable = JdkListConfigurable.getInstance(project); - DefaultActionGroup group = new DefaultActionGroup(); - jdksModel.createAddActions(group, JdkComboBox.this, new Consumer() { - public void consume(final Sdk jdk) { - configurable.addJdkNode(jdk, false); - reloadModel(firstItem, project); - setSelectedJdk(jdk); //restore selection - if (additionalSetup != null) { - if (additionalSetup.value(jdk)) { //leave old selection - setSelectedJdk(firstItem.getJdk()); - } - } - } - }); - JBPopupFactory.getInstance() - .createActionGroupPopup(ProjectBundle.message("project.roots.set.up.jdk.title", moduleJdkSetup ? 1 : 2), group, - DataManager.getInstance().getDataContext(), JBPopupFactory.ActionSelectionAid.MNEMONICS, false) - .showUnderneathOf(setUpButton); - } - }); - return setUpButton; - } - - public void appendEditButton(final Project project, final JPanel panel, GridBagConstraints gc, final Computable retrieveJDK){ - myEditButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final Sdk projectJdk = retrieveJDK.compute(); - ProjectStructureConfigurable.getInstance(project).select(projectJdk, true); - } - }); - addActionListener(new ActionListener(){ - public void actionPerformed(ActionEvent e) { - final JdkComboBoxItem selectedItem = getSelectedItem(); - if (selectedItem instanceof ProjectJdkComboBoxItem) { - myEditButton.setEnabled(ProjectStructureConfigurable.getInstance(project).getProjectJdksModel().getProjectSdk() != null); - } else { - myEditButton.setEnabled(!(selectedItem instanceof InvalidJdkComboBoxItem) && selectedItem != null && selectedItem.getJdk() != null); - } - } - }); - panel.add(myEditButton, gc); - } - - public JdkComboBoxItem getSelectedItem() { - return (JdkComboBoxItem)super.getSelectedItem(); - } - - public Sdk getSelectedJdk() { - final JdkComboBoxItem selectedItem = (JdkComboBoxItem)super.getSelectedItem(); - return selectedItem != null? selectedItem.getJdk() : null; - } - - public void setSelectedJdk(Sdk jdk) { - final int index = indexOf(jdk); - if (index >= 0) { - setSelectedIndex(index); - } - } - - public void setInvalidJdk(String name) { - removeInvalidElement(); - addItem(new InvalidJdkComboBoxItem(name)); - setSelectedIndex(getModel().getSize() - 1); - } - - private int indexOf(Sdk jdk) { - final JdkComboBoxModel model = (JdkComboBoxModel)getModel(); - final int count = model.getSize(); - for (int idx = 0; idx < count; idx++) { - final JdkComboBoxItem elementAt = model.getElementAt(idx); - if (jdk == null) { - if (elementAt instanceof NoneJdkComboBoxItem || elementAt instanceof ProjectJdkComboBoxItem) { - return idx; - } - } - else { - if (jdk.equals(elementAt.getJdk())) { - return idx; - } - } - } - return -1; - } - - private void removeInvalidElement() { - final JdkComboBoxModel model = (JdkComboBoxModel)getModel(); - final int count = model.getSize(); - for (int idx = 0; idx < count; idx++) { - final JdkComboBoxItem elementAt = model.getElementAt(idx); - if (elementAt instanceof InvalidJdkComboBoxItem) { - removeItemAt(idx); - break; - } - } - } - - public void reloadModel(JdkComboBoxItem firstItem, Project project) { - final DefaultComboBoxModel model = ((DefaultComboBoxModel)getModel()); - model.removeAllElements(); - model.addElement(firstItem); - final ProjectSdksModel projectJdksModel = ProjectStructureConfigurable.getInstance(project).getProjectJdksModel(); - final ArrayList projectJdks = new ArrayList(projectJdksModel.getProjectSdks().values()); - Collections.sort(projectJdks, new Comparator() { - public int compare(final Sdk o1, final Sdk o2) { - return o1.getName().compareToIgnoreCase(o2.getName()); - } - }); - for (Sdk projectJdk : projectJdks) { - model.addElement(new JdkComboBox.JdkComboBoxItem(projectJdk)); - } - } - - private static class JdkComboBoxModel extends DefaultComboBoxModel { - public JdkComboBoxModel(final ProjectSdksModel jdksModel) { - super(); - final Sdk[] jdks = jdksModel.getSdks(); - Arrays.sort(jdks, new Comparator() { - public int compare(final Sdk s1, final Sdk s2) { - return s1.getName().compareToIgnoreCase(s2.getName()); - } - }); - for (Sdk jdk : jdks) { - addElement(new JdkComboBoxItem(jdk)); - } - } - - // implements javax.swing.ListModel - public JdkComboBoxItem getElementAt(int index) { - return (JdkComboBoxItem)super.getElementAt(index); - } - } - - public static class JdkComboBoxItem { - private final Sdk myJdk; - - public JdkComboBoxItem(Sdk jdk) { - myJdk = jdk; - } - - public Sdk getJdk() { - return myJdk; - } - - public String toString() { - return myJdk.getName(); - } - } - - public static class ProjectJdkComboBoxItem extends JdkComboBoxItem { - public ProjectJdkComboBoxItem() { - super(null); - } - - public String toString() { - return ProjectBundle.message("jdk.combo.box.project.item"); - } - } - - public static class NoneJdkComboBoxItem extends JdkComboBoxItem { - public NoneJdkComboBoxItem() { - super(null); - } - - public String toString() { - return ProjectBundle.message("jdk.combo.box.none.item"); - } - } - - private static class InvalidJdkComboBoxItem extends JdkComboBoxItem { - private final String myName; - - public InvalidJdkComboBoxItem(String name) { - super(null); - myName = ProjectBundle.message("jdk.combo.box.invalid.item", name); - } - - public String toString() { - return myName; - } - } -} +/* + * 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.roots.ui.configuration; + +import com.intellij.ide.DataManager; +import com.intellij.ide.util.projectWizard.ProjectJdkListRenderer; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.application.ApplicationBundle; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.ui.configuration.projectRoot.JdkListConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; +import com.intellij.openapi.ui.ComboBoxWithWidePopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Condition; +import com.intellij.ui.ScreenUtil; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Consumer; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; + +/** + * @author Eugene Zhuravlev + * Date: May 18, 2005 + */ +class JdkComboBox extends ComboBoxWithWidePopup { + private final JButton myEditButton = new JButton(ApplicationBundle.message("button.edit")); + + public JdkComboBox(final ProjectSdksModel jdksModel) { + super(new JdkComboBoxModel(jdksModel)); + setRenderer(new ProjectJdkListRenderer(getRenderer()) { + @Override + public void doCustomize(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (JdkComboBox.this.isEnabled()) { + if (value instanceof InvalidJdkComboBoxItem) { + final String str = value.toString(); + append(str, SimpleTextAttributes.ERROR_ATTRIBUTES); + } + else if (value instanceof ProjectJdkComboBoxItem){ + final Sdk jdk = jdksModel.getProjectSdk(); + if (jdk != null){ + setIcon(jdk.getSdkType().getIcon()); + append(ProjectBundle.message("project.roots.project.jdk.inherited"), SimpleTextAttributes.REGULAR_ATTRIBUTES); + append(" (" + jdk.getName() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); + } else { + final String str = value.toString(); + append(str, SimpleTextAttributes.ERROR_ATTRIBUTES); + } + } + else { + super.doCustomize(list, value != null ? ((JdkComboBoxItem)value).getJdk() : new NoneJdkComboBoxItem(), index, selected, + hasFocus); + } + } + } + }); + } + + @Override + public Dimension getPreferredSize() { + final Rectangle rec = ScreenUtil.getScreenRectangle(0, 0); + final Dimension size = super.getPreferredSize(); + final int maxWidth = rec.width / 4; + if (size.width > maxWidth) { + size.width = maxWidth; + } + return size; + } + + @Override + public Dimension getMinimumSize() { + final Dimension minSize = super.getMinimumSize(); + final Dimension prefSize = getPreferredSize(); + if (minSize.width > prefSize.width) { + minSize.width = prefSize.width; + } + return minSize; + } + + public JButton createSetupButton(final Project project, final ProjectSdksModel jdksModel, final JdkComboBoxItem firstItem) { + return createSetupButton(project, jdksModel, firstItem, null, false); + } + + + public JButton createSetupButton(final Project project, + final ProjectSdksModel jdksModel, + final JdkComboBoxItem firstItem, + final Condition additionalSetup, + final boolean moduleJdkSetup) { + final JButton setUpButton = new JButton(ApplicationBundle.message("button.new")); + setUpButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final JdkListConfigurable configurable = JdkListConfigurable.getInstance(project); + DefaultActionGroup group = new DefaultActionGroup(); + jdksModel.createAddActions(group, JdkComboBox.this, new Consumer() { + public void consume(final Sdk jdk) { + configurable.addJdkNode(jdk, false); + reloadModel(firstItem, project); + setSelectedJdk(jdk); //restore selection + if (additionalSetup != null) { + if (additionalSetup.value(jdk)) { //leave old selection + setSelectedJdk(firstItem.getJdk()); + } + } + } + }); + JBPopupFactory.getInstance() + .createActionGroupPopup(ProjectBundle.message("project.roots.set.up.jdk.title", moduleJdkSetup ? 1 : 2), group, + DataManager.getInstance().getDataContext(), JBPopupFactory.ActionSelectionAid.MNEMONICS, false) + .showUnderneathOf(setUpButton); + } + }); + return setUpButton; + } + + public void appendEditButton(final Project project, final JPanel panel, GridBagConstraints gc, final Computable retrieveJDK){ + myEditButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final Sdk projectJdk = retrieveJDK.compute(); + ProjectStructureConfigurable.getInstance(project).select(projectJdk, true); + } + }); + addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + final JdkComboBoxItem selectedItem = getSelectedItem(); + if (selectedItem instanceof ProjectJdkComboBoxItem) { + myEditButton.setEnabled(ProjectStructureConfigurable.getInstance(project).getProjectJdksModel().getProjectSdk() != null); + } else { + myEditButton.setEnabled(!(selectedItem instanceof InvalidJdkComboBoxItem) && selectedItem != null && selectedItem.getJdk() != null); + } + } + }); + panel.add(myEditButton, gc); + } + + public JdkComboBoxItem getSelectedItem() { + return (JdkComboBoxItem)super.getSelectedItem(); + } + + public Sdk getSelectedJdk() { + final JdkComboBoxItem selectedItem = (JdkComboBoxItem)super.getSelectedItem(); + return selectedItem != null? selectedItem.getJdk() : null; + } + + public void setSelectedJdk(Sdk jdk) { + final int index = indexOf(jdk); + if (index >= 0) { + setSelectedIndex(index); + } + } + + public void setInvalidJdk(String name) { + removeInvalidElement(); + addItem(new InvalidJdkComboBoxItem(name)); + setSelectedIndex(getModel().getSize() - 1); + } + + private int indexOf(Sdk jdk) { + final JdkComboBoxModel model = (JdkComboBoxModel)getModel(); + final int count = model.getSize(); + for (int idx = 0; idx < count; idx++) { + final JdkComboBoxItem elementAt = model.getElementAt(idx); + if (jdk == null) { + if (elementAt instanceof NoneJdkComboBoxItem || elementAt instanceof ProjectJdkComboBoxItem) { + return idx; + } + } + else { + if (jdk.equals(elementAt.getJdk())) { + return idx; + } + } + } + return -1; + } + + private void removeInvalidElement() { + final JdkComboBoxModel model = (JdkComboBoxModel)getModel(); + final int count = model.getSize(); + for (int idx = 0; idx < count; idx++) { + final JdkComboBoxItem elementAt = model.getElementAt(idx); + if (elementAt instanceof InvalidJdkComboBoxItem) { + removeItemAt(idx); + break; + } + } + } + + public void reloadModel(JdkComboBoxItem firstItem, Project project) { + final DefaultComboBoxModel model = ((DefaultComboBoxModel)getModel()); + model.removeAllElements(); + model.addElement(firstItem); + final ProjectSdksModel projectJdksModel = ProjectStructureConfigurable.getInstance(project).getProjectJdksModel(); + final ArrayList projectJdks = new ArrayList(projectJdksModel.getProjectSdks().values()); + Collections.sort(projectJdks, new Comparator() { + public int compare(final Sdk o1, final Sdk o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }); + for (Sdk projectJdk : projectJdks) { + model.addElement(new JdkComboBox.JdkComboBoxItem(projectJdk)); + } + } + + private static class JdkComboBoxModel extends DefaultComboBoxModel { + public JdkComboBoxModel(final ProjectSdksModel jdksModel) { + super(); + final Sdk[] jdks = jdksModel.getSdks(); + Arrays.sort(jdks, new Comparator() { + public int compare(final Sdk s1, final Sdk s2) { + return s1.getName().compareToIgnoreCase(s2.getName()); + } + }); + for (Sdk jdk : jdks) { + addElement(new JdkComboBoxItem(jdk)); + } + } + + // implements javax.swing.ListModel + public JdkComboBoxItem getElementAt(int index) { + return (JdkComboBoxItem)super.getElementAt(index); + } + } + + public static class JdkComboBoxItem { + private final Sdk myJdk; + + public JdkComboBoxItem(Sdk jdk) { + myJdk = jdk; + } + + public Sdk getJdk() { + return myJdk; + } + + public String toString() { + return myJdk.getName(); + } + } + + public static class ProjectJdkComboBoxItem extends JdkComboBoxItem { + public ProjectJdkComboBoxItem() { + super(null); + } + + public String toString() { + return ProjectBundle.message("jdk.combo.box.project.item"); + } + } + + public static class NoneJdkComboBoxItem extends JdkComboBoxItem { + public NoneJdkComboBoxItem() { + super(null); + } + + public String toString() { + return ProjectBundle.message("jdk.combo.box.none.item"); + } + } + + private static class InvalidJdkComboBoxItem extends JdkComboBoxItem { + private final String myName; + + public InvalidJdkComboBoxItem(String name) { + super(null); + myName = ProjectBundle.message("jdk.combo.box.invalid.item", name); + } + + public String toString() { + return myName; + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/LanguageLevelCombo.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/LanguageLevelCombo.java index df4c79d4691a..b854fbad9839 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/LanguageLevelCombo.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/LanguageLevelCombo.java @@ -1,60 +1,60 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.ide.ui.ListCellRendererWrapper; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.roots.LanguageLevelProjectExtension; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.pom.java.LanguageLevel; - -import javax.swing.*; - -/** - * @author ven - */ -public class LanguageLevelCombo extends ComboBox { - public static final String USE_PROJECT_LANGUAGE_LEVEL = ProjectBundle.message("project.language.level.combo.item"); - - public LanguageLevelCombo() { - for (LanguageLevel level : LanguageLevel.values()) { - addItem(level); - } - setRenderer(new ListCellRendererWrapper(getRenderer()) { - @Override - public void customize(final JList list, final Object value, final int index, final boolean selected, final boolean hasFocus) { - if (value instanceof LanguageLevel) { - setText(((LanguageLevel)value).getPresentableText()); - } - else if (value instanceof String) { - setText((String)value); - } - } - }); - } - - public void reset(Project project) { - setSelectedItem(LanguageLevelProjectExtension.getInstance(project).getLanguageLevel()); - } - - public void setSelectedItem(Object anObject) { - if (anObject == null) { - anObject = USE_PROJECT_LANGUAGE_LEVEL; - } - super.setSelectedItem(anObject); - } -} +/* + * 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.roots.ui.configuration; + +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.pom.java.LanguageLevel; + +import javax.swing.*; + +/** + * @author ven + */ +public class LanguageLevelCombo extends ComboBox { + public static final String USE_PROJECT_LANGUAGE_LEVEL = ProjectBundle.message("project.language.level.combo.item"); + + public LanguageLevelCombo() { + for (LanguageLevel level : LanguageLevel.values()) { + addItem(level); + } + setRenderer(new ListCellRendererWrapper(getRenderer()) { + @Override + public void customize(final JList list, final Object value, final int index, final boolean selected, final boolean hasFocus) { + if (value instanceof LanguageLevel) { + setText(((LanguageLevel)value).getPresentableText()); + } + else if (value instanceof String) { + setText((String)value); + } + } + }); + } + + public void reset(Project project) { + setSelectedItem(LanguageLevelProjectExtension.getInstance(project).getLanguageLevel()); + } + + public void setSelectedItem(Object anObject) { + if (anObject == null) { + anObject = USE_PROJECT_LANGUAGE_LEVEL; + } + super.setSelectedItem(anObject); + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java index fe08e5f46b16..59e25af14f35 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleEditor.java @@ -1,612 +1,612 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.facet.impl.ProjectFacetsConfigurator; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleConfigurationEditor; -import com.intellij.openapi.module.impl.ModuleConfigurationStateImpl; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.ModuleRootModel; -import com.intellij.openapi.roots.OrderEntry; -import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.ui.TabbedPaneWrapper; -import com.intellij.ui.navigation.History; -import com.intellij.ui.navigation.Place; -import com.intellij.util.EventDispatcher; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import java.awt.*; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.*; -import java.util.List; - -/** - * @author Eugene Zhuravlev - * Date: Oct 4, 2003 - * Time: 6:29:56 PM - */ -@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"}) -public abstract class ModuleEditor implements Place.Navigator, Disposable { - - public static final String MODULE_TAB = "moduleTab"; - private final Project myProject; - private JPanel myGenericSettingsPanel; - private ModifiableRootModel myModifiableRootModel; // important: in order to correctly update OrderEntries UI use corresponding proxy for the model - - private static String ourSelectedTabName; - - private TabbedPaneWrapper myTabbedPane; - private final ModulesProvider myModulesProvider; - private String myName; - private final Module myModule; - - private final List myEditors = new ArrayList(); - private ModifiableRootModel myModifiableRootModelProxy; - - private final EventDispatcher myEventDispatcher = EventDispatcher.create(ChangeListener.class); - @NonNls private static final String METHOD_COMMIT = "commit"; - - private History myHistory; - - public ModuleEditor(Project project, ModulesProvider modulesProvider, - @NotNull Module module) { - myProject = project; - myModulesProvider = modulesProvider; - myModule = module; - myName = module.getName(); - } - - public void init(final String selectedTab, History history) { - myHistory = history; - - for (ModuleConfigurationEditor each : myEditors) { - if (each instanceof ModuleElementsEditor) { - ((ModuleElementsEditor)each).setHistory(myHistory); - } - } - - setSelectedTabName(selectedTab); - } - - public abstract ProjectFacetsConfigurator getFacetsConfigurator(); - - public interface ChangeListener extends EventListener { - void moduleStateChanged(ModifiableRootModel moduleRootModel); - } - - public void addChangeListener(ChangeListener listener) { - myEventDispatcher.addListener(listener); - } - - public void removeChangeListener(ChangeListener listener) { - myEventDispatcher.removeListener(listener); - } - - @Nullable - public Module getModule() { - final Module[] all = myModulesProvider.getModules(); - for (Module each : all) { - if (each == myModule) return myModule; - } - - return myModulesProvider.getModule(myName); - } - - public ModifiableRootModel getModifiableRootModel() { - if (myModifiableRootModel == null) { - final Module module = getModule(); - if (module != null) { - myModifiableRootModel = ((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).getModifiableModel(new UIRootConfigurationAccessor(myProject)); - } - } - return myModifiableRootModel; - } - - public OrderEntry[] getOrderEntries() { - if (myModifiableRootModel == null) { // do not clone all model if not necessary - return ModuleRootManager.getInstance(getModule()).getOrderEntries(); - } - else { - return myModifiableRootModel.getOrderEntries(); - } - } - - public ModifiableRootModel getModifiableRootModelProxy() { - if (myModifiableRootModelProxy == null) { - final ModifiableRootModel rootModel = getModifiableRootModel(); - if (rootModel != null) { - myModifiableRootModelProxy = (ModifiableRootModel)Proxy.newProxyInstance( - getClass().getClassLoader(), new Class[]{ModifiableRootModel.class}, new ModifiableRootModelInvocationHandler(rootModel) - ); - } - } - return myModifiableRootModelProxy; - } - - public ModuleRootModel getRootModel() { - if (myModifiableRootModel != null) { - return getModifiableRootModelProxy(); - } - return ModuleRootManager.getInstance(myModule); - } - - public boolean isModified() { - for (ModuleConfigurationEditor moduleElementsEditor : myEditors) { - if (moduleElementsEditor.isModified()) { - return true; - } - } - return false; - } - - private void createEditors(Module module) { - ModuleConfigurationEditorProvider[] providers = collectProviders(module); - ModuleConfigurationState state = createModuleConfigurationState(); - List moduleLevelProviders = new ArrayList(); - for (ModuleConfigurationEditorProvider provider : providers) { - if (provider instanceof ModuleLevelConfigurablesEditorProvider) { - moduleLevelProviders.add((ModuleLevelConfigurablesEditorProvider)provider); - continue; - } - processEditorsProvider(provider, state); - } - for (ModuleLevelConfigurablesEditorProvider provider : moduleLevelProviders) { - processEditorsProvider(provider, state); - } - } - - private static ModuleConfigurationEditorProvider[] collectProviders(final Module module) { - List result = new ArrayList(); - ContainerUtil.addAll(result, module.getComponents(ModuleConfigurationEditorProvider.class)); - ContainerUtil.addAll(result, Extensions.getExtensions(ModuleConfigurationEditorProvider.EP_NAME, module)); - return result.toArray(new ModuleConfigurationEditorProvider[result.size()]); - } - - public ModuleConfigurationState createModuleConfigurationState() { - return new ModuleConfigurationStateImpl(myProject, myModulesProvider) { - @Override - public ModifiableRootModel getRootModel() { - return getModifiableRootModelProxy(); - } - - @Override - public FacetsProvider getFacetsProvider() { - return getFacetsConfigurator(); - } - }; - } - - private void processEditorsProvider(final ModuleConfigurationEditorProvider provider, final ModuleConfigurationState state) { - final ModuleConfigurationEditor[] editors = provider.createEditors(state); - ContainerUtil.addAll(myEditors, editors); - } - - private JPanel createPanel() { - getModifiableRootModel(); //initialize model if needed - getModifiableRootModelProxy(); - - myGenericSettingsPanel = new ModuleEditorPanel(); - - createEditors(getModule()); - - JPanel northPanel = new JPanel(new GridBagLayout()); - - myGenericSettingsPanel.add(northPanel, BorderLayout.NORTH); - - myTabbedPane = new TabbedPaneWrapper(this); - - for (ModuleConfigurationEditor editor : myEditors) { - myTabbedPane.addTab(editor.getDisplayName(), editor.getIcon(), editor.createComponent(), null); - editor.reset(); - } - setSelectedTabName(ourSelectedTabName); - - myGenericSettingsPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); - myTabbedPane.addChangeListener(new javax.swing.event.ChangeListener() { - public void stateChanged(ChangeEvent e) { - ourSelectedTabName = getSelectedTabName(); - if (myHistory != null) { - myHistory.pushQueryPlace(); - } - } - }); - - return myGenericSettingsPanel; - } - - public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { - myTabbedPane.setSelectedTitle((String)place.getPath(MODULE_TAB)); - return new ActionCallback.Done(); - } - - public void queryPlace(@NotNull final Place place) { - place.putPath(MODULE_TAB, ourSelectedTabName); - } - - public static String getSelectedTab(){ - return ourSelectedTabName; - } - - private int getEditorTabIndex(final String editorName) { - if (myTabbedPane != null && editorName != null) { - final int tabCount = myTabbedPane.getTabCount(); - for (int idx = 0; idx < tabCount; idx++) { - if (editorName.equals(myTabbedPane.getTitleAt(idx))) { - return idx; - } - } - } - return -1; - } - - public JPanel getPanel() { - if (myGenericSettingsPanel == null) { - myGenericSettingsPanel = createPanel(); - } - - return myGenericSettingsPanel; - } - - public void moduleCountChanged() { - updateOrderEntriesInEditors(); - } - - private void updateOrderEntriesInEditors() { - if (getModule() != null) { //module with attached module libraries was deleted - getPanel(); //init editor if needed - for (final ModuleConfigurationEditor myEditor : myEditors) { - myEditor.moduleStateChanged(); - } - myEventDispatcher.getMulticaster().moduleStateChanged(getModifiableRootModelProxy()); - } - } - - public void updateCompilerOutputPathChanged(String baseUrl, String moduleName){ - if (myGenericSettingsPanel == null) return; //wasn't initialized yet - for (final ModuleConfigurationEditor myEditor : myEditors) { - if (myEditor instanceof ModuleElementsEditor) { - ((ModuleElementsEditor)myEditor).moduleCompileOutputChanged(baseUrl, moduleName); - } - } - } - - public void dispose() { - try { - for (final ModuleConfigurationEditor myEditor : myEditors) { - myEditor.disposeUIResources(); - } - - myEditors.clear(); - - if (myTabbedPane != null) { - ourSelectedTabName = getSelectedTabName(); - myTabbedPane = null; - } - - if (myModifiableRootModel != null) { - myModifiableRootModel.dispose(); - } - - myGenericSettingsPanel = null; - } - finally { - myModifiableRootModel = null; - myModifiableRootModelProxy = null; - } - } - - public ModifiableRootModel apply() throws ConfigurationException { - try { - for (ModuleConfigurationEditor editor : myEditors) { - editor.saveData(); - editor.apply(); - } - - return myModifiableRootModel; - } - finally { - myModifiableRootModel = null; - myModifiableRootModelProxy = null; - } - } - - public void canApply() throws ConfigurationException { - for (ModuleConfigurationEditor editor : myEditors) { - if (editor instanceof ModuleElementsEditor) { - ((ModuleElementsEditor)editor).canApply(); - } - } - } - - public String getName() { - return myName; - } - - @Nullable - public String getSelectedTabName() { - return myTabbedPane == null || myTabbedPane.getSelectedIndex() == -1 ? null : myTabbedPane.getTitleAt(myTabbedPane.getSelectedIndex()); - } - - public void setSelectedTabName(@Nullable String name) { - if (name != null) { - getPanel(); - final int editorTabIndex = getEditorTabIndex(name); - if (editorTabIndex >= 0 && editorTabIndex < myTabbedPane.getTabCount()) { - myTabbedPane.setSelectedIndex(editorTabIndex); - ourSelectedTabName = name; - } - } - } - - @Nullable - public ModuleConfigurationEditor getEditor(@NotNull String tabName) { - int index = getEditorTabIndex(tabName); - if (0 <= index && index < myEditors.size()) { - return myEditors.get(index); - } - return null; - } - - private class ModifiableRootModelInvocationHandler implements InvocationHandler { - private final ModifiableRootModel myDelegateModel; - @NonNls private final Set myCheckedNames = new HashSet( - Arrays.asList("addOrderEntry", "addLibraryEntry", "addInvalidLibrary", "addModuleOrderEntry", "addInvalidModuleEntry", - "removeOrderEntry", "setSdk", "inheritSdk", "inheritCompilerOutputPath", "setExcludeOutput", "replaceEntryOfType")); - - ModifiableRootModelInvocationHandler(ModifiableRootModel model) { - myDelegateModel = model; - } - - public Object invoke(Object object, Method method, Object[] params) throws Throwable { - final boolean needUpdate = myCheckedNames.contains(method.getName()); - try { - final Object result = method.invoke(myDelegateModel, unwrapParams(params)); - if (result instanceof LibraryTable) { - return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTable.class}, - new LibraryTableInvocationHandler((LibraryTable)result)); - } - return result; - } - catch (InvocationTargetException e) { - throw e.getCause(); - } - finally { - if (needUpdate) { - updateOrderEntriesInEditors(); - } - } - } - } - - private class LibraryTableInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { - private final LibraryTable myDelegateTable; - @NonNls private final Set myCheckedNames = new HashSet(Arrays.asList("removeLibrary" /*,"createLibrary"*/)); - - LibraryTableInvocationHandler(LibraryTable table) { - myDelegateTable = table; - } - - public Object invoke(Object object, Method method, Object[] params) throws Throwable { - final boolean needUpdate = myCheckedNames.contains(method.getName()); - try { - final Object result = method.invoke(myDelegateTable, unwrapParams(params)); - if (result instanceof Library) { - return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, - new LibraryInvocationHandler((Library)result)); - } - else if (result instanceof LibraryTable.ModifiableModel) { - return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTable.ModifiableModel.class}, - new LibraryTableModelInvocationHandler((LibraryTable.ModifiableModel)result)); - } - if (result instanceof Library[]) { - Library[] libraries = (Library[])result; - for (int idx = 0; idx < libraries.length; idx++) { - Library library = libraries[idx]; - libraries[idx] = - (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, - new LibraryInvocationHandler(library)); - } - } - return result; - } - catch (InvocationTargetException e) { - throw e.getCause(); - } - finally { - if (needUpdate) { - updateOrderEntriesInEditors(); - } - } - } - - @Override - public Object getDelegate() { - return myDelegateTable; - } - } - - private class LibraryInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { - private final Library myDelegateLibrary; - - LibraryInvocationHandler(Library delegateLibrary) { - myDelegateLibrary = delegateLibrary; - } - - public Object invoke(Object object, Method method, Object[] params) throws Throwable { - try { - final Object result = method.invoke(myDelegateLibrary, unwrapParams(params)); - if (result instanceof Library.ModifiableModel) { - return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.ModifiableModel.class}, - new LibraryModifiableModelInvocationHandler((Library.ModifiableModel)result)); - } - return result; - } - catch (InvocationTargetException e) { - throw e.getCause(); - } - } - - public Object getDelegate() { - return myDelegateLibrary; - } - } - - private class LibraryModifiableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { - private final Library.ModifiableModel myDelegateModel; - - LibraryModifiableModelInvocationHandler(Library.ModifiableModel delegateModel) { - myDelegateModel = delegateModel; - } - - public Object invoke(Object object, Method method, Object[] params) throws Throwable { - final boolean needUpdate = METHOD_COMMIT.equals(method.getName()); - try { - return method.invoke(myDelegateModel, unwrapParams(params)); - } - catch (InvocationTargetException e) { - throw e.getCause(); - } - finally { - if (needUpdate) { - updateOrderEntriesInEditors(); - } - } - } - - @Override - public Object getDelegate() { - return myDelegateModel; - } - } - - private class LibraryTableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { - private final LibraryTable.ModifiableModel myDelegateModel; - - LibraryTableModelInvocationHandler(LibraryTable.ModifiableModel delegateModel) { - myDelegateModel = delegateModel; - } - - public Object invoke(Object object, Method method, Object[] params) throws Throwable { - final boolean needUpdate = METHOD_COMMIT.equals(method.getName()); - try { - Object result = method.invoke(myDelegateModel, unwrapParams(params)); - if (result instanceof Library[]) { - Library[] libraries = (Library[])result; - for (int idx = 0; idx < libraries.length; idx++) { - Library library = libraries[idx]; - libraries[idx] = - (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, - new LibraryInvocationHandler(library)); - } - } - if (result instanceof Library) { - result = - Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, - new LibraryInvocationHandler((Library)result)); - } - return result; - } - catch (InvocationTargetException e) { - throw e.getCause(); - } - finally { - if (needUpdate) { - updateOrderEntriesInEditors(); - } - } - } - - @Override - public Object getDelegate() { - return myDelegateModel; - } - } - - public interface ProxyDelegateAccessor { - Object getDelegate(); - } - - private static Object[] unwrapParams(Object[] params) { - if (params == null || params.length == 0) { - return params; - } - final Object[] unwrappedParams = new Object[params.length]; - for (int idx = 0; idx < params.length; idx++) { - Object param = params[idx]; - if (param != null && Proxy.isProxyClass(param.getClass())) { - final InvocationHandler invocationHandler = Proxy.getInvocationHandler(param); - if (invocationHandler instanceof ProxyDelegateAccessor) { - param = ((ProxyDelegateAccessor)invocationHandler).getDelegate(); - } - } - unwrappedParams[idx] = param; - } - return unwrappedParams; - } - - @Nullable - public String getHelpTopic() { - if (myTabbedPane == null || myEditors.isEmpty()) { - return null; - } - final int selectedIdx = myTabbedPane.getSelectedIndex(); - if (selectedIdx == -1) { - return null; - } - final ModuleConfigurationEditor moduleElementsEditor = myEditors.get(selectedIdx); - return moduleElementsEditor.getHelpTopic(); - } - - public void setModuleName(final String name) { - myName = name; - } - - private class ModuleEditorPanel extends JPanel implements DataProvider{ - public ModuleEditorPanel() { - super(new BorderLayout()); - } - - public Object getData(String dataId) { - if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { - return getModule(); - } - return null; - } - - } - - public void setHistory(final History history) { - } -} +/* + * 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.roots.ui.configuration; + +import com.intellij.facet.impl.ProjectFacetsConfigurator; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleConfigurationEditor; +import com.intellij.openapi.module.impl.ModuleConfigurationStateImpl; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ModuleRootModel; +import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.ui.TabbedPaneWrapper; +import com.intellij.ui.navigation.History; +import com.intellij.ui.navigation.Place; +import com.intellij.util.EventDispatcher; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import java.awt.*; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.List; + +/** + * @author Eugene Zhuravlev + * Date: Oct 4, 2003 + * Time: 6:29:56 PM + */ +@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"}) +public abstract class ModuleEditor implements Place.Navigator, Disposable { + + public static final String MODULE_TAB = "moduleTab"; + private final Project myProject; + private JPanel myGenericSettingsPanel; + private ModifiableRootModel myModifiableRootModel; // important: in order to correctly update OrderEntries UI use corresponding proxy for the model + + private static String ourSelectedTabName; + + private TabbedPaneWrapper myTabbedPane; + private final ModulesProvider myModulesProvider; + private String myName; + private final Module myModule; + + private final List myEditors = new ArrayList(); + private ModifiableRootModel myModifiableRootModelProxy; + + private final EventDispatcher myEventDispatcher = EventDispatcher.create(ChangeListener.class); + @NonNls private static final String METHOD_COMMIT = "commit"; + + private History myHistory; + + public ModuleEditor(Project project, ModulesProvider modulesProvider, + @NotNull Module module) { + myProject = project; + myModulesProvider = modulesProvider; + myModule = module; + myName = module.getName(); + } + + public void init(final String selectedTab, History history) { + myHistory = history; + + for (ModuleConfigurationEditor each : myEditors) { + if (each instanceof ModuleElementsEditor) { + ((ModuleElementsEditor)each).setHistory(myHistory); + } + } + + setSelectedTabName(selectedTab); + } + + public abstract ProjectFacetsConfigurator getFacetsConfigurator(); + + public interface ChangeListener extends EventListener { + void moduleStateChanged(ModifiableRootModel moduleRootModel); + } + + public void addChangeListener(ChangeListener listener) { + myEventDispatcher.addListener(listener); + } + + public void removeChangeListener(ChangeListener listener) { + myEventDispatcher.removeListener(listener); + } + + @Nullable + public Module getModule() { + final Module[] all = myModulesProvider.getModules(); + for (Module each : all) { + if (each == myModule) return myModule; + } + + return myModulesProvider.getModule(myName); + } + + public ModifiableRootModel getModifiableRootModel() { + if (myModifiableRootModel == null) { + final Module module = getModule(); + if (module != null) { + myModifiableRootModel = ((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).getModifiableModel(new UIRootConfigurationAccessor(myProject)); + } + } + return myModifiableRootModel; + } + + public OrderEntry[] getOrderEntries() { + if (myModifiableRootModel == null) { // do not clone all model if not necessary + return ModuleRootManager.getInstance(getModule()).getOrderEntries(); + } + else { + return myModifiableRootModel.getOrderEntries(); + } + } + + public ModifiableRootModel getModifiableRootModelProxy() { + if (myModifiableRootModelProxy == null) { + final ModifiableRootModel rootModel = getModifiableRootModel(); + if (rootModel != null) { + myModifiableRootModelProxy = (ModifiableRootModel)Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[]{ModifiableRootModel.class}, new ModifiableRootModelInvocationHandler(rootModel) + ); + } + } + return myModifiableRootModelProxy; + } + + public ModuleRootModel getRootModel() { + if (myModifiableRootModel != null) { + return getModifiableRootModelProxy(); + } + return ModuleRootManager.getInstance(myModule); + } + + public boolean isModified() { + for (ModuleConfigurationEditor moduleElementsEditor : myEditors) { + if (moduleElementsEditor.isModified()) { + return true; + } + } + return false; + } + + private void createEditors(Module module) { + ModuleConfigurationEditorProvider[] providers = collectProviders(module); + ModuleConfigurationState state = createModuleConfigurationState(); + List moduleLevelProviders = new ArrayList(); + for (ModuleConfigurationEditorProvider provider : providers) { + if (provider instanceof ModuleLevelConfigurablesEditorProvider) { + moduleLevelProviders.add((ModuleLevelConfigurablesEditorProvider)provider); + continue; + } + processEditorsProvider(provider, state); + } + for (ModuleLevelConfigurablesEditorProvider provider : moduleLevelProviders) { + processEditorsProvider(provider, state); + } + } + + private static ModuleConfigurationEditorProvider[] collectProviders(final Module module) { + List result = new ArrayList(); + ContainerUtil.addAll(result, module.getComponents(ModuleConfigurationEditorProvider.class)); + ContainerUtil.addAll(result, Extensions.getExtensions(ModuleConfigurationEditorProvider.EP_NAME, module)); + return result.toArray(new ModuleConfigurationEditorProvider[result.size()]); + } + + public ModuleConfigurationState createModuleConfigurationState() { + return new ModuleConfigurationStateImpl(myProject, myModulesProvider) { + @Override + public ModifiableRootModel getRootModel() { + return getModifiableRootModelProxy(); + } + + @Override + public FacetsProvider getFacetsProvider() { + return getFacetsConfigurator(); + } + }; + } + + private void processEditorsProvider(final ModuleConfigurationEditorProvider provider, final ModuleConfigurationState state) { + final ModuleConfigurationEditor[] editors = provider.createEditors(state); + ContainerUtil.addAll(myEditors, editors); + } + + private JPanel createPanel() { + getModifiableRootModel(); //initialize model if needed + getModifiableRootModelProxy(); + + myGenericSettingsPanel = new ModuleEditorPanel(); + + createEditors(getModule()); + + JPanel northPanel = new JPanel(new GridBagLayout()); + + myGenericSettingsPanel.add(northPanel, BorderLayout.NORTH); + + myTabbedPane = new TabbedPaneWrapper(this); + + for (ModuleConfigurationEditor editor : myEditors) { + myTabbedPane.addTab(editor.getDisplayName(), editor.getIcon(), editor.createComponent(), null); + editor.reset(); + } + setSelectedTabName(ourSelectedTabName); + + myGenericSettingsPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); + myTabbedPane.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(ChangeEvent e) { + ourSelectedTabName = getSelectedTabName(); + if (myHistory != null) { + myHistory.pushQueryPlace(); + } + } + }); + + return myGenericSettingsPanel; + } + + public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { + myTabbedPane.setSelectedTitle((String)place.getPath(MODULE_TAB)); + return new ActionCallback.Done(); + } + + public void queryPlace(@NotNull final Place place) { + place.putPath(MODULE_TAB, ourSelectedTabName); + } + + public static String getSelectedTab(){ + return ourSelectedTabName; + } + + private int getEditorTabIndex(final String editorName) { + if (myTabbedPane != null && editorName != null) { + final int tabCount = myTabbedPane.getTabCount(); + for (int idx = 0; idx < tabCount; idx++) { + if (editorName.equals(myTabbedPane.getTitleAt(idx))) { + return idx; + } + } + } + return -1; + } + + public JPanel getPanel() { + if (myGenericSettingsPanel == null) { + myGenericSettingsPanel = createPanel(); + } + + return myGenericSettingsPanel; + } + + public void moduleCountChanged() { + updateOrderEntriesInEditors(); + } + + private void updateOrderEntriesInEditors() { + if (getModule() != null) { //module with attached module libraries was deleted + getPanel(); //init editor if needed + for (final ModuleConfigurationEditor myEditor : myEditors) { + myEditor.moduleStateChanged(); + } + myEventDispatcher.getMulticaster().moduleStateChanged(getModifiableRootModelProxy()); + } + } + + public void updateCompilerOutputPathChanged(String baseUrl, String moduleName){ + if (myGenericSettingsPanel == null) return; //wasn't initialized yet + for (final ModuleConfigurationEditor myEditor : myEditors) { + if (myEditor instanceof ModuleElementsEditor) { + ((ModuleElementsEditor)myEditor).moduleCompileOutputChanged(baseUrl, moduleName); + } + } + } + + public void dispose() { + try { + for (final ModuleConfigurationEditor myEditor : myEditors) { + myEditor.disposeUIResources(); + } + + myEditors.clear(); + + if (myTabbedPane != null) { + ourSelectedTabName = getSelectedTabName(); + myTabbedPane = null; + } + + if (myModifiableRootModel != null) { + myModifiableRootModel.dispose(); + } + + myGenericSettingsPanel = null; + } + finally { + myModifiableRootModel = null; + myModifiableRootModelProxy = null; + } + } + + public ModifiableRootModel apply() throws ConfigurationException { + try { + for (ModuleConfigurationEditor editor : myEditors) { + editor.saveData(); + editor.apply(); + } + + return myModifiableRootModel; + } + finally { + myModifiableRootModel = null; + myModifiableRootModelProxy = null; + } + } + + public void canApply() throws ConfigurationException { + for (ModuleConfigurationEditor editor : myEditors) { + if (editor instanceof ModuleElementsEditor) { + ((ModuleElementsEditor)editor).canApply(); + } + } + } + + public String getName() { + return myName; + } + + @Nullable + public String getSelectedTabName() { + return myTabbedPane == null || myTabbedPane.getSelectedIndex() == -1 ? null : myTabbedPane.getTitleAt(myTabbedPane.getSelectedIndex()); + } + + public void setSelectedTabName(@Nullable String name) { + if (name != null) { + getPanel(); + final int editorTabIndex = getEditorTabIndex(name); + if (editorTabIndex >= 0 && editorTabIndex < myTabbedPane.getTabCount()) { + myTabbedPane.setSelectedIndex(editorTabIndex); + ourSelectedTabName = name; + } + } + } + + @Nullable + public ModuleConfigurationEditor getEditor(@NotNull String tabName) { + int index = getEditorTabIndex(tabName); + if (0 <= index && index < myEditors.size()) { + return myEditors.get(index); + } + return null; + } + + private class ModifiableRootModelInvocationHandler implements InvocationHandler { + private final ModifiableRootModel myDelegateModel; + @NonNls private final Set myCheckedNames = new HashSet( + Arrays.asList("addOrderEntry", "addLibraryEntry", "addInvalidLibrary", "addModuleOrderEntry", "addInvalidModuleEntry", + "removeOrderEntry", "setSdk", "inheritSdk", "inheritCompilerOutputPath", "setExcludeOutput", "replaceEntryOfType")); + + ModifiableRootModelInvocationHandler(ModifiableRootModel model) { + myDelegateModel = model; + } + + public Object invoke(Object object, Method method, Object[] params) throws Throwable { + final boolean needUpdate = myCheckedNames.contains(method.getName()); + try { + final Object result = method.invoke(myDelegateModel, unwrapParams(params)); + if (result instanceof LibraryTable) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTable.class}, + new LibraryTableInvocationHandler((LibraryTable)result)); + } + return result; + } + catch (InvocationTargetException e) { + throw e.getCause(); + } + finally { + if (needUpdate) { + updateOrderEntriesInEditors(); + } + } + } + } + + private class LibraryTableInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { + private final LibraryTable myDelegateTable; + @NonNls private final Set myCheckedNames = new HashSet(Arrays.asList("removeLibrary" /*,"createLibrary"*/)); + + LibraryTableInvocationHandler(LibraryTable table) { + myDelegateTable = table; + } + + public Object invoke(Object object, Method method, Object[] params) throws Throwable { + final boolean needUpdate = myCheckedNames.contains(method.getName()); + try { + final Object result = method.invoke(myDelegateTable, unwrapParams(params)); + if (result instanceof Library) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, + new LibraryInvocationHandler((Library)result)); + } + else if (result instanceof LibraryTable.ModifiableModel) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{LibraryTable.ModifiableModel.class}, + new LibraryTableModelInvocationHandler((LibraryTable.ModifiableModel)result)); + } + if (result instanceof Library[]) { + Library[] libraries = (Library[])result; + for (int idx = 0; idx < libraries.length; idx++) { + Library library = libraries[idx]; + libraries[idx] = + (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, + new LibraryInvocationHandler(library)); + } + } + return result; + } + catch (InvocationTargetException e) { + throw e.getCause(); + } + finally { + if (needUpdate) { + updateOrderEntriesInEditors(); + } + } + } + + @Override + public Object getDelegate() { + return myDelegateTable; + } + } + + private class LibraryInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { + private final Library myDelegateLibrary; + + LibraryInvocationHandler(Library delegateLibrary) { + myDelegateLibrary = delegateLibrary; + } + + public Object invoke(Object object, Method method, Object[] params) throws Throwable { + try { + final Object result = method.invoke(myDelegateLibrary, unwrapParams(params)); + if (result instanceof Library.ModifiableModel) { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.ModifiableModel.class}, + new LibraryModifiableModelInvocationHandler((Library.ModifiableModel)result)); + } + return result; + } + catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + public Object getDelegate() { + return myDelegateLibrary; + } + } + + private class LibraryModifiableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { + private final Library.ModifiableModel myDelegateModel; + + LibraryModifiableModelInvocationHandler(Library.ModifiableModel delegateModel) { + myDelegateModel = delegateModel; + } + + public Object invoke(Object object, Method method, Object[] params) throws Throwable { + final boolean needUpdate = METHOD_COMMIT.equals(method.getName()); + try { + return method.invoke(myDelegateModel, unwrapParams(params)); + } + catch (InvocationTargetException e) { + throw e.getCause(); + } + finally { + if (needUpdate) { + updateOrderEntriesInEditors(); + } + } + } + + @Override + public Object getDelegate() { + return myDelegateModel; + } + } + + private class LibraryTableModelInvocationHandler implements InvocationHandler, ProxyDelegateAccessor { + private final LibraryTable.ModifiableModel myDelegateModel; + + LibraryTableModelInvocationHandler(LibraryTable.ModifiableModel delegateModel) { + myDelegateModel = delegateModel; + } + + public Object invoke(Object object, Method method, Object[] params) throws Throwable { + final boolean needUpdate = METHOD_COMMIT.equals(method.getName()); + try { + Object result = method.invoke(myDelegateModel, unwrapParams(params)); + if (result instanceof Library[]) { + Library[] libraries = (Library[])result; + for (int idx = 0; idx < libraries.length; idx++) { + Library library = libraries[idx]; + libraries[idx] = + (Library)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, + new LibraryInvocationHandler(library)); + } + } + if (result instanceof Library) { + result = + Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.class}, + new LibraryInvocationHandler((Library)result)); + } + return result; + } + catch (InvocationTargetException e) { + throw e.getCause(); + } + finally { + if (needUpdate) { + updateOrderEntriesInEditors(); + } + } + } + + @Override + public Object getDelegate() { + return myDelegateModel; + } + } + + public interface ProxyDelegateAccessor { + Object getDelegate(); + } + + private static Object[] unwrapParams(Object[] params) { + if (params == null || params.length == 0) { + return params; + } + final Object[] unwrappedParams = new Object[params.length]; + for (int idx = 0; idx < params.length; idx++) { + Object param = params[idx]; + if (param != null && Proxy.isProxyClass(param.getClass())) { + final InvocationHandler invocationHandler = Proxy.getInvocationHandler(param); + if (invocationHandler instanceof ProxyDelegateAccessor) { + param = ((ProxyDelegateAccessor)invocationHandler).getDelegate(); + } + } + unwrappedParams[idx] = param; + } + return unwrappedParams; + } + + @Nullable + public String getHelpTopic() { + if (myTabbedPane == null || myEditors.isEmpty()) { + return null; + } + final int selectedIdx = myTabbedPane.getSelectedIndex(); + if (selectedIdx == -1) { + return null; + } + final ModuleConfigurationEditor moduleElementsEditor = myEditors.get(selectedIdx); + return moduleElementsEditor.getHelpTopic(); + } + + public void setModuleName(final String name) { + myName = name; + } + + private class ModuleEditorPanel extends JPanel implements DataProvider{ + public ModuleEditorPanel() { + super(new BorderLayout()); + } + + public Object getData(String dataId) { + if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { + return getModule(); + } + return null; + } + + } + + public void setHistory(final History history) { + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectConfigurable.java index c46b9ede27a0..19b020404d7e 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectConfigurable.java @@ -1,381 +1,381 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.compiler.ModuleCompilerUtil; -import com.intellij.ide.util.BrowseFilesListener; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.StorageScheme; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserFactory; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.project.ex.ProjectEx; -import com.intellij.openapi.roots.CompilerProjectExtension; -import com.intellij.openapi.roots.LanguageLevelProjectExtension; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectConfigurationProblems; -import com.intellij.openapi.ui.DetailsComponent; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.NamedConfigurable; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.EmptyRunnable; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.FieldPanel; -import com.intellij.ui.InsertPathAction; -import com.intellij.util.Alarm; -import com.intellij.util.Chunk; -import com.intellij.util.graph.Graph; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.IOException; -import java.util.Collection; -import java.util.Set; - -/** - * @author Eugene Zhuravlev - * Date: Dec 15, 2003 - */ -public class ProjectConfigurable extends NamedConfigurable implements DetailsComponent.Facade { - - private final Project myProject; - - private static final Icon PROJECT_ICON = IconLoader.getIcon("/nodes/project.png"); - - private boolean myStartModuleWizardOnShow; - private LanguageLevelCombo myLanguageLevelCombo; - private ProjectJdkConfigurable myProjectJdkConfigurable; - - private FieldPanel myProjectCompilerOutput; - - private JTextField myProjectName; - - private MyJPanel myPanel; - - private final Alarm myUpdateWarningAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); - - private final JLabel myWarningLabel = new JLabel(""); - private final ModulesConfigurator myModulesConfigurator; - private JPanel myWholePanel; - - private boolean myFreeze = false; - private DetailsComponent myDetailsComponent; - private CircularDependencyWarning myCircularDependencyWarning; - - public ProjectConfigurable(Project project, ModulesConfigurator configurator, ProjectSdksModel model) { - myProject = project; - myModulesConfigurator = configurator; - init(model); - } - - - public DetailsComponent getDetailsComponent() { - return myDetailsComponent; - } - - public JComponent createOptionsPanel() { - myDetailsComponent = new DetailsComponent(); - myDetailsComponent.setContent(myPanel); - myDetailsComponent.setText(getBannerSlogan()); - - myProjectJdkConfigurable.createComponent(); //reload changed jdks - - return myDetailsComponent.getComponent(); - } - - private void init(final ProjectSdksModel model) { - myPanel = new MyJPanel(); - myPanel.setPreferredSize(new Dimension(700, 500)); - - if (((ProjectEx)myProject).getStateStore().getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) { - final JPanel namePanel = new JPanel(new BorderLayout()); - final JLabel label = - new JLabel("Project name:", SwingConstants.LEFT); - namePanel.add(label, BorderLayout.NORTH); - - myProjectName = new JTextField(); - myProjectName.setColumns(40); - - final JPanel nameFieldPanel = new JPanel(); - nameFieldPanel.setLayout(new BoxLayout(nameFieldPanel, BoxLayout.X_AXIS)); - nameFieldPanel.add(Box.createHorizontalStrut(4)); - nameFieldPanel.add(myProjectName); - - namePanel.add(nameFieldPanel, BorderLayout.CENTER); - final JPanel wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT)); - wrapper.add(namePanel); - wrapper.setAlignmentX(0); - myPanel.add(wrapper, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(4, 0, 10, 0), 0, 0)); - } - - myProjectJdkConfigurable = new ProjectJdkConfigurable(myProject, model); - myPanel.add(myProjectJdkConfigurable.createComponent(), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - new Insets(4, 4, 0, 0), 0, 0)); - - myPanel.add(myWholePanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, - GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0)); - - //myWarningLabel.setUI(new MultiLineLabelUI()); - myPanel.add(myWarningLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, - GridBagConstraints.BOTH, new Insets(10, 6, 10, 0), 0, 0)); - - myProjectCompilerOutput.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { - protected void textChanged(DocumentEvent e) { - if (myFreeze) return; - myModulesConfigurator.processModuleCompilerOutputChanged(getCompilerOutputUrl()); - } - }); - } - - public void disposeUIResources() { - myUpdateWarningAlarm.cancelAllRequests(); - if (myProjectJdkConfigurable != null) { - myProjectJdkConfigurable.disposeUIResources(); - } - } - - public void reset() { - myFreeze = true; - try { - myProjectJdkConfigurable.reset(); - final String compilerOutput = CompilerProjectExtension.getInstance(myProject).getCompilerOutputUrl(); - if (compilerOutput != null) { - myProjectCompilerOutput.setText(FileUtil.toSystemDependentName(VfsUtil.urlToPath(compilerOutput))); - } - myLanguageLevelCombo.reset(myProject); - updateCircularDependencyWarning(); - - if (myProjectName != null) { - myProjectName.setText(myProject.getName()); - } - } - finally { - myFreeze = false; - } - } - - void updateCircularDependencyWarning() { - myUpdateWarningAlarm.cancelAllRequests(); - myUpdateWarningAlarm.addRequest(new Runnable() { - public void run() { - ApplicationManager.getApplication().runReadAction(new Runnable(){ - public void run() { - final Graph> graph = ModuleCompilerUtil.toChunkGraph(myModulesConfigurator.createGraphGenerator()); - final Collection> chunks = graph.getNodes(); - String cycles = ""; - int count = 0; - for (Chunk chunk : chunks) { - final Set modules = chunk.getNodes(); - String cycle = ""; - for (ModifiableRootModel model : modules) { - cycle += ", " + model.getModule().getName(); - } - if (modules.size() > 1) { - @NonNls final String br = "
    "; - cycles += br + (++count) + ". " + cycle.substring(2); - } - } - @NonNls final String leftBrace = ""; - @NonNls final String rightBrace = ""; - final String warningMessage = - leftBrace + (count > 0 ? ProjectBundle.message("module.circular.dependency.warning", cycles, count) : "") + rightBrace; - if (ProjectConfigurationProblems.isVisible()) { - if (myCircularDependencyWarning != null) { - ConfigurationErrors.Bus.removeError(myCircularDependencyWarning, myProject); - } - if (count > 0) { - myCircularDependencyWarning = new CircularDependencyWarning("Circular dependencies", warningMessage); - ConfigurationErrors.Bus.addError(myCircularDependencyWarning, myProject); - } - } - final Icon icon = count > 0 ? Messages.getWarningIcon() : null; - //noinspection SSBasedInspection - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myWarningLabel.setIcon(icon); - myWarningLabel.setText(warningMessage); - myWarningLabel.repaint(); - } - }); - } - }); - } - }, 300); - } - - - public void apply() throws ConfigurationException { - final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(myProject); - - if (myProjectName != null && myProjectName.getText().trim().length() == 0) { - throw new ConfigurationException("Please, specify project name!"); - } - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - // set the output path first so that handlers of RootsChanged event sent after JDK is set - // would see the updated path - String canonicalPath = myProjectCompilerOutput.getText(); - if (canonicalPath != null && canonicalPath.length() > 0) { - try { - canonicalPath = FileUtil.resolveShortWindowsName(canonicalPath); - } - catch (IOException e) { - //file doesn't exist yet - } - canonicalPath = FileUtil.toSystemIndependentName(canonicalPath); - compilerProjectExtension.setCompilerOutputUrl(VfsUtil.pathToUrl(canonicalPath)); - } - else { - compilerProjectExtension.setCompilerOutputPointer(null); - } - - final LanguageLevel newLevel = (LanguageLevel)myLanguageLevelCombo.getSelectedItem(); - LanguageLevelProjectExtension.getInstance(myProject).setLanguageLevel(newLevel); - try { - myProjectJdkConfigurable.apply(); - - if (myProjectName != null) { - ((ProjectEx)myProject).setProjectName(myProjectName.getText().trim()); - if (myDetailsComponent != null) myDetailsComponent.setText(getBannerSlogan()); - } - } - catch (ConfigurationException e) { - //cant't be - } - } - }); - } - - - public void setDisplayName(final String name) { - //do nothing - } - - public Project getEditableObject() { - return myProject; - } - - public String getBannerSlogan() { - return ProjectBundle.message("project.roots.project.banner.text", myProject.getName()); - } - - public String getDisplayName() { - return ProjectBundle.message("project.roots.project.display.name"); - } - - public Icon getIcon() { - return PROJECT_ICON; - } - - @Nullable - @NonNls - public String getHelpTopic() { - return "reference.settingsdialog.project.structure.general"; - } - - - @SuppressWarnings({"SimplifiableIfStatement"}) - public boolean isModified() { - if (!LanguageLevelProjectExtension.getInstance(myProject).getLanguageLevel().equals(myLanguageLevelCombo.getSelectedItem())) { - return true; - } - final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(myProject); - final String compilerOutput = compilerProjectExtension.getCompilerOutputUrl(); - if (!Comparing.strEqual(FileUtil.toSystemIndependentName(VfsUtil.urlToPath(compilerOutput)), - FileUtil.toSystemIndependentName(myProjectCompilerOutput.getText()))) return true; - if (myProjectJdkConfigurable.isModified()) return true; - if (myProjectName != null) { - if (!myProjectName.getText().trim().equals(myProject.getName())) return true; - } - - return false; - } - - private void createUIComponents() { - myLanguageLevelCombo = new LanguageLevelCombo(); - final JTextField textField = new JTextField(); - final FileChooserDescriptor outputPathsChooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false); - InsertPathAction.addTo(textField, outputPathsChooserDescriptor); - outputPathsChooserDescriptor.setHideIgnored(false); - BrowseFilesListener listener = new BrowseFilesListener(textField, "", ProjectBundle.message("project.compiler.output"), outputPathsChooserDescriptor); - myProjectCompilerOutput = new FieldPanel(textField, null, null, listener, EmptyRunnable.getInstance()); - FileChooserFactory.getInstance().installFileCompletion(myProjectCompilerOutput.getTextField(), outputPathsChooserDescriptor, true, null); - } - - public void setStartModuleWizardOnShow(final boolean show) { - myStartModuleWizardOnShow = show; - } - - public String getCompilerOutputUrl() { - return VfsUtil.pathToUrl(myProjectCompilerOutput.getText().trim()); - } - - private static class CircularDependencyWarning extends ConfigurationError { - private CircularDependencyWarning(String plainTextTitle, String description) { - super(plainTextTitle, description); - } - - @Override - public void fix(JComponent contextComponent) { - } - - @Override - public void navigate() { - } - } - - private class MyJPanel extends JPanel { - public MyJPanel() { - super(new GridBagLayout()); - } - - public void addNotify() { - super.addNotify(); - if (myStartModuleWizardOnShow) { - final Window parentWindow = (Window)SwingUtilities.getAncestorOfClass(Window.class, this); - parentWindow.addWindowListener(new WindowAdapter() { - public void windowActivated(WindowEvent e) { - parentWindow.removeWindowListener(this); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myModulesConfigurator.addModule(parentWindow); - } - }); - } - }); - } - } - } - - -} +/* + * 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.roots.ui.configuration; + +import com.intellij.compiler.ModuleCompilerUtil; +import com.intellij.ide.util.BrowseFilesListener; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.StorageScheme; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserFactory; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ex.ProjectEx; +import com.intellij.openapi.roots.CompilerProjectExtension; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectConfigurationProblems; +import com.intellij.openapi.ui.DetailsComponent; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.NamedConfigurable; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.EmptyRunnable; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.FieldPanel; +import com.intellij.ui.InsertPathAction; +import com.intellij.util.Alarm; +import com.intellij.util.Chunk; +import com.intellij.util.graph.Graph; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.util.Collection; +import java.util.Set; + +/** + * @author Eugene Zhuravlev + * Date: Dec 15, 2003 + */ +public class ProjectConfigurable extends NamedConfigurable implements DetailsComponent.Facade { + + private final Project myProject; + + private static final Icon PROJECT_ICON = IconLoader.getIcon("/nodes/project.png"); + + private boolean myStartModuleWizardOnShow; + private LanguageLevelCombo myLanguageLevelCombo; + private ProjectJdkConfigurable myProjectJdkConfigurable; + + private FieldPanel myProjectCompilerOutput; + + private JTextField myProjectName; + + private MyJPanel myPanel; + + private final Alarm myUpdateWarningAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + + private final JLabel myWarningLabel = new JLabel(""); + private final ModulesConfigurator myModulesConfigurator; + private JPanel myWholePanel; + + private boolean myFreeze = false; + private DetailsComponent myDetailsComponent; + private CircularDependencyWarning myCircularDependencyWarning; + + public ProjectConfigurable(Project project, ModulesConfigurator configurator, ProjectSdksModel model) { + myProject = project; + myModulesConfigurator = configurator; + init(model); + } + + + public DetailsComponent getDetailsComponent() { + return myDetailsComponent; + } + + public JComponent createOptionsPanel() { + myDetailsComponent = new DetailsComponent(); + myDetailsComponent.setContent(myPanel); + myDetailsComponent.setText(getBannerSlogan()); + + myProjectJdkConfigurable.createComponent(); //reload changed jdks + + return myDetailsComponent.getComponent(); + } + + private void init(final ProjectSdksModel model) { + myPanel = new MyJPanel(); + myPanel.setPreferredSize(new Dimension(700, 500)); + + if (((ProjectEx)myProject).getStateStore().getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) { + final JPanel namePanel = new JPanel(new BorderLayout()); + final JLabel label = + new JLabel("Project name:", SwingConstants.LEFT); + namePanel.add(label, BorderLayout.NORTH); + + myProjectName = new JTextField(); + myProjectName.setColumns(40); + + final JPanel nameFieldPanel = new JPanel(); + nameFieldPanel.setLayout(new BoxLayout(nameFieldPanel, BoxLayout.X_AXIS)); + nameFieldPanel.add(Box.createHorizontalStrut(4)); + nameFieldPanel.add(myProjectName); + + namePanel.add(nameFieldPanel, BorderLayout.CENTER); + final JPanel wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT)); + wrapper.add(namePanel); + wrapper.setAlignmentX(0); + myPanel.add(wrapper, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(4, 0, 10, 0), 0, 0)); + } + + myProjectJdkConfigurable = new ProjectJdkConfigurable(myProject, model); + myPanel.add(myProjectJdkConfigurable.createComponent(), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(4, 4, 0, 0), 0, 0)); + + myPanel.add(myWholePanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, + GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0)); + + //myWarningLabel.setUI(new MultiLineLabelUI()); + myPanel.add(myWarningLabel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, + GridBagConstraints.BOTH, new Insets(10, 6, 10, 0), 0, 0)); + + myProjectCompilerOutput.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(DocumentEvent e) { + if (myFreeze) return; + myModulesConfigurator.processModuleCompilerOutputChanged(getCompilerOutputUrl()); + } + }); + } + + public void disposeUIResources() { + myUpdateWarningAlarm.cancelAllRequests(); + if (myProjectJdkConfigurable != null) { + myProjectJdkConfigurable.disposeUIResources(); + } + } + + public void reset() { + myFreeze = true; + try { + myProjectJdkConfigurable.reset(); + final String compilerOutput = CompilerProjectExtension.getInstance(myProject).getCompilerOutputUrl(); + if (compilerOutput != null) { + myProjectCompilerOutput.setText(FileUtil.toSystemDependentName(VfsUtil.urlToPath(compilerOutput))); + } + myLanguageLevelCombo.reset(myProject); + updateCircularDependencyWarning(); + + if (myProjectName != null) { + myProjectName.setText(myProject.getName()); + } + } + finally { + myFreeze = false; + } + } + + void updateCircularDependencyWarning() { + myUpdateWarningAlarm.cancelAllRequests(); + myUpdateWarningAlarm.addRequest(new Runnable() { + public void run() { + ApplicationManager.getApplication().runReadAction(new Runnable(){ + public void run() { + final Graph> graph = ModuleCompilerUtil.toChunkGraph(myModulesConfigurator.createGraphGenerator()); + final Collection> chunks = graph.getNodes(); + String cycles = ""; + int count = 0; + for (Chunk chunk : chunks) { + final Set modules = chunk.getNodes(); + String cycle = ""; + for (ModifiableRootModel model : modules) { + cycle += ", " + model.getModule().getName(); + } + if (modules.size() > 1) { + @NonNls final String br = "
    "; + cycles += br + (++count) + ". " + cycle.substring(2); + } + } + @NonNls final String leftBrace = ""; + @NonNls final String rightBrace = ""; + final String warningMessage = + leftBrace + (count > 0 ? ProjectBundle.message("module.circular.dependency.warning", cycles, count) : "") + rightBrace; + if (ProjectConfigurationProblems.isVisible()) { + if (myCircularDependencyWarning != null) { + ConfigurationErrors.Bus.removeError(myCircularDependencyWarning, myProject); + } + if (count > 0) { + myCircularDependencyWarning = new CircularDependencyWarning("Circular dependencies", warningMessage); + ConfigurationErrors.Bus.addError(myCircularDependencyWarning, myProject); + } + } + final Icon icon = count > 0 ? Messages.getWarningIcon() : null; + //noinspection SSBasedInspection + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myWarningLabel.setIcon(icon); + myWarningLabel.setText(warningMessage); + myWarningLabel.repaint(); + } + }); + } + }); + } + }, 300); + } + + + public void apply() throws ConfigurationException { + final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(myProject); + + if (myProjectName != null && myProjectName.getText().trim().length() == 0) { + throw new ConfigurationException("Please, specify project name!"); + } + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + // set the output path first so that handlers of RootsChanged event sent after JDK is set + // would see the updated path + String canonicalPath = myProjectCompilerOutput.getText(); + if (canonicalPath != null && canonicalPath.length() > 0) { + try { + canonicalPath = FileUtil.resolveShortWindowsName(canonicalPath); + } + catch (IOException e) { + //file doesn't exist yet + } + canonicalPath = FileUtil.toSystemIndependentName(canonicalPath); + compilerProjectExtension.setCompilerOutputUrl(VfsUtil.pathToUrl(canonicalPath)); + } + else { + compilerProjectExtension.setCompilerOutputPointer(null); + } + + final LanguageLevel newLevel = (LanguageLevel)myLanguageLevelCombo.getSelectedItem(); + LanguageLevelProjectExtension.getInstance(myProject).setLanguageLevel(newLevel); + try { + myProjectJdkConfigurable.apply(); + + if (myProjectName != null) { + ((ProjectEx)myProject).setProjectName(myProjectName.getText().trim()); + if (myDetailsComponent != null) myDetailsComponent.setText(getBannerSlogan()); + } + } + catch (ConfigurationException e) { + //cant't be + } + } + }); + } + + + public void setDisplayName(final String name) { + //do nothing + } + + public Project getEditableObject() { + return myProject; + } + + public String getBannerSlogan() { + return ProjectBundle.message("project.roots.project.banner.text", myProject.getName()); + } + + public String getDisplayName() { + return ProjectBundle.message("project.roots.project.display.name"); + } + + public Icon getIcon() { + return PROJECT_ICON; + } + + @Nullable + @NonNls + public String getHelpTopic() { + return "reference.settingsdialog.project.structure.general"; + } + + + @SuppressWarnings({"SimplifiableIfStatement"}) + public boolean isModified() { + if (!LanguageLevelProjectExtension.getInstance(myProject).getLanguageLevel().equals(myLanguageLevelCombo.getSelectedItem())) { + return true; + } + final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(myProject); + final String compilerOutput = compilerProjectExtension.getCompilerOutputUrl(); + if (!Comparing.strEqual(FileUtil.toSystemIndependentName(VfsUtil.urlToPath(compilerOutput)), + FileUtil.toSystemIndependentName(myProjectCompilerOutput.getText()))) return true; + if (myProjectJdkConfigurable.isModified()) return true; + if (myProjectName != null) { + if (!myProjectName.getText().trim().equals(myProject.getName())) return true; + } + + return false; + } + + private void createUIComponents() { + myLanguageLevelCombo = new LanguageLevelCombo(); + final JTextField textField = new JTextField(); + final FileChooserDescriptor outputPathsChooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false); + InsertPathAction.addTo(textField, outputPathsChooserDescriptor); + outputPathsChooserDescriptor.setHideIgnored(false); + BrowseFilesListener listener = new BrowseFilesListener(textField, "", ProjectBundle.message("project.compiler.output"), outputPathsChooserDescriptor); + myProjectCompilerOutput = new FieldPanel(textField, null, null, listener, EmptyRunnable.getInstance()); + FileChooserFactory.getInstance().installFileCompletion(myProjectCompilerOutput.getTextField(), outputPathsChooserDescriptor, true, null); + } + + public void setStartModuleWizardOnShow(final boolean show) { + myStartModuleWizardOnShow = show; + } + + public String getCompilerOutputUrl() { + return VfsUtil.pathToUrl(myProjectCompilerOutput.getText().trim()); + } + + private static class CircularDependencyWarning extends ConfigurationError { + private CircularDependencyWarning(String plainTextTitle, String description) { + super(plainTextTitle, description); + } + + @Override + public void fix(JComponent contextComponent) { + } + + @Override + public void navigate() { + } + } + + private class MyJPanel extends JPanel { + public MyJPanel() { + super(new GridBagLayout()); + } + + public void addNotify() { + super.addNotify(); + if (myStartModuleWizardOnShow) { + final Window parentWindow = (Window)SwingUtilities.getAncestorOfClass(Window.class, this); + parentWindow.addWindowListener(new WindowAdapter() { + public void windowActivated(WindowEvent e) { + parentWindow.removeWindowListener(this); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myModulesConfigurator.addModule(parentWindow); + } + }); + } + }); + } + } + } + + +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurable.java index 3ab37c8843ff..376c88ca09b8 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurable.java @@ -1,167 +1,167 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.UnnamedConfigurable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.SdkModel; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.roots.ui.configuration.projectRoot.ModuleStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; -import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Computable; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -/** - * User: anna - * Date: 05-Jun-2006 - */ -public class ProjectJdkConfigurable implements UnnamedConfigurable { - private JdkComboBox myCbProjectJdk; - private JPanel myJdkPanel; - private final Project myProject; - private final ProjectSdksModel myJdksModel; - private final SdkModel.Listener myListener = new SdkModel.Listener() { - public void sdkAdded(Sdk sdk) { - reloadModel(); - } - - public void beforeSdkRemove(Sdk sdk) { - reloadModel(); - } - - public void sdkChanged(Sdk sdk, String previousName) { - reloadModel(); - } - - public void sdkHomeSelected(Sdk sdk, String newSdkHome) { - reloadModel(); - } - }; - - private boolean myFreeze = false; - - public ProjectJdkConfigurable(Project project, final ProjectSdksModel jdksModel) { - myProject = project; - myJdksModel = jdksModel; - myJdksModel.addListener(myListener); - init(); - } - - @Nullable - public Sdk getSelectedProjectJdk() { - return myJdksModel.findSdk(myCbProjectJdk.getSelectedJdk()); - } - - public JComponent createComponent() { - return myJdkPanel; - } - - private void reloadModel() { - myFreeze = true; - final Sdk projectJdk = myJdksModel.getProjectSdk(); - myCbProjectJdk.reloadModel(new JdkComboBox.NoneJdkComboBoxItem(), myProject); - final String sdkName = projectJdk == null ? ProjectRootManager.getInstance(myProject).getProjectSdkName() : projectJdk.getName(); - if (sdkName != null) { - final Sdk jdk = myJdksModel.findSdk(sdkName); - if (jdk != null) { - myCbProjectJdk.setSelectedJdk(jdk); - } else { - myCbProjectJdk.setInvalidJdk(sdkName); - clearCaches(); - } - } else { - myCbProjectJdk.setSelectedJdk(null); - } - myFreeze = false; - } - - private void init() { - myJdkPanel = new JPanel(new GridBagLayout()); - myCbProjectJdk = new JdkComboBox(myJdksModel); - myCbProjectJdk.insertItemAt(new JdkComboBox.NoneJdkComboBoxItem(), 0); - myCbProjectJdk.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (myFreeze) return; - myJdksModel.setProjectSdk(myCbProjectJdk.getSelectedJdk()); - clearCaches(); - } - }); - myJdkPanel.add(new JLabel(ProjectBundle.message("module.libraries.target.jdk.project.radio")), new GridBagConstraints(0, 0, 3, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 4, 0), 0, 0)); - myJdkPanel.add(myCbProjectJdk, new GridBagConstraints(0, 1, 1, 1, 0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0)); - final JButton setUpButton = myCbProjectJdk.createSetupButton(myProject, myJdksModel, new JdkComboBox.NoneJdkComboBoxItem()); - myJdkPanel.add(setUpButton, new GridBagConstraints(1, 1, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0)); - myCbProjectJdk.appendEditButton(myProject, myJdkPanel, new GridBagConstraints(GridBagConstraints.RELATIVE, 1, 1, 1, 1.0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0), new Computable() { - @Nullable - public Sdk compute() { - return myJdksModel.getProjectSdk(); - } - }); - } - - private void clearCaches() { - final ModuleStructureConfigurable rootConfigurable = ModuleStructureConfigurable.getInstance(myProject); - Module[] modules = rootConfigurable.getModules(); - for (Module module : modules) { - final StructureConfigurableContext context = rootConfigurable.getContext(); - context.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(context, module)); - } - } - - public boolean isModified() { - final Sdk projectJdk = ProjectRootManager.getInstance(myProject).getProjectSdk(); - return !Comparing.equal(projectJdk, getSelectedProjectJdk()); - } - - public void apply() throws ConfigurationException { - ProjectRootManager.getInstance(myProject).setProjectSdk(getSelectedProjectJdk()); - } - - public void reset() { - reloadModel(); - - final String sdkName = ProjectRootManager.getInstance(myProject).getProjectSdkName(); - if (sdkName != null) { - final Sdk jdk = myJdksModel.findSdk(sdkName); - if (jdk != null) { - myCbProjectJdk.setSelectedJdk(jdk); - } else { - myCbProjectJdk.setInvalidJdk(sdkName); - } - } else { - myCbProjectJdk.setSelectedJdk(null); - } - } - - public void disposeUIResources() { - myJdksModel.removeListener(myListener); - myJdkPanel = null; - myCbProjectJdk = null; - } - -} +/* + * 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.roots.ui.configuration; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.UnnamedConfigurable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkModel; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ModuleStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Computable; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * User: anna + * Date: 05-Jun-2006 + */ +public class ProjectJdkConfigurable implements UnnamedConfigurable { + private JdkComboBox myCbProjectJdk; + private JPanel myJdkPanel; + private final Project myProject; + private final ProjectSdksModel myJdksModel; + private final SdkModel.Listener myListener = new SdkModel.Listener() { + public void sdkAdded(Sdk sdk) { + reloadModel(); + } + + public void beforeSdkRemove(Sdk sdk) { + reloadModel(); + } + + public void sdkChanged(Sdk sdk, String previousName) { + reloadModel(); + } + + public void sdkHomeSelected(Sdk sdk, String newSdkHome) { + reloadModel(); + } + }; + + private boolean myFreeze = false; + + public ProjectJdkConfigurable(Project project, final ProjectSdksModel jdksModel) { + myProject = project; + myJdksModel = jdksModel; + myJdksModel.addListener(myListener); + init(); + } + + @Nullable + public Sdk getSelectedProjectJdk() { + return myJdksModel.findSdk(myCbProjectJdk.getSelectedJdk()); + } + + public JComponent createComponent() { + return myJdkPanel; + } + + private void reloadModel() { + myFreeze = true; + final Sdk projectJdk = myJdksModel.getProjectSdk(); + myCbProjectJdk.reloadModel(new JdkComboBox.NoneJdkComboBoxItem(), myProject); + final String sdkName = projectJdk == null ? ProjectRootManager.getInstance(myProject).getProjectSdkName() : projectJdk.getName(); + if (sdkName != null) { + final Sdk jdk = myJdksModel.findSdk(sdkName); + if (jdk != null) { + myCbProjectJdk.setSelectedJdk(jdk); + } else { + myCbProjectJdk.setInvalidJdk(sdkName); + clearCaches(); + } + } else { + myCbProjectJdk.setSelectedJdk(null); + } + myFreeze = false; + } + + private void init() { + myJdkPanel = new JPanel(new GridBagLayout()); + myCbProjectJdk = new JdkComboBox(myJdksModel); + myCbProjectJdk.insertItemAt(new JdkComboBox.NoneJdkComboBoxItem(), 0); + myCbProjectJdk.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (myFreeze) return; + myJdksModel.setProjectSdk(myCbProjectJdk.getSelectedJdk()); + clearCaches(); + } + }); + myJdkPanel.add(new JLabel(ProjectBundle.message("module.libraries.target.jdk.project.radio")), new GridBagConstraints(0, 0, 3, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 4, 0), 0, 0)); + myJdkPanel.add(myCbProjectJdk, new GridBagConstraints(0, 1, 1, 1, 0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0)); + final JButton setUpButton = myCbProjectJdk.createSetupButton(myProject, myJdksModel, new JdkComboBox.NoneJdkComboBoxItem()); + myJdkPanel.add(setUpButton, new GridBagConstraints(1, 1, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0)); + myCbProjectJdk.appendEditButton(myProject, myJdkPanel, new GridBagConstraints(GridBagConstraints.RELATIVE, 1, 1, 1, 1.0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0), new Computable() { + @Nullable + public Sdk compute() { + return myJdksModel.getProjectSdk(); + } + }); + } + + private void clearCaches() { + final ModuleStructureConfigurable rootConfigurable = ModuleStructureConfigurable.getInstance(myProject); + Module[] modules = rootConfigurable.getModules(); + for (Module module : modules) { + final StructureConfigurableContext context = rootConfigurable.getContext(); + context.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(context, module)); + } + } + + public boolean isModified() { + final Sdk projectJdk = ProjectRootManager.getInstance(myProject).getProjectSdk(); + return !Comparing.equal(projectJdk, getSelectedProjectJdk()); + } + + public void apply() throws ConfigurationException { + ProjectRootManager.getInstance(myProject).setProjectSdk(getSelectedProjectJdk()); + } + + public void reset() { + reloadModel(); + + final String sdkName = ProjectRootManager.getInstance(myProject).getProjectSdkName(); + if (sdkName != null) { + final Sdk jdk = myJdksModel.findSdk(sdkName); + if (jdk != null) { + myCbProjectJdk.setSelectedJdk(jdk); + } else { + myCbProjectJdk.setInvalidJdk(sdkName); + } + } else { + myCbProjectJdk.setSelectedJdk(null); + } + } + + public void disposeUIResources() { + myJdksModel.removeListener(myListener); + myJdkPanel = null; + myCbProjectJdk = null; + } + +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java index af07d099b9db..63132c974ee3 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java @@ -1,582 +1,582 @@ -/* - * 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.roots.ui.configuration; - -import com.intellij.facet.Facet; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.options.BaseConfigurable; -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.LibraryOrderEntry; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; -import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactsStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.*; -import com.intellij.openapi.ui.DetailsComponent; -import com.intellij.openapi.ui.MasterDetailsComponent; -import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; -import com.intellij.packaging.artifacts.Artifact; -import com.intellij.ui.components.panels.Wrapper; -import com.intellij.ui.navigation.BackAction; -import com.intellij.ui.navigation.ForwardAction; -import com.intellij.ui.navigation.History; -import com.intellij.ui.navigation.Place; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; - -public class ProjectStructureConfigurable extends BaseConfigurable implements SearchableConfigurable, Place.Navigator { - - public static final DataKey KEY = DataKey.create("ProjectStructureConfiguration"); - - private final UIState myUiState = new UIState(); - private Splitter mySplitter; - private JComponent myToolbarComponent; - @NonNls private static final String CATEGORY = "category"; - private JComponent myToFocus; - private boolean myWasUiDisposed; - private ConfigurationErrorsComponent myErrorsComponent; - - public static class UIState { - public float proportion; - public float sideProportion; - - public String lastEditedConfigurable; - } - - private final Project myProject; - private final ModuleManager myModuleManager; - private final FacetStructureConfigurable myFacetStructureConfigurable; - private final ArtifactsStructureConfigurable myArtifactsStructureConfigurable; - - private History myHistory = new History(this); - private SidePanel mySidePanel; - - private JPanel myComponent; - private final Wrapper myDetails = new Wrapper(); - - private Configurable mySelectedConfigurable; - - private final ProjectSdksModel myProjectJdksModel = new ProjectSdksModel(); - - private ProjectConfigurable myProjectConfig; - private final ProjectLibrariesConfigurable myProjectLibrariesConfig; - private final GlobalLibrariesConfigurable myGlobalLibrariesConfig; - private ModuleStructureConfigurable myModulesConfig; - - private boolean myWasIntialized; - - private final List myName2Config = new ArrayList(); - private final StructureConfigurableContext myContext; - private final ModulesConfigurator myModuleConfigurator; - private JdkListConfigurable myJdkListConfig; - - private final JLabel myEmptySelection = new JLabel("
Select a setting to view or edit its details here
", JLabel.CENTER); - - public ProjectStructureConfigurable(final Project project, final ModuleManager moduleManager, final ProjectLibrariesConfigurable projectLibrariesConfigurable, - final GlobalLibrariesConfigurable globalLibrariesConfigurable, - final ModuleStructureConfigurable moduleStructureConfigurable, - FacetStructureConfigurable facetStructureConfigurable, - ArtifactsStructureConfigurable artifactsStructureConfigurable) { - myProject = project; - myModuleManager = moduleManager; - myFacetStructureConfigurable = facetStructureConfigurable; - myArtifactsStructureConfigurable = artifactsStructureConfigurable; - - myModuleConfigurator = new ModulesConfigurator(myProject, myProjectJdksModel); - myContext = new StructureConfigurableContext(myProject, myModuleConfigurator); - myModuleConfigurator.setContext(myContext); - - myProjectLibrariesConfig = projectLibrariesConfigurable; - myGlobalLibrariesConfig = globalLibrariesConfigurable; - myModulesConfig = moduleStructureConfigurable; - - myProjectLibrariesConfig.init(myContext); - myGlobalLibrariesConfig.init(myContext); - myModulesConfig.init(myContext); - myFacetStructureConfigurable.init(myContext); - myArtifactsStructureConfigurable.init(myContext, myModulesConfig, myProjectLibrariesConfig, myGlobalLibrariesConfig); - - final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject); - myUiState.lastEditedConfigurable = propertiesComponent.getValue("project.structure.last.edited"); - final String proportion = propertiesComponent.getValue("project.structure.proportion"); - myUiState.proportion = proportion != null ? Float.parseFloat(proportion) : 0; - final String sideProportion = propertiesComponent.getValue("project.structure.side.proportion"); - myUiState.sideProportion = sideProportion != null ? Float.parseFloat(sideProportion) : 0; - } - - @NonNls - public String getId() { - return "project.structure"; - } - - @Nullable - public Runnable enableSearch(final String option) { - return null; - } - - @Nls - public String getDisplayName() { - return ProjectBundle.message("project.settings.display.name"); - } - - @Nullable - public Icon getIcon() { - return IconLoader.getIcon("/modules/modules.png"); - } - - @Nullable - @NonNls - public String getHelpTopic() { - return mySelectedConfigurable != null ? mySelectedConfigurable.getHelpTopic() : ""; - } - - public JComponent createComponent() { - myComponent = new MyPanel(); - - mySplitter = new Splitter(false, .15f); - mySplitter.setHonorComponentsMinimumSize(true); - - initSidePanel(); - - final JPanel left = new JPanel(new BorderLayout()) { - public Dimension getMinimumSize() { - final Dimension original = super.getMinimumSize(); - return new Dimension(Math.max(original.width, 100), original.height); - } - }; - - final DefaultActionGroup toolbarGroup = new DefaultActionGroup(); - toolbarGroup.add(new BackAction(myComponent)); - toolbarGroup.add(new ForwardAction(myComponent)); - final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarGroup, true); - toolbar.setTargetComponent(myComponent); - myToolbarComponent = toolbar.getComponent(); - left.add(myToolbarComponent, BorderLayout.NORTH); - left.add(mySidePanel, BorderLayout.CENTER); - - mySplitter.setFirstComponent(left); - mySplitter.setSecondComponent(myDetails); - - myComponent.add(mySplitter, BorderLayout.CENTER); - myErrorsComponent = new ConfigurationErrorsComponent(myProject); - myComponent.add(myErrorsComponent, BorderLayout.SOUTH); - - myWasIntialized = true; - - return myComponent; - } - - private void initSidePanel() { - boolean isDefaultProject = myProject == ProjectManager.getInstance().getDefaultProject(); - - mySidePanel = new SidePanel(this, myHistory); - mySidePanel.addSeparator("Project Settings"); - addProjectConfig(); - if (!isDefaultProject) { - addModulesConfig(); - } - addProjectLibrariesConfig(); - - if (!isDefaultProject) { - addFacetsConfig(); - addArtifactsConfig(); - } - mySidePanel.addSeparator("Platform Settings"); - addJdkListConfig(); - addGlobalLibrariesConfig(); - } - - private void addArtifactsConfig() { - addConfigurable(myArtifactsStructureConfigurable); - } - - public ArtifactsStructureConfigurable getArtifactsStructureConfigurable() { - return myArtifactsStructureConfigurable; - } - - private void addFacetsConfig() { - if (myFacetStructureConfigurable.isVisible()) { - addConfigurable(myFacetStructureConfigurable); - } - } - - private void addJdkListConfig() { - if (myJdkListConfig == null) { - myJdkListConfig = JdkListConfigurable.getInstance(myProject); - myJdkListConfig.init(myContext); - } - addConfigurable(myJdkListConfig); - } - - private void addProjectConfig() { - myProjectConfig = new ProjectConfigurable(myProject, myModuleConfigurator, myProjectJdksModel); - addConfigurable(myProjectConfig); - } - - private void addProjectLibrariesConfig() { - addConfigurable(myProjectLibrariesConfig); - } - - private void addGlobalLibrariesConfig() { - addConfigurable(myGlobalLibrariesConfig); - } - - private void addModulesConfig() { - myModulesConfig = ModuleStructureConfigurable.getInstance(myProject); - addConfigurable(myModulesConfig); - } - - public boolean isModified() { - for (Configurable each : myName2Config) { - if (each.isModified()) return true; - } - - return false; - } - - public void apply() throws ConfigurationException { - for (Configurable each : myName2Config) { - if (each.isModified()) { - each.apply(); - } - } - - myContext.getDaemonAnalyzer().clearCaches(); - } - - public void reset() { - myWasUiDisposed = false; - - myContext.reset(); - - myProjectJdksModel.reset(myProject); - - Configurable toSelect = null; - for (Configurable each : myName2Config) { - if (myUiState.lastEditedConfigurable != null && myUiState.lastEditedConfigurable.equals(each.getDisplayName())) { - toSelect = each; - } - if (each instanceof MasterDetailsComponent) { - ((MasterDetailsComponent)each).setHistory(myHistory); - } - each.reset(); - } - - myHistory.clear(); - - if (toSelect == null && myName2Config.size() > 0) { - toSelect = myName2Config.iterator().next(); - } - - removeSelected(); - - navigateTo(toSelect != null ? createPlaceFor(toSelect) : null, false); - - if (myUiState.proportion > 0) { - mySplitter.setProportion(myUiState.proportion); - } - } - - public void disposeUIResources() { - if (!myWasIntialized) return; - final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject); - propertiesComponent.setValue("project.structure.last.edited", myUiState.lastEditedConfigurable); - propertiesComponent.setValue("project.structure.proportion", String.valueOf(myUiState.proportion)); - propertiesComponent.setValue("project.structure.side.proportion", String.valueOf(myUiState.sideProportion)); - - myWasUiDisposed = true; - - myUiState.proportion = mySplitter.getProportion(); - saveSideProportion(); - myContext.getDaemonAnalyzer().stop(); - for (Configurable each : myName2Config) { - each.disposeUIResources(); - } - myContext.clear(); - myName2Config.clear(); - - myModuleConfigurator.getFacetsConfigurator().clearMaps(); - - Disposer.dispose(myErrorsComponent); - - myWasIntialized = false; - } - - public History getHistory() { - return myHistory; - } - - public void setHistory(final History history) { - myHistory = history; - } - - public void queryPlace(@NotNull final Place place) { - place.putPath(CATEGORY, mySelectedConfigurable); - Place.queryFurther(mySelectedConfigurable, place); - } - - public ActionCallback selectProjectGeneralSettings(final boolean requestFocus) { - Place place = createPlaceFor(myProjectConfig); - return navigateTo(place, requestFocus); - } - - public ActionCallback select(@Nullable final String moduleToSelect, String tab, final boolean requestFocus) { - Place place = createPlaceFor(myModulesConfig); - if (moduleToSelect != null) { - final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleToSelect); - assert module != null; - place = place.putPath(ModuleStructureConfigurable.TREE_OBJECT, module).putPath(ModuleEditor.MODULE_TAB, tab); - } - return navigateTo(place, requestFocus); - } - - public Place createModulePlace(@NotNull Module module) { - return createPlaceFor(myModulesConfig).putPath(ModuleStructureConfigurable.TREE_OBJECT, module); - } - - public ActionCallback select(@Nullable final Facet facetToSelect, final boolean requestFocus) { - Place place = createPlaceFor(myModulesConfig); - if (facetToSelect != null) { - place = place.putPath(ModuleStructureConfigurable.TREE_OBJECT, facetToSelect); - } - return navigateTo(place, requestFocus); - } - - public ActionCallback select(@NotNull Sdk sdk, final boolean requestFocus) { - Place place = createPlaceFor(myJdkListConfig); - place.putPath(BaseStructureConfigurable.TREE_NAME, sdk.getName()); - return navigateTo(place, requestFocus); - } - - public ActionCallback selectProjectOrGlobalLibrary(@NotNull Library library, boolean requestFocus) { - Place place = createProjectOrGlobalLibraryPlace(library); - return navigateTo(place, requestFocus); - } - - public Place createProjectOrGlobalLibraryPlace(Library library) { - Place place = createPlaceFor(getConfigurableFor(library)); - place.putPath(BaseStructureConfigurable.TREE_NAME, library.getName()); - return place; - } - - public ActionCallback select(@Nullable Artifact artifact, boolean requestFocus) { - Place place = createArtifactPlace(artifact); - return navigateTo(place, requestFocus); - } - - public Place createArtifactPlace(Artifact artifact) { - Place place = createPlaceFor(myArtifactsStructureConfigurable); - if (artifact != null) { - place.putPath(BaseStructureConfigurable.TREE_NAME, artifact.getName()); - } - return place; - } - - public ActionCallback select(@NotNull LibraryOrderEntry libraryOrderEntry, final boolean requestFocus) { - final Library lib = libraryOrderEntry.getLibrary(); - if (lib == null || lib.getTable() == null) { - Place place = createPlaceFor(myModulesConfig); - place.putPath(BaseStructureConfigurable.TREE_OBJECT, libraryOrderEntry.getOwnerModule()); - return navigateTo(place, requestFocus); - } - Place place = createPlaceFor(getConfigurableFor(lib)); - place.putPath(BaseStructureConfigurable.TREE_NAME, libraryOrderEntry.getLibraryName()); - return navigateTo(place, requestFocus); - } - - public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { - final Configurable toSelect = (Configurable)place.getPath(CATEGORY); - - JComponent detailsContent = myDetails.getTargetComponent(); - - if (mySelectedConfigurable != toSelect) { - if (mySelectedConfigurable instanceof BaseStructureConfigurable) { - ((BaseStructureConfigurable)mySelectedConfigurable).onStructureUnselected(); - } - saveSideProportion(); - removeSelected(); - - if (toSelect != null) { - detailsContent = toSelect.createComponent(); - myDetails.setContent(detailsContent); - } - - mySelectedConfigurable = toSelect; - if (mySelectedConfigurable != null) { - myUiState.lastEditedConfigurable = mySelectedConfigurable.getDisplayName(); - } - - if (toSelect instanceof MasterDetailsComponent) { - final MasterDetailsComponent masterDetails = (MasterDetailsComponent)toSelect; - if (myUiState.sideProportion > 0) { - masterDetails.getSplitter().setProportion(myUiState.sideProportion); - } - masterDetails.setHistory(myHistory); - } - - if (toSelect instanceof DetailsComponent.Facade) { - ((DetailsComponent.Facade)toSelect).getDetailsComponent().setBannerMinHeight(myToolbarComponent.getPreferredSize().height); - } - - if (toSelect instanceof BaseStructureConfigurable) { - ((BaseStructureConfigurable)toSelect).onStructureSelected(); - } - } - - - - if (detailsContent != null) { - JComponent toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(detailsContent); - if (toFocus == null) { - toFocus = detailsContent; - } - if (requestFocus) { - myToFocus = toFocus; - UIUtil.requestFocus(toFocus); - } - } - - final ActionCallback result = new ActionCallback(); - Place.goFurther(toSelect, place, requestFocus).notifyWhenDone(result); - - myDetails.revalidate(); - myDetails.repaint(); - - if (toSelect != null) { - mySidePanel.select(createPlaceFor(toSelect)); - } - - if (!myHistory.isNavigatingNow() && mySelectedConfigurable != null) { - myHistory.pushQueryPlace(); - } - - return result; - } - - private void saveSideProportion() { - if (mySelectedConfigurable instanceof MasterDetailsComponent) { - myUiState.sideProportion = ((MasterDetailsComponent)mySelectedConfigurable).getSplitter().getProportion(); - } - } - - private void removeSelected() { - myDetails.removeAll(); - mySelectedConfigurable = null; - myUiState.lastEditedConfigurable = null; - - myDetails.add(myEmptySelection, BorderLayout.CENTER); - } - - public static ProjectStructureConfigurable getInstance(final Project project) { - return ServiceManager.getService(project, ProjectStructureConfigurable.class); - } - - public ProjectSdksModel getProjectJdksModel() { - return myProjectJdksModel; - } - - public JdkListConfigurable getJdkConfig() { - return myJdkListConfig; - } - - public ProjectLibrariesConfigurable getProjectLibrariesConfig() { - return myProjectLibrariesConfig; - } - - public GlobalLibrariesConfigurable getGlobalLibrariesConfig() { - return myGlobalLibrariesConfig; - } - - public ModuleStructureConfigurable getModulesConfig() { - return myModulesConfig; - } - - public ProjectConfigurable getProjectConfig() { - return myProjectConfig; - } - - private void addConfigurable(Configurable configurable) { - myName2Config.add(configurable); - - mySidePanel.addPlace(createPlaceFor(configurable), new Presentation(configurable.getDisplayName())); - } - - private static Place createPlaceFor(final Configurable configurable) { - return new Place().putPath(CATEGORY, configurable); - } - - - public StructureConfigurableContext getContext() { - return myContext; - } - - private class MyPanel extends JPanel implements DataProvider { - public MyPanel() { - super(new BorderLayout()); - } - - @Nullable - public Object getData(@NonNls final String dataId) { - if (KEY.is(dataId)) { - return ProjectStructureConfigurable.this; - } else if (History.KEY.is(dataId)) { - return getHistory(); - } else { - return null; - } - } - - public Dimension getPreferredSize() { - return new Dimension(1024, 768); - } - } - - public BaseLibrariesConfigurable getConfigurableFor(final Library library) { - if (LibraryTablesRegistrar.PROJECT_LEVEL.equals(library.getTable().getTableLevel())) { - return myProjectLibrariesConfig; - } else { - return myGlobalLibrariesConfig; - } - } - - public JComponent getPreferredFocusedComponent() { - return myToFocus; - } - - - -} +/* + * 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.roots.ui.configuration; + +import com.intellij.facet.Facet; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.options.BaseConfigurable; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactsStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.*; +import com.intellij.openapi.ui.DetailsComponent; +import com.intellij.openapi.ui.MasterDetailsComponent; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.ui.components.panels.Wrapper; +import com.intellij.ui.navigation.BackAction; +import com.intellij.ui.navigation.ForwardAction; +import com.intellij.ui.navigation.History; +import com.intellij.ui.navigation.Place; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class ProjectStructureConfigurable extends BaseConfigurable implements SearchableConfigurable, Place.Navigator { + + public static final DataKey KEY = DataKey.create("ProjectStructureConfiguration"); + + private final UIState myUiState = new UIState(); + private Splitter mySplitter; + private JComponent myToolbarComponent; + @NonNls private static final String CATEGORY = "category"; + private JComponent myToFocus; + private boolean myWasUiDisposed; + private ConfigurationErrorsComponent myErrorsComponent; + + public static class UIState { + public float proportion; + public float sideProportion; + + public String lastEditedConfigurable; + } + + private final Project myProject; + private final ModuleManager myModuleManager; + private final FacetStructureConfigurable myFacetStructureConfigurable; + private final ArtifactsStructureConfigurable myArtifactsStructureConfigurable; + + private History myHistory = new History(this); + private SidePanel mySidePanel; + + private JPanel myComponent; + private final Wrapper myDetails = new Wrapper(); + + private Configurable mySelectedConfigurable; + + private final ProjectSdksModel myProjectJdksModel = new ProjectSdksModel(); + + private ProjectConfigurable myProjectConfig; + private final ProjectLibrariesConfigurable myProjectLibrariesConfig; + private final GlobalLibrariesConfigurable myGlobalLibrariesConfig; + private ModuleStructureConfigurable myModulesConfig; + + private boolean myWasIntialized; + + private final List myName2Config = new ArrayList(); + private final StructureConfigurableContext myContext; + private final ModulesConfigurator myModuleConfigurator; + private JdkListConfigurable myJdkListConfig; + + private final JLabel myEmptySelection = new JLabel("
Select a setting to view or edit its details here
", JLabel.CENTER); + + public ProjectStructureConfigurable(final Project project, final ModuleManager moduleManager, final ProjectLibrariesConfigurable projectLibrariesConfigurable, + final GlobalLibrariesConfigurable globalLibrariesConfigurable, + final ModuleStructureConfigurable moduleStructureConfigurable, + FacetStructureConfigurable facetStructureConfigurable, + ArtifactsStructureConfigurable artifactsStructureConfigurable) { + myProject = project; + myModuleManager = moduleManager; + myFacetStructureConfigurable = facetStructureConfigurable; + myArtifactsStructureConfigurable = artifactsStructureConfigurable; + + myModuleConfigurator = new ModulesConfigurator(myProject, myProjectJdksModel); + myContext = new StructureConfigurableContext(myProject, myModuleConfigurator); + myModuleConfigurator.setContext(myContext); + + myProjectLibrariesConfig = projectLibrariesConfigurable; + myGlobalLibrariesConfig = globalLibrariesConfigurable; + myModulesConfig = moduleStructureConfigurable; + + myProjectLibrariesConfig.init(myContext); + myGlobalLibrariesConfig.init(myContext); + myModulesConfig.init(myContext); + myFacetStructureConfigurable.init(myContext); + myArtifactsStructureConfigurable.init(myContext, myModulesConfig, myProjectLibrariesConfig, myGlobalLibrariesConfig); + + final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject); + myUiState.lastEditedConfigurable = propertiesComponent.getValue("project.structure.last.edited"); + final String proportion = propertiesComponent.getValue("project.structure.proportion"); + myUiState.proportion = proportion != null ? Float.parseFloat(proportion) : 0; + final String sideProportion = propertiesComponent.getValue("project.structure.side.proportion"); + myUiState.sideProportion = sideProportion != null ? Float.parseFloat(sideProportion) : 0; + } + + @NonNls + public String getId() { + return "project.structure"; + } + + @Nullable + public Runnable enableSearch(final String option) { + return null; + } + + @Nls + public String getDisplayName() { + return ProjectBundle.message("project.settings.display.name"); + } + + @Nullable + public Icon getIcon() { + return IconLoader.getIcon("/modules/modules.png"); + } + + @Nullable + @NonNls + public String getHelpTopic() { + return mySelectedConfigurable != null ? mySelectedConfigurable.getHelpTopic() : ""; + } + + public JComponent createComponent() { + myComponent = new MyPanel(); + + mySplitter = new Splitter(false, .15f); + mySplitter.setHonorComponentsMinimumSize(true); + + initSidePanel(); + + final JPanel left = new JPanel(new BorderLayout()) { + public Dimension getMinimumSize() { + final Dimension original = super.getMinimumSize(); + return new Dimension(Math.max(original.width, 100), original.height); + } + }; + + final DefaultActionGroup toolbarGroup = new DefaultActionGroup(); + toolbarGroup.add(new BackAction(myComponent)); + toolbarGroup.add(new ForwardAction(myComponent)); + final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarGroup, true); + toolbar.setTargetComponent(myComponent); + myToolbarComponent = toolbar.getComponent(); + left.add(myToolbarComponent, BorderLayout.NORTH); + left.add(mySidePanel, BorderLayout.CENTER); + + mySplitter.setFirstComponent(left); + mySplitter.setSecondComponent(myDetails); + + myComponent.add(mySplitter, BorderLayout.CENTER); + myErrorsComponent = new ConfigurationErrorsComponent(myProject); + myComponent.add(myErrorsComponent, BorderLayout.SOUTH); + + myWasIntialized = true; + + return myComponent; + } + + private void initSidePanel() { + boolean isDefaultProject = myProject == ProjectManager.getInstance().getDefaultProject(); + + mySidePanel = new SidePanel(this, myHistory); + mySidePanel.addSeparator("Project Settings"); + addProjectConfig(); + if (!isDefaultProject) { + addModulesConfig(); + } + addProjectLibrariesConfig(); + + if (!isDefaultProject) { + addFacetsConfig(); + addArtifactsConfig(); + } + mySidePanel.addSeparator("Platform Settings"); + addJdkListConfig(); + addGlobalLibrariesConfig(); + } + + private void addArtifactsConfig() { + addConfigurable(myArtifactsStructureConfigurable); + } + + public ArtifactsStructureConfigurable getArtifactsStructureConfigurable() { + return myArtifactsStructureConfigurable; + } + + private void addFacetsConfig() { + if (myFacetStructureConfigurable.isVisible()) { + addConfigurable(myFacetStructureConfigurable); + } + } + + private void addJdkListConfig() { + if (myJdkListConfig == null) { + myJdkListConfig = JdkListConfigurable.getInstance(myProject); + myJdkListConfig.init(myContext); + } + addConfigurable(myJdkListConfig); + } + + private void addProjectConfig() { + myProjectConfig = new ProjectConfigurable(myProject, myModuleConfigurator, myProjectJdksModel); + addConfigurable(myProjectConfig); + } + + private void addProjectLibrariesConfig() { + addConfigurable(myProjectLibrariesConfig); + } + + private void addGlobalLibrariesConfig() { + addConfigurable(myGlobalLibrariesConfig); + } + + private void addModulesConfig() { + myModulesConfig = ModuleStructureConfigurable.getInstance(myProject); + addConfigurable(myModulesConfig); + } + + public boolean isModified() { + for (Configurable each : myName2Config) { + if (each.isModified()) return true; + } + + return false; + } + + public void apply() throws ConfigurationException { + for (Configurable each : myName2Config) { + if (each.isModified()) { + each.apply(); + } + } + + myContext.getDaemonAnalyzer().clearCaches(); + } + + public void reset() { + myWasUiDisposed = false; + + myContext.reset(); + + myProjectJdksModel.reset(myProject); + + Configurable toSelect = null; + for (Configurable each : myName2Config) { + if (myUiState.lastEditedConfigurable != null && myUiState.lastEditedConfigurable.equals(each.getDisplayName())) { + toSelect = each; + } + if (each instanceof MasterDetailsComponent) { + ((MasterDetailsComponent)each).setHistory(myHistory); + } + each.reset(); + } + + myHistory.clear(); + + if (toSelect == null && myName2Config.size() > 0) { + toSelect = myName2Config.iterator().next(); + } + + removeSelected(); + + navigateTo(toSelect != null ? createPlaceFor(toSelect) : null, false); + + if (myUiState.proportion > 0) { + mySplitter.setProportion(myUiState.proportion); + } + } + + public void disposeUIResources() { + if (!myWasIntialized) return; + final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject); + propertiesComponent.setValue("project.structure.last.edited", myUiState.lastEditedConfigurable); + propertiesComponent.setValue("project.structure.proportion", String.valueOf(myUiState.proportion)); + propertiesComponent.setValue("project.structure.side.proportion", String.valueOf(myUiState.sideProportion)); + + myWasUiDisposed = true; + + myUiState.proportion = mySplitter.getProportion(); + saveSideProportion(); + myContext.getDaemonAnalyzer().stop(); + for (Configurable each : myName2Config) { + each.disposeUIResources(); + } + myContext.clear(); + myName2Config.clear(); + + myModuleConfigurator.getFacetsConfigurator().clearMaps(); + + Disposer.dispose(myErrorsComponent); + + myWasIntialized = false; + } + + public History getHistory() { + return myHistory; + } + + public void setHistory(final History history) { + myHistory = history; + } + + public void queryPlace(@NotNull final Place place) { + place.putPath(CATEGORY, mySelectedConfigurable); + Place.queryFurther(mySelectedConfigurable, place); + } + + public ActionCallback selectProjectGeneralSettings(final boolean requestFocus) { + Place place = createPlaceFor(myProjectConfig); + return navigateTo(place, requestFocus); + } + + public ActionCallback select(@Nullable final String moduleToSelect, String tab, final boolean requestFocus) { + Place place = createPlaceFor(myModulesConfig); + if (moduleToSelect != null) { + final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleToSelect); + assert module != null; + place = place.putPath(ModuleStructureConfigurable.TREE_OBJECT, module).putPath(ModuleEditor.MODULE_TAB, tab); + } + return navigateTo(place, requestFocus); + } + + public Place createModulePlace(@NotNull Module module) { + return createPlaceFor(myModulesConfig).putPath(ModuleStructureConfigurable.TREE_OBJECT, module); + } + + public ActionCallback select(@Nullable final Facet facetToSelect, final boolean requestFocus) { + Place place = createPlaceFor(myModulesConfig); + if (facetToSelect != null) { + place = place.putPath(ModuleStructureConfigurable.TREE_OBJECT, facetToSelect); + } + return navigateTo(place, requestFocus); + } + + public ActionCallback select(@NotNull Sdk sdk, final boolean requestFocus) { + Place place = createPlaceFor(myJdkListConfig); + place.putPath(BaseStructureConfigurable.TREE_NAME, sdk.getName()); + return navigateTo(place, requestFocus); + } + + public ActionCallback selectProjectOrGlobalLibrary(@NotNull Library library, boolean requestFocus) { + Place place = createProjectOrGlobalLibraryPlace(library); + return navigateTo(place, requestFocus); + } + + public Place createProjectOrGlobalLibraryPlace(Library library) { + Place place = createPlaceFor(getConfigurableFor(library)); + place.putPath(BaseStructureConfigurable.TREE_NAME, library.getName()); + return place; + } + + public ActionCallback select(@Nullable Artifact artifact, boolean requestFocus) { + Place place = createArtifactPlace(artifact); + return navigateTo(place, requestFocus); + } + + public Place createArtifactPlace(Artifact artifact) { + Place place = createPlaceFor(myArtifactsStructureConfigurable); + if (artifact != null) { + place.putPath(BaseStructureConfigurable.TREE_NAME, artifact.getName()); + } + return place; + } + + public ActionCallback select(@NotNull LibraryOrderEntry libraryOrderEntry, final boolean requestFocus) { + final Library lib = libraryOrderEntry.getLibrary(); + if (lib == null || lib.getTable() == null) { + Place place = createPlaceFor(myModulesConfig); + place.putPath(BaseStructureConfigurable.TREE_OBJECT, libraryOrderEntry.getOwnerModule()); + return navigateTo(place, requestFocus); + } + Place place = createPlaceFor(getConfigurableFor(lib)); + place.putPath(BaseStructureConfigurable.TREE_NAME, libraryOrderEntry.getLibraryName()); + return navigateTo(place, requestFocus); + } + + public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { + final Configurable toSelect = (Configurable)place.getPath(CATEGORY); + + JComponent detailsContent = myDetails.getTargetComponent(); + + if (mySelectedConfigurable != toSelect) { + if (mySelectedConfigurable instanceof BaseStructureConfigurable) { + ((BaseStructureConfigurable)mySelectedConfigurable).onStructureUnselected(); + } + saveSideProportion(); + removeSelected(); + + if (toSelect != null) { + detailsContent = toSelect.createComponent(); + myDetails.setContent(detailsContent); + } + + mySelectedConfigurable = toSelect; + if (mySelectedConfigurable != null) { + myUiState.lastEditedConfigurable = mySelectedConfigurable.getDisplayName(); + } + + if (toSelect instanceof MasterDetailsComponent) { + final MasterDetailsComponent masterDetails = (MasterDetailsComponent)toSelect; + if (myUiState.sideProportion > 0) { + masterDetails.getSplitter().setProportion(myUiState.sideProportion); + } + masterDetails.setHistory(myHistory); + } + + if (toSelect instanceof DetailsComponent.Facade) { + ((DetailsComponent.Facade)toSelect).getDetailsComponent().setBannerMinHeight(myToolbarComponent.getPreferredSize().height); + } + + if (toSelect instanceof BaseStructureConfigurable) { + ((BaseStructureConfigurable)toSelect).onStructureSelected(); + } + } + + + + if (detailsContent != null) { + JComponent toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(detailsContent); + if (toFocus == null) { + toFocus = detailsContent; + } + if (requestFocus) { + myToFocus = toFocus; + UIUtil.requestFocus(toFocus); + } + } + + final ActionCallback result = new ActionCallback(); + Place.goFurther(toSelect, place, requestFocus).notifyWhenDone(result); + + myDetails.revalidate(); + myDetails.repaint(); + + if (toSelect != null) { + mySidePanel.select(createPlaceFor(toSelect)); + } + + if (!myHistory.isNavigatingNow() && mySelectedConfigurable != null) { + myHistory.pushQueryPlace(); + } + + return result; + } + + private void saveSideProportion() { + if (mySelectedConfigurable instanceof MasterDetailsComponent) { + myUiState.sideProportion = ((MasterDetailsComponent)mySelectedConfigurable).getSplitter().getProportion(); + } + } + + private void removeSelected() { + myDetails.removeAll(); + mySelectedConfigurable = null; + myUiState.lastEditedConfigurable = null; + + myDetails.add(myEmptySelection, BorderLayout.CENTER); + } + + public static ProjectStructureConfigurable getInstance(final Project project) { + return ServiceManager.getService(project, ProjectStructureConfigurable.class); + } + + public ProjectSdksModel getProjectJdksModel() { + return myProjectJdksModel; + } + + public JdkListConfigurable getJdkConfig() { + return myJdkListConfig; + } + + public ProjectLibrariesConfigurable getProjectLibrariesConfig() { + return myProjectLibrariesConfig; + } + + public GlobalLibrariesConfigurable getGlobalLibrariesConfig() { + return myGlobalLibrariesConfig; + } + + public ModuleStructureConfigurable getModulesConfig() { + return myModulesConfig; + } + + public ProjectConfigurable getProjectConfig() { + return myProjectConfig; + } + + private void addConfigurable(Configurable configurable) { + myName2Config.add(configurable); + + mySidePanel.addPlace(createPlaceFor(configurable), new Presentation(configurable.getDisplayName())); + } + + private static Place createPlaceFor(final Configurable configurable) { + return new Place().putPath(CATEGORY, configurable); + } + + + public StructureConfigurableContext getContext() { + return myContext; + } + + private class MyPanel extends JPanel implements DataProvider { + public MyPanel() { + super(new BorderLayout()); + } + + @Nullable + public Object getData(@NonNls final String dataId) { + if (KEY.is(dataId)) { + return ProjectStructureConfigurable.this; + } else if (History.KEY.is(dataId)) { + return getHistory(); + } else { + return null; + } + } + + public Dimension getPreferredSize() { + return new Dimension(1024, 768); + } + } + + public BaseLibrariesConfigurable getConfigurableFor(final Library library) { + if (LibraryTablesRegistrar.PROJECT_LEVEL.equals(library.getTable().getTableLevel())) { + return myProjectLibrariesConfig; + } else { + return myGlobalLibrariesConfig; + } + } + + public JComponent getPreferredFocusedComponent() { + return myToFocus; + } + + + +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemDescription.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemDescription.java index 0f1a055e387a..20317b52c8d1 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemDescription.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemDescription.java @@ -1,54 +1,54 @@ -/* - * 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.roots.ui.configuration.artifacts; - -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ConfigurationErrorQuickFix; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemDescription; -import com.intellij.packaging.elements.PackagingElement; -import com.intellij.packaging.ui.ArtifactProblemQuickFix; -import com.intellij.ui.navigation.Place; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -/** - * @author nik - */ -public class ArtifactProblemDescription extends ProjectStructureProblemDescription { - private List myQuickFixes; - private List> myPathToPlace; - - public ArtifactProblemDescription(@NotNull String message, - @NotNull Severity severity, - @Nullable List> pathToPlace, - @NotNull List quickFixes, @NotNull Place place) { - super(message, null, severity, place, Collections.emptyList()); - myPathToPlace = pathToPlace; - myQuickFixes = quickFixes; - } - - @NotNull - public List getQuickFixes() { - return myQuickFixes; - } - - @Nullable - public List> getPathToPlace() { - return myPathToPlace; - } -} +/* + * 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.roots.ui.configuration.artifacts; + +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ConfigurationErrorQuickFix; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemDescription; +import com.intellij.packaging.elements.PackagingElement; +import com.intellij.packaging.ui.ArtifactProblemQuickFix; +import com.intellij.ui.navigation.Place; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class ArtifactProblemDescription extends ProjectStructureProblemDescription { + private List myQuickFixes; + private List> myPathToPlace; + + public ArtifactProblemDescription(@NotNull String message, + @NotNull Severity severity, + @Nullable List> pathToPlace, + @NotNull List quickFixes, @NotNull Place place) { + super(message, null, severity, place, Collections.emptyList()); + myPathToPlace = pathToPlace; + myQuickFixes = quickFixes; + } + + @NotNull + public List getQuickFixes() { + return myQuickFixes; + } + + @Nullable + public List> getPathToPlace() { + return myPathToPlace; + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemsHolderImpl.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemsHolderImpl.java index 4b1c47f9997b..380260c8885f 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemsHolderImpl.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProblemsHolderImpl.java @@ -1,59 +1,59 @@ -/* - * 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.roots.ui.configuration.artifacts; - -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemDescription; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemsHolder; -import com.intellij.packaging.elements.PackagingElement; -import com.intellij.packaging.impl.ui.ArtifactProblemsHolderBase; -import com.intellij.packaging.ui.ArtifactEditorContext; -import com.intellij.packaging.ui.ArtifactProblemQuickFix; -import com.intellij.ui.navigation.Place; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; - -/** - * @author nik - */ -public class ArtifactProblemsHolderImpl extends ArtifactProblemsHolderBase { - private final ProjectStructureProblemsHolder myProblemsHolder; - - public ArtifactProblemsHolderImpl(ArtifactEditorContext context, ProjectStructureProblemsHolder problemsHolder) { - super(context); - myProblemsHolder = problemsHolder; - } - - public void registerError(@NotNull String message, @Nullable List> pathToPlace, @NotNull ArtifactProblemQuickFix... quickFixes) { - registerProblem(message, pathToPlace, ProjectStructureProblemDescription.Severity.ERROR, quickFixes); - } - - private void registerProblem(@NotNull String message, @Nullable List> pathToPlace, - final ProjectStructureProblemDescription.Severity severity, @NotNull ArtifactProblemQuickFix... quickFixes) { - final ArtifactEditorContext context = getContext(); - final Place place = ProjectStructureConfigurable.getInstance(context.getProject()).createArtifactPlace(context.getArtifact()); - myProblemsHolder.registerProblem(new ArtifactProblemDescription(message, severity, pathToPlace, Arrays.asList(quickFixes), place)); - } - - public void registerWarning(@NotNull String message, - @Nullable List> pathToPlace, - @NotNull ArtifactProblemQuickFix... quickFixes) { - registerProblem(message, pathToPlace, ProjectStructureProblemDescription.Severity.WARNING, quickFixes); - } -} +/* + * 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.roots.ui.configuration.artifacts; + +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemDescription; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemsHolder; +import com.intellij.packaging.elements.PackagingElement; +import com.intellij.packaging.impl.ui.ArtifactProblemsHolderBase; +import com.intellij.packaging.ui.ArtifactEditorContext; +import com.intellij.packaging.ui.ArtifactProblemQuickFix; +import com.intellij.ui.navigation.Place; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +/** + * @author nik + */ +public class ArtifactProblemsHolderImpl extends ArtifactProblemsHolderBase { + private final ProjectStructureProblemsHolder myProblemsHolder; + + public ArtifactProblemsHolderImpl(ArtifactEditorContext context, ProjectStructureProblemsHolder problemsHolder) { + super(context); + myProblemsHolder = problemsHolder; + } + + public void registerError(@NotNull String message, @Nullable List> pathToPlace, @NotNull ArtifactProblemQuickFix... quickFixes) { + registerProblem(message, pathToPlace, ProjectStructureProblemDescription.Severity.ERROR, quickFixes); + } + + private void registerProblem(@NotNull String message, @Nullable List> pathToPlace, + final ProjectStructureProblemDescription.Severity severity, @NotNull ArtifactProblemQuickFix... quickFixes) { + final ArtifactEditorContext context = getContext(); + final Place place = ProjectStructureConfigurable.getInstance(context.getProject()).createArtifactPlace(context.getArtifact()); + myProblemsHolder.registerProblem(new ArtifactProblemDescription(message, severity, pathToPlace, Arrays.asList(quickFixes), place)); + } + + public void registerWarning(@NotNull String message, + @Nullable List> pathToPlace, + @NotNull ArtifactProblemQuickFix... quickFixes) { + registerProblem(message, pathToPlace, ProjectStructureProblemDescription.Severity.WARNING, quickFixes); + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/BaseStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/BaseStructureConfigurable.java index c3c5aad239fe..eb3973368615 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/BaseStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/BaseStructureConfigurable.java @@ -1,477 +1,477 @@ -/* - * 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.roots.ui.configuration.projectRoot; - -import com.intellij.facet.Facet; -import com.intellij.ide.CommonActionsManager; -import com.intellij.ide.TreeExpander; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.*; -import com.intellij.openapi.ui.MasterDetailsComponent; -import com.intellij.openapi.ui.MasterDetailsState; -import com.intellij.openapi.ui.NamedConfigurable; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Disposer; -import com.intellij.packaging.artifacts.Artifact; -import com.intellij.ui.ColoredTreeCellRenderer; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.TreeSpeedSearch; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.navigation.Place; -import com.intellij.util.Icons; -import com.intellij.util.containers.Convertor; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.tree.TreePath; -import java.awt.*; -import java.util.*; -import java.util.List; - -public abstract class BaseStructureConfigurable extends MasterDetailsComponent implements SearchableConfigurable, Disposable, Place.Navigator { - - protected StructureConfigurableContext myContext; - - protected final Project myProject; - - protected boolean myUiDisposed = true; - - private boolean myWasTreeInitialized; - - protected boolean myAutoScrollEnabled = true; - - protected BaseStructureConfigurable(Project project, MasterDetailsState state) { - super(state); - myProject = project; - } - - protected BaseStructureConfigurable(final Project project) { - myProject = project; - } - - public void init(StructureConfigurableContext context) { - myContext = context; - myContext.getDaemonAnalyzer().addListener(new ProjectStructureDaemonAnalyzerListener() { - public void usagesCollected(@NotNull ProjectStructureElement containingElement) { - updateTree(); - } - - public void problemsChanged(@NotNull ProjectStructureElement element) { - updateTree(); - } - - public void allProblemsChanged() { - updateTree(); - } - - private void updateTree() { - if (!myTree.isShowing()) return; - - myTree.revalidate(); - myTree.repaint(); - } - }); - } - - - public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { - if (place == null) return new ActionCallback.Done(); - - final Object object = place.getPath(TREE_OBJECT); - final String byName = (String)place.getPath(TREE_NAME); - - if (object == null && byName == null) return new ActionCallback.Done(); - - final MyNode node = object == null ? null : findNodeByObject(myRoot, object); - final MyNode nodeByName = byName == null ? null : findNodeByName(myRoot, byName); - - if (node == null && nodeByName == null) return new ActionCallback.Done(); - - final NamedConfigurable config; - if (node != null) { - config = node.getConfigurable(); - } else { - config = nodeByName.getConfigurable(); - } - - final ActionCallback result = new ActionCallback().doWhenDone(new Runnable() { - public void run() { - myAutoScrollEnabled = true; - } - }); - - myAutoScrollEnabled = false; - myAutoScrollHandler.cancelAllRequests(); - final MyNode nodeToSelect = node != null ? node : nodeByName; - selectNodeInTree(nodeToSelect, requestFocus).doWhenDone(new Runnable() { - public void run() { - setSelectedNode(nodeToSelect); - Place.goFurther(config, place, requestFocus).notifyWhenDone(result); - } - }); - - return result; - } - - - public void queryPlace(@NotNull final Place place) { - if (myCurrentConfigurable != null) { - place.putPath(TREE_OBJECT, myCurrentConfigurable.getEditableObject()); - Place.queryFurther(myCurrentConfigurable, place); - } - } - - protected void initTree() { - if (myWasTreeInitialized) return; - myWasTreeInitialized = true; - - super.initTree(); - new TreeSpeedSearch(myTree, new Convertor() { - public String convert(final TreePath treePath) { - return ((MyNode)treePath.getLastPathComponent()).getDisplayName(); - } - }, true); - ToolTipManager.sharedInstance().registerComponent(myTree); - myTree.setCellRenderer(new ColoredTreeCellRenderer(){ - public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { - if (value instanceof MyNode) { - final MyNode node = (MyNode)value; - - final NamedConfigurable namedConfigurable = node.getConfigurable(); - if (namedConfigurable == null) { - return; - } - - final String displayName = node.getDisplayName(); - final Icon icon = namedConfigurable.getIcon(expanded); - setIcon(icon); - setToolTipText(null); - setFont(UIUtil.getTreeFont()); - - SimpleTextAttributes textAttributes = - selected && hasFocus ? SimpleTextAttributes.SELECTED_SIMPLE_CELL_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES; - if (node.isDisplayInBold()) { - textAttributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; - } else if (namedConfigurable instanceof ProjectStructureElementConfigurable) { - final ProjectStructureElement projectStructureElement = - ((ProjectStructureElementConfigurable)namedConfigurable).getProjectStructureElement(); - if (projectStructureElement != null) { - final ProjectStructureDaemonAnalyzer daemonAnalyzer = myContext.getDaemonAnalyzer(); - final boolean unused = daemonAnalyzer.isUnused(projectStructureElement); - final ProjectStructureProblemsHolderImpl problemsHolder = daemonAnalyzer.getProblemsHolder(projectStructureElement); - if (problemsHolder == null) { - daemonAnalyzer.queueUpdate(projectStructureElement, true, false); - } - final ProjectStructureProblemDescription.Severity level = problemsHolder != null ? problemsHolder.getSeverity() : null; - final boolean invalid = level != null; - if (unused || invalid) { - Color fg = unused - ? UIUtil.getInactiveTextColor() - : selected && hasFocus ? UIUtil.getTreeSelectionForeground() : UIUtil.getTreeForeground(); - textAttributes = new SimpleTextAttributes(invalid ? SimpleTextAttributes.STYLE_WAVED : SimpleTextAttributes.STYLE_PLAIN, fg, - level == ProjectStructureProblemDescription.Severity.ERROR ? Color.RED : Color.GRAY); - String text = problemsHolder != null ? problemsHolder.composeTooltipMessage() : ""; - if (unused) { - text += ProjectBundle.message("project.roots.tooltip.unused", displayName); - } - setToolTipText(text); - } - } - } - append(displayName, textAttributes); - } - } - }); - - } - - public void disposeUIResources() { - if (myUiDisposed) return; - - super.disposeUIResources(); - - myUiDisposed = true; - - myAutoScrollHandler.cancelAllRequests(); - - myContext.getDaemonAnalyzer().clear(); - - Disposer.dispose(this); - } - - protected void addCollapseExpandActions(final List result) { - final TreeExpander expander = new TreeExpander() { - public void expandAll() { - TreeUtil.expandAll(myTree); - } - - public boolean canExpand() { - return true; - } - - public void collapseAll() { - TreeUtil.collapseAll(myTree, 0); - } - - public boolean canCollapse() { - return true; - } - }; - final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); - result.add(actionsManager.createExpandAllAction(expander, myTree)); - result.add(actionsManager.createCollapseAllAction(expander, myTree)); - } - - @Nullable - public ProjectStructureElement getSelectedElement() { - final TreePath selectionPath = myTree.getSelectionPath(); - if (selectionPath != null && selectionPath.getLastPathComponent() instanceof MyNode) { - MyNode node = (MyNode)selectionPath.getLastPathComponent(); - final NamedConfigurable configurable = node.getConfigurable(); - if (configurable instanceof ProjectStructureElementConfigurable) { - return ((ProjectStructureElementConfigurable)configurable).getProjectStructureElement(); - } - } - return null; - } - - private class MyFindUsagesAction extends FindUsagesInProjectStructureActionBase { - - public MyFindUsagesAction(JComponent parentComponent) { - super(parentComponent, myProject); - } - - protected boolean isEnabled() { - final TreePath selectionPath = myTree.getSelectionPath(); - if (selectionPath != null){ - final MyNode node = (MyNode)selectionPath.getLastPathComponent(); - return !node.isDisplayInBold(); - } else { - return false; - } - } - - protected StructureConfigurableContext getContext() { - return myContext; - } - - protected ProjectStructureElement getSelectedElement() { - return BaseStructureConfigurable.this.getSelectedElement(); - } - - protected RelativePoint getPointToShowResults() { - final int selectedRow = myTree.getSelectionRows()[0]; - final Rectangle rowBounds = myTree.getRowBounds(selectedRow); - final Point location = rowBounds.getLocation(); - location.x += rowBounds.width; - return new RelativePoint(myTree, location); - } - } - - - public void reset() { - myUiDisposed = false; - - if (!myWasTreeInitialized) { - initTree(); - myTree.setShowsRootHandles(false); - loadTree(); - } else { - super.disposeUIResources(); - myTree.setShowsRootHandles(false); - loadTree(); - } - for (ProjectStructureElement element : getProjectStructureElements()) { - myContext.getDaemonAnalyzer().queueUpdate(element, false, true); - } - - super.reset(); - } - - @NotNull - protected Collection getProjectStructureElements() { - return Collections.emptyList(); - } - - protected abstract void loadTree(); - - - @NotNull - protected ArrayList createActions(final boolean fromPopup) { - final ArrayList result = new ArrayList(); - AbstractAddGroup addAction = createAddAction(); - if (addAction != null) { - result.add(addAction); - } - result.add(new MyRemoveAction()); - - final AnAction copyAction = createCopyAction(); - if (copyAction != null) { - result.add(copyAction); - } - result.add(Separator.getInstance()); - - result.add(new MyFindUsagesAction(myTree)); - - - return result; - } - - @Nullable - protected AnAction createCopyAction() { - return null; - } - - public void onStructureUnselected() { - } - - public void onStructureSelected() { - } - - @Nullable - protected abstract AbstractAddGroup createAddAction(); - - protected class MyRemoveAction extends MyDeleteAction { - public MyRemoveAction() { - super(new Condition() { - public boolean value(final Object object) { - if (object instanceof MyNode) { - final NamedConfigurable namedConfigurable = ((MyNode)object).getConfigurable(); - if (namedConfigurable != null) { - final Object editableObject = namedConfigurable.getEditableObject(); - if (editableObject instanceof Sdk || editableObject instanceof Module || editableObject instanceof Facet || editableObject instanceof Artifact) return true; - if (editableObject instanceof Library) { - final LibraryTable table = ((Library)editableObject).getTable(); - return table == null || table.isEditable(); - } - } - } - return false; - } - }); - } - - public void actionPerformed(AnActionEvent e) { - final TreePath[] paths = myTree.getSelectionPaths(); - if (paths == null) return; - - final Set pathsToRemove = new HashSet(); - for (TreePath path : paths) { - if (removeFromModel(path)) { - pathsToRemove.add(path); - } - } - removePaths(pathsToRemove.toArray(new TreePath[pathsToRemove.size()])); - } - - private boolean removeFromModel(final TreePath selectionPath) { - final Object last = selectionPath.getLastPathComponent(); - - if (!(last instanceof MyNode)) return false; - - final MyNode node = (MyNode)last; - final NamedConfigurable configurable = node.getConfigurable(); - final Object editableObject = configurable.getEditableObject(); - if (editableObject instanceof Sdk) { - removeJdk((Sdk)editableObject); - } - else if (editableObject instanceof Module) { - if (!removeModule((Module)editableObject)) return false; - } - else if (editableObject instanceof Facet) { - if (removeFacet((Facet)editableObject).isEmpty()) return false; - } - else if (editableObject instanceof Library) { - if (!removeLibrary((Library)editableObject)) return false; - } - else if (editableObject instanceof Artifact) { - removeArtifact((Artifact)editableObject); - } - return true; - } - } - - protected void removeArtifact(Artifact artifact) { - } - - - protected boolean removeLibrary(Library library) { - return false; - } - - protected void removeFacetNodes(@NotNull List facets) { - for (Facet facet : facets) { - MyNode node = findNodeByObject(myRoot, facet); - if (node != null) { - removePaths(TreeUtil.getPathFromRoot(node)); - } - } - } - - protected List removeFacet(final Facet facet) { - return myContext.myModulesConfigurator.getFacetsConfigurator().removeFacet(facet); - } - - protected boolean removeModule(final Module module) { - return true; - } - - protected void removeJdk(final Sdk editableObject) { - } - - protected abstract static class AbstractAddGroup extends ActionGroup implements ActionGroupWithPreselection { - - protected AbstractAddGroup(String text, Icon icon) { - super(text, true); - - final Presentation presentation = getTemplatePresentation(); - presentation.setIcon(icon); - - final Keymap active = KeymapManager.getInstance().getActiveKeymap(); - if (active != null) { - final Shortcut[] shortcuts = active.getShortcuts("NewElement"); - setShortcutSet(new CustomShortcutSet(shortcuts)); - } - } - - public AbstractAddGroup(String text) { - this(text, Icons.ADD_ICON); - } - - public ActionGroup getActionGroup() { - return this; - } - - public int getDefaultIndex() { - return 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.roots.ui.configuration.projectRoot; + +import com.intellij.facet.Facet; +import com.intellij.ide.CommonActionsManager; +import com.intellij.ide.TreeExpander; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.*; +import com.intellij.openapi.ui.MasterDetailsComponent; +import com.intellij.openapi.ui.MasterDetailsState; +import com.intellij.openapi.ui.NamedConfigurable; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.TreeSpeedSearch; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.navigation.Place; +import com.intellij.util.Icons; +import com.intellij.util.containers.Convertor; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.util.*; +import java.util.List; + +public abstract class BaseStructureConfigurable extends MasterDetailsComponent implements SearchableConfigurable, Disposable, Place.Navigator { + + protected StructureConfigurableContext myContext; + + protected final Project myProject; + + protected boolean myUiDisposed = true; + + private boolean myWasTreeInitialized; + + protected boolean myAutoScrollEnabled = true; + + protected BaseStructureConfigurable(Project project, MasterDetailsState state) { + super(state); + myProject = project; + } + + protected BaseStructureConfigurable(final Project project) { + myProject = project; + } + + public void init(StructureConfigurableContext context) { + myContext = context; + myContext.getDaemonAnalyzer().addListener(new ProjectStructureDaemonAnalyzerListener() { + public void usagesCollected(@NotNull ProjectStructureElement containingElement) { + updateTree(); + } + + public void problemsChanged(@NotNull ProjectStructureElement element) { + updateTree(); + } + + public void allProblemsChanged() { + updateTree(); + } + + private void updateTree() { + if (!myTree.isShowing()) return; + + myTree.revalidate(); + myTree.repaint(); + } + }); + } + + + public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { + if (place == null) return new ActionCallback.Done(); + + final Object object = place.getPath(TREE_OBJECT); + final String byName = (String)place.getPath(TREE_NAME); + + if (object == null && byName == null) return new ActionCallback.Done(); + + final MyNode node = object == null ? null : findNodeByObject(myRoot, object); + final MyNode nodeByName = byName == null ? null : findNodeByName(myRoot, byName); + + if (node == null && nodeByName == null) return new ActionCallback.Done(); + + final NamedConfigurable config; + if (node != null) { + config = node.getConfigurable(); + } else { + config = nodeByName.getConfigurable(); + } + + final ActionCallback result = new ActionCallback().doWhenDone(new Runnable() { + public void run() { + myAutoScrollEnabled = true; + } + }); + + myAutoScrollEnabled = false; + myAutoScrollHandler.cancelAllRequests(); + final MyNode nodeToSelect = node != null ? node : nodeByName; + selectNodeInTree(nodeToSelect, requestFocus).doWhenDone(new Runnable() { + public void run() { + setSelectedNode(nodeToSelect); + Place.goFurther(config, place, requestFocus).notifyWhenDone(result); + } + }); + + return result; + } + + + public void queryPlace(@NotNull final Place place) { + if (myCurrentConfigurable != null) { + place.putPath(TREE_OBJECT, myCurrentConfigurable.getEditableObject()); + Place.queryFurther(myCurrentConfigurable, place); + } + } + + protected void initTree() { + if (myWasTreeInitialized) return; + myWasTreeInitialized = true; + + super.initTree(); + new TreeSpeedSearch(myTree, new Convertor() { + public String convert(final TreePath treePath) { + return ((MyNode)treePath.getLastPathComponent()).getDisplayName(); + } + }, true); + ToolTipManager.sharedInstance().registerComponent(myTree); + myTree.setCellRenderer(new ColoredTreeCellRenderer(){ + public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + if (value instanceof MyNode) { + final MyNode node = (MyNode)value; + + final NamedConfigurable namedConfigurable = node.getConfigurable(); + if (namedConfigurable == null) { + return; + } + + final String displayName = node.getDisplayName(); + final Icon icon = namedConfigurable.getIcon(expanded); + setIcon(icon); + setToolTipText(null); + setFont(UIUtil.getTreeFont()); + + SimpleTextAttributes textAttributes = + selected && hasFocus ? SimpleTextAttributes.SELECTED_SIMPLE_CELL_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES; + if (node.isDisplayInBold()) { + textAttributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; + } else if (namedConfigurable instanceof ProjectStructureElementConfigurable) { + final ProjectStructureElement projectStructureElement = + ((ProjectStructureElementConfigurable)namedConfigurable).getProjectStructureElement(); + if (projectStructureElement != null) { + final ProjectStructureDaemonAnalyzer daemonAnalyzer = myContext.getDaemonAnalyzer(); + final boolean unused = daemonAnalyzer.isUnused(projectStructureElement); + final ProjectStructureProblemsHolderImpl problemsHolder = daemonAnalyzer.getProblemsHolder(projectStructureElement); + if (problemsHolder == null) { + daemonAnalyzer.queueUpdate(projectStructureElement, true, false); + } + final ProjectStructureProblemDescription.Severity level = problemsHolder != null ? problemsHolder.getSeverity() : null; + final boolean invalid = level != null; + if (unused || invalid) { + Color fg = unused + ? UIUtil.getInactiveTextColor() + : selected && hasFocus ? UIUtil.getTreeSelectionForeground() : UIUtil.getTreeForeground(); + textAttributes = new SimpleTextAttributes(invalid ? SimpleTextAttributes.STYLE_WAVED : SimpleTextAttributes.STYLE_PLAIN, fg, + level == ProjectStructureProblemDescription.Severity.ERROR ? Color.RED : Color.GRAY); + String text = problemsHolder != null ? problemsHolder.composeTooltipMessage() : ""; + if (unused) { + text += ProjectBundle.message("project.roots.tooltip.unused", displayName); + } + setToolTipText(text); + } + } + } + append(displayName, textAttributes); + } + } + }); + + } + + public void disposeUIResources() { + if (myUiDisposed) return; + + super.disposeUIResources(); + + myUiDisposed = true; + + myAutoScrollHandler.cancelAllRequests(); + + myContext.getDaemonAnalyzer().clear(); + + Disposer.dispose(this); + } + + protected void addCollapseExpandActions(final List result) { + final TreeExpander expander = new TreeExpander() { + public void expandAll() { + TreeUtil.expandAll(myTree); + } + + public boolean canExpand() { + return true; + } + + public void collapseAll() { + TreeUtil.collapseAll(myTree, 0); + } + + public boolean canCollapse() { + return true; + } + }; + final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); + result.add(actionsManager.createExpandAllAction(expander, myTree)); + result.add(actionsManager.createCollapseAllAction(expander, myTree)); + } + + @Nullable + public ProjectStructureElement getSelectedElement() { + final TreePath selectionPath = myTree.getSelectionPath(); + if (selectionPath != null && selectionPath.getLastPathComponent() instanceof MyNode) { + MyNode node = (MyNode)selectionPath.getLastPathComponent(); + final NamedConfigurable configurable = node.getConfigurable(); + if (configurable instanceof ProjectStructureElementConfigurable) { + return ((ProjectStructureElementConfigurable)configurable).getProjectStructureElement(); + } + } + return null; + } + + private class MyFindUsagesAction extends FindUsagesInProjectStructureActionBase { + + public MyFindUsagesAction(JComponent parentComponent) { + super(parentComponent, myProject); + } + + protected boolean isEnabled() { + final TreePath selectionPath = myTree.getSelectionPath(); + if (selectionPath != null){ + final MyNode node = (MyNode)selectionPath.getLastPathComponent(); + return !node.isDisplayInBold(); + } else { + return false; + } + } + + protected StructureConfigurableContext getContext() { + return myContext; + } + + protected ProjectStructureElement getSelectedElement() { + return BaseStructureConfigurable.this.getSelectedElement(); + } + + protected RelativePoint getPointToShowResults() { + final int selectedRow = myTree.getSelectionRows()[0]; + final Rectangle rowBounds = myTree.getRowBounds(selectedRow); + final Point location = rowBounds.getLocation(); + location.x += rowBounds.width; + return new RelativePoint(myTree, location); + } + } + + + public void reset() { + myUiDisposed = false; + + if (!myWasTreeInitialized) { + initTree(); + myTree.setShowsRootHandles(false); + loadTree(); + } else { + super.disposeUIResources(); + myTree.setShowsRootHandles(false); + loadTree(); + } + for (ProjectStructureElement element : getProjectStructureElements()) { + myContext.getDaemonAnalyzer().queueUpdate(element, false, true); + } + + super.reset(); + } + + @NotNull + protected Collection getProjectStructureElements() { + return Collections.emptyList(); + } + + protected abstract void loadTree(); + + + @NotNull + protected ArrayList createActions(final boolean fromPopup) { + final ArrayList result = new ArrayList(); + AbstractAddGroup addAction = createAddAction(); + if (addAction != null) { + result.add(addAction); + } + result.add(new MyRemoveAction()); + + final AnAction copyAction = createCopyAction(); + if (copyAction != null) { + result.add(copyAction); + } + result.add(Separator.getInstance()); + + result.add(new MyFindUsagesAction(myTree)); + + + return result; + } + + @Nullable + protected AnAction createCopyAction() { + return null; + } + + public void onStructureUnselected() { + } + + public void onStructureSelected() { + } + + @Nullable + protected abstract AbstractAddGroup createAddAction(); + + protected class MyRemoveAction extends MyDeleteAction { + public MyRemoveAction() { + super(new Condition() { + public boolean value(final Object object) { + if (object instanceof MyNode) { + final NamedConfigurable namedConfigurable = ((MyNode)object).getConfigurable(); + if (namedConfigurable != null) { + final Object editableObject = namedConfigurable.getEditableObject(); + if (editableObject instanceof Sdk || editableObject instanceof Module || editableObject instanceof Facet || editableObject instanceof Artifact) return true; + if (editableObject instanceof Library) { + final LibraryTable table = ((Library)editableObject).getTable(); + return table == null || table.isEditable(); + } + } + } + return false; + } + }); + } + + public void actionPerformed(AnActionEvent e) { + final TreePath[] paths = myTree.getSelectionPaths(); + if (paths == null) return; + + final Set pathsToRemove = new HashSet(); + for (TreePath path : paths) { + if (removeFromModel(path)) { + pathsToRemove.add(path); + } + } + removePaths(pathsToRemove.toArray(new TreePath[pathsToRemove.size()])); + } + + private boolean removeFromModel(final TreePath selectionPath) { + final Object last = selectionPath.getLastPathComponent(); + + if (!(last instanceof MyNode)) return false; + + final MyNode node = (MyNode)last; + final NamedConfigurable configurable = node.getConfigurable(); + final Object editableObject = configurable.getEditableObject(); + if (editableObject instanceof Sdk) { + removeJdk((Sdk)editableObject); + } + else if (editableObject instanceof Module) { + if (!removeModule((Module)editableObject)) return false; + } + else if (editableObject instanceof Facet) { + if (removeFacet((Facet)editableObject).isEmpty()) return false; + } + else if (editableObject instanceof Library) { + if (!removeLibrary((Library)editableObject)) return false; + } + else if (editableObject instanceof Artifact) { + removeArtifact((Artifact)editableObject); + } + return true; + } + } + + protected void removeArtifact(Artifact artifact) { + } + + + protected boolean removeLibrary(Library library) { + return false; + } + + protected void removeFacetNodes(@NotNull List facets) { + for (Facet facet : facets) { + MyNode node = findNodeByObject(myRoot, facet); + if (node != null) { + removePaths(TreeUtil.getPathFromRoot(node)); + } + } + } + + protected List removeFacet(final Facet facet) { + return myContext.myModulesConfigurator.getFacetsConfigurator().removeFacet(facet); + } + + protected boolean removeModule(final Module module) { + return true; + } + + protected void removeJdk(final Sdk editableObject) { + } + + protected abstract static class AbstractAddGroup extends ActionGroup implements ActionGroupWithPreselection { + + protected AbstractAddGroup(String text, Icon icon) { + super(text, true); + + final Presentation presentation = getTemplatePresentation(); + presentation.setIcon(icon); + + final Keymap active = KeymapManager.getInstance().getActiveKeymap(); + if (active != null) { + final Shortcut[] shortcuts = active.getShortcuts("NewElement"); + setShortcutSet(new CustomShortcutSet(shortcuts)); + } + } + + public AbstractAddGroup(String text) { + this(text, Icons.ADD_ICON); + } + + public ActionGroup getActionGroup() { + return this; + } + + public int getDefaultIndex() { + return 0; + } + } + +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryConfigurable.java index 8d8b3fce64f7..d3b7fa5e27e2 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryConfigurable.java @@ -1,180 +1,180 @@ -/* - * 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.roots.ui.configuration.projectRoot; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.ui.configuration.libraries.LibraryPresentationManager; -import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditor; -import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryRootsComponent; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Disposer; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * User: anna - * Date: 02-Jun-2006 - */ -public class LibraryConfigurable extends ProjectStructureElementConfigurable { - private LibraryRootsComponent myLibraryEditorComponent; - private final Library myLibrary; - private final StructureLibraryTableModifiableModelProvider myModel; - private final StructureConfigurableContext myContext; - private final Project myProject; - private final LibraryProjectStructureElement myProjectStructureElement; - private boolean myUpdatingName; - private boolean myPropertiesLoaded; - - protected LibraryConfigurable(final StructureLibraryTableModifiableModelProvider modelProvider, - final Library library, - final StructureConfigurableContext context, - final Runnable updateTree) { - super(true, updateTree); - myModel = modelProvider; - myContext = context; - myProject = context.getProject(); - myLibrary = library; - myProjectStructureElement = new LibraryProjectStructureElement(context, myLibrary); - } - - public JComponent createOptionsPanel() { - myLibraryEditorComponent = new LibraryRootsComponent(myProject, new Computable() { - @Override - public LibraryEditor compute() { - return getLibraryEditor(); - } - }); - final StructureConfigurableContext context = ModuleStructureConfigurable.getInstance(myProject).getContext(); - myLibraryEditorComponent.addListener(new Runnable() { - public void run() { - context.getDaemonAnalyzer().queueUpdate(myProjectStructureElement); - } - }); - return myLibraryEditorComponent.getComponent(); - } - - public boolean isModified() { - return myLibraryEditorComponent != null && myLibraryEditorComponent.hasChanges(); - } - - @Override - public ProjectStructureElement getProjectStructureElement() { - return myProjectStructureElement; - } - - public void apply() { - applyProperties(); - } - - public void reset() { - resetProperties(); - } - - public void disposeUIResources() { - if (myLibraryEditorComponent != null) { - Disposer.dispose(myLibraryEditorComponent); - myLibraryEditorComponent = null; - } - } - - public void setDisplayName(final String name) { - if (!myUpdatingName) { - getLibraryEditor().setName(name); - } - } - - private LibraryEditor getLibraryEditor() { - return myModel.getModifiableModel().getLibraryEditor(myLibrary); - } - - @Override - public void updateName() { - //todo[nik] pull up to NamedConfigurable - myUpdatingName = true; - try { - super.updateName(); - } - finally { - myUpdatingName = false; - } - } - - public Library getEditableObject() { - return myLibrary; - } - - public String getBannerSlogan() { - final LibraryTable libraryTable = myLibrary.getTable(); - String libraryType = libraryTable == null - ? ProjectBundle.message("module.library.display.name", 1) - : libraryTable.getPresentation().getDisplayName(false); - return ProjectBundle.message("project.roots.library.banner.text", getDisplayName(), libraryType); - } - - public String getDisplayName() { - if (myModel.getModifiableModel().hasLibraryEditor(myLibrary)) { - return getLibraryEditor().getName(); - } - - return myLibrary.getName(); - } - - public void onSelected() { - resetProperties(); - } - - public void onUnselected() { - applyProperties(); - } - - private void resetProperties() { - if (myLibraryEditorComponent != null) { - myLibraryEditorComponent.resetProperties(); - myPropertiesLoaded = true; - } - } - - private void applyProperties() { - if (myLibraryEditorComponent != null && myPropertiesLoaded) { - myLibraryEditorComponent.applyProperties(); - myPropertiesLoaded = false; - } - } - - public Icon getIcon() { - return LibraryPresentationManager.getInstance().getNamedLibraryIcon(myLibrary, myContext); - } - - @Nullable - @NonNls - public String getHelpTopic() { - return "preferences.jdkGlobalLibs"; //todo - } - - public void updateComponent() { - if (myLibraryEditorComponent != null) { - myLibraryEditorComponent.updateRootsTree(); - } - } -} +/* + * 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.roots.ui.configuration.projectRoot; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.ui.configuration.libraries.LibraryPresentationManager; +import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditor; +import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryRootsComponent; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Disposer; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * User: anna + * Date: 02-Jun-2006 + */ +public class LibraryConfigurable extends ProjectStructureElementConfigurable { + private LibraryRootsComponent myLibraryEditorComponent; + private final Library myLibrary; + private final StructureLibraryTableModifiableModelProvider myModel; + private final StructureConfigurableContext myContext; + private final Project myProject; + private final LibraryProjectStructureElement myProjectStructureElement; + private boolean myUpdatingName; + private boolean myPropertiesLoaded; + + protected LibraryConfigurable(final StructureLibraryTableModifiableModelProvider modelProvider, + final Library library, + final StructureConfigurableContext context, + final Runnable updateTree) { + super(true, updateTree); + myModel = modelProvider; + myContext = context; + myProject = context.getProject(); + myLibrary = library; + myProjectStructureElement = new LibraryProjectStructureElement(context, myLibrary); + } + + public JComponent createOptionsPanel() { + myLibraryEditorComponent = new LibraryRootsComponent(myProject, new Computable() { + @Override + public LibraryEditor compute() { + return getLibraryEditor(); + } + }); + final StructureConfigurableContext context = ModuleStructureConfigurable.getInstance(myProject).getContext(); + myLibraryEditorComponent.addListener(new Runnable() { + public void run() { + context.getDaemonAnalyzer().queueUpdate(myProjectStructureElement); + } + }); + return myLibraryEditorComponent.getComponent(); + } + + public boolean isModified() { + return myLibraryEditorComponent != null && myLibraryEditorComponent.hasChanges(); + } + + @Override + public ProjectStructureElement getProjectStructureElement() { + return myProjectStructureElement; + } + + public void apply() { + applyProperties(); + } + + public void reset() { + resetProperties(); + } + + public void disposeUIResources() { + if (myLibraryEditorComponent != null) { + Disposer.dispose(myLibraryEditorComponent); + myLibraryEditorComponent = null; + } + } + + public void setDisplayName(final String name) { + if (!myUpdatingName) { + getLibraryEditor().setName(name); + } + } + + private LibraryEditor getLibraryEditor() { + return myModel.getModifiableModel().getLibraryEditor(myLibrary); + } + + @Override + public void updateName() { + //todo[nik] pull up to NamedConfigurable + myUpdatingName = true; + try { + super.updateName(); + } + finally { + myUpdatingName = false; + } + } + + public Library getEditableObject() { + return myLibrary; + } + + public String getBannerSlogan() { + final LibraryTable libraryTable = myLibrary.getTable(); + String libraryType = libraryTable == null + ? ProjectBundle.message("module.library.display.name", 1) + : libraryTable.getPresentation().getDisplayName(false); + return ProjectBundle.message("project.roots.library.banner.text", getDisplayName(), libraryType); + } + + public String getDisplayName() { + if (myModel.getModifiableModel().hasLibraryEditor(myLibrary)) { + return getLibraryEditor().getName(); + } + + return myLibrary.getName(); + } + + public void onSelected() { + resetProperties(); + } + + public void onUnselected() { + applyProperties(); + } + + private void resetProperties() { + if (myLibraryEditorComponent != null) { + myLibraryEditorComponent.resetProperties(); + myPropertiesLoaded = true; + } + } + + private void applyProperties() { + if (myLibraryEditorComponent != null && myPropertiesLoaded) { + myLibraryEditorComponent.applyProperties(); + myPropertiesLoaded = false; + } + } + + public Icon getIcon() { + return LibraryPresentationManager.getInstance().getNamedLibraryIcon(myLibrary, myContext); + } + + @Nullable + @NonNls + public String getHelpTopic() { + return "preferences.jdkGlobalLibs"; //todo + } + + public void updateComponent() { + if (myLibraryEditorComponent != null) { + myLibraryEditorComponent.updateRootsTree(); + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ConfigurationErrorQuickFix.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ConfigurationErrorQuickFix.java index df8242fcd571..c6ebb4220289 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ConfigurationErrorQuickFix.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ConfigurationErrorQuickFix.java @@ -1,30 +1,30 @@ -/* - * 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.roots.ui.configuration.projectRoot.daemon; - -/** - * @author nik - */ -public abstract class ConfigurationErrorQuickFix { - private final String myActionName; - - protected ConfigurationErrorQuickFix(String actionName) { - myActionName = actionName; - } - - public String getActionName() { - return myActionName; - } - - public abstract void performFix(); -} +/* + * 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.roots.ui.configuration.projectRoot.daemon; + +/** + * @author nik + */ +public abstract class ConfigurationErrorQuickFix { + private final String myActionName; + + protected ConfigurationErrorQuickFix(String actionName) { + myActionName = actionName; + } + + public String getActionName() { + return myActionName; + } + + public abstract void performFix(); +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/LibraryProjectStructureElement.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/LibraryProjectStructureElement.java index e407a0cf200c..3873df365b1a 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/LibraryProjectStructureElement.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/LibraryProjectStructureElement.java @@ -1,159 +1,159 @@ -package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.roots.JavadocOrderRootType; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.impl.libraries.LibraryEx; -import com.intellij.openapi.roots.impl.libraries.LibraryImpl; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; -import com.intellij.openapi.roots.ui.configuration.ModuleEditor; -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor; -import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesModifiableModel; -import com.intellij.openapi.roots.ui.configuration.projectRoot.LibraryConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; -import com.intellij.openapi.ui.NamedConfigurable; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.ui.navigation.Place; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author nik - */ -public class LibraryProjectStructureElement extends ProjectStructureElement { - private final Library myLibrary; - - public LibraryProjectStructureElement(@NotNull StructureConfigurableContext context, @NotNull Library library) { - super(context); - myLibrary = library; - } - - public Library getLibrary() { - return myLibrary; - } - - @Override - public void check(ProjectStructureProblemsHolder problemsHolder) { - if (((LibraryEx)myLibrary).isDisposed()) return; - final LibraryEx library = (LibraryEx)myContext.getLibraryModel(myLibrary); - if (library == null || library.isDisposed()) return; - - final String libraryName = library.getName(); - final List invalidClasses = library.getInvalidRootUrls(OrderRootType.CLASSES); - if (!invalidClasses.isEmpty()) { - final String description = createInvalidRootsDescription(invalidClasses, libraryName); - problemsHolder.registerError(ProjectBundle.message("project.roots.tooltip.library.misconfigured", libraryName), description, createPlace(), - new RemoveInvalidRootsQuickFix(Collections.singletonMap(OrderRootType.CLASSES, invalidClasses), library)); - } - final List invalidJavadocs = library.getInvalidRootUrls(JavadocOrderRootType.getInstance()); - final List invalidSources = library.getInvalidRootUrls(OrderRootType.SOURCES); - if (!invalidJavadocs.isEmpty() || !invalidSources.isEmpty()) { - final Map> invalidRoots = new HashMap>(); - invalidRoots.put(OrderRootType.SOURCES, invalidSources); - invalidRoots.put(JavadocOrderRootType.getInstance(), invalidJavadocs); - final String description = createInvalidRootsDescription(ContainerUtil.concat(invalidJavadocs, invalidSources), libraryName); - problemsHolder.registerWarning(ProjectBundle.message("project.roots.tooltip.library.misconfigured", libraryName), description, createPlace(), - new RemoveInvalidRootsQuickFix(invalidRoots, library)); - } - } - - private static String createInvalidRootsDescription(List invalidClasses, String libraryName) { - StringBuilder buffer = new StringBuilder(); - buffer.append(""); - buffer.append("Library '").append(libraryName).append("' has broken paths:"); - for (String url : invalidClasses) { - buffer.append("
  "); - buffer.append(VfsUtil.urlToPath(url)); - } - buffer.append(""); - return buffer.toString(); - } - - @NotNull - private Place createPlace() { - return ProjectStructureConfigurable.getInstance(myContext.getProject()).createProjectOrGlobalLibraryPlace(myLibrary); - } - - @Override - public List getUsagesInElement() { - return Collections.emptyList(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof LibraryProjectStructureElement)) return false; - - return getSourceOrThis().equals(((LibraryProjectStructureElement)o).getSourceOrThis()); - } - - @NotNull - private Library getSourceOrThis() { - final InvocationHandler invocationHandler = Proxy.isProxyClass(myLibrary.getClass()) ? Proxy.getInvocationHandler(myLibrary) : null; - final Library realLibrary = invocationHandler instanceof ModuleEditor.ProxyDelegateAccessor ? - (Library)((ModuleEditor.ProxyDelegateAccessor)invocationHandler).getDelegate() : myLibrary; - final Library source = realLibrary instanceof LibraryImpl? ((LibraryImpl)realLibrary).getSource() : null; - return source != null ? source : myLibrary; - } - - @Override - public int hashCode() { - return getSourceOrThis().hashCode(); - } - - @Override - public String toString() { - return "library:" + myLibrary.getName(); - } - - @Override - public boolean highlightIfUnused() { - final LibraryTable libraryTable = myLibrary.getTable(); - return libraryTable != null && LibraryTablesRegistrar.PROJECT_LEVEL.equals(libraryTable.getTableLevel()); - } - - private class RemoveInvalidRootsQuickFix extends ConfigurationErrorQuickFix { - private final Map> myInvalidRoots; - private final Library myLibrary; - - public RemoveInvalidRootsQuickFix(Map> invalidRoots, Library library) { - super("Remove invalid roots"); - myInvalidRoots = invalidRoots; - myLibrary = library; - } - - @Override - public void performFix() { - final LibraryTable.ModifiableModel libraryTable = myContext.getModifiableLibraryTable(myLibrary.getTable()); - if (libraryTable instanceof LibrariesModifiableModel) { - for (OrderRootType rootType : myInvalidRoots.keySet()) { - for (String invalidRoot : myInvalidRoots.get(rootType)) { - final ExistingLibraryEditor libraryEditor = ((LibrariesModifiableModel)libraryTable).getLibraryEditor(myLibrary); - libraryEditor.removeRoot(invalidRoot, rootType); - } - } - myContext.getDaemonAnalyzer().queueUpdate(LibraryProjectStructureElement.this); - final ProjectStructureConfigurable structureConfigurable = ProjectStructureConfigurable.getInstance(myContext.getProject()); - structureConfigurable.navigateTo(createPlace(), true).doWhenDone(new Runnable() { - @Override - public void run() { - final NamedConfigurable configurable = structureConfigurable.getConfigurableFor(myLibrary).getSelectedConfugurable(); - if (configurable instanceof LibraryConfigurable) { - ((LibraryConfigurable)configurable).updateComponent(); - } - } - }); - } - } - } -} +package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.roots.JavadocOrderRootType; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.impl.libraries.LibraryEx; +import com.intellij.openapi.roots.impl.libraries.LibraryImpl; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.roots.ui.configuration.ModuleEditor; +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor; +import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesModifiableModel; +import com.intellij.openapi.roots.ui.configuration.projectRoot.LibraryConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import com.intellij.openapi.ui.NamedConfigurable; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.ui.navigation.Place; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author nik + */ +public class LibraryProjectStructureElement extends ProjectStructureElement { + private final Library myLibrary; + + public LibraryProjectStructureElement(@NotNull StructureConfigurableContext context, @NotNull Library library) { + super(context); + myLibrary = library; + } + + public Library getLibrary() { + return myLibrary; + } + + @Override + public void check(ProjectStructureProblemsHolder problemsHolder) { + if (((LibraryEx)myLibrary).isDisposed()) return; + final LibraryEx library = (LibraryEx)myContext.getLibraryModel(myLibrary); + if (library == null || library.isDisposed()) return; + + final String libraryName = library.getName(); + final List invalidClasses = library.getInvalidRootUrls(OrderRootType.CLASSES); + if (!invalidClasses.isEmpty()) { + final String description = createInvalidRootsDescription(invalidClasses, libraryName); + problemsHolder.registerError(ProjectBundle.message("project.roots.tooltip.library.misconfigured", libraryName), description, createPlace(), + new RemoveInvalidRootsQuickFix(Collections.singletonMap(OrderRootType.CLASSES, invalidClasses), library)); + } + final List invalidJavadocs = library.getInvalidRootUrls(JavadocOrderRootType.getInstance()); + final List invalidSources = library.getInvalidRootUrls(OrderRootType.SOURCES); + if (!invalidJavadocs.isEmpty() || !invalidSources.isEmpty()) { + final Map> invalidRoots = new HashMap>(); + invalidRoots.put(OrderRootType.SOURCES, invalidSources); + invalidRoots.put(JavadocOrderRootType.getInstance(), invalidJavadocs); + final String description = createInvalidRootsDescription(ContainerUtil.concat(invalidJavadocs, invalidSources), libraryName); + problemsHolder.registerWarning(ProjectBundle.message("project.roots.tooltip.library.misconfigured", libraryName), description, createPlace(), + new RemoveInvalidRootsQuickFix(invalidRoots, library)); + } + } + + private static String createInvalidRootsDescription(List invalidClasses, String libraryName) { + StringBuilder buffer = new StringBuilder(); + buffer.append(""); + buffer.append("Library '").append(libraryName).append("' has broken paths:"); + for (String url : invalidClasses) { + buffer.append("
  "); + buffer.append(VfsUtil.urlToPath(url)); + } + buffer.append(""); + return buffer.toString(); + } + + @NotNull + private Place createPlace() { + return ProjectStructureConfigurable.getInstance(myContext.getProject()).createProjectOrGlobalLibraryPlace(myLibrary); + } + + @Override + public List getUsagesInElement() { + return Collections.emptyList(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LibraryProjectStructureElement)) return false; + + return getSourceOrThis().equals(((LibraryProjectStructureElement)o).getSourceOrThis()); + } + + @NotNull + private Library getSourceOrThis() { + final InvocationHandler invocationHandler = Proxy.isProxyClass(myLibrary.getClass()) ? Proxy.getInvocationHandler(myLibrary) : null; + final Library realLibrary = invocationHandler instanceof ModuleEditor.ProxyDelegateAccessor ? + (Library)((ModuleEditor.ProxyDelegateAccessor)invocationHandler).getDelegate() : myLibrary; + final Library source = realLibrary instanceof LibraryImpl? ((LibraryImpl)realLibrary).getSource() : null; + return source != null ? source : myLibrary; + } + + @Override + public int hashCode() { + return getSourceOrThis().hashCode(); + } + + @Override + public String toString() { + return "library:" + myLibrary.getName(); + } + + @Override + public boolean highlightIfUnused() { + final LibraryTable libraryTable = myLibrary.getTable(); + return libraryTable != null && LibraryTablesRegistrar.PROJECT_LEVEL.equals(libraryTable.getTableLevel()); + } + + private class RemoveInvalidRootsQuickFix extends ConfigurationErrorQuickFix { + private final Map> myInvalidRoots; + private final Library myLibrary; + + public RemoveInvalidRootsQuickFix(Map> invalidRoots, Library library) { + super("Remove invalid roots"); + myInvalidRoots = invalidRoots; + myLibrary = library; + } + + @Override + public void performFix() { + final LibraryTable.ModifiableModel libraryTable = myContext.getModifiableLibraryTable(myLibrary.getTable()); + if (libraryTable instanceof LibrariesModifiableModel) { + for (OrderRootType rootType : myInvalidRoots.keySet()) { + for (String invalidRoot : myInvalidRoots.get(rootType)) { + final ExistingLibraryEditor libraryEditor = ((LibrariesModifiableModel)libraryTable).getLibraryEditor(myLibrary); + libraryEditor.removeRoot(invalidRoot, rootType); + } + } + myContext.getDaemonAnalyzer().queueUpdate(LibraryProjectStructureElement.this); + final ProjectStructureConfigurable structureConfigurable = ProjectStructureConfigurable.getInstance(myContext.getProject()); + structureConfigurable.navigateTo(createPlace(), true).doWhenDone(new Runnable() { + @Override + public void run() { + final NamedConfigurable configurable = structureConfigurable.getConfigurableFor(myLibrary).getSelectedConfugurable(); + if (configurable instanceof LibraryConfigurable) { + ((LibraryConfigurable)configurable).updateComponent(); + } + } + }); + } + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ModuleProjectStructureElement.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ModuleProjectStructureElement.java index 10024f6eb27a..a457a840cbd5 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ModuleProjectStructureElement.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ModuleProjectStructureElement.java @@ -1,133 +1,133 @@ -package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.openapi.module.ModifiableModuleModel; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.ui.configuration.ModuleEditor; -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; -import com.intellij.ui.navigation.Place; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author nik - */ -public class ModuleProjectStructureElement extends ProjectStructureElement { - private final Module myModule; - - public ModuleProjectStructureElement(@NotNull StructureConfigurableContext context, @NotNull Module module) { - super(context); - myModule = module; - } - - public Module getModule() { - return myModule; - } - - @Override - public void check(ProjectStructureProblemsHolder problemsHolder) { - final ModifiableModuleModel moduleModel = myContext.getModulesConfigurator().getModuleModel(); - final Module[] all = moduleModel.getModules(); - if (!ArrayUtil.contains(myModule, all)) { - return;//module has been deleted - } - - for (Module each : all) { - if (each != myModule && myContext.getRealName(each).equals(myContext.getRealName(myModule))) { - problemsHolder.registerError(ProjectBundle.message("project.roots.module.duplicate.name.message"), null, createPlace(), null); - break; - } - } - - final ModuleRootModel rootModel = myContext.getModulesConfigurator().getRootModel(myModule); - if (rootModel == null) return; //already disposed - final OrderEntry[] entries = rootModel.getOrderEntries(); - for (OrderEntry entry : entries) { - if (!entry.isValid()){ - if (entry instanceof JdkOrderEntry && ((JdkOrderEntry)entry).getJdkName() == null) { - problemsHolder.registerError(ProjectBundle.message("project.roots.module.jdk.problem.message"), null, createPlace(), null); - } else { - problemsHolder.registerError(ProjectBundle.message("project.roots.library.problem.message", entry.getPresentableName()), null, - createPlace(), - null); - } - } - //todo[nik] highlight libraries with invalid paths in ClasspathEditor - //else if (entry instanceof LibraryOrderEntry) { - // final LibraryEx library = (LibraryEx)((LibraryOrderEntry)entry).getLibrary(); - // if (library != null) { - // if (!library.allPathsValid(OrderRootType.CLASSES)) { - // problemsHolder.registerError(ProjectBundle.message("project.roots.tooltip.library.misconfigured", entry.getName())); - // } - // else if (!library.allPathsValid(OrderRootType.SOURCES)) { - // problemsHolder.registerWarning(ProjectBundle.message("project.roots.tooltip.library.misconfigured", entry.getName())); - // } - // } - //} - } - } - - private Place createPlace() { - return ProjectStructureConfigurable.getInstance(myContext.getProject()).createModulePlace(myModule); - } - - @Override - public List getUsagesInElement() { - final List usages = new ArrayList(); - final ModuleEditor moduleEditor = myContext.getModulesConfigurator().getModuleEditor(myModule); - if (moduleEditor != null) { - for (OrderEntry entry : moduleEditor.getOrderEntries()) { - if (entry instanceof ModuleOrderEntry) { - final Module module = ((ModuleOrderEntry)entry).getModule(); - if (module != null) { - usages.add(new UsageInModuleClasspath(myContext, this, new ModuleProjectStructureElement(myContext, module))); - } - } - else if (entry instanceof LibraryOrderEntry) { - final Library library = ((LibraryOrderEntry)entry).getLibrary(); - if (library != null) { - usages.add(new UsageInModuleClasspath(myContext, this, new LibraryProjectStructureElement(myContext, library))); - } - } - else if (entry instanceof JdkOrderEntry) { - final Sdk jdk = ((JdkOrderEntry)entry).getJdk(); - if (jdk != null) { - usages.add(new UsageInModuleClasspath(myContext, this, new SdkProjectStructureElement(myContext, jdk))); - } - } - } - } - return usages; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ModuleProjectStructureElement)) return false; - - return myModule.equals(((ModuleProjectStructureElement)o).myModule); - - } - - @Override - public int hashCode() { - return myModule.hashCode(); - } - - @Override - public String toString() { - return "module:" + myModule.getName(); - } - - @Override - public boolean highlightIfUnused() { - return false; - } -} +package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.openapi.module.ModifiableModuleModel; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.ui.configuration.ModuleEditor; +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import com.intellij.ui.navigation.Place; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author nik + */ +public class ModuleProjectStructureElement extends ProjectStructureElement { + private final Module myModule; + + public ModuleProjectStructureElement(@NotNull StructureConfigurableContext context, @NotNull Module module) { + super(context); + myModule = module; + } + + public Module getModule() { + return myModule; + } + + @Override + public void check(ProjectStructureProblemsHolder problemsHolder) { + final ModifiableModuleModel moduleModel = myContext.getModulesConfigurator().getModuleModel(); + final Module[] all = moduleModel.getModules(); + if (!ArrayUtil.contains(myModule, all)) { + return;//module has been deleted + } + + for (Module each : all) { + if (each != myModule && myContext.getRealName(each).equals(myContext.getRealName(myModule))) { + problemsHolder.registerError(ProjectBundle.message("project.roots.module.duplicate.name.message"), null, createPlace(), null); + break; + } + } + + final ModuleRootModel rootModel = myContext.getModulesConfigurator().getRootModel(myModule); + if (rootModel == null) return; //already disposed + final OrderEntry[] entries = rootModel.getOrderEntries(); + for (OrderEntry entry : entries) { + if (!entry.isValid()){ + if (entry instanceof JdkOrderEntry && ((JdkOrderEntry)entry).getJdkName() == null) { + problemsHolder.registerError(ProjectBundle.message("project.roots.module.jdk.problem.message"), null, createPlace(), null); + } else { + problemsHolder.registerError(ProjectBundle.message("project.roots.library.problem.message", entry.getPresentableName()), null, + createPlace(), + null); + } + } + //todo[nik] highlight libraries with invalid paths in ClasspathEditor + //else if (entry instanceof LibraryOrderEntry) { + // final LibraryEx library = (LibraryEx)((LibraryOrderEntry)entry).getLibrary(); + // if (library != null) { + // if (!library.allPathsValid(OrderRootType.CLASSES)) { + // problemsHolder.registerError(ProjectBundle.message("project.roots.tooltip.library.misconfigured", entry.getName())); + // } + // else if (!library.allPathsValid(OrderRootType.SOURCES)) { + // problemsHolder.registerWarning(ProjectBundle.message("project.roots.tooltip.library.misconfigured", entry.getName())); + // } + // } + //} + } + } + + private Place createPlace() { + return ProjectStructureConfigurable.getInstance(myContext.getProject()).createModulePlace(myModule); + } + + @Override + public List getUsagesInElement() { + final List usages = new ArrayList(); + final ModuleEditor moduleEditor = myContext.getModulesConfigurator().getModuleEditor(myModule); + if (moduleEditor != null) { + for (OrderEntry entry : moduleEditor.getOrderEntries()) { + if (entry instanceof ModuleOrderEntry) { + final Module module = ((ModuleOrderEntry)entry).getModule(); + if (module != null) { + usages.add(new UsageInModuleClasspath(myContext, this, new ModuleProjectStructureElement(myContext, module))); + } + } + else if (entry instanceof LibraryOrderEntry) { + final Library library = ((LibraryOrderEntry)entry).getLibrary(); + if (library != null) { + usages.add(new UsageInModuleClasspath(myContext, this, new LibraryProjectStructureElement(myContext, library))); + } + } + else if (entry instanceof JdkOrderEntry) { + final Sdk jdk = ((JdkOrderEntry)entry).getJdk(); + if (jdk != null) { + usages.add(new UsageInModuleClasspath(myContext, this, new SdkProjectStructureElement(myContext, jdk))); + } + } + } + } + return usages; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ModuleProjectStructureElement)) return false; + + return myModule.equals(((ModuleProjectStructureElement)o).myModule); + + } + + @Override + public int hashCode() { + return myModule.hashCode(); + } + + @Override + public String toString() { + return "module:" + myModule.getName(); + } + + @Override + public boolean highlightIfUnused() { + return false; + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblem.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblem.java index fd8157db4720..500b7460a792 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblem.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblem.java @@ -1,71 +1,71 @@ -/* - * 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.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.openapi.roots.ui.configuration.ConfigurationError; -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; -import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.util.List; - -/** -* @author nik -*/ -class ProjectConfigurationProblem extends ConfigurationError { - private final StructureConfigurableContext myContext; - private final ProjectStructureProblemDescription myDescription; - - public ProjectConfigurationProblem(StructureConfigurableContext context, ProjectStructureProblemDescription description) { - super(description.getMessage(), description.getDescription() != null ? description.getDescription() : description.getMessage()); - myContext = context; - myDescription = description; - } - - @Override - public void navigate() { - ProjectStructureConfigurable.getInstance(myContext.getProject()).navigateTo(myDescription.getPlace(), true); - } - - @Override - public boolean canBeFixed() { - return !myDescription.getFixes().isEmpty(); - } - - @Override - public void fix(JComponent contextComponent) { - final List fixes = myDescription.getFixes(); - if (fixes.size() == 1) { - fixes.get(0).performFix(); - } - else { - JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep(null, fixes) { - @NotNull - @Override - public String getTextFor(ConfigurationErrorQuickFix value) { - return value.getActionName(); - } - - @Override - public PopupStep onChosen(ConfigurationErrorQuickFix selectedValue, boolean finalChoice) { - selectedValue.performFix(); - return FINAL_CHOICE; - } - }).showUnderneathOf(contextComponent); - } - } -} +/* + * 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.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.openapi.roots.ui.configuration.ConfigurationError; +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.List; + +/** +* @author nik +*/ +class ProjectConfigurationProblem extends ConfigurationError { + private final StructureConfigurableContext myContext; + private final ProjectStructureProblemDescription myDescription; + + public ProjectConfigurationProblem(StructureConfigurableContext context, ProjectStructureProblemDescription description) { + super(description.getMessage(), description.getDescription() != null ? description.getDescription() : description.getMessage()); + myContext = context; + myDescription = description; + } + + @Override + public void navigate() { + ProjectStructureConfigurable.getInstance(myContext.getProject()).navigateTo(myDescription.getPlace(), true); + } + + @Override + public boolean canBeFixed() { + return !myDescription.getFixes().isEmpty(); + } + + @Override + public void fix(JComponent contextComponent) { + final List fixes = myDescription.getFixes(); + if (fixes.size() == 1) { + fixes.get(0).performFix(); + } + else { + JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep(null, fixes) { + @NotNull + @Override + public String getTextFor(ConfigurationErrorQuickFix value) { + return value.getActionName(); + } + + @Override + public PopupStep onChosen(ConfigurationErrorQuickFix selectedValue, boolean finalChoice) { + selectedValue.performFix(); + return FINAL_CHOICE; + } + }).showUnderneathOf(contextComponent); + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblems.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblems.java index f015984feed6..049fe43a80e6 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblems.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectConfigurationProblems.java @@ -1,84 +1,84 @@ -/* - * 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.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.openapi.roots.ui.configuration.ConfigurationError; -import com.intellij.openapi.roots.ui.configuration.ConfigurationErrors; -import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; -import com.intellij.openapi.util.MultiValuesMap; -import com.intellij.openapi.util.registry.Registry; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.List; - -/** - * @author nik - */ -public class ProjectConfigurationProblems { - private MultiValuesMap myErrors = new MultiValuesMap(); - private final ProjectStructureDaemonAnalyzer myAnalyzer; - private final StructureConfigurableContext myContext; - - public static boolean isVisible() { - return Registry.is("ide.configuration.new.project.structure.errors"); - } - - public ProjectConfigurationProblems(ProjectStructureDaemonAnalyzer analyzer, StructureConfigurableContext context) { - myAnalyzer = analyzer; - myContext = context; - analyzer.addListener(new ProjectStructureDaemonAnalyzerListener() { - @Override - public void usagesCollected(@NotNull ProjectStructureElement containingElement) { - } - - @Override - public void problemsChanged(@NotNull ProjectStructureElement element) { - updateErrors(element); - } - - @Override - public void allProblemsChanged() { - clearProblems(); - } - }); - } - - public void clearProblems() { - removeErrors(myErrors.values()); - myErrors.clear(); - } - - private void updateErrors(ProjectStructureElement element) { - removeErrors(myErrors.removeAll(element)); - final ProjectStructureProblemsHolderImpl problemsHolder = myAnalyzer.getProblemsHolder(element); - if (problemsHolder != null) { - final List descriptions = problemsHolder.getProblemDescriptions(); - if (descriptions != null) { - for (ProjectStructureProblemDescription description : descriptions) { - final ProjectConfigurationProblem error = new ProjectConfigurationProblem(myContext, description); - myErrors.put(element, error); - ConfigurationErrors.Bus.addError(error, myContext.getProject()); - } - } - } - } - - private void removeErrors(@Nullable Collection errors) { - if (errors == null) return; - for (ConfigurationError error : errors) { - ConfigurationErrors.Bus.removeError(error, myContext.getProject()); - } - } -} +/* + * 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.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.openapi.roots.ui.configuration.ConfigurationError; +import com.intellij.openapi.roots.ui.configuration.ConfigurationErrors; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import com.intellij.openapi.util.MultiValuesMap; +import com.intellij.openapi.util.registry.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * @author nik + */ +public class ProjectConfigurationProblems { + private MultiValuesMap myErrors = new MultiValuesMap(); + private final ProjectStructureDaemonAnalyzer myAnalyzer; + private final StructureConfigurableContext myContext; + + public static boolean isVisible() { + return Registry.is("ide.configuration.new.project.structure.errors"); + } + + public ProjectConfigurationProblems(ProjectStructureDaemonAnalyzer analyzer, StructureConfigurableContext context) { + myAnalyzer = analyzer; + myContext = context; + analyzer.addListener(new ProjectStructureDaemonAnalyzerListener() { + @Override + public void usagesCollected(@NotNull ProjectStructureElement containingElement) { + } + + @Override + public void problemsChanged(@NotNull ProjectStructureElement element) { + updateErrors(element); + } + + @Override + public void allProblemsChanged() { + clearProblems(); + } + }); + } + + public void clearProblems() { + removeErrors(myErrors.values()); + myErrors.clear(); + } + + private void updateErrors(ProjectStructureElement element) { + removeErrors(myErrors.removeAll(element)); + final ProjectStructureProblemsHolderImpl problemsHolder = myAnalyzer.getProblemsHolder(element); + if (problemsHolder != null) { + final List descriptions = problemsHolder.getProblemDescriptions(); + if (descriptions != null) { + for (ProjectStructureProblemDescription description : descriptions) { + final ProjectConfigurationProblem error = new ProjectConfigurationProblem(myContext, description); + myErrors.put(element, error); + ConfigurationErrors.Bus.addError(error, myContext.getProject()); + } + } + } + } + + private void removeErrors(@Nullable Collection errors) { + if (errors == null) return; + for (ConfigurationError error : errors) { + ConfigurationErrors.Bus.removeError(error, myContext.getProject()); + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemDescription.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemDescription.java index c06a344b1002..d4b125dc58fb 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemDescription.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemDescription.java @@ -1,65 +1,65 @@ -/* - * 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.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.ui.navigation.Place; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * @author nik - */ -public class ProjectStructureProblemDescription { - private final String myMessage; - private final String myDescription; - private final Severity mySeverity; - private final Place myPlace; - private final List myFixes; - - public ProjectStructureProblemDescription(@NotNull String message, @Nullable String description, @NotNull Severity severity, @NotNull Place place, - @NotNull List fixes) { - myMessage = message; - myDescription = description; - mySeverity = severity; - myPlace = place; - myFixes = fixes; - } - - public String getMessage() { - return myMessage; - } - - @Nullable - public String getDescription() { - return myDescription; - } - - public List getFixes() { - return myFixes; - } - - public Severity getSeverity() { - return mySeverity; - } - - public Place getPlace() { - return myPlace; - } - - public enum Severity { ERROR, WARNING } -} +/* + * 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.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.ui.navigation.Place; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author nik + */ +public class ProjectStructureProblemDescription { + private final String myMessage; + private final String myDescription; + private final Severity mySeverity; + private final Place myPlace; + private final List myFixes; + + public ProjectStructureProblemDescription(@NotNull String message, @Nullable String description, @NotNull Severity severity, @NotNull Place place, + @NotNull List fixes) { + myMessage = message; + myDescription = description; + mySeverity = severity; + myPlace = place; + myFixes = fixes; + } + + public String getMessage() { + return myMessage; + } + + @Nullable + public String getDescription() { + return myDescription; + } + + public List getFixes() { + return myFixes; + } + + public Severity getSeverity() { + return mySeverity; + } + + public Place getPlace() { + return myPlace; + } + + public enum Severity { ERROR, WARNING } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolder.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolder.java index 1cf9d62f1d7d..411451abfdcc 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolder.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolder.java @@ -1,31 +1,31 @@ -/* - * 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.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.ui.navigation.Place; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author nik - */ -public interface ProjectStructureProblemsHolder { - void registerError(@NotNull String message, @Nullable String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix); - - void registerWarning(@NotNull String message, @Nullable String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix); - - void registerProblem(@NotNull ProjectStructureProblemDescription description); -} +/* + * 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.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.ui.navigation.Place; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author nik + */ +public interface ProjectStructureProblemsHolder { + void registerError(@NotNull String message, @Nullable String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix); + + void registerWarning(@NotNull String message, @Nullable String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix); + + void registerProblem(@NotNull ProjectStructureProblemDescription description); +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java index 3984a1a7ee0e..ea515d43263f 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/ProjectStructureProblemsHolderImpl.java @@ -1,75 +1,75 @@ -package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; - -import com.intellij.ui.navigation.Place; -import com.intellij.util.SmartList; -import com.intellij.util.StringBuilderSpinAllocator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -/** - * @author nik - */ -public class ProjectStructureProblemsHolderImpl implements ProjectStructureProblemsHolder { - private List myProblemDescriptions; - - public void registerError(@NotNull String message, String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix) { - final List fixes = fix != null ? Collections.singletonList(fix) : Collections.emptyList(); - registerProblem(new ProjectStructureProblemDescription(message, description, ProjectStructureProblemDescription.Severity.ERROR, place, fixes)); - } - - public void registerWarning(@NotNull String message, String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix) { - final List fixes = Collections.singletonList(fix); - registerProblem(new ProjectStructureProblemDescription(message, description, ProjectStructureProblemDescription.Severity.WARNING, place, fixes)); - } - - public void registerProblem(final @NotNull ProjectStructureProblemDescription description) { - if (myProblemDescriptions == null) { - myProblemDescriptions = new SmartList(); - } - myProblemDescriptions.add(description); - } - - @Nullable - public ProjectStructureProblemDescription.Severity getSeverity() { - if (myProblemDescriptions == null || myProblemDescriptions.isEmpty()) { - return null; - } - for (ProjectStructureProblemDescription description : myProblemDescriptions) { - if (description.getSeverity() == ProjectStructureProblemDescription.Severity.ERROR) { - return ProjectStructureProblemDescription.Severity.ERROR; - } - } - return ProjectStructureProblemDescription.Severity.WARNING; - } - - public String composeTooltipMessage() { - final StringBuilder buf = StringBuilderSpinAllocator.alloc(); - try { - buf.append(""); - if (myProblemDescriptions != null) { - int problems = 0; - for (ProjectStructureProblemDescription problemDescription : myProblemDescriptions) { - buf.append(problemDescription.getMessage()).append("
"); - problems++; - if (problems >= 10 && myProblemDescriptions.size() > 12) { - buf.append(myProblemDescriptions.size() - problems).append(" more problems...
"); - break; - } - } - } - buf.append(""); - return buf.toString(); - } - finally { - StringBuilderSpinAllocator.dispose(buf); - } - } - - @Nullable - public List getProblemDescriptions() { - return myProblemDescriptions; - } -} +package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.ui.navigation.Place; +import com.intellij.util.SmartList; +import com.intellij.util.StringBuilderSpinAllocator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class ProjectStructureProblemsHolderImpl implements ProjectStructureProblemsHolder { + private List myProblemDescriptions; + + public void registerError(@NotNull String message, String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix) { + final List fixes = fix != null ? Collections.singletonList(fix) : Collections.emptyList(); + registerProblem(new ProjectStructureProblemDescription(message, description, ProjectStructureProblemDescription.Severity.ERROR, place, fixes)); + } + + public void registerWarning(@NotNull String message, String description, @NotNull Place place, @Nullable ConfigurationErrorQuickFix fix) { + final List fixes = Collections.singletonList(fix); + registerProblem(new ProjectStructureProblemDescription(message, description, ProjectStructureProblemDescription.Severity.WARNING, place, fixes)); + } + + public void registerProblem(final @NotNull ProjectStructureProblemDescription description) { + if (myProblemDescriptions == null) { + myProblemDescriptions = new SmartList(); + } + myProblemDescriptions.add(description); + } + + @Nullable + public ProjectStructureProblemDescription.Severity getSeverity() { + if (myProblemDescriptions == null || myProblemDescriptions.isEmpty()) { + return null; + } + for (ProjectStructureProblemDescription description : myProblemDescriptions) { + if (description.getSeverity() == ProjectStructureProblemDescription.Severity.ERROR) { + return ProjectStructureProblemDescription.Severity.ERROR; + } + } + return ProjectStructureProblemDescription.Severity.WARNING; + } + + public String composeTooltipMessage() { + final StringBuilder buf = StringBuilderSpinAllocator.alloc(); + try { + buf.append(""); + if (myProblemDescriptions != null) { + int problems = 0; + for (ProjectStructureProblemDescription problemDescription : myProblemDescriptions) { + buf.append(problemDescription.getMessage()).append("
"); + problems++; + if (problems >= 10 && myProblemDescriptions.size() > 12) { + buf.append(myProblemDescriptions.size() - problems).append(" more problems...
"); + break; + } + } + } + buf.append(""); + return buf.toString(); + } + finally { + StringBuilderSpinAllocator.dispose(buf); + } + } + + @Nullable + public List getProblemDescriptions() { + return myProblemDescriptions; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java index e82240100a50..cf8e5c9ccea2 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java @@ -1,1229 +1,1229 @@ -/* - * 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.codeInsight; - -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.codeStyle.VariableKind; -import com.intellij.psi.impl.source.jsp.jspJava.JspMethodCall; -import com.intellij.psi.infos.CandidateInfo; -import com.intellij.psi.infos.MethodCandidateInfo; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.PsiShortNamesCache; -import com.intellij.psi.search.searches.DeepestSuperMethodsSearch; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PropertyUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.NullableFunction; -import com.intellij.util.Processor; -import com.intellij.util.containers.HashMap; -import gnu.trove.THashSet; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author ven - */ -public class ExpectedTypesProvider { - private static final ExpectedTypeInfo VOID_EXPECTED = new ExpectedTypeInfoImpl(PsiType.VOID, ExpectedTypeInfo.TYPE_OR_SUBTYPE, 0, PsiType.VOID, - TailType.SEMICOLON); - - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.ExpectedTypesProvider"); - public static ExpectedTypesProvider getInstance(Project project) { - return ServiceManager.getService(project, ExpectedTypesProvider.class); - } - - private static final ExpectedClassProvider ourGlobalScopeClassProvider = new ExpectedClassProvider() { - public PsiField[] findDeclaredFields(final PsiManager manager, String name) { - final PsiShortNamesCache cache = JavaPsiFacade.getInstance(manager.getProject()).getShortNamesCache(); - GlobalSearchScope scope = GlobalSearchScope.allScope(manager.getProject()); - return cache.getFieldsByName(name, scope); - } - - public PsiMethod[] findDeclaredMethods(final PsiManager manager, String name) { - final PsiShortNamesCache cache = JavaPsiFacade.getInstance(manager.getProject()).getShortNamesCache(); - GlobalSearchScope scope = GlobalSearchScope.allScope(manager.getProject()); - return cache.getMethodsByName(name, scope); - } - }; - private static final PsiType[] PRIMITIVE_TYPES = {PsiType.BYTE, PsiType.CHAR, PsiType.SHORT, PsiType.INT, PsiType.LONG, PsiType.FLOAT, PsiType.DOUBLE}; - - @NotNull - public static ExpectedTypeInfo createInfo(@NotNull PsiType type, int kind, PsiType defaultType, TailType tailType) { - return createInfoImpl(type, kind, defaultType, tailType); - } - - @NotNull - private static ExpectedTypeInfoImpl createInfoImpl(@NotNull PsiType type, int kind, PsiType defaultType, TailType tailType) { - int dims = 0; - while (type instanceof PsiArrayType) { - type = ((PsiArrayType) type).getComponentType(); - LOG.assertTrue(defaultType instanceof PsiArrayType); - defaultType = ((PsiArrayType) defaultType).getComponentType(); - dims++; - } - return new ExpectedTypeInfoImpl(type, kind, dims, defaultType, tailType); - } - - @NotNull - public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, boolean forCompletion) { - return getExpectedTypes(expr, forCompletion, false, false); - } - - @NotNull - public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, boolean forCompletion, final boolean voidable, boolean usedAfter) { - return getExpectedTypes(expr, forCompletion, ourGlobalScopeClassProvider, voidable, usedAfter); - } - - @NotNull - public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, - boolean forCompletion, - ExpectedClassProvider classProvider, boolean usedAfter) { - return getExpectedTypes(expr, forCompletion, classProvider, false, usedAfter); - } - - @NotNull - public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, boolean forCompletion, ExpectedClassProvider classProvider, - final boolean voidable, boolean usedAfter) { - if (expr == null) return ExpectedTypeInfo.EMPTY_ARRAY; - PsiElement parent = expr.getParent(); - while (parent instanceof PsiParenthesizedExpression) { - expr = (PsiExpression)parent; - parent = parent.getParent(); - } - MyParentVisitor visitor = new MyParentVisitor(expr, forCompletion, classProvider, voidable, usedAfter); - parent.accept(visitor); - return visitor.getResult(); - } - - public static PsiType[] processExpectedTypes(ExpectedTypeInfo[] infos, - final PsiTypeVisitor visitor, Project project) { - Set set = new LinkedHashSet(); - for (ExpectedTypeInfo info : infos) { - ExpectedTypeInfoImpl infoImpl = (ExpectedTypeInfoImpl)info; - - if (infoImpl.getDefaultType() instanceof PsiClassType && infoImpl.getDimCount() == 0) { - JavaResolveResult result = ((PsiClassType)infoImpl.getDefaultType()).resolveGenerics(); - PsiClass aClass = (PsiClass)result.getElement(); - if (aClass instanceof PsiAnonymousClass) { - processType(((PsiAnonymousClass)aClass).getBaseClassType(), visitor, set); - ((PsiAnonymousClass)aClass).getBaseClassType().accept(visitor); - } - else { - processType(infoImpl.getDefaultType(), visitor, set); - } - } - else { - processType(infoImpl.getDefaultType(), visitor, set); - } - - if (infoImpl.kind == ExpectedTypeInfo.TYPE_OR_SUPERTYPE) { - processAllSuperTypes(infoImpl.getType(), infoImpl.getDimCount(), visitor, project, set); - } - else if (infoImpl.getKind() == ExpectedTypeInfo.TYPE_OR_SUBTYPE) { - if (infoImpl.getType() instanceof PsiPrimitiveType && infoImpl.getDimCount() == 0) { - processPrimitiveTypeAndSubtypes((PsiPrimitiveType)infoImpl.getType(), visitor, set); - } //else too expensive to search - } - } - - return set.toArray(new PsiType[set.size()]); - } - - private static void processType(@NotNull PsiType type, PsiTypeVisitor visitor, Set typeSet) { - PsiType accepted = type.accept(visitor); - if (accepted != null) typeSet.add(accepted); - } - - public static void processPrimitiveTypeAndSubtypes(PsiPrimitiveType type, PsiTypeVisitor visitor, Set set) { - if (type.equals(PsiType.BOOLEAN) || type.equals(PsiType.VOID) || type.equals(PsiType.NULL)) return; - - for (int i = 0; ; i++) { - final PsiType primitive = PRIMITIVE_TYPES[i]; - processType(primitive, visitor, set); - if (primitive.equals(type)) return; - } - } - - public static void processAllSuperTypes(PsiType type, int dimCount, PsiTypeVisitor visitor, Project project, Set set) { - if (type instanceof PsiPrimitiveType) { - if (type.equals(PsiType.BOOLEAN) || type.equals(PsiType.VOID) || type.equals(PsiType.NULL)) return; - - Stack stack = new Stack(); - for (int i = PRIMITIVE_TYPES.length - 1; !PRIMITIVE_TYPES[i].equals(type); i--) { - stack.push(PRIMITIVE_TYPES[i]); - } - while(!stack.empty()) { - processType(stack.pop(), visitor, set); - } - } - else{ - PsiManager manager = PsiManager.getInstance(project); - GlobalSearchScope resolveScope = type.getResolveScope(); - if (resolveScope == null) resolveScope = GlobalSearchScope.allScope(project); - PsiClassType objectType = PsiType.getJavaLangObject(manager, resolveScope); - processType(objectType, visitor, set); - - if (type instanceof PsiClassType) { - PsiType[] superTypes = type.getSuperTypes(); - for (PsiType superType : superTypes) { - PsiType wrappedType = superType; - for (int j = 0; j < dimCount; j++) { - wrappedType = wrappedType.createArrayType(); - } - processType(wrappedType, visitor, set); - processAllSuperTypes(superType, dimCount, visitor, project, set); - } - } - } - } - - private static class MyParentVisitor extends JavaElementVisitor { - private PsiExpression myExpr; - private final boolean myForCompletion; - private final boolean myUsedAfter; - private final ExpectedClassProvider myClassProvider; - private final boolean myVoidable; - private ExpectedTypeInfo[] myResult = ExpectedTypeInfo.EMPTY_ARRAY; - @NonNls private static final String LENGTH_SYNTHETIC_ARRAY_FIELD = "length"; - - private MyParentVisitor(PsiExpression expr, - boolean forCompletion, - ExpectedClassProvider classProvider, - boolean voidable, - boolean usedAfter) { - myExpr = expr; - myForCompletion = forCompletion; - myClassProvider = classProvider; - myVoidable = voidable; - myUsedAfter = usedAfter; - } - - public ExpectedTypeInfo[] getResult() { - return myResult; - } - - @Override - public void visitAnnotationMethod(final PsiAnnotationMethod method) { - if (myExpr == method.getDefaultValue()) { - final PsiType type = method.getReturnType(); - if (type != null) { - myResult = new ExpectedTypeInfo[]{createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.SEMICOLON)}; - } - } - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - if (myForCompletion) { - final MyParentVisitor visitor = new MyParentVisitor(expression, myForCompletion, myClassProvider, myVoidable, myUsedAfter); - expression.getParent().accept(visitor); - myResult = visitor.getResult(); - return; - } - - String referenceName = expression.getReferenceName(); - if (referenceName != null) { - final PsiElement parent = expression.getParent(); - if (parent instanceof PsiMethodCallExpression) { - myResult = findClassesWithDeclaredMethod((PsiMethodCallExpression)parent, myForCompletion); - } - else if (parent instanceof PsiReferenceExpression || parent instanceof PsiVariable || - parent instanceof PsiExpression) { - if (LENGTH_SYNTHETIC_ARRAY_FIELD.equals(referenceName)) { - myResult = anyArrayType(); - } - else { - myResult = findClassesWithDeclaredField(expression); - } - } - } - } - - @Override - public void visitExpressionStatement(PsiExpressionStatement statement) { - if (myVoidable) { - myResult = new ExpectedTypeInfo[]{VOID_EXPECTED}; - } - } - - @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { - myExpr = (PsiExpression)myExpr.getParent(); - expression.getParent().accept(this); - } - - @Override public void visitAnnotationArrayInitializer(PsiArrayInitializerMemberValue initializer) { - final PsiElement parent = initializer.getParent(); - final PsiType type; - if (parent instanceof PsiNameValuePair) { - type = getAnnotationMethodType((PsiNameValuePair)parent); - } - else { - type = ((PsiAnnotationMethod)parent).getReturnType(); - } - if (type instanceof PsiArrayType) { - myResult = new ExpectedTypeInfo[]{createInfoImpl(((PsiArrayType)type).getComponentType(), ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.UNKNOWN)}; - } - } - - @Override public void visitNameValuePair(PsiNameValuePair pair) { - final PsiType type = getAnnotationMethodType(pair); - if (type == null) return; - final ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.UNKNOWN); - if (type instanceof PsiArrayType) { - myResult = new ExpectedTypeInfo[]{info, createInfoImpl(((PsiArrayType)type).getComponentType(), ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.UNKNOWN)}; - } else { - myResult = new ExpectedTypeInfo[] {info}; - } - } - - @Nullable - private static PsiType getAnnotationMethodType(final PsiNameValuePair pair) { - final PsiReference reference = pair.getReference(); - if (reference != null) { - final PsiElement method = reference.resolve(); - if (method instanceof PsiMethod) { - return ((PsiMethod)method).getReturnType(); - } - } - return null; - } - - @Override public void visitReturnStatement(PsiReturnStatement statement) { - PsiMethod scopeMethod = PsiTreeUtil.getParentOfType(statement, PsiMethod.class); - if (scopeMethod != null) { - PsiType type = scopeMethod.getReturnType(); - if (type != null) { - ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, - TailType.SEMICOLON); - if (PropertyUtil.isSimplePropertyAccessor(scopeMethod)) { - info.expectedName = PropertyUtil.getPropertyName(scopeMethod); - } - - myResult = new ExpectedTypeInfo[]{info}; - } - else { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - } - } - - @Override public void visitIfStatement(PsiIfStatement statement) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailTypes.IF_RPARENTH); - myResult = new ExpectedTypeInfo[]{info}; - } - - @Override public void visitWhileStatement(PsiWhileStatement statement) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailTypes.WHILE_RPARENTH); - myResult = new ExpectedTypeInfo[]{info}; - } - - @Override public void visitDoWhileStatement(PsiDoWhileStatement statement) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailTypes.WHILE_RPARENTH); - myResult = new ExpectedTypeInfo[]{info}; - } - - @Override public void visitForStatement(PsiForStatement statement) { - if (myExpr.equals(statement.getCondition())) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailType.SEMICOLON); - myResult = new ExpectedTypeInfo[]{info}; - } - } - - @Override - public void visitAssertStatement(PsiAssertStatement statement) { - if (statement.getAssertDescription() == myExpr) { - final PsiClassType stringType = PsiType.getJavaLangString(myExpr.getManager(), myExpr.getResolveScope()); - ExpectedTypeInfoImpl info = createInfoImpl(stringType, ExpectedTypeInfo.TYPE_STRICTLY, - stringType, TailType.SEMICOLON); - myResult = new ExpectedTypeInfo[]{info}; - } else { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailType.SEMICOLON); - myResult = new ExpectedTypeInfo[]{info}; - } - } - - @Override public void visitForeachStatement(PsiForeachStatement statement) { - if (myExpr.equals(statement.getIteratedValue())) { - PsiType type = statement.getIterationParameter().getType(); - - PsiType arrayType = type.createArrayType(); - ExpectedTypeInfoImpl info1 = createInfoImpl(arrayType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - arrayType, TailType.NONE); - - PsiManager manager = statement.getManager(); - PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); - PsiClass iterableClass = - JavaPsiFacade.getInstance(manager.getProject()).findClass("java.lang.Iterable", statement.getResolveScope()); - if (iterableClass == null || iterableClass.getTypeParameters().length != 1) { - myResult = new ExpectedTypeInfo[]{info1}; - } else { - Map map = new HashMap(); - map.put(iterableClass.getTypeParameters()[0], PsiWildcardType.createExtends(manager, type)); - PsiSubstitutor substitutor = factory.createSubstitutor(map); - PsiType iterableType = factory.createType(iterableClass, substitutor); - ExpectedTypeInfoImpl info2 = createInfoImpl(iterableType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - iterableType, TailType.NONE); - - myResult = new ExpectedTypeInfo[]{info1, info2}; - } - } - } - - @Override public void visitSwitchStatement(PsiSwitchStatement statement) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, - TailType.NONE); - if (!PsiUtil.isLanguageLevel5OrHigher(statement)) { - myResult = new ExpectedTypeInfo[]{info}; - return; - } - - PsiManager manager = statement.getManager(); - PsiClassType enumType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName("java.lang.Enum", statement.getResolveScope()); - ExpectedTypeInfoImpl enumInfo = createInfoImpl(enumType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, enumType, TailType.NONE); - myResult = new ExpectedTypeInfo[] {info, enumInfo}; - } - - @Override - public void visitSwitchLabelStatement(final PsiSwitchLabelStatement statement) { - final PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement(); - if (switchStatement != null) { - final PsiExpression expression = switchStatement.getExpression(); - if (expression != null) { - final PsiType type = expression.getType(); - if (type != null) { - myResult = new ExpectedTypeInfo[]{createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.CASE_COLON)}; - } - } - } - } - - @Override public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { - PsiElementFactory factory = JavaPsiFacade.getInstance(statement.getProject()).getElementFactory(); - PsiType objectType = factory.createTypeByFQClassName("java.lang.Object", myExpr.getResolveScope()); - myResult = new ExpectedTypeInfo[]{createInfoImpl(objectType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, objectType, TailType.NONE)}; - } - - @Override public void visitVariable(PsiVariable variable) { - PsiType type = variable.getType(); - ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, - TailType.SEMICOLON); - info.expectedName = getPropertyName(variable); - myResult = new ExpectedTypeInfo[]{info}; - } - - @Override public void visitAssignmentExpression(PsiAssignmentExpression assignment) { - if (myExpr.equals(assignment.getRExpression())) { - PsiExpression lExpr = assignment.getLExpression(); - PsiType type = lExpr.getType(); - if (type != null) { - TailType tailType = getAssignmentRValueTailType(assignment); - ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, tailType); - if (lExpr instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression)lExpr).resolve(); - if (refElement instanceof PsiVariable) { - info.expectedName = getPropertyName((PsiVariable)refElement); - } - } - myResult = new ExpectedTypeInfo[]{info}; - } - else { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - } - else { - if (myForCompletion) { - myExpr = (PsiExpression)myExpr.getParent(); - assignment.getParent().accept(this); - return; - } - - PsiExpression rExpr = assignment.getRExpression(); - if (rExpr != null) { - PsiType type = rExpr.getType(); - if (type != null) { - if (type instanceof PsiClassType) { - final PsiClass resolved = ((PsiClassType)type).resolve(); - if (resolved instanceof PsiAnonymousClass) { - type = ((PsiAnonymousClass)resolved).getBaseClassType(); - } - } - final int kind = assignment.getOperationSign().getTokenType() != JavaTokenType.EQ - ? ExpectedTypeInfo.TYPE_STRICTLY - : ExpectedTypeInfo.TYPE_OR_SUPERTYPE; - ExpectedTypeInfoImpl info = createInfoImpl(type, kind, type, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - return; - } - } - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - } - - private static TailType getAssignmentRValueTailType(PsiAssignmentExpression assignment) { - if (assignment.getParent() instanceof PsiExpressionStatement) { - if (!(assignment.getParent().getParent() instanceof PsiForStatement)) { - return TailType.SEMICOLON; - } - - PsiForStatement forStatement = (PsiForStatement)assignment.getParent().getParent(); - if (!assignment.getParent().equals(forStatement.getUpdate())) { - return TailType.SEMICOLON; - } - } - return TailType.NONE; - } - - @Override public void visitExpressionList(PsiExpressionList list) { - PsiResolveHelper helper = JavaPsiFacade.getInstance(list.getProject()).getResolveHelper(); - if (list.getParent() instanceof PsiMethodCallExpression) { - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)list.getParent(); - CandidateInfo[] candidates = helper.getReferencedMethodCandidates(methodCall, false); - myResult = getExpectedArgumentTypesForMethodCall(candidates, list, myExpr, myForCompletion); - } - else if (list.getParent() instanceof PsiEnumConstant) { - getExpectedArgumentsTypesForEnumConstant((PsiEnumConstant)list.getParent(), helper, list); - } - else if (list.getParent() instanceof PsiNewExpression) { - getExpectedArgumentsTypesForNewExpression((PsiNewExpression)list.getParent(), helper, list); - } - else if (list.getParent() instanceof PsiAnonymousClass) { - getExpectedArgumentsTypesForNewExpression((PsiNewExpression)list.getParent().getParent(), helper, list); - } - } - - private void getExpectedArgumentsTypesForEnumConstant(final PsiEnumConstant enumConstant, - final PsiResolveHelper helper, - final PsiExpressionList list) { - final PsiClass aClass = enumConstant.getContainingClass(); - if (aClass != null) { - LOG.assertTrue(aClass.isEnum()); - getExpectedTypesForConstructorCall(aClass, helper, list, PsiSubstitutor.EMPTY); - } - } - - private void getExpectedArgumentsTypesForNewExpression(final PsiNewExpression newExpr, final PsiResolveHelper helper, final PsiExpressionList list) { - PsiType newType = newExpr.getType(); - if (newType instanceof PsiClassType) { - JavaResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(newType); - PsiClass newClass = (PsiClass)resolveResult.getElement(); - final PsiSubstitutor substitutor; - if (newClass instanceof PsiAnonymousClass) { - final PsiAnonymousClass anonymous = (PsiAnonymousClass)newClass; - newClass = anonymous.getBaseClassType().resolve(); - if (newClass == null) return; - - substitutor = TypeConversionUtil.getSuperClassSubstitutor(newClass, anonymous, PsiSubstitutor.EMPTY); - } else if (newClass != null) { - substitutor = resolveResult.getSubstitutor(); - } - else { - return; - } - getExpectedTypesForConstructorCall(newClass, helper, list, substitutor); - } - } - - private void getExpectedTypesForConstructorCall(final PsiClass referencedClass, - final PsiResolveHelper helper, - final PsiExpressionList argumentList, - final PsiSubstitutor substitutor) { - List array = new ArrayList(); - PsiMethod[] constructors = referencedClass.getConstructors(); - for (PsiMethod constructor : constructors) { - if (helper.isAccessible(constructor, argumentList, null)) { - array.add(new MethodCandidateInfo(constructor, substitutor, false, false, argumentList, null, argumentList.getExpressionTypes(), null)); - } - } - CandidateInfo[] candidates = array.toArray(new CandidateInfo[array.size()]); - myResult = getExpectedArgumentTypesForMethodCall(candidates, argumentList, myExpr, myForCompletion); - } - - @Override public void visitBinaryExpression(PsiBinaryExpression expr) { - - PsiExpression op1 = expr.getLOperand(); - PsiExpression op2 = expr.getROperand(); - PsiJavaToken sign = expr.getOperationSign(); - if (myForCompletion && op1.equals(myExpr)) { - final MyParentVisitor visitor = new MyParentVisitor(expr, myForCompletion, myClassProvider, myVoidable, myUsedAfter); - myExpr = (PsiExpression)myExpr.getParent(); - expr.getParent().accept(visitor); - myResult = visitor.getResult(); - if (!(expr.getParent() instanceof PsiExpressionList)) { - for (final ExpectedTypeInfo info : myResult) { - ((ExpectedTypeInfoImpl)info).myTailType = TailType.NONE; - } - } - return; - } - PsiExpression anotherExpr = op1.equals(myExpr) ? op2 : op1; - PsiType anotherType = anotherExpr != null ? anotherExpr.getType() : null; - PsiElementFactory factory = JavaPsiFacade.getInstance(expr.getProject()).getElementFactory(); - IElementType i = sign.getTokenType(); - if (i == JavaTokenType.MINUS || - i == JavaTokenType.ASTERISK || - i == JavaTokenType.DIV || - i == JavaTokenType.PERC || - i == JavaTokenType.LT || - i == JavaTokenType.GT || - i == JavaTokenType.LE || - i == JavaTokenType.GE) { - if (anotherType == null) { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - else { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - anotherType, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - } - } - else if (i == JavaTokenType.PLUS) { - if (anotherType == null) { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - else { - if (anotherType.equalsToText("java.lang.String")) { - PsiType objType = PsiType.getJavaLangObject(myExpr.getManager(), myExpr.getResolveScope()); - ExpectedTypeInfo info = createInfoImpl(objType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, anotherType, - TailType.NONE); - ExpectedTypeInfo info1 = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - PsiType.INT, TailType.NONE); - PsiType booleanType = PsiType.BOOLEAN; - ExpectedTypeInfo info2 = createInfoImpl(booleanType, ExpectedTypeInfo.TYPE_STRICTLY, booleanType, - TailType.NONE); - myResult = new ExpectedTypeInfo[]{info, info1, info2}; - } - else { - if (PsiType.DOUBLE.isAssignableFrom(anotherType)) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - anotherType, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - } - } - } - } - else if (i == JavaTokenType.EQEQ || i == JavaTokenType.NE) { - if (anotherType == null) { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - else { - ExpectedTypeInfoImpl info; - if (anotherType instanceof PsiPrimitiveType) { - if (PsiType.BOOLEAN.equals(anotherType)) { - info = createInfoImpl(anotherType, ExpectedTypeInfo.TYPE_STRICTLY, anotherType, TailType.NONE); - } - else if (PsiType.NULL.equals(anotherType)) { - PsiType objectType = factory.createTypeByFQClassName("java.lang.Object", myExpr.getResolveScope()); - info = createInfoImpl(objectType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, objectType, TailType.NONE); - } - else { - info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, anotherType, TailType.NONE); - } - } - else { - info = createInfoImpl(anotherType, ExpectedTypeInfo.TYPE_STRICTLY, anotherType, TailType.NONE); - } - - if (anotherExpr instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression)anotherExpr).resolve(); - if (refElement instanceof PsiVariable) { - info.expectedName = getPropertyName((PsiVariable)refElement); - } - } - - myResult = new ExpectedTypeInfo[]{info}; - } - } - else if (i == JavaTokenType.LTLT || i == JavaTokenType.GTGT || i == JavaTokenType.GTGTGT) { - if (anotherType == null) { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - else { - myResult = new ExpectedTypeInfo[]{createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_BETWEEN, PsiType.SHORT, TailType.NONE)}; - } - } - else if (i == JavaTokenType.OROR || i == JavaTokenType.ANDAND) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - } - else if (i == JavaTokenType.OR || i == JavaTokenType.XOR || i == JavaTokenType.AND) { - if (anotherType == null) { - myResult = ExpectedTypeInfo.EMPTY_ARRAY; - } - else { - ExpectedTypeInfoImpl info; - if (PsiType.BOOLEAN.equals(anotherType)) { - info = createInfoImpl(anotherType, ExpectedTypeInfo.TYPE_STRICTLY, anotherType, TailType.NONE); - } - else { - info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, anotherType, TailType.NONE); - } - myResult = new ExpectedTypeInfo[]{info}; - } - } - } - - @Override public void visitPrefixExpression(PsiPrefixExpression expr) { - PsiJavaToken sign = expr.getOperationSign(); - IElementType i = sign.getTokenType(); - final PsiType type = expr.getType(); - final TailType tailType = expr.getParent() instanceof PsiAssignmentExpression && ((PsiAssignmentExpression) expr.getParent()).getRExpression() == expr ? - getAssignmentRValueTailType((PsiAssignmentExpression) expr.getParent()) : - TailType.NONE; - if (i == JavaTokenType.PLUSPLUS || i == JavaTokenType.MINUSMINUS || i == JavaTokenType.TILDE) { - ExpectedTypeInfoImpl info; - if (myUsedAfter && type != null) { - info = createInfoImpl(type, ExpectedTypeInfo.TYPE_STRICTLY, type, tailType); - } - else { - if (type != null) { - info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, PsiType.INT, tailType); - } - else { - info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, tailType); - } - } - myResult = new ExpectedTypeInfo[]{info}; - } - else if (i == JavaTokenType.PLUS || i == JavaTokenType.MINUS) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - PsiType.INT, tailType); - myResult = new ExpectedTypeInfo[]{info}; - } - else if (i == JavaTokenType.EXCL) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, tailType); - myResult = new ExpectedTypeInfo[]{info}; - } - } - - @Override public void visitPostfixExpression(PsiPostfixExpression expr) { - if (myForCompletion) return; - PsiType type = expr.getType(); - ExpectedTypeInfoImpl info; - if (myUsedAfter && type != null) { - info = createInfoImpl(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.NONE); - } - else { - if (type != null) { - info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, PsiType.INT, TailType.NONE); - } - else { - info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, TailType.NONE); - } - } - myResult = new ExpectedTypeInfo[]{info}; - } - - @Override public void visitArrayInitializerExpression(PsiArrayInitializerExpression expr) { - PsiElement pparent = expr.getParent(); - PsiType arrayType = null; - if (pparent instanceof PsiVariable) { - arrayType = ((PsiVariable)pparent).getType(); - } - else if (pparent instanceof PsiNewExpression) { - arrayType = ((PsiNewExpression)pparent).getType(); - } - else if (pparent instanceof PsiArrayInitializerExpression) { - PsiType type = ((PsiArrayInitializerExpression)pparent).getType(); - if (type instanceof PsiArrayType) { - arrayType = ((PsiArrayType)type).getComponentType(); - } - } - - if (arrayType instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType)arrayType).getComponentType(); - ExpectedTypeInfoImpl info = createInfoImpl(componentType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - componentType, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - } - } - - @Override public void visitNewExpression(PsiNewExpression expression) { - PsiExpression[] arrayDimensions = expression.getArrayDimensions(); - for (PsiExpression dimension : arrayDimensions) { - if (myExpr.equals(dimension)) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.INT, ExpectedTypeInfo.TYPE_OR_SUBTYPE, - PsiType.INT, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - return; - } - } - } - - @Override public void visitArrayAccessExpression(PsiArrayAccessExpression expr) { - if (myExpr.equals(expr.getIndexExpression())) { - ExpectedTypeInfoImpl info = createInfoImpl(PsiType.INT, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, TailType.NONE) - ; //todo: special tail type - myResult = new ExpectedTypeInfo[]{info}; - } - else if (myExpr.equals(expr.getArrayExpression())) { - if (myForCompletion) { - myExpr = (PsiExpression)myExpr.getParent(); - expr.getParent().accept(this); - return; - } - - PsiElement parent = expr.getParent(); - MyParentVisitor visitor = new MyParentVisitor(expr, myForCompletion, myClassProvider, myVoidable, myUsedAfter); - myExpr = (PsiExpression)myExpr.getParent(); - parent.accept(visitor); - ExpectedTypeInfo[] componentTypeInfo = visitor.getResult(); - if (componentTypeInfo.length == 0) { - myResult = anyArrayType(); - } - else { - myResult = new ExpectedTypeInfoImpl[componentTypeInfo.length]; - for (int i = 0; i < componentTypeInfo.length; i++) { - ExpectedTypeInfo compInfo = componentTypeInfo[i]; - PsiType expectedArrayType = compInfo.getType().createArrayType(); - myResult[i] = createInfoImpl(expectedArrayType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, expectedArrayType, TailType.NONE); - } - } - } - } - - @Override public void visitConditionalExpression(PsiConditionalExpression expr) { - if (myExpr.equals(expr.getCondition())) { - if (myForCompletion) { - myExpr = expr; - myExpr.getParent().accept(this); - return; - } - - ExpectedTypeInfo info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, - PsiType.BOOLEAN, TailType.NONE); - myResult = new ExpectedTypeInfo[]{info}; - } - else if (myExpr.equals(expr.getThenExpression())) { - ExpectedTypeInfo[] types = getExpectedTypes(expr, myForCompletion); - if (types != null) { - for (ExpectedTypeInfo info : types) { - ExpectedTypeInfoImpl infoImpl = (ExpectedTypeInfoImpl)info; - infoImpl.setInsertExplicitTypeParams(true); - infoImpl.myTailType = TailType.COND_EXPR_COLON; - } - } - myResult = types; - } - else { - LOG.assertTrue(myExpr.equals(expr.getElseExpression())); - myResult = getExpectedTypes(expr, myForCompletion); - if (myResult != null) { - for (ExpectedTypeInfo info : myResult) { - ((ExpectedTypeInfoImpl)info).setInsertExplicitTypeParams(true); - } - } - - } - } - - @Override public void visitThrowStatement(PsiThrowStatement statement) { - if (statement.getException() == myExpr) { - PsiManager manager = statement.getManager(); - PsiType throwableType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName("java.lang.Throwable", myExpr.getResolveScope()); - PsiMember container = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, PsiClass.class); - PsiType[] throwsTypes = PsiType.EMPTY_ARRAY; - if (container instanceof PsiMethod) { - throwsTypes = ((PsiMethod)container).getThrowsList().getReferencedTypes(); - } - - if (throwsTypes.length == 0) { - final PsiClassType exceptionType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName("java.lang.Exception", myExpr.getResolveScope()); - throwsTypes = new PsiClassType[]{exceptionType}; - } - - ExpectedTypeInfo[] infos = new ExpectedTypeInfo[throwsTypes.length]; - for (int i = 0; i < infos.length; i++) { - infos[i] = createInfoImpl( - myExpr instanceof PsiTypeCastExpression && myForCompletion ? - throwsTypes[i] : - throwableType, - ExpectedTypeInfo.TYPE_OR_SUBTYPE, - throwsTypes[i], - TailType.SEMICOLON - ); - } - myResult = infos; - } - } - - @Override public void visitCodeFragment(JavaCodeFragment codeFragment) { - if (codeFragment instanceof PsiExpressionCodeFragment) { - final PsiType type = ((PsiExpressionCodeFragment)codeFragment).getExpectedType(); - if (type != null) { - myResult = new ExpectedTypeInfo[] {createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.NONE)}; - } - } - } - - private ExpectedTypeInfo[] getExpectedArgumentTypesForMethodCall(CandidateInfo[] methodCandidates, - PsiExpressionList argumentList, - PsiExpression argument, - boolean forCompletion) { - if (methodCandidates.length == 0) { - return ExpectedTypeInfo.EMPTY_ARRAY; - } - final PsiExpression[] args = argumentList.getExpressions(); - final int index = ArrayUtil.indexOf(args, argument); - LOG.assertTrue(index >= 0); - - final PsiExpression[] leftArgs; - if (index <= args.length - 1) { - leftArgs = new PsiExpression[index]; - System.arraycopy(args, 0, leftArgs, 0, index); - } else { - leftArgs = null; - } - - Set array = new LinkedHashSet(); - for (CandidateInfo candidateInfo : methodCandidates) { - PsiMethod method = (PsiMethod)candidateInfo.getElement(); - PsiSubstitutor substitutor; - if (candidateInfo instanceof MethodCandidateInfo) { - final MethodCandidateInfo info = (MethodCandidateInfo)candidateInfo; - substitutor = info.inferTypeArguments(forCompletion); - if (!info.isStaticsScopeCorrect() && method != null && !method.hasModifierProperty(PsiModifier.STATIC)) continue; - } - else { - substitutor = candidateInfo.getSubstitutor(); - } - inferMethodCallArgumentTypes(argument, forCompletion, args, index, method, substitutor, array); - - if (leftArgs != null && candidateInfo instanceof MethodCandidateInfo) { - substitutor = ((MethodCandidateInfo)candidateInfo).inferTypeArguments(forCompletion, leftArgs); - inferMethodCallArgumentTypes(argument, forCompletion, leftArgs, index, method, substitutor, array); - } - } - - // try to find some variants without considering previous argument PRIMITIVE_TYPES - if (forCompletion && array.isEmpty()) { - for (CandidateInfo candidate : methodCandidates) { - PsiMethod method = (PsiMethod)candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - PsiParameter[] parms = method.getParameterList().getParameters(); - if (parms.length <= index) continue; - PsiParameter parm = parms[index]; - PsiType parmType = getParameterType(parm, substitutor); - TailType tailType = getMethodArgumentTailType(argument, index, method, substitutor, parms); - ExpectedTypeInfoImpl info = createInfoImpl(parmType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, parmType, - tailType); - info.expectedName = getPropertyName(parm); - info.setCalledMethod(method); - array.add(info); - } - } - - return array.toArray(new ExpectedTypeInfo[array.size()]); - } - - private static TailType getMethodArgumentTailType(final PsiExpression argument, final int index, final PsiMethod method, final PsiSubstitutor substitutor, - final PsiParameter[] parms) { - if (index >= parms.length) { - return TailType.NONE; - } - if (index == parms.length - 1) { - //myTailType = CompletionUtil.NONE_TAIL; - final PsiElement call = argument.getParent().getParent(); - if (call instanceof JspMethodCall) return TailType.NONE; - - PsiType returnType = method.getReturnType(); - if (returnType != null) returnType = substitutor.substitute(returnType); - return getFinalCallParameterTailType(call, returnType, method); - } - return TailType.COMMA; - } - - private void inferMethodCallArgumentTypes(final PsiExpression argument, - final boolean forCompletion, - final PsiExpression[] args, - final int index, - final PsiMethod method, final PsiSubstitutor substitutor, final Set array) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (!forCompletion && parameters.length != args.length) return; - if (parameters.length <= index && !method.isVarArgs()) return; - - for (int j = 0; j < index; j++) { - PsiType paramType = getParameterType(parameters[Math.min(parameters.length - 1, j)], - substitutor); - PsiType argType = args[j].getType(); - if (argType != null && !paramType.isAssignableFrom(argType)) return; - } - PsiParameter parameter = parameters[Math.min(parameters.length - 1, index)]; - PsiType parameterType = getParameterType(parameter, substitutor); - - TailType tailType = getMethodArgumentTailType(argument, index, method, substitutor, parameters); - PsiType defaultType = getDefautType(method, substitutor, parameterType, argument); - - ExpectedTypeInfoImpl info = createInfoImpl(parameterType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, defaultType, tailType); - info.setInsertExplicitTypeParams(true); - info.setCalledMethod(method); - String propertyName = getPropertyName(parameter); - if (propertyName != null) info.expectedName = propertyName; - array.add(info); - - if (index == parameters.length - 1 && parameter.isVarArgs()) { - //Then we may still want to call with array argument - final PsiArrayType arrayType = parameterType.createArrayType(); - ExpectedTypeInfoImpl info1 = createInfoImpl(arrayType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, arrayType, tailType); - info1.setInsertExplicitTypeParams(true); - info1.setCalledMethod(method); - info1.expectedName = propertyName; - array.add(info1); - } - } - - @Nullable - private static PsiType getTypeParameterValue(PsiClass rootClass, PsiClass derivedClass, PsiSubstitutor substitutor, int index) { - final PsiTypeParameter[] typeParameters = rootClass.getTypeParameters(); - if (typeParameters.length > index) { - final PsiSubstitutor psiSubstitutor = TypeConversionUtil.getClassSubstitutor(rootClass, derivedClass, substitutor); - if (psiSubstitutor != null) { - PsiType type = psiSubstitutor.substitute(typeParameters[index]); - if (type != null) return type; - } - } - return null; - } - - @Nullable - protected static PsiType checkMethod(PsiMethod method, @NonNls final String className, final NullableFunction function) { - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) return null; - - if (className.equals(containingClass.getQualifiedName())) { - return function.fun(containingClass); - } - final PsiType[] type = {null}; - DeepestSuperMethodsSearch.search(method).forEach(new Processor() { - @Override - public boolean process(PsiMethod psiMethod) { - final PsiClass rootClass = psiMethod.getContainingClass(); - if (className.equals(rootClass.getQualifiedName())) { - type[0] = function.fun(rootClass); - return false; - } - return true; - } - }); - return type[0]; - } - - @Nullable - private PsiType getDefautType(final PsiMethod method, final PsiSubstitutor substitutor, final PsiType parameterType, - final PsiExpression argumentList) { - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) return parameterType; - - @NonNls final String name = method.getName(); - if ("contains".equals(name) || "remove".equals(name)) { - final PsiType type = checkMethod(method, CommonClassNames.JAVA_UTIL_COLLECTION, new NullableFunction() { - public PsiType fun(final PsiClass psiClass) { - return getTypeParameterValue(psiClass, containingClass, substitutor, 0); - } - }); - if (type != null) return type; - } - if ("containsKey".equals(name) || "remove".equals(name) || "get".equals(name) || "containsValue".equals(name)) { - final PsiType type = checkMethod(method, CommonClassNames.JAVA_UTIL_MAP, new NullableFunction() { - public PsiType fun(final PsiClass psiClass) { - return getTypeParameterValue(psiClass, containingClass, substitutor, name.equals("containsValue") ? 1 : 0); - } - }); - if (type != null) return type; - } - if ("equals".equals(name)) { - final PsiType type = checkMethod(method, CommonClassNames.JAVA_LANG_OBJECT, new NullableFunction() { - public PsiType fun(final PsiClass psiClass) { - final PsiElement parent = argumentList.getParent().getParent(); - if (parent instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression expression = (PsiMethodCallExpression)parent; - final PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression(); - if (qualifierExpression != null) { - return qualifierExpression.getType(); - } - final PsiClass aClass = PsiTreeUtil.getContextOfType(parent, PsiClass.class, true); - if (aClass != null) { - return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); - } - } - return null; - } - }); - if (type != null) return type; - } - return parameterType; - } - - private static PsiType getParameterType(PsiParameter parameter, PsiSubstitutor substitutor) { - PsiType type = parameter.getType(); - if (parameter.isVarArgs()) { - if (type instanceof PsiArrayType) { - type = ((PsiArrayType)type).getComponentType(); - } else { - LOG.error("Vararg parameter with non-array type. Class=" + parameter.getClass() + "; type=" + parameter.getType()); - } - } - PsiType parameterType = substitutor.substitute(type); - if (parameterType instanceof PsiCapturedWildcardType) { - parameterType = ((PsiCapturedWildcardType)parameterType).getWildcard(); - } - if (parameterType instanceof PsiWildcardType) { - final PsiWildcardType psiWildcardType = (PsiWildcardType)parameterType; - if (psiWildcardType.isExtends()) { - final PsiType superBound = psiWildcardType.getBound(); - if (superBound != null) { - parameterType = superBound; - } - } - } - return parameterType; - } - - @Nullable - private String getPropertyName(PsiVariable variable) { - final String name = variable.getName(); - if (name == null) return null; - JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(variable.getProject()); - VariableKind variableKind = codeStyleManager.getVariableKind(variable); - return codeStyleManager.variableNameToPropertyName(name, variableKind); - } - - private void addBaseType(Set types, PsiClassType type, PsiMethod method) { - PsiType[] supers = type.getSuperTypes(); - boolean addedSuper = false; - for (PsiType aSuper : supers) { - PsiClassType superType = (PsiClassType)aSuper; - PsiClass superClass = superType.resolve(); - if (superClass != null) { - if (superClass.findMethodBySignature(method, false) != null) { - addBaseType(types, superType, method); - addedSuper = true; - } - } - } - if (!addedSuper) { - types.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.DOT)); - } - } - - private ExpectedTypeInfo[] anyArrayType() { - PsiType objType = PsiType.getJavaLangObject(myExpr.getManager(), myExpr.getResolveScope()).createArrayType(); - ExpectedTypeInfo info = createInfoImpl(objType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, objType, - TailType.NONE); - ExpectedTypeInfo info1 = createInfoImpl(PsiType.DOUBLE.createArrayType(), ExpectedTypeInfo.TYPE_OR_SUBTYPE, - PsiType.INT.createArrayType(), TailType.NONE); - PsiType booleanType = PsiType.BOOLEAN.createArrayType(); - ExpectedTypeInfo info2 = createInfoImpl(booleanType, ExpectedTypeInfo.TYPE_STRICTLY, booleanType, - TailType.NONE); - return new ExpectedTypeInfo[]{info, info1, info2}; - } - - private ExpectedTypeInfo[] findClassesWithDeclaredMethod(final PsiMethodCallExpression methodCallExpr, final boolean forCompletion) { - final PsiReferenceExpression reference = methodCallExpr.getMethodExpression(); - final PsiManager manager = methodCallExpr.getManager(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); - final PsiMethod[] methods = myClassProvider.findDeclaredMethods(reference.getManager(), reference.getReferenceName()); - Set types = new THashSet(); - for (PsiMethod method : methods) { - final PsiClass aClass = method.getContainingClass(); - if (aClass == null || !facade.getResolveHelper().isAccessible(method, reference, aClass)) continue; - - final PsiSubstitutor substitutor = ExpectedTypeUtil.inferSubstitutor(method, methodCallExpr, forCompletion); - final PsiClassType type = - substitutor == null ? facade.getElementFactory().createType(aClass) : facade.getElementFactory().createType(aClass, substitutor); - - if (method.hasModifierProperty(PsiModifier.STATIC) || - method.hasModifierProperty(PsiModifier.FINAL) || - method.hasModifierProperty(PsiModifier.PRIVATE)) { - types.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.DOT)); - } - else { - addBaseType(types, type, method); - } - } - - return types.toArray(new ExpectedTypeInfo[types.size()]); - } - - private ExpectedTypeInfo[] findClassesWithDeclaredField(PsiReferenceExpression expression) { - final JavaPsiFacade facade = JavaPsiFacade.getInstance(expression.getProject()); - PsiField[] fields = myClassProvider.findDeclaredFields(expression.getManager(), expression.getReferenceName()); - List types = new ArrayList(); - for (PsiField field : fields) { - final PsiClass aClass = field.getContainingClass(); - if (aClass == null || !facade.getResolveHelper().isAccessible(field, expression, aClass)) continue; - - final PsiType type = facade.getElementFactory().createType(aClass); - - int kind = field.hasModifierProperty(PsiModifier.STATIC) || - field.hasModifierProperty(PsiModifier.FINAL) || - field.hasModifierProperty(PsiModifier.PRIVATE) - ? ExpectedTypeInfo.TYPE_STRICTLY - : ExpectedTypeInfo.TYPE_OR_SUBTYPE; - ExpectedTypeInfo info = createInfoImpl(type, kind, type, TailType.DOT); - //Do not filter inheritors! - types.add(info); - } - return types.toArray(new ExpectedTypeInfo[types.size()]); - } - } - - /** - * Finds fields and methods of specified name whenever corresponding reference has been encountered. - * By default searhes in the global scope (see ourGlobalScopeClassProvider), but caller can provide its own algorithm e.g. to narrow search scope - */ - public interface ExpectedClassProvider { - PsiField[] findDeclaredFields(final PsiManager manager, String name); - - PsiMethod[] findDeclaredMethods(final PsiManager manager, String name); - } - - public static TailType getFinalCallParameterTailType(PsiElement call, PsiType returnType, PsiMethod method) { - if (method.isConstructor() && - call instanceof PsiMethodCallExpression && ((PsiMethodCallExpression)call).getMethodExpression() instanceof PsiSuperExpression) { - return TailTypes.CALL_RPARENTH_SEMICOLON; - } - - final boolean chainable = !PsiType.VOID.equals(returnType) && returnType != null; - - final PsiElement parent = call.getParent(); - final boolean statementContext = parent instanceof PsiExpressionStatement || parent instanceof PsiVariable || - parent instanceof PsiCodeBlock || parent instanceof PsiThrowStatement; - - if (statementContext && !chainable) { - return TailTypes.CALL_RPARENTH_SEMICOLON; - } - - return TailTypes.CALL_RPARENTH; - } - -} +/* + * 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.codeInsight; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.impl.source.jsp.jspJava.JspMethodCall; +import com.intellij.psi.infos.CandidateInfo; +import com.intellij.psi.infos.MethodCandidateInfo; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; +import com.intellij.psi.search.searches.DeepestSuperMethodsSearch; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PropertyUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.NullableFunction; +import com.intellij.util.Processor; +import com.intellij.util.containers.HashMap; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author ven + */ +public class ExpectedTypesProvider { + private static final ExpectedTypeInfo VOID_EXPECTED = new ExpectedTypeInfoImpl(PsiType.VOID, ExpectedTypeInfo.TYPE_OR_SUBTYPE, 0, PsiType.VOID, + TailType.SEMICOLON); + + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.ExpectedTypesProvider"); + public static ExpectedTypesProvider getInstance(Project project) { + return ServiceManager.getService(project, ExpectedTypesProvider.class); + } + + private static final ExpectedClassProvider ourGlobalScopeClassProvider = new ExpectedClassProvider() { + public PsiField[] findDeclaredFields(final PsiManager manager, String name) { + final PsiShortNamesCache cache = JavaPsiFacade.getInstance(manager.getProject()).getShortNamesCache(); + GlobalSearchScope scope = GlobalSearchScope.allScope(manager.getProject()); + return cache.getFieldsByName(name, scope); + } + + public PsiMethod[] findDeclaredMethods(final PsiManager manager, String name) { + final PsiShortNamesCache cache = JavaPsiFacade.getInstance(manager.getProject()).getShortNamesCache(); + GlobalSearchScope scope = GlobalSearchScope.allScope(manager.getProject()); + return cache.getMethodsByName(name, scope); + } + }; + private static final PsiType[] PRIMITIVE_TYPES = {PsiType.BYTE, PsiType.CHAR, PsiType.SHORT, PsiType.INT, PsiType.LONG, PsiType.FLOAT, PsiType.DOUBLE}; + + @NotNull + public static ExpectedTypeInfo createInfo(@NotNull PsiType type, int kind, PsiType defaultType, TailType tailType) { + return createInfoImpl(type, kind, defaultType, tailType); + } + + @NotNull + private static ExpectedTypeInfoImpl createInfoImpl(@NotNull PsiType type, int kind, PsiType defaultType, TailType tailType) { + int dims = 0; + while (type instanceof PsiArrayType) { + type = ((PsiArrayType) type).getComponentType(); + LOG.assertTrue(defaultType instanceof PsiArrayType); + defaultType = ((PsiArrayType) defaultType).getComponentType(); + dims++; + } + return new ExpectedTypeInfoImpl(type, kind, dims, defaultType, tailType); + } + + @NotNull + public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, boolean forCompletion) { + return getExpectedTypes(expr, forCompletion, false, false); + } + + @NotNull + public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, boolean forCompletion, final boolean voidable, boolean usedAfter) { + return getExpectedTypes(expr, forCompletion, ourGlobalScopeClassProvider, voidable, usedAfter); + } + + @NotNull + public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, + boolean forCompletion, + ExpectedClassProvider classProvider, boolean usedAfter) { + return getExpectedTypes(expr, forCompletion, classProvider, false, usedAfter); + } + + @NotNull + public static ExpectedTypeInfo[] getExpectedTypes(@Nullable PsiExpression expr, boolean forCompletion, ExpectedClassProvider classProvider, + final boolean voidable, boolean usedAfter) { + if (expr == null) return ExpectedTypeInfo.EMPTY_ARRAY; + PsiElement parent = expr.getParent(); + while (parent instanceof PsiParenthesizedExpression) { + expr = (PsiExpression)parent; + parent = parent.getParent(); + } + MyParentVisitor visitor = new MyParentVisitor(expr, forCompletion, classProvider, voidable, usedAfter); + parent.accept(visitor); + return visitor.getResult(); + } + + public static PsiType[] processExpectedTypes(ExpectedTypeInfo[] infos, + final PsiTypeVisitor visitor, Project project) { + Set set = new LinkedHashSet(); + for (ExpectedTypeInfo info : infos) { + ExpectedTypeInfoImpl infoImpl = (ExpectedTypeInfoImpl)info; + + if (infoImpl.getDefaultType() instanceof PsiClassType && infoImpl.getDimCount() == 0) { + JavaResolveResult result = ((PsiClassType)infoImpl.getDefaultType()).resolveGenerics(); + PsiClass aClass = (PsiClass)result.getElement(); + if (aClass instanceof PsiAnonymousClass) { + processType(((PsiAnonymousClass)aClass).getBaseClassType(), visitor, set); + ((PsiAnonymousClass)aClass).getBaseClassType().accept(visitor); + } + else { + processType(infoImpl.getDefaultType(), visitor, set); + } + } + else { + processType(infoImpl.getDefaultType(), visitor, set); + } + + if (infoImpl.kind == ExpectedTypeInfo.TYPE_OR_SUPERTYPE) { + processAllSuperTypes(infoImpl.getType(), infoImpl.getDimCount(), visitor, project, set); + } + else if (infoImpl.getKind() == ExpectedTypeInfo.TYPE_OR_SUBTYPE) { + if (infoImpl.getType() instanceof PsiPrimitiveType && infoImpl.getDimCount() == 0) { + processPrimitiveTypeAndSubtypes((PsiPrimitiveType)infoImpl.getType(), visitor, set); + } //else too expensive to search + } + } + + return set.toArray(new PsiType[set.size()]); + } + + private static void processType(@NotNull PsiType type, PsiTypeVisitor visitor, Set typeSet) { + PsiType accepted = type.accept(visitor); + if (accepted != null) typeSet.add(accepted); + } + + public static void processPrimitiveTypeAndSubtypes(PsiPrimitiveType type, PsiTypeVisitor visitor, Set set) { + if (type.equals(PsiType.BOOLEAN) || type.equals(PsiType.VOID) || type.equals(PsiType.NULL)) return; + + for (int i = 0; ; i++) { + final PsiType primitive = PRIMITIVE_TYPES[i]; + processType(primitive, visitor, set); + if (primitive.equals(type)) return; + } + } + + public static void processAllSuperTypes(PsiType type, int dimCount, PsiTypeVisitor visitor, Project project, Set set) { + if (type instanceof PsiPrimitiveType) { + if (type.equals(PsiType.BOOLEAN) || type.equals(PsiType.VOID) || type.equals(PsiType.NULL)) return; + + Stack stack = new Stack(); + for (int i = PRIMITIVE_TYPES.length - 1; !PRIMITIVE_TYPES[i].equals(type); i--) { + stack.push(PRIMITIVE_TYPES[i]); + } + while(!stack.empty()) { + processType(stack.pop(), visitor, set); + } + } + else{ + PsiManager manager = PsiManager.getInstance(project); + GlobalSearchScope resolveScope = type.getResolveScope(); + if (resolveScope == null) resolveScope = GlobalSearchScope.allScope(project); + PsiClassType objectType = PsiType.getJavaLangObject(manager, resolveScope); + processType(objectType, visitor, set); + + if (type instanceof PsiClassType) { + PsiType[] superTypes = type.getSuperTypes(); + for (PsiType superType : superTypes) { + PsiType wrappedType = superType; + for (int j = 0; j < dimCount; j++) { + wrappedType = wrappedType.createArrayType(); + } + processType(wrappedType, visitor, set); + processAllSuperTypes(superType, dimCount, visitor, project, set); + } + } + } + } + + private static class MyParentVisitor extends JavaElementVisitor { + private PsiExpression myExpr; + private final boolean myForCompletion; + private final boolean myUsedAfter; + private final ExpectedClassProvider myClassProvider; + private final boolean myVoidable; + private ExpectedTypeInfo[] myResult = ExpectedTypeInfo.EMPTY_ARRAY; + @NonNls private static final String LENGTH_SYNTHETIC_ARRAY_FIELD = "length"; + + private MyParentVisitor(PsiExpression expr, + boolean forCompletion, + ExpectedClassProvider classProvider, + boolean voidable, + boolean usedAfter) { + myExpr = expr; + myForCompletion = forCompletion; + myClassProvider = classProvider; + myVoidable = voidable; + myUsedAfter = usedAfter; + } + + public ExpectedTypeInfo[] getResult() { + return myResult; + } + + @Override + public void visitAnnotationMethod(final PsiAnnotationMethod method) { + if (myExpr == method.getDefaultValue()) { + final PsiType type = method.getReturnType(); + if (type != null) { + myResult = new ExpectedTypeInfo[]{createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.SEMICOLON)}; + } + } + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + if (myForCompletion) { + final MyParentVisitor visitor = new MyParentVisitor(expression, myForCompletion, myClassProvider, myVoidable, myUsedAfter); + expression.getParent().accept(visitor); + myResult = visitor.getResult(); + return; + } + + String referenceName = expression.getReferenceName(); + if (referenceName != null) { + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiMethodCallExpression) { + myResult = findClassesWithDeclaredMethod((PsiMethodCallExpression)parent, myForCompletion); + } + else if (parent instanceof PsiReferenceExpression || parent instanceof PsiVariable || + parent instanceof PsiExpression) { + if (LENGTH_SYNTHETIC_ARRAY_FIELD.equals(referenceName)) { + myResult = anyArrayType(); + } + else { + myResult = findClassesWithDeclaredField(expression); + } + } + } + } + + @Override + public void visitExpressionStatement(PsiExpressionStatement statement) { + if (myVoidable) { + myResult = new ExpectedTypeInfo[]{VOID_EXPECTED}; + } + } + + @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { + myExpr = (PsiExpression)myExpr.getParent(); + expression.getParent().accept(this); + } + + @Override public void visitAnnotationArrayInitializer(PsiArrayInitializerMemberValue initializer) { + final PsiElement parent = initializer.getParent(); + final PsiType type; + if (parent instanceof PsiNameValuePair) { + type = getAnnotationMethodType((PsiNameValuePair)parent); + } + else { + type = ((PsiAnnotationMethod)parent).getReturnType(); + } + if (type instanceof PsiArrayType) { + myResult = new ExpectedTypeInfo[]{createInfoImpl(((PsiArrayType)type).getComponentType(), ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.UNKNOWN)}; + } + } + + @Override public void visitNameValuePair(PsiNameValuePair pair) { + final PsiType type = getAnnotationMethodType(pair); + if (type == null) return; + final ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.UNKNOWN); + if (type instanceof PsiArrayType) { + myResult = new ExpectedTypeInfo[]{info, createInfoImpl(((PsiArrayType)type).getComponentType(), ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.UNKNOWN)}; + } else { + myResult = new ExpectedTypeInfo[] {info}; + } + } + + @Nullable + private static PsiType getAnnotationMethodType(final PsiNameValuePair pair) { + final PsiReference reference = pair.getReference(); + if (reference != null) { + final PsiElement method = reference.resolve(); + if (method instanceof PsiMethod) { + return ((PsiMethod)method).getReturnType(); + } + } + return null; + } + + @Override public void visitReturnStatement(PsiReturnStatement statement) { + PsiMethod scopeMethod = PsiTreeUtil.getParentOfType(statement, PsiMethod.class); + if (scopeMethod != null) { + PsiType type = scopeMethod.getReturnType(); + if (type != null) { + ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, + TailType.SEMICOLON); + if (PropertyUtil.isSimplePropertyAccessor(scopeMethod)) { + info.expectedName = PropertyUtil.getPropertyName(scopeMethod); + } + + myResult = new ExpectedTypeInfo[]{info}; + } + else { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + } + } + + @Override public void visitIfStatement(PsiIfStatement statement) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailTypes.IF_RPARENTH); + myResult = new ExpectedTypeInfo[]{info}; + } + + @Override public void visitWhileStatement(PsiWhileStatement statement) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailTypes.WHILE_RPARENTH); + myResult = new ExpectedTypeInfo[]{info}; + } + + @Override public void visitDoWhileStatement(PsiDoWhileStatement statement) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailTypes.WHILE_RPARENTH); + myResult = new ExpectedTypeInfo[]{info}; + } + + @Override public void visitForStatement(PsiForStatement statement) { + if (myExpr.equals(statement.getCondition())) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailType.SEMICOLON); + myResult = new ExpectedTypeInfo[]{info}; + } + } + + @Override + public void visitAssertStatement(PsiAssertStatement statement) { + if (statement.getAssertDescription() == myExpr) { + final PsiClassType stringType = PsiType.getJavaLangString(myExpr.getManager(), myExpr.getResolveScope()); + ExpectedTypeInfoImpl info = createInfoImpl(stringType, ExpectedTypeInfo.TYPE_STRICTLY, + stringType, TailType.SEMICOLON); + myResult = new ExpectedTypeInfo[]{info}; + } else { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailType.SEMICOLON); + myResult = new ExpectedTypeInfo[]{info}; + } + } + + @Override public void visitForeachStatement(PsiForeachStatement statement) { + if (myExpr.equals(statement.getIteratedValue())) { + PsiType type = statement.getIterationParameter().getType(); + + PsiType arrayType = type.createArrayType(); + ExpectedTypeInfoImpl info1 = createInfoImpl(arrayType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + arrayType, TailType.NONE); + + PsiManager manager = statement.getManager(); + PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); + PsiClass iterableClass = + JavaPsiFacade.getInstance(manager.getProject()).findClass("java.lang.Iterable", statement.getResolveScope()); + if (iterableClass == null || iterableClass.getTypeParameters().length != 1) { + myResult = new ExpectedTypeInfo[]{info1}; + } else { + Map map = new HashMap(); + map.put(iterableClass.getTypeParameters()[0], PsiWildcardType.createExtends(manager, type)); + PsiSubstitutor substitutor = factory.createSubstitutor(map); + PsiType iterableType = factory.createType(iterableClass, substitutor); + ExpectedTypeInfoImpl info2 = createInfoImpl(iterableType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + iterableType, TailType.NONE); + + myResult = new ExpectedTypeInfo[]{info1, info2}; + } + } + } + + @Override public void visitSwitchStatement(PsiSwitchStatement statement) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, + TailType.NONE); + if (!PsiUtil.isLanguageLevel5OrHigher(statement)) { + myResult = new ExpectedTypeInfo[]{info}; + return; + } + + PsiManager manager = statement.getManager(); + PsiClassType enumType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName("java.lang.Enum", statement.getResolveScope()); + ExpectedTypeInfoImpl enumInfo = createInfoImpl(enumType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, enumType, TailType.NONE); + myResult = new ExpectedTypeInfo[] {info, enumInfo}; + } + + @Override + public void visitSwitchLabelStatement(final PsiSwitchLabelStatement statement) { + final PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement(); + if (switchStatement != null) { + final PsiExpression expression = switchStatement.getExpression(); + if (expression != null) { + final PsiType type = expression.getType(); + if (type != null) { + myResult = new ExpectedTypeInfo[]{createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.CASE_COLON)}; + } + } + } + } + + @Override public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { + PsiElementFactory factory = JavaPsiFacade.getInstance(statement.getProject()).getElementFactory(); + PsiType objectType = factory.createTypeByFQClassName("java.lang.Object", myExpr.getResolveScope()); + myResult = new ExpectedTypeInfo[]{createInfoImpl(objectType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, objectType, TailType.NONE)}; + } + + @Override public void visitVariable(PsiVariable variable) { + PsiType type = variable.getType(); + ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, + TailType.SEMICOLON); + info.expectedName = getPropertyName(variable); + myResult = new ExpectedTypeInfo[]{info}; + } + + @Override public void visitAssignmentExpression(PsiAssignmentExpression assignment) { + if (myExpr.equals(assignment.getRExpression())) { + PsiExpression lExpr = assignment.getLExpression(); + PsiType type = lExpr.getType(); + if (type != null) { + TailType tailType = getAssignmentRValueTailType(assignment); + ExpectedTypeInfoImpl info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, tailType); + if (lExpr instanceof PsiReferenceExpression) { + PsiElement refElement = ((PsiReferenceExpression)lExpr).resolve(); + if (refElement instanceof PsiVariable) { + info.expectedName = getPropertyName((PsiVariable)refElement); + } + } + myResult = new ExpectedTypeInfo[]{info}; + } + else { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + } + else { + if (myForCompletion) { + myExpr = (PsiExpression)myExpr.getParent(); + assignment.getParent().accept(this); + return; + } + + PsiExpression rExpr = assignment.getRExpression(); + if (rExpr != null) { + PsiType type = rExpr.getType(); + if (type != null) { + if (type instanceof PsiClassType) { + final PsiClass resolved = ((PsiClassType)type).resolve(); + if (resolved instanceof PsiAnonymousClass) { + type = ((PsiAnonymousClass)resolved).getBaseClassType(); + } + } + final int kind = assignment.getOperationSign().getTokenType() != JavaTokenType.EQ + ? ExpectedTypeInfo.TYPE_STRICTLY + : ExpectedTypeInfo.TYPE_OR_SUPERTYPE; + ExpectedTypeInfoImpl info = createInfoImpl(type, kind, type, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + return; + } + } + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + } + + private static TailType getAssignmentRValueTailType(PsiAssignmentExpression assignment) { + if (assignment.getParent() instanceof PsiExpressionStatement) { + if (!(assignment.getParent().getParent() instanceof PsiForStatement)) { + return TailType.SEMICOLON; + } + + PsiForStatement forStatement = (PsiForStatement)assignment.getParent().getParent(); + if (!assignment.getParent().equals(forStatement.getUpdate())) { + return TailType.SEMICOLON; + } + } + return TailType.NONE; + } + + @Override public void visitExpressionList(PsiExpressionList list) { + PsiResolveHelper helper = JavaPsiFacade.getInstance(list.getProject()).getResolveHelper(); + if (list.getParent() instanceof PsiMethodCallExpression) { + PsiMethodCallExpression methodCall = (PsiMethodCallExpression)list.getParent(); + CandidateInfo[] candidates = helper.getReferencedMethodCandidates(methodCall, false); + myResult = getExpectedArgumentTypesForMethodCall(candidates, list, myExpr, myForCompletion); + } + else if (list.getParent() instanceof PsiEnumConstant) { + getExpectedArgumentsTypesForEnumConstant((PsiEnumConstant)list.getParent(), helper, list); + } + else if (list.getParent() instanceof PsiNewExpression) { + getExpectedArgumentsTypesForNewExpression((PsiNewExpression)list.getParent(), helper, list); + } + else if (list.getParent() instanceof PsiAnonymousClass) { + getExpectedArgumentsTypesForNewExpression((PsiNewExpression)list.getParent().getParent(), helper, list); + } + } + + private void getExpectedArgumentsTypesForEnumConstant(final PsiEnumConstant enumConstant, + final PsiResolveHelper helper, + final PsiExpressionList list) { + final PsiClass aClass = enumConstant.getContainingClass(); + if (aClass != null) { + LOG.assertTrue(aClass.isEnum()); + getExpectedTypesForConstructorCall(aClass, helper, list, PsiSubstitutor.EMPTY); + } + } + + private void getExpectedArgumentsTypesForNewExpression(final PsiNewExpression newExpr, final PsiResolveHelper helper, final PsiExpressionList list) { + PsiType newType = newExpr.getType(); + if (newType instanceof PsiClassType) { + JavaResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(newType); + PsiClass newClass = (PsiClass)resolveResult.getElement(); + final PsiSubstitutor substitutor; + if (newClass instanceof PsiAnonymousClass) { + final PsiAnonymousClass anonymous = (PsiAnonymousClass)newClass; + newClass = anonymous.getBaseClassType().resolve(); + if (newClass == null) return; + + substitutor = TypeConversionUtil.getSuperClassSubstitutor(newClass, anonymous, PsiSubstitutor.EMPTY); + } else if (newClass != null) { + substitutor = resolveResult.getSubstitutor(); + } + else { + return; + } + getExpectedTypesForConstructorCall(newClass, helper, list, substitutor); + } + } + + private void getExpectedTypesForConstructorCall(final PsiClass referencedClass, + final PsiResolveHelper helper, + final PsiExpressionList argumentList, + final PsiSubstitutor substitutor) { + List array = new ArrayList(); + PsiMethod[] constructors = referencedClass.getConstructors(); + for (PsiMethod constructor : constructors) { + if (helper.isAccessible(constructor, argumentList, null)) { + array.add(new MethodCandidateInfo(constructor, substitutor, false, false, argumentList, null, argumentList.getExpressionTypes(), null)); + } + } + CandidateInfo[] candidates = array.toArray(new CandidateInfo[array.size()]); + myResult = getExpectedArgumentTypesForMethodCall(candidates, argumentList, myExpr, myForCompletion); + } + + @Override public void visitBinaryExpression(PsiBinaryExpression expr) { + + PsiExpression op1 = expr.getLOperand(); + PsiExpression op2 = expr.getROperand(); + PsiJavaToken sign = expr.getOperationSign(); + if (myForCompletion && op1.equals(myExpr)) { + final MyParentVisitor visitor = new MyParentVisitor(expr, myForCompletion, myClassProvider, myVoidable, myUsedAfter); + myExpr = (PsiExpression)myExpr.getParent(); + expr.getParent().accept(visitor); + myResult = visitor.getResult(); + if (!(expr.getParent() instanceof PsiExpressionList)) { + for (final ExpectedTypeInfo info : myResult) { + ((ExpectedTypeInfoImpl)info).myTailType = TailType.NONE; + } + } + return; + } + PsiExpression anotherExpr = op1.equals(myExpr) ? op2 : op1; + PsiType anotherType = anotherExpr != null ? anotherExpr.getType() : null; + PsiElementFactory factory = JavaPsiFacade.getInstance(expr.getProject()).getElementFactory(); + IElementType i = sign.getTokenType(); + if (i == JavaTokenType.MINUS || + i == JavaTokenType.ASTERISK || + i == JavaTokenType.DIV || + i == JavaTokenType.PERC || + i == JavaTokenType.LT || + i == JavaTokenType.GT || + i == JavaTokenType.LE || + i == JavaTokenType.GE) { + if (anotherType == null) { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + else { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + anotherType, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + } + } + else if (i == JavaTokenType.PLUS) { + if (anotherType == null) { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + else { + if (anotherType.equalsToText("java.lang.String")) { + PsiType objType = PsiType.getJavaLangObject(myExpr.getManager(), myExpr.getResolveScope()); + ExpectedTypeInfo info = createInfoImpl(objType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, anotherType, + TailType.NONE); + ExpectedTypeInfo info1 = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + PsiType.INT, TailType.NONE); + PsiType booleanType = PsiType.BOOLEAN; + ExpectedTypeInfo info2 = createInfoImpl(booleanType, ExpectedTypeInfo.TYPE_STRICTLY, booleanType, + TailType.NONE); + myResult = new ExpectedTypeInfo[]{info, info1, info2}; + } + else { + if (PsiType.DOUBLE.isAssignableFrom(anotherType)) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + anotherType, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + } + } + } + } + else if (i == JavaTokenType.EQEQ || i == JavaTokenType.NE) { + if (anotherType == null) { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + else { + ExpectedTypeInfoImpl info; + if (anotherType instanceof PsiPrimitiveType) { + if (PsiType.BOOLEAN.equals(anotherType)) { + info = createInfoImpl(anotherType, ExpectedTypeInfo.TYPE_STRICTLY, anotherType, TailType.NONE); + } + else if (PsiType.NULL.equals(anotherType)) { + PsiType objectType = factory.createTypeByFQClassName("java.lang.Object", myExpr.getResolveScope()); + info = createInfoImpl(objectType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, objectType, TailType.NONE); + } + else { + info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, anotherType, TailType.NONE); + } + } + else { + info = createInfoImpl(anotherType, ExpectedTypeInfo.TYPE_STRICTLY, anotherType, TailType.NONE); + } + + if (anotherExpr instanceof PsiReferenceExpression) { + PsiElement refElement = ((PsiReferenceExpression)anotherExpr).resolve(); + if (refElement instanceof PsiVariable) { + info.expectedName = getPropertyName((PsiVariable)refElement); + } + } + + myResult = new ExpectedTypeInfo[]{info}; + } + } + else if (i == JavaTokenType.LTLT || i == JavaTokenType.GTGT || i == JavaTokenType.GTGTGT) { + if (anotherType == null) { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + else { + myResult = new ExpectedTypeInfo[]{createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_BETWEEN, PsiType.SHORT, TailType.NONE)}; + } + } + else if (i == JavaTokenType.OROR || i == JavaTokenType.ANDAND) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + } + else if (i == JavaTokenType.OR || i == JavaTokenType.XOR || i == JavaTokenType.AND) { + if (anotherType == null) { + myResult = ExpectedTypeInfo.EMPTY_ARRAY; + } + else { + ExpectedTypeInfoImpl info; + if (PsiType.BOOLEAN.equals(anotherType)) { + info = createInfoImpl(anotherType, ExpectedTypeInfo.TYPE_STRICTLY, anotherType, TailType.NONE); + } + else { + info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, anotherType, TailType.NONE); + } + myResult = new ExpectedTypeInfo[]{info}; + } + } + } + + @Override public void visitPrefixExpression(PsiPrefixExpression expr) { + PsiJavaToken sign = expr.getOperationSign(); + IElementType i = sign.getTokenType(); + final PsiType type = expr.getType(); + final TailType tailType = expr.getParent() instanceof PsiAssignmentExpression && ((PsiAssignmentExpression) expr.getParent()).getRExpression() == expr ? + getAssignmentRValueTailType((PsiAssignmentExpression) expr.getParent()) : + TailType.NONE; + if (i == JavaTokenType.PLUSPLUS || i == JavaTokenType.MINUSMINUS || i == JavaTokenType.TILDE) { + ExpectedTypeInfoImpl info; + if (myUsedAfter && type != null) { + info = createInfoImpl(type, ExpectedTypeInfo.TYPE_STRICTLY, type, tailType); + } + else { + if (type != null) { + info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, PsiType.INT, tailType); + } + else { + info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, tailType); + } + } + myResult = new ExpectedTypeInfo[]{info}; + } + else if (i == JavaTokenType.PLUS || i == JavaTokenType.MINUS) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.DOUBLE, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + PsiType.INT, tailType); + myResult = new ExpectedTypeInfo[]{info}; + } + else if (i == JavaTokenType.EXCL) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, tailType); + myResult = new ExpectedTypeInfo[]{info}; + } + } + + @Override public void visitPostfixExpression(PsiPostfixExpression expr) { + if (myForCompletion) return; + PsiType type = expr.getType(); + ExpectedTypeInfoImpl info; + if (myUsedAfter && type != null) { + info = createInfoImpl(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.NONE); + } + else { + if (type != null) { + info = createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, PsiType.INT, TailType.NONE); + } + else { + info = createInfoImpl(PsiType.LONG, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, TailType.NONE); + } + } + myResult = new ExpectedTypeInfo[]{info}; + } + + @Override public void visitArrayInitializerExpression(PsiArrayInitializerExpression expr) { + PsiElement pparent = expr.getParent(); + PsiType arrayType = null; + if (pparent instanceof PsiVariable) { + arrayType = ((PsiVariable)pparent).getType(); + } + else if (pparent instanceof PsiNewExpression) { + arrayType = ((PsiNewExpression)pparent).getType(); + } + else if (pparent instanceof PsiArrayInitializerExpression) { + PsiType type = ((PsiArrayInitializerExpression)pparent).getType(); + if (type instanceof PsiArrayType) { + arrayType = ((PsiArrayType)type).getComponentType(); + } + } + + if (arrayType instanceof PsiArrayType) { + PsiType componentType = ((PsiArrayType)arrayType).getComponentType(); + ExpectedTypeInfoImpl info = createInfoImpl(componentType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + componentType, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + } + } + + @Override public void visitNewExpression(PsiNewExpression expression) { + PsiExpression[] arrayDimensions = expression.getArrayDimensions(); + for (PsiExpression dimension : arrayDimensions) { + if (myExpr.equals(dimension)) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.INT, ExpectedTypeInfo.TYPE_OR_SUBTYPE, + PsiType.INT, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + return; + } + } + } + + @Override public void visitArrayAccessExpression(PsiArrayAccessExpression expr) { + if (myExpr.equals(expr.getIndexExpression())) { + ExpectedTypeInfoImpl info = createInfoImpl(PsiType.INT, ExpectedTypeInfo.TYPE_OR_SUBTYPE, PsiType.INT, TailType.NONE) + ; //todo: special tail type + myResult = new ExpectedTypeInfo[]{info}; + } + else if (myExpr.equals(expr.getArrayExpression())) { + if (myForCompletion) { + myExpr = (PsiExpression)myExpr.getParent(); + expr.getParent().accept(this); + return; + } + + PsiElement parent = expr.getParent(); + MyParentVisitor visitor = new MyParentVisitor(expr, myForCompletion, myClassProvider, myVoidable, myUsedAfter); + myExpr = (PsiExpression)myExpr.getParent(); + parent.accept(visitor); + ExpectedTypeInfo[] componentTypeInfo = visitor.getResult(); + if (componentTypeInfo.length == 0) { + myResult = anyArrayType(); + } + else { + myResult = new ExpectedTypeInfoImpl[componentTypeInfo.length]; + for (int i = 0; i < componentTypeInfo.length; i++) { + ExpectedTypeInfo compInfo = componentTypeInfo[i]; + PsiType expectedArrayType = compInfo.getType().createArrayType(); + myResult[i] = createInfoImpl(expectedArrayType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, expectedArrayType, TailType.NONE); + } + } + } + } + + @Override public void visitConditionalExpression(PsiConditionalExpression expr) { + if (myExpr.equals(expr.getCondition())) { + if (myForCompletion) { + myExpr = expr; + myExpr.getParent().accept(this); + return; + } + + ExpectedTypeInfo info = createInfoImpl(PsiType.BOOLEAN, ExpectedTypeInfo.TYPE_STRICTLY, + PsiType.BOOLEAN, TailType.NONE); + myResult = new ExpectedTypeInfo[]{info}; + } + else if (myExpr.equals(expr.getThenExpression())) { + ExpectedTypeInfo[] types = getExpectedTypes(expr, myForCompletion); + if (types != null) { + for (ExpectedTypeInfo info : types) { + ExpectedTypeInfoImpl infoImpl = (ExpectedTypeInfoImpl)info; + infoImpl.setInsertExplicitTypeParams(true); + infoImpl.myTailType = TailType.COND_EXPR_COLON; + } + } + myResult = types; + } + else { + LOG.assertTrue(myExpr.equals(expr.getElseExpression())); + myResult = getExpectedTypes(expr, myForCompletion); + if (myResult != null) { + for (ExpectedTypeInfo info : myResult) { + ((ExpectedTypeInfoImpl)info).setInsertExplicitTypeParams(true); + } + } + + } + } + + @Override public void visitThrowStatement(PsiThrowStatement statement) { + if (statement.getException() == myExpr) { + PsiManager manager = statement.getManager(); + PsiType throwableType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName("java.lang.Throwable", myExpr.getResolveScope()); + PsiMember container = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, PsiClass.class); + PsiType[] throwsTypes = PsiType.EMPTY_ARRAY; + if (container instanceof PsiMethod) { + throwsTypes = ((PsiMethod)container).getThrowsList().getReferencedTypes(); + } + + if (throwsTypes.length == 0) { + final PsiClassType exceptionType = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName("java.lang.Exception", myExpr.getResolveScope()); + throwsTypes = new PsiClassType[]{exceptionType}; + } + + ExpectedTypeInfo[] infos = new ExpectedTypeInfo[throwsTypes.length]; + for (int i = 0; i < infos.length; i++) { + infos[i] = createInfoImpl( + myExpr instanceof PsiTypeCastExpression && myForCompletion ? + throwsTypes[i] : + throwableType, + ExpectedTypeInfo.TYPE_OR_SUBTYPE, + throwsTypes[i], + TailType.SEMICOLON + ); + } + myResult = infos; + } + } + + @Override public void visitCodeFragment(JavaCodeFragment codeFragment) { + if (codeFragment instanceof PsiExpressionCodeFragment) { + final PsiType type = ((PsiExpressionCodeFragment)codeFragment).getExpectedType(); + if (type != null) { + myResult = new ExpectedTypeInfo[] {createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.NONE)}; + } + } + } + + private ExpectedTypeInfo[] getExpectedArgumentTypesForMethodCall(CandidateInfo[] methodCandidates, + PsiExpressionList argumentList, + PsiExpression argument, + boolean forCompletion) { + if (methodCandidates.length == 0) { + return ExpectedTypeInfo.EMPTY_ARRAY; + } + final PsiExpression[] args = argumentList.getExpressions(); + final int index = ArrayUtil.indexOf(args, argument); + LOG.assertTrue(index >= 0); + + final PsiExpression[] leftArgs; + if (index <= args.length - 1) { + leftArgs = new PsiExpression[index]; + System.arraycopy(args, 0, leftArgs, 0, index); + } else { + leftArgs = null; + } + + Set array = new LinkedHashSet(); + for (CandidateInfo candidateInfo : methodCandidates) { + PsiMethod method = (PsiMethod)candidateInfo.getElement(); + PsiSubstitutor substitutor; + if (candidateInfo instanceof MethodCandidateInfo) { + final MethodCandidateInfo info = (MethodCandidateInfo)candidateInfo; + substitutor = info.inferTypeArguments(forCompletion); + if (!info.isStaticsScopeCorrect() && method != null && !method.hasModifierProperty(PsiModifier.STATIC)) continue; + } + else { + substitutor = candidateInfo.getSubstitutor(); + } + inferMethodCallArgumentTypes(argument, forCompletion, args, index, method, substitutor, array); + + if (leftArgs != null && candidateInfo instanceof MethodCandidateInfo) { + substitutor = ((MethodCandidateInfo)candidateInfo).inferTypeArguments(forCompletion, leftArgs); + inferMethodCallArgumentTypes(argument, forCompletion, leftArgs, index, method, substitutor, array); + } + } + + // try to find some variants without considering previous argument PRIMITIVE_TYPES + if (forCompletion && array.isEmpty()) { + for (CandidateInfo candidate : methodCandidates) { + PsiMethod method = (PsiMethod)candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + PsiParameter[] parms = method.getParameterList().getParameters(); + if (parms.length <= index) continue; + PsiParameter parm = parms[index]; + PsiType parmType = getParameterType(parm, substitutor); + TailType tailType = getMethodArgumentTailType(argument, index, method, substitutor, parms); + ExpectedTypeInfoImpl info = createInfoImpl(parmType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, parmType, + tailType); + info.expectedName = getPropertyName(parm); + info.setCalledMethod(method); + array.add(info); + } + } + + return array.toArray(new ExpectedTypeInfo[array.size()]); + } + + private static TailType getMethodArgumentTailType(final PsiExpression argument, final int index, final PsiMethod method, final PsiSubstitutor substitutor, + final PsiParameter[] parms) { + if (index >= parms.length) { + return TailType.NONE; + } + if (index == parms.length - 1) { + //myTailType = CompletionUtil.NONE_TAIL; + final PsiElement call = argument.getParent().getParent(); + if (call instanceof JspMethodCall) return TailType.NONE; + + PsiType returnType = method.getReturnType(); + if (returnType != null) returnType = substitutor.substitute(returnType); + return getFinalCallParameterTailType(call, returnType, method); + } + return TailType.COMMA; + } + + private void inferMethodCallArgumentTypes(final PsiExpression argument, + final boolean forCompletion, + final PsiExpression[] args, + final int index, + final PsiMethod method, final PsiSubstitutor substitutor, final Set array) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (!forCompletion && parameters.length != args.length) return; + if (parameters.length <= index && !method.isVarArgs()) return; + + for (int j = 0; j < index; j++) { + PsiType paramType = getParameterType(parameters[Math.min(parameters.length - 1, j)], + substitutor); + PsiType argType = args[j].getType(); + if (argType != null && !paramType.isAssignableFrom(argType)) return; + } + PsiParameter parameter = parameters[Math.min(parameters.length - 1, index)]; + PsiType parameterType = getParameterType(parameter, substitutor); + + TailType tailType = getMethodArgumentTailType(argument, index, method, substitutor, parameters); + PsiType defaultType = getDefautType(method, substitutor, parameterType, argument); + + ExpectedTypeInfoImpl info = createInfoImpl(parameterType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, defaultType, tailType); + info.setInsertExplicitTypeParams(true); + info.setCalledMethod(method); + String propertyName = getPropertyName(parameter); + if (propertyName != null) info.expectedName = propertyName; + array.add(info); + + if (index == parameters.length - 1 && parameter.isVarArgs()) { + //Then we may still want to call with array argument + final PsiArrayType arrayType = parameterType.createArrayType(); + ExpectedTypeInfoImpl info1 = createInfoImpl(arrayType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, arrayType, tailType); + info1.setInsertExplicitTypeParams(true); + info1.setCalledMethod(method); + info1.expectedName = propertyName; + array.add(info1); + } + } + + @Nullable + private static PsiType getTypeParameterValue(PsiClass rootClass, PsiClass derivedClass, PsiSubstitutor substitutor, int index) { + final PsiTypeParameter[] typeParameters = rootClass.getTypeParameters(); + if (typeParameters.length > index) { + final PsiSubstitutor psiSubstitutor = TypeConversionUtil.getClassSubstitutor(rootClass, derivedClass, substitutor); + if (psiSubstitutor != null) { + PsiType type = psiSubstitutor.substitute(typeParameters[index]); + if (type != null) return type; + } + } + return null; + } + + @Nullable + protected static PsiType checkMethod(PsiMethod method, @NonNls final String className, final NullableFunction function) { + final PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) return null; + + if (className.equals(containingClass.getQualifiedName())) { + return function.fun(containingClass); + } + final PsiType[] type = {null}; + DeepestSuperMethodsSearch.search(method).forEach(new Processor() { + @Override + public boolean process(PsiMethod psiMethod) { + final PsiClass rootClass = psiMethod.getContainingClass(); + if (className.equals(rootClass.getQualifiedName())) { + type[0] = function.fun(rootClass); + return false; + } + return true; + } + }); + return type[0]; + } + + @Nullable + private PsiType getDefautType(final PsiMethod method, final PsiSubstitutor substitutor, final PsiType parameterType, + final PsiExpression argumentList) { + final PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) return parameterType; + + @NonNls final String name = method.getName(); + if ("contains".equals(name) || "remove".equals(name)) { + final PsiType type = checkMethod(method, CommonClassNames.JAVA_UTIL_COLLECTION, new NullableFunction() { + public PsiType fun(final PsiClass psiClass) { + return getTypeParameterValue(psiClass, containingClass, substitutor, 0); + } + }); + if (type != null) return type; + } + if ("containsKey".equals(name) || "remove".equals(name) || "get".equals(name) || "containsValue".equals(name)) { + final PsiType type = checkMethod(method, CommonClassNames.JAVA_UTIL_MAP, new NullableFunction() { + public PsiType fun(final PsiClass psiClass) { + return getTypeParameterValue(psiClass, containingClass, substitutor, name.equals("containsValue") ? 1 : 0); + } + }); + if (type != null) return type; + } + if ("equals".equals(name)) { + final PsiType type = checkMethod(method, CommonClassNames.JAVA_LANG_OBJECT, new NullableFunction() { + public PsiType fun(final PsiClass psiClass) { + final PsiElement parent = argumentList.getParent().getParent(); + if (parent instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression expression = (PsiMethodCallExpression)parent; + final PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression(); + if (qualifierExpression != null) { + return qualifierExpression.getType(); + } + final PsiClass aClass = PsiTreeUtil.getContextOfType(parent, PsiClass.class, true); + if (aClass != null) { + return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); + } + } + return null; + } + }); + if (type != null) return type; + } + return parameterType; + } + + private static PsiType getParameterType(PsiParameter parameter, PsiSubstitutor substitutor) { + PsiType type = parameter.getType(); + if (parameter.isVarArgs()) { + if (type instanceof PsiArrayType) { + type = ((PsiArrayType)type).getComponentType(); + } else { + LOG.error("Vararg parameter with non-array type. Class=" + parameter.getClass() + "; type=" + parameter.getType()); + } + } + PsiType parameterType = substitutor.substitute(type); + if (parameterType instanceof PsiCapturedWildcardType) { + parameterType = ((PsiCapturedWildcardType)parameterType).getWildcard(); + } + if (parameterType instanceof PsiWildcardType) { + final PsiWildcardType psiWildcardType = (PsiWildcardType)parameterType; + if (psiWildcardType.isExtends()) { + final PsiType superBound = psiWildcardType.getBound(); + if (superBound != null) { + parameterType = superBound; + } + } + } + return parameterType; + } + + @Nullable + private String getPropertyName(PsiVariable variable) { + final String name = variable.getName(); + if (name == null) return null; + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(variable.getProject()); + VariableKind variableKind = codeStyleManager.getVariableKind(variable); + return codeStyleManager.variableNameToPropertyName(name, variableKind); + } + + private void addBaseType(Set types, PsiClassType type, PsiMethod method) { + PsiType[] supers = type.getSuperTypes(); + boolean addedSuper = false; + for (PsiType aSuper : supers) { + PsiClassType superType = (PsiClassType)aSuper; + PsiClass superClass = superType.resolve(); + if (superClass != null) { + if (superClass.findMethodBySignature(method, false) != null) { + addBaseType(types, superType, method); + addedSuper = true; + } + } + } + if (!addedSuper) { + types.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.DOT)); + } + } + + private ExpectedTypeInfo[] anyArrayType() { + PsiType objType = PsiType.getJavaLangObject(myExpr.getManager(), myExpr.getResolveScope()).createArrayType(); + ExpectedTypeInfo info = createInfoImpl(objType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, objType, + TailType.NONE); + ExpectedTypeInfo info1 = createInfoImpl(PsiType.DOUBLE.createArrayType(), ExpectedTypeInfo.TYPE_OR_SUBTYPE, + PsiType.INT.createArrayType(), TailType.NONE); + PsiType booleanType = PsiType.BOOLEAN.createArrayType(); + ExpectedTypeInfo info2 = createInfoImpl(booleanType, ExpectedTypeInfo.TYPE_STRICTLY, booleanType, + TailType.NONE); + return new ExpectedTypeInfo[]{info, info1, info2}; + } + + private ExpectedTypeInfo[] findClassesWithDeclaredMethod(final PsiMethodCallExpression methodCallExpr, final boolean forCompletion) { + final PsiReferenceExpression reference = methodCallExpr.getMethodExpression(); + final PsiManager manager = methodCallExpr.getManager(); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); + final PsiMethod[] methods = myClassProvider.findDeclaredMethods(reference.getManager(), reference.getReferenceName()); + Set types = new THashSet(); + for (PsiMethod method : methods) { + final PsiClass aClass = method.getContainingClass(); + if (aClass == null || !facade.getResolveHelper().isAccessible(method, reference, aClass)) continue; + + final PsiSubstitutor substitutor = ExpectedTypeUtil.inferSubstitutor(method, methodCallExpr, forCompletion); + final PsiClassType type = + substitutor == null ? facade.getElementFactory().createType(aClass) : facade.getElementFactory().createType(aClass, substitutor); + + if (method.hasModifierProperty(PsiModifier.STATIC) || + method.hasModifierProperty(PsiModifier.FINAL) || + method.hasModifierProperty(PsiModifier.PRIVATE)) { + types.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.DOT)); + } + else { + addBaseType(types, type, method); + } + } + + return types.toArray(new ExpectedTypeInfo[types.size()]); + } + + private ExpectedTypeInfo[] findClassesWithDeclaredField(PsiReferenceExpression expression) { + final JavaPsiFacade facade = JavaPsiFacade.getInstance(expression.getProject()); + PsiField[] fields = myClassProvider.findDeclaredFields(expression.getManager(), expression.getReferenceName()); + List types = new ArrayList(); + for (PsiField field : fields) { + final PsiClass aClass = field.getContainingClass(); + if (aClass == null || !facade.getResolveHelper().isAccessible(field, expression, aClass)) continue; + + final PsiType type = facade.getElementFactory().createType(aClass); + + int kind = field.hasModifierProperty(PsiModifier.STATIC) || + field.hasModifierProperty(PsiModifier.FINAL) || + field.hasModifierProperty(PsiModifier.PRIVATE) + ? ExpectedTypeInfo.TYPE_STRICTLY + : ExpectedTypeInfo.TYPE_OR_SUBTYPE; + ExpectedTypeInfo info = createInfoImpl(type, kind, type, TailType.DOT); + //Do not filter inheritors! + types.add(info); + } + return types.toArray(new ExpectedTypeInfo[types.size()]); + } + } + + /** + * Finds fields and methods of specified name whenever corresponding reference has been encountered. + * By default searhes in the global scope (see ourGlobalScopeClassProvider), but caller can provide its own algorithm e.g. to narrow search scope + */ + public interface ExpectedClassProvider { + PsiField[] findDeclaredFields(final PsiManager manager, String name); + + PsiMethod[] findDeclaredMethods(final PsiManager manager, String name); + } + + public static TailType getFinalCallParameterTailType(PsiElement call, PsiType returnType, PsiMethod method) { + if (method.isConstructor() && + call instanceof PsiMethodCallExpression && ((PsiMethodCallExpression)call).getMethodExpression() instanceof PsiSuperExpression) { + return TailTypes.CALL_RPARENTH_SEMICOLON; + } + + final boolean chainable = !PsiType.VOID.equals(returnType) && returnType != null; + + final PsiElement parent = call.getParent(); + final boolean statementContext = parent instanceof PsiExpressionStatement || parent instanceof PsiVariable || + parent instanceof PsiCodeBlock || parent instanceof PsiThrowStatement; + + if (statementContext && !chainable) { + return TailTypes.CALL_RPARENTH_SEMICOLON; + } + + return TailTypes.CALL_RPARENTH; + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/BasicExpressionCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/BasicExpressionCompletionContributor.java index fd6159fc381d..562465af4319 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/BasicExpressionCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/BasicExpressionCompletionContributor.java @@ -1,182 +1,182 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.guess.GuessManager; -import com.intellij.codeInsight.lookup.*; -import com.intellij.codeInsight.template.SmartCompletionContextType; -import com.intellij.codeInsight.template.impl.TemplateImpl; -import com.intellij.codeInsight.template.impl.TemplateSettings; -import com.intellij.patterns.PsiJavaPatterns; -import com.intellij.psi.*; -import com.intellij.psi.filters.getters.ClassLiteralGetter; -import com.intellij.psi.filters.getters.MembersGetter; -import com.intellij.psi.filters.getters.ThisGetter; -import com.intellij.psi.scope.BaseScopeProcessor; -import com.intellij.psi.scope.util.PsiScopesUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.Consumer; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ProcessingContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -import static com.intellij.patterns.PsiJavaPatterns.psiClass; -import static com.intellij.patterns.PsiJavaPatterns.psiElement; -import static com.intellij.patterns.StandardPatterns.not; - -/** - * @author peter - */ -public class BasicExpressionCompletionContributor extends ExpressionSmartCompletionContributor{ - - private static void addKeyword(final CompletionResultSet result, final PsiElement element, final String s) { - result.addElement(createKeywordLookupItem(element, s)); - } - - public static LookupElement createKeywordLookupItem(final PsiElement element, final String s) { - try { - final PsiKeyword keyword = JavaPsiFacade.getInstance(element.getProject()).getElementFactory().createKeyword(s); - return new KeywordLookupItem(keyword, element).setAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE); - } - catch (IncorrectOperationException e) { - throw new RuntimeException(e); - } - } - - - public BasicExpressionCompletionContributor() { - extend(PsiJavaPatterns.psiElement().afterLeaf( - PsiJavaPatterns.psiElement().withText(".").afterLeaf( - PsiJavaPatterns.psiElement().withParent( - PsiJavaPatterns.psiElement().referencing(psiClass())))), new CompletionProvider() { - public void addCompletions(@NotNull final JavaSmartCompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) { - final PsiElement element = parameters.getPosition(); - addKeyword(result, element, PsiKeyword.CLASS); - addKeyword(result, element, PsiKeyword.THIS); - } - - }); - - extend(not(psiElement().afterLeaf(".")), new CollectionsUtilityMethodsProvider()); - extend(not(psiElement().afterLeaf(".")), new ClassLiteralGetter()); - - extend(not(psiElement().afterLeaf(".")), new CompletionProvider() { - protected void addCompletions(@NotNull final JavaSmartCompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) { - final PsiElement position = parameters.getPosition(); - final PsiType expectedType = parameters.getExpectedType(); - - for (final TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { - if (!template.isDeactivated() && template.getTemplateContext().isEnabled(new SmartCompletionContextType())) { - result.addElement(new SmartCompletionTemplateItem(template, position)); - } - } - - addKeyword(result, position, PsiKeyword.TRUE); - addKeyword(result, position, PsiKeyword.FALSE); - - final PsiElement parent = position.getParent(); - if (parent != null && !(parent.getParent() instanceof PsiSwitchLabelStatement)) { - MembersGetter.addMembers(parameters.getPosition(), expectedType, result); - if (!parameters.getDefaultType().equals(expectedType)) { - MembersGetter.addMembers(parameters.getPosition(), parameters.getDefaultType(), result); - } - - for (final PsiExpression expression : ThisGetter.getThisExpressionVariants(position)) { - result.addElement(new ExpressionLookupItem(expression)); - } - } - - processDataflowExpressionTypes(position, expectedType, result.getPrefixMatcher(), new Consumer() { - public void consume(LookupElement decorator) { - result.addElement(decorator); - } - }); - } - }); - - } - - public static void processDataflowExpressionTypes(PsiElement position, @Nullable PsiType expectedType, final PrefixMatcher matcher, Consumer consumer) { - final PsiExpression context = PsiTreeUtil.getParentOfType(position, PsiExpression.class); - if (context == null) return; - - final Map map = GuessManager.getInstance(position.getProject()).getControlFlowExpressionTypes(context); - if (map.isEmpty()) { - return; - } - - PsiScopesUtil.treeWalkUp(new BaseScopeProcessor() { - public boolean execute(PsiElement element, ResolveState state) { - if (element instanceof PsiLocalVariable) { - if (!matcher.prefixMatches(((PsiLocalVariable)element).getName())) { - return true; - } - - final PsiExpression expression = ((PsiLocalVariable)element).getInitializer(); - if (expression instanceof PsiTypeCastExpression) { - PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)expression; - final PsiExpression operand = typeCastExpression.getOperand(); - if (operand != null) { - final PsiType dfaCasted = map.get(operand); - if (dfaCasted != null && dfaCasted.equals(typeCastExpression.getType())) { - map.remove(operand); - } - } - } - } - return true; - } - }, context, context.getContainingFile()); - - for (final PsiExpression expression : map.keySet()) { - final PsiType castType = map.get(expression); - final PsiType baseType = expression.getType(); - if (expectedType == null || (expectedType.isAssignableFrom(castType) && (baseType == null || !expectedType.isAssignableFrom(baseType)))) { - consumer.consume(CastingLookupElementDecorator.createCastingElement(expressionToLookupElement(expression), castType)); - } - } - } - - @NotNull - private static LookupElement expressionToLookupElement(@NotNull PsiExpression expression) { - if (expression instanceof PsiReferenceExpression) { - final PsiReferenceExpression refExpr = (PsiReferenceExpression)expression; - if (!refExpr.isQualified()) { - final PsiElement target = refExpr.resolve(); - if (target instanceof PsiVariable) { - final VariableLookupItem item = new VariableLookupItem((PsiVariable)target); - item.setSubstitutor(PsiSubstitutor.EMPTY); - return item; - } - } - } - if (expression instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression call = (PsiMethodCallExpression)expression; - if (!call.getMethodExpression().isQualified()) { - final PsiMethod method = call.resolveMethod(); - if (method != null) { - return new JavaMethodCallElement(method); - } - } - } - - return new ExpressionLookupItem(expression); - } - -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.guess.GuessManager; +import com.intellij.codeInsight.lookup.*; +import com.intellij.codeInsight.template.SmartCompletionContextType; +import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.codeInsight.template.impl.TemplateSettings; +import com.intellij.patterns.PsiJavaPatterns; +import com.intellij.psi.*; +import com.intellij.psi.filters.getters.ClassLiteralGetter; +import com.intellij.psi.filters.getters.MembersGetter; +import com.intellij.psi.filters.getters.ThisGetter; +import com.intellij.psi.scope.BaseScopeProcessor; +import com.intellij.psi.scope.util.PsiScopesUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +import static com.intellij.patterns.PsiJavaPatterns.psiClass; +import static com.intellij.patterns.PsiJavaPatterns.psiElement; +import static com.intellij.patterns.StandardPatterns.not; + +/** + * @author peter + */ +public class BasicExpressionCompletionContributor extends ExpressionSmartCompletionContributor{ + + private static void addKeyword(final CompletionResultSet result, final PsiElement element, final String s) { + result.addElement(createKeywordLookupItem(element, s)); + } + + public static LookupElement createKeywordLookupItem(final PsiElement element, final String s) { + try { + final PsiKeyword keyword = JavaPsiFacade.getInstance(element.getProject()).getElementFactory().createKeyword(s); + return new KeywordLookupItem(keyword, element).setAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE); + } + catch (IncorrectOperationException e) { + throw new RuntimeException(e); + } + } + + + public BasicExpressionCompletionContributor() { + extend(PsiJavaPatterns.psiElement().afterLeaf( + PsiJavaPatterns.psiElement().withText(".").afterLeaf( + PsiJavaPatterns.psiElement().withParent( + PsiJavaPatterns.psiElement().referencing(psiClass())))), new CompletionProvider() { + public void addCompletions(@NotNull final JavaSmartCompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) { + final PsiElement element = parameters.getPosition(); + addKeyword(result, element, PsiKeyword.CLASS); + addKeyword(result, element, PsiKeyword.THIS); + } + + }); + + extend(not(psiElement().afterLeaf(".")), new CollectionsUtilityMethodsProvider()); + extend(not(psiElement().afterLeaf(".")), new ClassLiteralGetter()); + + extend(not(psiElement().afterLeaf(".")), new CompletionProvider() { + protected void addCompletions(@NotNull final JavaSmartCompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) { + final PsiElement position = parameters.getPosition(); + final PsiType expectedType = parameters.getExpectedType(); + + for (final TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { + if (!template.isDeactivated() && template.getTemplateContext().isEnabled(new SmartCompletionContextType())) { + result.addElement(new SmartCompletionTemplateItem(template, position)); + } + } + + addKeyword(result, position, PsiKeyword.TRUE); + addKeyword(result, position, PsiKeyword.FALSE); + + final PsiElement parent = position.getParent(); + if (parent != null && !(parent.getParent() instanceof PsiSwitchLabelStatement)) { + MembersGetter.addMembers(parameters.getPosition(), expectedType, result); + if (!parameters.getDefaultType().equals(expectedType)) { + MembersGetter.addMembers(parameters.getPosition(), parameters.getDefaultType(), result); + } + + for (final PsiExpression expression : ThisGetter.getThisExpressionVariants(position)) { + result.addElement(new ExpressionLookupItem(expression)); + } + } + + processDataflowExpressionTypes(position, expectedType, result.getPrefixMatcher(), new Consumer() { + public void consume(LookupElement decorator) { + result.addElement(decorator); + } + }); + } + }); + + } + + public static void processDataflowExpressionTypes(PsiElement position, @Nullable PsiType expectedType, final PrefixMatcher matcher, Consumer consumer) { + final PsiExpression context = PsiTreeUtil.getParentOfType(position, PsiExpression.class); + if (context == null) return; + + final Map map = GuessManager.getInstance(position.getProject()).getControlFlowExpressionTypes(context); + if (map.isEmpty()) { + return; + } + + PsiScopesUtil.treeWalkUp(new BaseScopeProcessor() { + public boolean execute(PsiElement element, ResolveState state) { + if (element instanceof PsiLocalVariable) { + if (!matcher.prefixMatches(((PsiLocalVariable)element).getName())) { + return true; + } + + final PsiExpression expression = ((PsiLocalVariable)element).getInitializer(); + if (expression instanceof PsiTypeCastExpression) { + PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)expression; + final PsiExpression operand = typeCastExpression.getOperand(); + if (operand != null) { + final PsiType dfaCasted = map.get(operand); + if (dfaCasted != null && dfaCasted.equals(typeCastExpression.getType())) { + map.remove(operand); + } + } + } + } + return true; + } + }, context, context.getContainingFile()); + + for (final PsiExpression expression : map.keySet()) { + final PsiType castType = map.get(expression); + final PsiType baseType = expression.getType(); + if (expectedType == null || (expectedType.isAssignableFrom(castType) && (baseType == null || !expectedType.isAssignableFrom(baseType)))) { + consumer.consume(CastingLookupElementDecorator.createCastingElement(expressionToLookupElement(expression), castType)); + } + } + } + + @NotNull + private static LookupElement expressionToLookupElement(@NotNull PsiExpression expression) { + if (expression instanceof PsiReferenceExpression) { + final PsiReferenceExpression refExpr = (PsiReferenceExpression)expression; + if (!refExpr.isQualified()) { + final PsiElement target = refExpr.resolve(); + if (target instanceof PsiVariable) { + final VariableLookupItem item = new VariableLookupItem((PsiVariable)target); + item.setSubstitutor(PsiSubstitutor.EMPTY); + return item; + } + } + } + if (expression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + if (!call.getMethodExpression().isQualified()) { + final PsiMethod method = call.resolveMethod(); + if (method != null) { + return new JavaMethodCallElement(method); + } + } + } + + return new ExpressionLookupItem(expression); + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaAwareCompletionData.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaAwareCompletionData.java index 2dc930f88a11..ae8c9070c875 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaAwareCompletionData.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaAwareCompletionData.java @@ -1,158 +1,158 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItem; -import com.intellij.codeInsight.lookup.LookupItemUtil; -import com.intellij.psi.*; -import com.intellij.psi.filters.ContextGetter; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Set; - -/** - * @author peter - */ -public class JavaAwareCompletionData extends CompletionData{ - - @NotNull - static TailType analyzeItem(final Object completion, final PsiElement position) { - if(completion instanceof PsiKeyword){ - final String text = ((PsiKeyword)completion).getText(); - if(PsiKeyword.BREAK.equals(text) || PsiKeyword.CONTINUE.equals(text)) { - PsiElement scope = position; - while(true){ - if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer){ - return TailType.SEMICOLON; - } - - if (scope instanceof PsiLabeledStatement){ - return TailType.NONE; - } - scope = scope.getParent(); - } - } - if(PsiKeyword.RETURN.equals(text)){ - PsiElement scope = position; - while(true){ - if (scope instanceof PsiFile || scope instanceof PsiClassInitializer){ - return TailType.NONE; - } - - if (scope instanceof PsiMethod){ - final PsiMethod method = (PsiMethod)scope; - if(method.isConstructor() || PsiType.VOID.equals(method.getReturnType())) { - return TailType.SEMICOLON; - } - - return TailType.SPACE; - } - scope = scope.getParent(); - } - } - } - return TailType.NONE; - } - - protected void addLookupItem(Set set, final TailType tailType, @NotNull Object completion, final PsiFile file, final CompletionVariant variant) { - if (completion instanceof LookupElement && !(completion instanceof LookupItem)) { - set.add((LookupElement)completion); - return; - } - - LookupElement _ret = LookupItemUtil.objectToLookupItem(completion); - if(_ret == null || !(_ret instanceof LookupItem)) return; - - LookupItem ret = (LookupItem)_ret; - final InsertHandler insertHandler = variant.getInsertHandler(); - if(insertHandler != null && ret.getInsertHandler() == null) { - } - ret.setInsertHandler(new InsertHandler() { - @Override - public void handleInsert(InsertionContext context, LookupElement item) { - TailType type = analyzeItem(item.getObject(), context.getFile().findElementAt(context.getStartOffset())); - if (type == TailType.NONE) { - type = tailType; - } - //new DefaultInsertHandler().handleInsert(context, item); - if (type != TailType.NONE) { - context.setAddCompletionChar(false); - type.processTail(context.getEditor(), context.getTailOffset()); - } - } - }); - - final Map itemProperties = variant.getItemProperties(); - for (final Object key : itemProperties.keySet()) { - ret.setAttribute(key, itemProperties.get(key)); - } - - set.add(ret); - } - - protected void addKeywords(final Set set, final PsiElement position, final PrefixMatcher matcher, final PsiFile file, - final CompletionVariant variant, final Object comp, final TailType tailType) { - final PsiElementFactory factory = JavaPsiFacade.getInstance(file.getProject()).getElementFactory(); - if (comp instanceof String) { - addKeyword(factory, set, tailType, comp, matcher, file, variant); - } - else { - final CompletionContext context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY); - if (comp instanceof ContextGetter) { - final Object[] elements = ((ContextGetter)comp).get(position, context); - for (Object element : elements) { - addLookupItem(set, tailType, element, file, variant); - } - } - // TODO: KeywordChooser -> ContextGetter - else if (comp instanceof KeywordChooser) { - final String[] keywords = ((KeywordChooser)comp).getKeywords(context, position); - for (String keyword : keywords) { - addKeyword(factory, set, tailType, keyword, matcher, file, variant); - } - } - } - } - - private void addKeyword(PsiElementFactory factory, Set set, final TailType tailType, final Object comp, final PrefixMatcher matcher, - final PsiFile file, - final CompletionVariant variant) { - for (final LookupElement item : set) { - if (item.getObject().toString().equals(comp.toString())) { - return; - } - } - if(factory == null){ - addLookupItem(set, tailType, comp, file, variant); - } - else{ - try{ - final PsiKeyword keyword = factory.createKeyword((String)comp); - addLookupItem(set, tailType, keyword, file, variant); - } - catch(IncorrectOperationException e){ - addLookupItem(set, tailType, comp, file, variant); - } - } - } - - public void fillCompletions(CompletionParameters parameters, CompletionResultSet result) { - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupItem; +import com.intellij.codeInsight.lookup.LookupItemUtil; +import com.intellij.psi.*; +import com.intellij.psi.filters.ContextGetter; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Set; + +/** + * @author peter + */ +public class JavaAwareCompletionData extends CompletionData{ + + @NotNull + static TailType analyzeItem(final Object completion, final PsiElement position) { + if(completion instanceof PsiKeyword){ + final String text = ((PsiKeyword)completion).getText(); + if(PsiKeyword.BREAK.equals(text) || PsiKeyword.CONTINUE.equals(text)) { + PsiElement scope = position; + while(true){ + if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer){ + return TailType.SEMICOLON; + } + + if (scope instanceof PsiLabeledStatement){ + return TailType.NONE; + } + scope = scope.getParent(); + } + } + if(PsiKeyword.RETURN.equals(text)){ + PsiElement scope = position; + while(true){ + if (scope instanceof PsiFile || scope instanceof PsiClassInitializer){ + return TailType.NONE; + } + + if (scope instanceof PsiMethod){ + final PsiMethod method = (PsiMethod)scope; + if(method.isConstructor() || PsiType.VOID.equals(method.getReturnType())) { + return TailType.SEMICOLON; + } + + return TailType.SPACE; + } + scope = scope.getParent(); + } + } + } + return TailType.NONE; + } + + protected void addLookupItem(Set set, final TailType tailType, @NotNull Object completion, final PsiFile file, final CompletionVariant variant) { + if (completion instanceof LookupElement && !(completion instanceof LookupItem)) { + set.add((LookupElement)completion); + return; + } + + LookupElement _ret = LookupItemUtil.objectToLookupItem(completion); + if(_ret == null || !(_ret instanceof LookupItem)) return; + + LookupItem ret = (LookupItem)_ret; + final InsertHandler insertHandler = variant.getInsertHandler(); + if(insertHandler != null && ret.getInsertHandler() == null) { + } + ret.setInsertHandler(new InsertHandler() { + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + TailType type = analyzeItem(item.getObject(), context.getFile().findElementAt(context.getStartOffset())); + if (type == TailType.NONE) { + type = tailType; + } + //new DefaultInsertHandler().handleInsert(context, item); + if (type != TailType.NONE) { + context.setAddCompletionChar(false); + type.processTail(context.getEditor(), context.getTailOffset()); + } + } + }); + + final Map itemProperties = variant.getItemProperties(); + for (final Object key : itemProperties.keySet()) { + ret.setAttribute(key, itemProperties.get(key)); + } + + set.add(ret); + } + + protected void addKeywords(final Set set, final PsiElement position, final PrefixMatcher matcher, final PsiFile file, + final CompletionVariant variant, final Object comp, final TailType tailType) { + final PsiElementFactory factory = JavaPsiFacade.getInstance(file.getProject()).getElementFactory(); + if (comp instanceof String) { + addKeyword(factory, set, tailType, comp, matcher, file, variant); + } + else { + final CompletionContext context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY); + if (comp instanceof ContextGetter) { + final Object[] elements = ((ContextGetter)comp).get(position, context); + for (Object element : elements) { + addLookupItem(set, tailType, element, file, variant); + } + } + // TODO: KeywordChooser -> ContextGetter + else if (comp instanceof KeywordChooser) { + final String[] keywords = ((KeywordChooser)comp).getKeywords(context, position); + for (String keyword : keywords) { + addKeyword(factory, set, tailType, keyword, matcher, file, variant); + } + } + } + } + + private void addKeyword(PsiElementFactory factory, Set set, final TailType tailType, final Object comp, final PrefixMatcher matcher, + final PsiFile file, + final CompletionVariant variant) { + for (final LookupElement item : set) { + if (item.getObject().toString().equals(comp.toString())) { + return; + } + } + if(factory == null){ + addLookupItem(set, tailType, comp, file, variant); + } + else{ + try{ + final PsiKeyword keyword = factory.createKeyword((String)comp); + addLookupItem(set, tailType, keyword, file, variant); + } + catch(IncorrectOperationException e){ + addLookupItem(set, tailType, comp, file, variant); + } + } + } + + public void fillCompletions(CompletionParameters parameters, CompletionResultSet result) { + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index 1686d950d5a7..1750e8e5ae58 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -1,556 +1,556 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix; -import com.intellij.codeInsight.hint.ShowParameterInfoHandler; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItem; -import com.intellij.codeInsight.lookup.LookupItemUtil; -import com.intellij.codeInsight.lookup.TailTypeDecorator; -import com.intellij.lang.ASTNode; -import com.intellij.lang.LangBundle; -import com.intellij.lang.StdLanguages; -import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.highlighter.HighlighterIterator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.patterns.ElementPattern; -import com.intellij.patterns.PatternCondition; -import com.intellij.patterns.PsiNameValuePairPattern; -import com.intellij.psi.*; -import com.intellij.psi.filters.*; -import com.intellij.psi.filters.classes.AssignableFromContextFilter; -import com.intellij.psi.filters.classes.ThisOrAnyInnerFilter; -import com.intellij.psi.filters.element.ExcludeDeclaredFilter; -import com.intellij.psi.filters.element.ModifierFilter; -import com.intellij.psi.filters.getters.ExpectedTypesGetter; -import com.intellij.psi.filters.types.AssignableFromFilter; -import com.intellij.psi.impl.source.tree.ElementType; -import com.intellij.psi.scope.ElementClassFilter; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.util.Consumer; -import com.intellij.util.PairConsumer; -import com.intellij.util.ProcessingContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -import static com.intellij.patterns.PsiJavaPatterns.*; - -/** - * @author peter - */ -public class JavaCompletionContributor extends CompletionContributor { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaCompletionContributor"); - private static final Java15CompletionData ourJava15CompletionData = new Java15CompletionData(); - private static final JavaCompletionData ourJavaCompletionData = new JavaCompletionData(); - private static final PsiNameValuePairPattern NAME_VALUE_PAIR = psiNameValuePair().withSuperParent( - 2, - psiElement(PsiAnnotation.class)); - private static final ElementPattern ANNOTATION_ATTRIBUTE_NAME = - or(psiElement(PsiIdentifier.class).withParent(NAME_VALUE_PAIR), - psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR))); - public static final ElementPattern SWITCH_LABEL = - psiElement().withSuperParent(2, psiElement(PsiSwitchLabelStatement.class).withSuperParent(2, - psiElement(PsiSwitchStatement.class).with(new PatternCondition("enumExpressionType") { - @Override - public boolean accepts(@NotNull PsiSwitchStatement psiSwitchStatement, ProcessingContext context) { - final PsiExpression expression = psiSwitchStatement.getExpression(); - if(expression == null) return false; - final PsiType type = expression.getType(); - return type instanceof PsiClassType; - } - }))); - - @Nullable - private static ElementFilter getReferenceFilter(PsiElement position) { - // Completion after extends in interface, type parameter and implements in class - final PsiClass containingClass = PsiTreeUtil.getParentOfType(position, PsiClass.class, false, PsiCodeBlock.class, PsiMethod.class, PsiExpressionList.class, PsiVariable.class); - if (containingClass != null && psiElement().afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS, ",", "&").accepts(position)) { - return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter())); - } - - if (JavaCompletionData.DECLARATION_START.isAcceptable(position, position) || - JavaCompletionData.INSIDE_PARAMETER_LIST.accepts(position)) { - return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE_FILTER); - } - - // Completion for classes in method throws section - if (psiElement().afterLeaf(PsiKeyword.THROWS, ",").inside(psiElement(PsiReferenceList.class).withParent(PsiMethod.class)).accepts(position)) { - return new OrFilter(new ThisOrAnyInnerFilter(new AssignableFromFilter("java.lang.Throwable")), ElementClassFilter.PACKAGE_FILTER); - } - - if (psiElement().afterLeaf(PsiKeyword.INSTANCEOF).accepts(position)) { - return new ElementExtractorFilter(ElementClassFilter.CLASS); - } - - if (JavaCompletionData.AFTER_FINAL.accepts(position)) { - return ElementClassFilter.CLASS; - } - - if (JavaCompletionData.AFTER_TRY_BLOCK.isAcceptable(position, position) || - JavaCompletionData.START_SWITCH.isAcceptable(position, position) || - JavaCompletionData.INSTANCEOF_PLACE.isAcceptable(position, position)) { - return null; - } - - if (JavaCompletionData.START_FOR.accepts(position)) { - return ElementClassFilter.VARIABLE; - } - - if (psiElement().afterLeaf(psiElement().withText("(").withParent(PsiTryStatement.class)).accepts(position)) { - return new OrFilter(new ThisOrAnyInnerFilter(new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE)), ElementClassFilter.PACKAGE_FILTER); - } - - if (JavaSmartCompletionContributor.AFTER_THROW_NEW.accepts(position)) { - return new ThisOrAnyInnerFilter(new AssignableFromFilter("java.lang.Throwable")); - } - - if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) { - return ElementClassFilter.CLASS; - } - - if (psiElement().inside(PsiReferenceParameterList.class).accepts(position)) { - return ElementClassFilter.CLASS; - } - - if (psiElement().inside(PsiAnnotationParameterList.class).accepts(position)) { - return new OrFilter(new ClassFilter(PsiAnnotationMethod.class), - ElementClassFilter.CLASS, - ElementClassFilter.PACKAGE_FILTER, - new AndFilter(new ClassFilter(PsiField.class), new ModifierFilter(PsiModifier.STATIC, PsiModifier.FINAL))); - } - - if (psiElement().afterLeaf("=").inside(PsiVariable.class).accepts(position)) { - return new OrFilter( - new ClassFilter(PsiVariable.class, false), - new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class))); - } - - if (SWITCH_LABEL.accepts(position)) { - return new ClassFilter(PsiField.class) { - @Override - public boolean isAcceptable(Object element, PsiElement context) { - return element instanceof PsiEnumConstant; - } - }; - } - - return TrueFilter.INSTANCE; - } - - public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet _result) { - if (parameters.getCompletionType() != CompletionType.BASIC) { - return; - } - - final PsiElement position = parameters.getPosition(); - if (!position.getContainingFile().getLanguage().isKindOf(StdLanguages.JAVA)) { - return; - } - - final PsiFile file = parameters.getOriginalFile(); - final int offset = parameters.getOffset(); - final PsiElement lastElement = file.findElementAt(offset - 1); - if (lastElement == null) { - return; - } - - final ASTNode node = lastElement.getNode(); - assert node != null; - if (node.getElementType() == JavaTokenType.DOUBLE_LITERAL) { - return; - } - - if (ANNOTATION_ATTRIBUTE_NAME.accepts(position)) { - completeAnnotationAttributeName(_result, position, parameters); - _result.stopHere(); - return; - } - - final JavaAwareCompletionData completionData = PsiUtil.isLanguageLevel5OrHigher(lastElement) ? ourJava15CompletionData : ourJavaCompletionData; - final boolean checkAccess = parameters.getInvocationCount() <= 1; - - LegacyCompletionContributor.processReferences(parameters, _result, completionData, new PairConsumer() { - public void consume(final PsiReference reference, final CompletionResultSet result) { - if (reference instanceof PsiJavaReference) { - final ElementFilter filter = getReferenceFilter(position); - if (filter != null) { - final boolean isSwitchLabel = SWITCH_LABEL.accepts(position); - for (LookupElement element : JavaCompletionUtil.processJavaReference(position, - (PsiJavaReference) reference, - new ElementExtractorFilter(filter), - checkAccess, - result.getPrefixMatcher(), parameters)) { - if (isSwitchLabel) { - result.addElement(TailTypeDecorator.withTail(element, TailType.createSimpleTailType(':'))); - } else { - final LookupItem item = element.as(LookupItem.class); - if (file instanceof PsiJavaCodeReferenceCodeFragment && - !((PsiJavaCodeReferenceCodeFragment)file).isClassesAccepted() && item != null) { - item.setTailType(TailType.NONE); - } - - result.addElement(element); - } - } - } - return; - } - - - final Object[] variants = reference.getVariants(); - if (variants == null) { - LOG.error("Reference=" + reference); - } - for (Object completion : variants) { - if (completion == null) { - LOG.error("Position=" + position + "\n;Reference=" + reference + "\n;variants=" + Arrays.toString(variants)); - } - if (completion instanceof LookupElement) { - result.addElement((LookupElement)completion); - } - else if (completion instanceof PsiClass) { - result.addElement(JavaClassNameCompletionContributor.createClassLookupItem((PsiClass)completion, true)); - } - else { - result.addElement(LookupItemUtil.objectToLookupItem(completion)); - } - } - } - }); - - final Set lookupSet = new LinkedHashSet(); - final Set keywordVariants = new HashSet(); - completionData.addKeywordVariants(keywordVariants, position, parameters.getOriginalFile()); - final CompletionResultSet result = _result.withPrefixMatcher(completionData.findPrefix(position, offset)); - completionData.completeKeywordsBySet(lookupSet, keywordVariants, position, result.getPrefixMatcher(), parameters.getOriginalFile()); - completionData.fillCompletions(parameters, result); - - for (final LookupElement item : lookupSet) { - result.addElement(item); - } - - if (shouldRunClassNameCompletion(result, position)) { - result.runRemainingContributors(new CompletionParameters(position, file, CompletionType.CLASS_NAME, offset, parameters.getInvocationCount()), new Consumer() { - @Override - public void consume(LookupElement lookupElement) { - result.addElement(lookupElement); - } - }); - } - result.stopHere(); - } - - private static boolean shouldRunClassNameCompletion(CompletionResultSet result, PsiElement position) { - final PsiElement parent = position.getParent(); - if (!(parent instanceof PsiJavaCodeReferenceElement)) return false; - if (((PsiJavaCodeReferenceElement)parent).getQualifier() != null) return false; - - PsiElement grand = parent.getParent(); - if (grand instanceof PsiSwitchLabelStatement) { - return false; - } - - if (grand instanceof PsiAnonymousClass) { - grand = grand.getParent(); - } - if (grand instanceof PsiNewExpression && ((PsiNewExpression)grand).getQualifier() != null) { - return false; - } - - final String s = result.getPrefixMatcher().getPrefix(); - if (StringUtil.isEmpty(s) || !Character.isUpperCase(s.charAt(0))) return false; - return true; - } - - - private static void completeAnnotationAttributeName(CompletionResultSet result, PsiElement insertedElement, - CompletionParameters parameters) { - PsiNameValuePair pair = PsiTreeUtil.getParentOfType(insertedElement, PsiNameValuePair.class); - PsiAnnotationParameterList parameterList = (PsiAnnotationParameterList)pair.getParent(); - PsiAnnotation anno = (PsiAnnotation)parameterList.getParent(); - boolean showClasses = psiElement().afterLeaf("(").accepts(insertedElement); - PsiClass annoClass = null; - final PsiJavaCodeReferenceElement referenceElement = anno.getNameReferenceElement(); - if (referenceElement != null) { - final PsiElement element = referenceElement.resolve(); - if (element instanceof PsiClass) { - annoClass = (PsiClass)element; - if (annoClass.findMethodsByName("value", false).length == 0) { - showClasses = false; - } - } - } - - if (showClasses && insertedElement.getParent() instanceof PsiReferenceExpression) { - final Set set = JavaCompletionUtil.processJavaReference(insertedElement, (PsiJavaReference)insertedElement.getParent(), TrueFilter.INSTANCE, true, result.getPrefixMatcher(), parameters); - - for (final LookupElement element : set) { - result.addElement(element); - } - - } - - if (annoClass != null) { - final PsiNameValuePair[] existingPairs = parameterList.getAttributes(); - - methods: for (PsiMethod method : annoClass.getMethods()) { - final String attrName = method.getName(); - for (PsiNameValuePair apair : existingPairs) { - if (Comparing.equal(apair.getName(), attrName)) continue methods; - } - result.addElement(new LookupItem(method, attrName).setInsertHandler(new InsertHandler() { - public void handleInsert(InsertionContext context, LookupElement item) { - final Editor editor = context.getEditor(); - TailType.EQ.processTail(editor, editor.getCaretModel().getOffset()); - context.setAddCompletionChar(false); - } - })); - } - } - } - - - public String advertise(@NotNull final CompletionParameters parameters) { - if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null; - - if (parameters.getCompletionType() != CompletionType.SMART && shouldSuggestSmartCompletion(parameters.getPosition())) { - if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_SMARTTYPE_GENERAL)) { - final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); - if (shortcut != null) { - return CompletionBundle.message("completion.smart.hint", shortcut); - } - } - } - - if (parameters.getCompletionType() != CompletionType.CLASS_NAME && shouldSuggestClassNameCompletion(parameters.getPosition())) { - if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_CLASSNAME)) { - final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION); - if (shortcut != null) { - return CompletionBundle.message("completion.class.name.hint", shortcut); - } - } - } - - if (parameters.getCompletionType() == CompletionType.SMART && parameters.getInvocationCount() == 1) { - final PsiType[] psiTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true); - if (psiTypes.length > 0) { - if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR)) { - final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); - if (shortcut != null) { - for (final PsiType psiType : psiTypes) { - final PsiType type = PsiUtil.extractIterableTypeParameter(psiType, false); - if (type != null) { - return CompletionBundle.message("completion.smart.aslist.hint", shortcut, type.getPresentableText()); - } - } - } - } - if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_ASLIST)) { - final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); - if (shortcut != null) { - for (final PsiType psiType : psiTypes) { - if (psiType instanceof PsiArrayType) { - final PsiType componentType = ((PsiArrayType)psiType).getComponentType(); - if (!(componentType instanceof PsiPrimitiveType)) { - return CompletionBundle.message("completion.smart.toar.hint", shortcut, componentType.getPresentableText()); - } - } - } - } - } - - if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_CHAIN)) { - final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); - if (shortcut != null) { - return CompletionBundle.message("completion.smart.chain.hint", shortcut); - } - } - } - } - return null; - } - - public String handleEmptyLookup(@NotNull final CompletionParameters parameters, final Editor editor) { - if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null; - - final String ad = advertise(parameters); - final String suffix = ad == null ? "" : "; " + StringUtil.decapitalize(ad); - if (parameters.getCompletionType() == CompletionType.SMART) { - if (!ApplicationManager.getApplication().isUnitTestMode()) { - - final Project project = parameters.getPosition().getProject(); - final PsiFile file = parameters.getOriginalFile(); - - PsiExpression expression = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true); - if (expression != null && expression.getParent() instanceof PsiExpressionList) { - int lbraceOffset = expression.getParent().getTextRange().getStartOffset(); - new ShowParameterInfoHandler().invoke(project, editor, file, lbraceOffset, null); - } - - if (expression instanceof PsiLiteralExpression) { - return LangBundle.message("completion.no.suggestions") + suffix; - } - - if (expression instanceof PsiInstanceOfExpression) { - final PsiInstanceOfExpression instanceOfExpression = (PsiInstanceOfExpression)expression; - if (PsiTreeUtil.isAncestor(instanceOfExpression.getCheckType(), parameters.getPosition(), false)) { - return LangBundle.message("completion.no.suggestions") + suffix; - } - } - } - - final Set expectedTypes = JavaCompletionUtil.getExpectedTypes(parameters); - if (expectedTypes != null) { - PsiType type = expectedTypes.size() == 1 ? expectedTypes.iterator().next() : null; - if (type != null) { - final PsiType deepComponentType = type.getDeepComponentType(); - if (deepComponentType instanceof PsiClassType) { - if (((PsiClassType)deepComponentType).resolve() != null) { - return CompletionBundle.message("completion.no.suggestions.of.type", type.getPresentableText()) + suffix; - } - return CompletionBundle.message("completion.unknown.type", type.getPresentableText()) + suffix; - } - if (!PsiType.NULL.equals(type)) { - return CompletionBundle.message("completion.no.suggestions.of.type", type.getPresentableText()) + suffix; - } - } - } - } - return LangBundle.message("completion.no.suggestions") + suffix; - } - - private static boolean shouldSuggestSmartCompletion(final PsiElement element) { - if (shouldSuggestClassNameCompletion(element)) return false; - - final PsiElement parent = element.getParent(); - if (parent instanceof PsiReferenceExpression && ((PsiReferenceExpression)parent).getQualifier() != null) return false; - if (parent instanceof PsiReferenceExpression && parent.getParent() instanceof PsiReferenceExpression) return true; - - return new ExpectedTypesGetter().get(element, null).length > 0; - } - - private static boolean shouldSuggestClassNameCompletion(final PsiElement element) { - if (element == null) return false; - final PsiElement parent = element.getParent(); - if (parent == null) return false; - return parent.getParent() instanceof PsiTypeElement || parent.getParent() instanceof PsiExpressionStatement || parent.getParent() instanceof PsiReferenceList; - } - - public void beforeCompletion(@NotNull final CompletionInitializationContext context) { - final PsiFile file = context.getFile(); - - if (file instanceof PsiJavaFile) { - JavaCompletionUtil.initOffsets(file, context.getProject(), context.getOffsetMap()); - - autoImport(file, context.getStartOffset() - 1, context.getEditor()); - - if (context.getCompletionType() == CompletionType.BASIC) { - if (semicolonNeeded(context)) { - context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER.trim() + ";"); - return; - } - - final PsiElement element = file.findElementAt(context.getStartOffset()); - - if (psiElement().inside(PsiAnnotation.class).accepts(element)) { - return; - } - - context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED); - } - } - } - - private static boolean semicolonNeeded(CompletionInitializationContext context) { - HighlighterIterator iterator = ((EditorEx) context.getEditor()).getHighlighter().createIterator(context.getStartOffset()); - if (iterator.atEnd()) return false; - - if (iterator.getTokenType() == JavaTokenType.IDENTIFIER) { - iterator.advance(); - } - - if (!iterator.atEnd() && iterator.getTokenType() == JavaTokenType.LPARENTH) { - return true; - } - - while (!iterator.atEnd() && ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(iterator.getTokenType())) { - iterator.advance(); - } - - if (iterator.atEnd() || iterator.getTokenType() != JavaTokenType.IDENTIFIER) return false; - iterator.advance(); - - while (!iterator.atEnd() && ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(iterator.getTokenType())) { - iterator.advance(); - } - if (iterator.atEnd()) return false; - - return iterator.getTokenType() == JavaTokenType.EQ || iterator.getTokenType() == JavaTokenType.LPARENTH; - } - - private static void autoImport(final PsiFile file, int offset, final Editor editor) { - final CharSequence text = editor.getDocument().getCharsSequence(); - while (offset > 0 && Character.isJavaIdentifierPart(text.charAt(offset))) offset--; - if (offset <= 0) return; - - while (offset > 0 && Character.isWhitespace(text.charAt(offset))) offset--; - if (offset <= 0 || text.charAt(offset) != '.') return; - - offset--; - - while (offset > 0 && Character.isWhitespace(text.charAt(offset))) offset--; - if (offset <= 0) return; - - PsiJavaCodeReferenceElement element = extractReference(PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiExpression.class, false)); - if (element == null) return; - - while (true) { - final PsiJavaCodeReferenceElement qualifier = extractReference(element.getQualifier()); - if (qualifier == null) break; - - element = qualifier; - } - if (!(element.getParent() instanceof PsiMethodCallExpression) && element.multiResolve(true).length == 0) { - new ImportClassFix(element).doFix(editor, false, false); - } - } - - @Nullable - private static PsiJavaCodeReferenceElement extractReference(@Nullable PsiElement expression) { - if (expression instanceof PsiJavaCodeReferenceElement) { - return (PsiJavaCodeReferenceElement)expression; - } - if (expression instanceof PsiMethodCallExpression) { - return ((PsiMethodCallExpression)expression).getMethodExpression(); - } - return null; - } - -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix; +import com.intellij.codeInsight.hint.ShowParameterInfoHandler; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupItem; +import com.intellij.codeInsight.lookup.LookupItemUtil; +import com.intellij.codeInsight.lookup.TailTypeDecorator; +import com.intellij.lang.ASTNode; +import com.intellij.lang.LangBundle; +import com.intellij.lang.StdLanguages; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.patterns.ElementPattern; +import com.intellij.patterns.PatternCondition; +import com.intellij.patterns.PsiNameValuePairPattern; +import com.intellij.psi.*; +import com.intellij.psi.filters.*; +import com.intellij.psi.filters.classes.AssignableFromContextFilter; +import com.intellij.psi.filters.classes.ThisOrAnyInnerFilter; +import com.intellij.psi.filters.element.ExcludeDeclaredFilter; +import com.intellij.psi.filters.element.ModifierFilter; +import com.intellij.psi.filters.getters.ExpectedTypesGetter; +import com.intellij.psi.filters.types.AssignableFromFilter; +import com.intellij.psi.impl.source.tree.ElementType; +import com.intellij.psi.scope.ElementClassFilter; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.Consumer; +import com.intellij.util.PairConsumer; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import static com.intellij.patterns.PsiJavaPatterns.*; + +/** + * @author peter + */ +public class JavaCompletionContributor extends CompletionContributor { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaCompletionContributor"); + private static final Java15CompletionData ourJava15CompletionData = new Java15CompletionData(); + private static final JavaCompletionData ourJavaCompletionData = new JavaCompletionData(); + private static final PsiNameValuePairPattern NAME_VALUE_PAIR = psiNameValuePair().withSuperParent( + 2, + psiElement(PsiAnnotation.class)); + private static final ElementPattern ANNOTATION_ATTRIBUTE_NAME = + or(psiElement(PsiIdentifier.class).withParent(NAME_VALUE_PAIR), + psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR))); + public static final ElementPattern SWITCH_LABEL = + psiElement().withSuperParent(2, psiElement(PsiSwitchLabelStatement.class).withSuperParent(2, + psiElement(PsiSwitchStatement.class).with(new PatternCondition("enumExpressionType") { + @Override + public boolean accepts(@NotNull PsiSwitchStatement psiSwitchStatement, ProcessingContext context) { + final PsiExpression expression = psiSwitchStatement.getExpression(); + if(expression == null) return false; + final PsiType type = expression.getType(); + return type instanceof PsiClassType; + } + }))); + + @Nullable + private static ElementFilter getReferenceFilter(PsiElement position) { + // Completion after extends in interface, type parameter and implements in class + final PsiClass containingClass = PsiTreeUtil.getParentOfType(position, PsiClass.class, false, PsiCodeBlock.class, PsiMethod.class, PsiExpressionList.class, PsiVariable.class); + if (containingClass != null && psiElement().afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS, ",", "&").accepts(position)) { + return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter())); + } + + if (JavaCompletionData.DECLARATION_START.isAcceptable(position, position) || + JavaCompletionData.INSIDE_PARAMETER_LIST.accepts(position)) { + return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE_FILTER); + } + + // Completion for classes in method throws section + if (psiElement().afterLeaf(PsiKeyword.THROWS, ",").inside(psiElement(PsiReferenceList.class).withParent(PsiMethod.class)).accepts(position)) { + return new OrFilter(new ThisOrAnyInnerFilter(new AssignableFromFilter("java.lang.Throwable")), ElementClassFilter.PACKAGE_FILTER); + } + + if (psiElement().afterLeaf(PsiKeyword.INSTANCEOF).accepts(position)) { + return new ElementExtractorFilter(ElementClassFilter.CLASS); + } + + if (JavaCompletionData.AFTER_FINAL.accepts(position)) { + return ElementClassFilter.CLASS; + } + + if (JavaCompletionData.AFTER_TRY_BLOCK.isAcceptable(position, position) || + JavaCompletionData.START_SWITCH.isAcceptable(position, position) || + JavaCompletionData.INSTANCEOF_PLACE.isAcceptable(position, position)) { + return null; + } + + if (JavaCompletionData.START_FOR.accepts(position)) { + return ElementClassFilter.VARIABLE; + } + + if (psiElement().afterLeaf(psiElement().withText("(").withParent(PsiTryStatement.class)).accepts(position)) { + return new OrFilter(new ThisOrAnyInnerFilter(new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE)), ElementClassFilter.PACKAGE_FILTER); + } + + if (JavaSmartCompletionContributor.AFTER_THROW_NEW.accepts(position)) { + return new ThisOrAnyInnerFilter(new AssignableFromFilter("java.lang.Throwable")); + } + + if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) { + return ElementClassFilter.CLASS; + } + + if (psiElement().inside(PsiReferenceParameterList.class).accepts(position)) { + return ElementClassFilter.CLASS; + } + + if (psiElement().inside(PsiAnnotationParameterList.class).accepts(position)) { + return new OrFilter(new ClassFilter(PsiAnnotationMethod.class), + ElementClassFilter.CLASS, + ElementClassFilter.PACKAGE_FILTER, + new AndFilter(new ClassFilter(PsiField.class), new ModifierFilter(PsiModifier.STATIC, PsiModifier.FINAL))); + } + + if (psiElement().afterLeaf("=").inside(PsiVariable.class).accepts(position)) { + return new OrFilter( + new ClassFilter(PsiVariable.class, false), + new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class))); + } + + if (SWITCH_LABEL.accepts(position)) { + return new ClassFilter(PsiField.class) { + @Override + public boolean isAcceptable(Object element, PsiElement context) { + return element instanceof PsiEnumConstant; + } + }; + } + + return TrueFilter.INSTANCE; + } + + public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet _result) { + if (parameters.getCompletionType() != CompletionType.BASIC) { + return; + } + + final PsiElement position = parameters.getPosition(); + if (!position.getContainingFile().getLanguage().isKindOf(StdLanguages.JAVA)) { + return; + } + + final PsiFile file = parameters.getOriginalFile(); + final int offset = parameters.getOffset(); + final PsiElement lastElement = file.findElementAt(offset - 1); + if (lastElement == null) { + return; + } + + final ASTNode node = lastElement.getNode(); + assert node != null; + if (node.getElementType() == JavaTokenType.DOUBLE_LITERAL) { + return; + } + + if (ANNOTATION_ATTRIBUTE_NAME.accepts(position)) { + completeAnnotationAttributeName(_result, position, parameters); + _result.stopHere(); + return; + } + + final JavaAwareCompletionData completionData = PsiUtil.isLanguageLevel5OrHigher(lastElement) ? ourJava15CompletionData : ourJavaCompletionData; + final boolean checkAccess = parameters.getInvocationCount() <= 1; + + LegacyCompletionContributor.processReferences(parameters, _result, completionData, new PairConsumer() { + public void consume(final PsiReference reference, final CompletionResultSet result) { + if (reference instanceof PsiJavaReference) { + final ElementFilter filter = getReferenceFilter(position); + if (filter != null) { + final boolean isSwitchLabel = SWITCH_LABEL.accepts(position); + for (LookupElement element : JavaCompletionUtil.processJavaReference(position, + (PsiJavaReference) reference, + new ElementExtractorFilter(filter), + checkAccess, + result.getPrefixMatcher(), parameters)) { + if (isSwitchLabel) { + result.addElement(TailTypeDecorator.withTail(element, TailType.createSimpleTailType(':'))); + } else { + final LookupItem item = element.as(LookupItem.class); + if (file instanceof PsiJavaCodeReferenceCodeFragment && + !((PsiJavaCodeReferenceCodeFragment)file).isClassesAccepted() && item != null) { + item.setTailType(TailType.NONE); + } + + result.addElement(element); + } + } + } + return; + } + + + final Object[] variants = reference.getVariants(); + if (variants == null) { + LOG.error("Reference=" + reference); + } + for (Object completion : variants) { + if (completion == null) { + LOG.error("Position=" + position + "\n;Reference=" + reference + "\n;variants=" + Arrays.toString(variants)); + } + if (completion instanceof LookupElement) { + result.addElement((LookupElement)completion); + } + else if (completion instanceof PsiClass) { + result.addElement(JavaClassNameCompletionContributor.createClassLookupItem((PsiClass)completion, true)); + } + else { + result.addElement(LookupItemUtil.objectToLookupItem(completion)); + } + } + } + }); + + final Set lookupSet = new LinkedHashSet(); + final Set keywordVariants = new HashSet(); + completionData.addKeywordVariants(keywordVariants, position, parameters.getOriginalFile()); + final CompletionResultSet result = _result.withPrefixMatcher(completionData.findPrefix(position, offset)); + completionData.completeKeywordsBySet(lookupSet, keywordVariants, position, result.getPrefixMatcher(), parameters.getOriginalFile()); + completionData.fillCompletions(parameters, result); + + for (final LookupElement item : lookupSet) { + result.addElement(item); + } + + if (shouldRunClassNameCompletion(result, position)) { + result.runRemainingContributors(new CompletionParameters(position, file, CompletionType.CLASS_NAME, offset, parameters.getInvocationCount()), new Consumer() { + @Override + public void consume(LookupElement lookupElement) { + result.addElement(lookupElement); + } + }); + } + result.stopHere(); + } + + private static boolean shouldRunClassNameCompletion(CompletionResultSet result, PsiElement position) { + final PsiElement parent = position.getParent(); + if (!(parent instanceof PsiJavaCodeReferenceElement)) return false; + if (((PsiJavaCodeReferenceElement)parent).getQualifier() != null) return false; + + PsiElement grand = parent.getParent(); + if (grand instanceof PsiSwitchLabelStatement) { + return false; + } + + if (grand instanceof PsiAnonymousClass) { + grand = grand.getParent(); + } + if (grand instanceof PsiNewExpression && ((PsiNewExpression)grand).getQualifier() != null) { + return false; + } + + final String s = result.getPrefixMatcher().getPrefix(); + if (StringUtil.isEmpty(s) || !Character.isUpperCase(s.charAt(0))) return false; + return true; + } + + + private static void completeAnnotationAttributeName(CompletionResultSet result, PsiElement insertedElement, + CompletionParameters parameters) { + PsiNameValuePair pair = PsiTreeUtil.getParentOfType(insertedElement, PsiNameValuePair.class); + PsiAnnotationParameterList parameterList = (PsiAnnotationParameterList)pair.getParent(); + PsiAnnotation anno = (PsiAnnotation)parameterList.getParent(); + boolean showClasses = psiElement().afterLeaf("(").accepts(insertedElement); + PsiClass annoClass = null; + final PsiJavaCodeReferenceElement referenceElement = anno.getNameReferenceElement(); + if (referenceElement != null) { + final PsiElement element = referenceElement.resolve(); + if (element instanceof PsiClass) { + annoClass = (PsiClass)element; + if (annoClass.findMethodsByName("value", false).length == 0) { + showClasses = false; + } + } + } + + if (showClasses && insertedElement.getParent() instanceof PsiReferenceExpression) { + final Set set = JavaCompletionUtil.processJavaReference(insertedElement, (PsiJavaReference)insertedElement.getParent(), TrueFilter.INSTANCE, true, result.getPrefixMatcher(), parameters); + + for (final LookupElement element : set) { + result.addElement(element); + } + + } + + if (annoClass != null) { + final PsiNameValuePair[] existingPairs = parameterList.getAttributes(); + + methods: for (PsiMethod method : annoClass.getMethods()) { + final String attrName = method.getName(); + for (PsiNameValuePair apair : existingPairs) { + if (Comparing.equal(apair.getName(), attrName)) continue methods; + } + result.addElement(new LookupItem(method, attrName).setInsertHandler(new InsertHandler() { + public void handleInsert(InsertionContext context, LookupElement item) { + final Editor editor = context.getEditor(); + TailType.EQ.processTail(editor, editor.getCaretModel().getOffset()); + context.setAddCompletionChar(false); + } + })); + } + } + } + + + public String advertise(@NotNull final CompletionParameters parameters) { + if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null; + + if (parameters.getCompletionType() != CompletionType.SMART && shouldSuggestSmartCompletion(parameters.getPosition())) { + if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_SMARTTYPE_GENERAL)) { + final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); + if (shortcut != null) { + return CompletionBundle.message("completion.smart.hint", shortcut); + } + } + } + + if (parameters.getCompletionType() != CompletionType.CLASS_NAME && shouldSuggestClassNameCompletion(parameters.getPosition())) { + if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_CLASSNAME)) { + final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION); + if (shortcut != null) { + return CompletionBundle.message("completion.class.name.hint", shortcut); + } + } + } + + if (parameters.getCompletionType() == CompletionType.SMART && parameters.getInvocationCount() == 1) { + final PsiType[] psiTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true); + if (psiTypes.length > 0) { + if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR)) { + final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); + if (shortcut != null) { + for (final PsiType psiType : psiTypes) { + final PsiType type = PsiUtil.extractIterableTypeParameter(psiType, false); + if (type != null) { + return CompletionBundle.message("completion.smart.aslist.hint", shortcut, type.getPresentableText()); + } + } + } + } + if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_ASLIST)) { + final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); + if (shortcut != null) { + for (final PsiType psiType : psiTypes) { + if (psiType instanceof PsiArrayType) { + final PsiType componentType = ((PsiArrayType)psiType).getComponentType(); + if (!(componentType instanceof PsiPrimitiveType)) { + return CompletionBundle.message("completion.smart.toar.hint", shortcut, componentType.getPresentableText()); + } + } + } + } + } + + if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_CHAIN)) { + final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION); + if (shortcut != null) { + return CompletionBundle.message("completion.smart.chain.hint", shortcut); + } + } + } + } + return null; + } + + public String handleEmptyLookup(@NotNull final CompletionParameters parameters, final Editor editor) { + if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null; + + final String ad = advertise(parameters); + final String suffix = ad == null ? "" : "; " + StringUtil.decapitalize(ad); + if (parameters.getCompletionType() == CompletionType.SMART) { + if (!ApplicationManager.getApplication().isUnitTestMode()) { + + final Project project = parameters.getPosition().getProject(); + final PsiFile file = parameters.getOriginalFile(); + + PsiExpression expression = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true); + if (expression != null && expression.getParent() instanceof PsiExpressionList) { + int lbraceOffset = expression.getParent().getTextRange().getStartOffset(); + new ShowParameterInfoHandler().invoke(project, editor, file, lbraceOffset, null); + } + + if (expression instanceof PsiLiteralExpression) { + return LangBundle.message("completion.no.suggestions") + suffix; + } + + if (expression instanceof PsiInstanceOfExpression) { + final PsiInstanceOfExpression instanceOfExpression = (PsiInstanceOfExpression)expression; + if (PsiTreeUtil.isAncestor(instanceOfExpression.getCheckType(), parameters.getPosition(), false)) { + return LangBundle.message("completion.no.suggestions") + suffix; + } + } + } + + final Set expectedTypes = JavaCompletionUtil.getExpectedTypes(parameters); + if (expectedTypes != null) { + PsiType type = expectedTypes.size() == 1 ? expectedTypes.iterator().next() : null; + if (type != null) { + final PsiType deepComponentType = type.getDeepComponentType(); + if (deepComponentType instanceof PsiClassType) { + if (((PsiClassType)deepComponentType).resolve() != null) { + return CompletionBundle.message("completion.no.suggestions.of.type", type.getPresentableText()) + suffix; + } + return CompletionBundle.message("completion.unknown.type", type.getPresentableText()) + suffix; + } + if (!PsiType.NULL.equals(type)) { + return CompletionBundle.message("completion.no.suggestions.of.type", type.getPresentableText()) + suffix; + } + } + } + } + return LangBundle.message("completion.no.suggestions") + suffix; + } + + private static boolean shouldSuggestSmartCompletion(final PsiElement element) { + if (shouldSuggestClassNameCompletion(element)) return false; + + final PsiElement parent = element.getParent(); + if (parent instanceof PsiReferenceExpression && ((PsiReferenceExpression)parent).getQualifier() != null) return false; + if (parent instanceof PsiReferenceExpression && parent.getParent() instanceof PsiReferenceExpression) return true; + + return new ExpectedTypesGetter().get(element, null).length > 0; + } + + private static boolean shouldSuggestClassNameCompletion(final PsiElement element) { + if (element == null) return false; + final PsiElement parent = element.getParent(); + if (parent == null) return false; + return parent.getParent() instanceof PsiTypeElement || parent.getParent() instanceof PsiExpressionStatement || parent.getParent() instanceof PsiReferenceList; + } + + public void beforeCompletion(@NotNull final CompletionInitializationContext context) { + final PsiFile file = context.getFile(); + + if (file instanceof PsiJavaFile) { + JavaCompletionUtil.initOffsets(file, context.getProject(), context.getOffsetMap()); + + autoImport(file, context.getStartOffset() - 1, context.getEditor()); + + if (context.getCompletionType() == CompletionType.BASIC) { + if (semicolonNeeded(context)) { + context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER.trim() + ";"); + return; + } + + final PsiElement element = file.findElementAt(context.getStartOffset()); + + if (psiElement().inside(PsiAnnotation.class).accepts(element)) { + return; + } + + context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED); + } + } + } + + private static boolean semicolonNeeded(CompletionInitializationContext context) { + HighlighterIterator iterator = ((EditorEx) context.getEditor()).getHighlighter().createIterator(context.getStartOffset()); + if (iterator.atEnd()) return false; + + if (iterator.getTokenType() == JavaTokenType.IDENTIFIER) { + iterator.advance(); + } + + if (!iterator.atEnd() && iterator.getTokenType() == JavaTokenType.LPARENTH) { + return true; + } + + while (!iterator.atEnd() && ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(iterator.getTokenType())) { + iterator.advance(); + } + + if (iterator.atEnd() || iterator.getTokenType() != JavaTokenType.IDENTIFIER) return false; + iterator.advance(); + + while (!iterator.atEnd() && ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(iterator.getTokenType())) { + iterator.advance(); + } + if (iterator.atEnd()) return false; + + return iterator.getTokenType() == JavaTokenType.EQ || iterator.getTokenType() == JavaTokenType.LPARENTH; + } + + private static void autoImport(final PsiFile file, int offset, final Editor editor) { + final CharSequence text = editor.getDocument().getCharsSequence(); + while (offset > 0 && Character.isJavaIdentifierPart(text.charAt(offset))) offset--; + if (offset <= 0) return; + + while (offset > 0 && Character.isWhitespace(text.charAt(offset))) offset--; + if (offset <= 0 || text.charAt(offset) != '.') return; + + offset--; + + while (offset > 0 && Character.isWhitespace(text.charAt(offset))) offset--; + if (offset <= 0) return; + + PsiJavaCodeReferenceElement element = extractReference(PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiExpression.class, false)); + if (element == null) return; + + while (true) { + final PsiJavaCodeReferenceElement qualifier = extractReference(element.getQualifier()); + if (qualifier == null) break; + + element = qualifier; + } + if (!(element.getParent() instanceof PsiMethodCallExpression) && element.multiResolve(true).length == 0) { + new ImportClassFix(element).doFix(editor, false, false); + } + } + + @Nullable + private static PsiJavaCodeReferenceElement extractReference(@Nullable PsiElement expression) { + if (expression instanceof PsiJavaCodeReferenceElement) { + return (PsiJavaCodeReferenceElement)expression; + } + if (expression instanceof PsiMethodCallExpression) { + return ((PsiMethodCallExpression)expression).getMethodExpression(); + } + return null; + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java index 29b75a15f95b..f7643dbd846b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java @@ -1,664 +1,664 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.TailTypes; -import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItem; -import com.intellij.codeInsight.lookup.TailTypeDecorator; -import com.intellij.patterns.ElementPattern; -import com.intellij.patterns.PsiElementPattern; -import com.intellij.patterns.PsiJavaElementPattern; -import com.intellij.patterns.PsiJavaPatterns; -import com.intellij.psi.*; -import com.intellij.psi.filters.*; -import com.intellij.psi.filters.classes.EnumOrAnnotationTypeFilter; -import com.intellij.psi.filters.classes.InterfaceFilter; -import com.intellij.psi.filters.element.ReferenceOnFilter; -import com.intellij.psi.filters.position.*; -import com.intellij.psi.filters.types.TypeCodeFragmentIsVoidEnabledFilter; -import com.intellij.psi.impl.source.jsp.jspJava.JspClassLevelDeclarationStatement; -import com.intellij.psi.jsp.JspElementType; -import com.intellij.psi.scope.ElementClassFilter; -import com.intellij.psi.templateLanguages.OuterLanguageElement; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ProcessingContext; -import org.jetbrains.annotations.NonNls; - -import static com.intellij.patterns.PsiJavaPatterns.*; -import static com.intellij.patterns.StandardPatterns.not; - -public class JavaCompletionData extends JavaAwareCompletionData{ - - private static final @NonNls String[] ourBlockFinalizers = {"{", "}", ";", ":", "else"}; - private static final PsiElementPattern AFTER_DOT = psiElement().afterLeaf("."); - public static final LeftNeighbour INSTANCEOF_PLACE = new LeftNeighbour(new OrFilter( - new ReferenceOnFilter(new ClassFilter(PsiVariable.class)), - new TextFilter(PsiKeyword.THIS), - new AndFilter(new TextFilter(")"), new ParentElementFilter(new AndFilter( - new ClassFilter(PsiTypeCastExpression.class, false), - new OrFilter( - new ParentElementFilter(new ClassFilter(PsiExpression.class)), - new ClassFilter(PsiExpression.class))))), - new AndFilter(new TextFilter("]"), new ParentElementFilter(new ClassFilter(PsiArrayAccessExpression.class))))); - public static final PsiJavaElementPattern.Capture AFTER_FINAL = - PsiJavaPatterns.psiElement().afterLeaf(PsiKeyword.FINAL).inside(PsiDeclarationStatement.class); - public static final LeftNeighbour AFTER_TRY_BLOCK = new LeftNeighbour(new AndFilter( - new TextFilter("}"), - new ParentElementFilter(new AndFilter( - new LeftNeighbour(new TextFilter(PsiKeyword.TRY)), - new ParentElementFilter(new ClassFilter(PsiTryStatement.class))) -))); - public static final PsiJavaElementPattern.Capture INSIDE_PARAMETER_LIST = - PsiJavaPatterns.psiElement().withParent( - psiElement(PsiJavaCodeReferenceElement.class).withParent( - psiElement(PsiTypeElement.class).withParent(or(psiElement(PsiParameter.class), psiElement(PsiParameterList.class))))); - - private static final AndFilter START_OF_CODE_FRAGMENT = new AndFilter( - new ScopeFilter(new AndFilter( - new ClassFilter(JavaCodeFragment.class), - new ClassFilter(PsiExpressionCodeFragment.class, false), - new ClassFilter(PsiJavaCodeReferenceCodeFragment.class, false), - new ClassFilter(PsiTypeCodeFragment.class, false) - )), - new StartElementFilter() - ); - - static final ElementFilter END_OF_BLOCK = new OrFilter( - new AndFilter( - new LeftNeighbour( - new OrFilter( - new AndFilter ( - new TextFilter(ourBlockFinalizers), - new NotFilter ( - new SuperParentFilter(new ClassFilter(PsiAnnotation.class)) - ) - ), - new TextFilter("*/"), - new TokenTypeFilter(JspElementType.HOLDER_TEMPLATE_DATA), - new ClassFilter(OuterLanguageElement.class), - new AndFilter( - new TextFilter(")"), - new NotFilter( - new OrFilter( - new ParentElementFilter(new ClassFilter(PsiExpressionList.class)), - new ParentElementFilter(new ClassFilter(PsiParameterList.class)), - new ParentElementFilter(new ClassFilter(PsiTypeCastExpression.class)) - ) - ) - ))), - new NotFilter(new TextFilter(".")) - ), - START_OF_CODE_FRAGMENT - ); - - static final AndFilter START_SWITCH = new AndFilter(END_OF_BLOCK, new LeftNeighbour( - new AndFilter(new TextFilter("{"), new ParentElementFilter(new ClassFilter(PsiSwitchStatement.class), 2)))); - - private static final ElementPattern SUPER_OR_THIS_PATTERN = - and(JavaSmartCompletionContributor.INSIDE_EXPRESSION, - not(psiElement().afterLeaf(PsiKeyword.CASE)), - not(psiElement().afterLeaf(psiElement().withText(".").afterLeaf(PsiKeyword.THIS, PsiKeyword.SUPER))), - not(new FilterPattern(START_SWITCH))); - - - protected static final AndFilter CLASS_START = new AndFilter( - new OrFilter( - END_OF_BLOCK, - new PatternFilter(psiElement().afterLeaf( - or( - psiElement().withoutText(".").inside(psiElement(PsiModifierList.class)), - psiElement().isNull()))) - ), - new PatternFilter(not(psiElement().afterLeaf("@")))); - - private static final String[] PRIMITIVE_TYPES = new String[]{ - PsiKeyword.SHORT, PsiKeyword.BOOLEAN, - PsiKeyword.DOUBLE, PsiKeyword.LONG, - PsiKeyword.INT, PsiKeyword.FLOAT, - PsiKeyword.VOID, PsiKeyword.CHAR, PsiKeyword.BYTE - }; - - final static ElementFilter CLASS_BODY = new OrFilter( - new AfterElementFilter(new TextFilter("{")), - new ScopeFilter(new ClassFilter(JspClassLevelDeclarationStatement.class))); - public static final ElementPattern START_FOR = - psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for")).withParents(PsiJavaCodeReferenceElement.class, PsiExpressionStatement.class, PsiForStatement.class); - - public JavaCompletionData(){ - declareCompletionSpaces(); - - final CompletionVariant variant = new CompletionVariant(PsiMethod.class, - new PatternFilter(not(psiElement().afterLeaf("@", ".")))); - variant.includeScopeClass(PsiVariable.class); - variant.includeScopeClass(PsiClass.class); - variant.includeScopeClass(PsiFile.class); - variant.includeScopeClass(PsiParameterList.class); - - variant.addCompletion(new ModifierChooser()); - - registerVariant(variant); - - initVariantsInFileScope(); - initVariantsInClassScope(); - initVariantsInMethodScope(); - - defineScopeEquivalence(PsiMethod.class, PsiClassInitializer.class); - defineScopeEquivalence(PsiMethod.class, JavaCodeFragment.class); - } - - public static final AndFilter DECLARATION_START = new AndFilter( - CLASS_BODY, - new OrFilter( - END_OF_BLOCK, - new LeftNeighbour(new OrFilter( - new SuperParentFilter(new ClassFilter(PsiModifierList.class)), - new AndFilter (new TokenTypeFilter(JavaTokenType.GT), - new SuperParentFilter(new ClassFilter(PsiTypeParameterList.class))))) - ), - new PatternFilter(not(psiElement().afterLeaf("@", ".")))); - - private void declareCompletionSpaces() { - declareFinalScope(PsiFile.class); - - { - // Class body - final CompletionVariant variant = new CompletionVariant(CLASS_BODY); - variant.includeScopeClass(PsiClass.class, true); - registerVariant(variant); - } - { - // Method body - final CompletionVariant variant = new CompletionVariant(new AndFilter(new InsideElementFilter(new ClassFilter(PsiCodeBlock.class)), - new NotFilter(new InsideElementFilter(new ClassFilter(JspClassLevelDeclarationStatement.class))))); - variant.includeScopeClass(PsiMethod.class, true); - variant.includeScopeClass(PsiClassInitializer.class, true); - registerVariant(variant); - } - - { - // Field initializer - final CompletionVariant variant = new CompletionVariant(new AfterElementFilter(new TextFilter("="))); - variant.includeScopeClass(PsiField.class, true); - registerVariant(variant); - } - - declareFinalScope(PsiLiteralExpression.class); - declareFinalScope(PsiComment.class); - } - - protected void initVariantsInFileScope(){ -// package keyword completion - { - final CompletionVariant variant = new CompletionVariant(PsiJavaFile.class, new StartElementFilter()); - variant.addCompletion(PsiKeyword.PACKAGE); - registerVariant(variant); - } - -// import keyword completion - { - final CompletionVariant variant = new CompletionVariant(PsiJavaFile.class, new OrFilter( - new StartElementFilter(), - END_OF_BLOCK - )); - variant.addCompletion(PsiKeyword.IMPORT); - - registerVariant(variant); - } -// other in file scope - { - final CompletionVariant variant = new CompletionVariant(PsiJavaFile.class, CLASS_START); - variant.includeScopeClass(PsiClass.class); - - variant.addCompletion(PsiKeyword.CLASS); - variant.addCompletion(PsiKeyword.INTERFACE); - - registerVariant(variant); - } - - { - final CompletionVariant variant = new CompletionVariant(PsiTypeCodeFragment.class, new StartElementFilter()); - addPrimitiveTypes(variant, TailType.NONE); - final CompletionVariant variant1 = new CompletionVariant(PsiTypeCodeFragment.class, - new AndFilter( - new StartElementFilter(), - new TypeCodeFragmentIsVoidEnabledFilter() - ) - ); - variant1.addCompletion(PsiKeyword.VOID, TailType.NONE); - registerVariant(variant); - registerVariant(variant1); - - } - - } - - /** - * aClass == null for JspDeclaration scope - */ - protected void initVariantsInClassScope() { -// Completion for extends keyword -// position - { - final ElementFilter position = new AndFilter( - new NotFilter(CLASS_BODY), - new NotFilter(new AfterElementFilter(new ContentFilter(new TextFilter(PsiKeyword.EXTENDS)))), - new NotFilter(new AfterElementFilter(new ContentFilter(new TextFilter(PsiKeyword.IMPLEMENTS)))), - new NotFilter(new LeftNeighbour(new LeftNeighbour(new TextFilter("<", ",")))), - new NotFilter(new ScopeFilter(new EnumOrAnnotationTypeFilter())), - new LeftNeighbour(new OrFilter( - new ClassFilter(PsiIdentifier.class), - new TextFilter(">")))); -// completion - final CompletionVariant variant = new CompletionVariant(position); - variant.includeScopeClass(PsiClass.class, true); - variant.addCompletion(PsiKeyword.EXTENDS); - variant.excludeScopeClass(PsiAnonymousClass.class); - variant.excludeScopeClass(PsiTypeParameter.class); - - registerVariant(variant); - } -// Completion for implements keyword -// position - { - final ElementFilter position = new AndFilter( - new NotFilter(CLASS_BODY), - new NotFilter(new BeforeElementFilter(new ContentFilter(new TextFilter(PsiKeyword.EXTENDS)))), - new NotFilter(new AfterElementFilter(new ContentFilter(new TextFilter(PsiKeyword.IMPLEMENTS)))), - new NotFilter(new LeftNeighbour(new LeftNeighbour(new TextFilter("<", ",")))), - new LeftNeighbour(new OrFilter( - new ClassFilter(PsiIdentifier.class), - new TextFilter(">"))), - new NotFilter(new ScopeFilter(new InterfaceFilter()))); -// completion - final CompletionVariant variant = new CompletionVariant(position); - variant.includeScopeClass(PsiClass.class, true); - variant.addCompletion(PsiKeyword.IMPLEMENTS); - variant.excludeScopeClass(PsiAnonymousClass.class); - - registerVariant(variant); - } - - - { -// declaration start -// position - final CompletionVariant variant = new CompletionVariant(PsiClass.class, DECLARATION_START); - variant.includeScopeClass(JspClassLevelDeclarationStatement.class); - -// completion - addPrimitiveTypes(variant); - variant.addCompletion(PsiKeyword.VOID); - - registerVariant(variant); - } - - { - final CompletionVariant variant = new CompletionVariant(PsiElement.class, psiElement().afterLeaf( - psiElement(PsiIdentifier.class).afterLeaf( - psiElement().withText(string().oneOf(",", "<")).withParent(PsiTypeParameterList.class)))); - //variant.includeScopeClass(PsiClass.class, true); - variant.addCompletion(PsiKeyword.EXTENDS, TailType.SPACE); - registerVariant(variant); - } - } - - private void initVariantsInMethodScope() { - { -// parameters list completion - final CompletionVariant variant = new CompletionVariant(INSIDE_PARAMETER_LIST); - variant.includeScopeClass(PsiParameterList.class, true); - addPrimitiveTypes(variant); - registerVariant(variant); - } - -// Completion for classes in method throws section -// position - { - final ElementFilter position = new LeftNeighbour(new AndFilter( - new TextFilter(")"), - new ParentElementFilter(new ClassFilter(PsiParameterList.class)))); - -// completion - CompletionVariant variant = new CompletionVariant(PsiMethod.class, position); - variant.includeScopeClass(PsiClass.class); // for throws on separate line - variant.addCompletion(PsiKeyword.THROWS); - - registerVariant(variant); - -//in annotation methods - variant = new CompletionVariant(PsiAnnotationMethod.class, position); - variant.addCompletion(PsiKeyword.DEFAULT); - registerVariant(variant); - } - - { -// completion for declarations - final CompletionVariant variant = new CompletionVariant(new OrFilter(END_OF_BLOCK, new LeftNeighbour(new TextFilter(PsiKeyword.FINAL)))); - variant.includeScopeClass(PsiCodeBlock.class, false); - addPrimitiveTypes(variant); - variant.addCompletion(PsiKeyword.CLASS); - registerVariant(variant); - } - -// Completion in cast expressions - { - final CompletionVariant variant = new CompletionVariant(PsiMethod.class, new LeftNeighbour(new AndFilter( - new TextFilter("("), - new ParentElementFilter(new OrFilter( - new ClassFilter(PsiParenthesizedExpression.class), - new ClassFilter(PsiTypeCastExpression.class)))))); - addPrimitiveTypes(variant); - registerVariant(variant); - } - - { -// instanceof keyword - final ElementFilter position = INSTANCEOF_PLACE; - final CompletionVariant variant = new CompletionVariant(position); - variant.includeScopeClass(PsiExpression.class, true); - variant.includeScopeClass(PsiMethod.class); - variant.addCompletion(PsiKeyword.INSTANCEOF); - - registerVariant(variant); - } - - { -// after final keyword - final ElementFilter position = new PatternFilter(AFTER_FINAL); - final CompletionVariant variant = new CompletionVariant(position); - variant.includeScopeClass(PsiDeclarationStatement.class, true); - addPrimitiveTypes(variant); - - registerVariant(variant); - } - - - { -// Keyword completion in start of declaration - final CompletionVariant variant = new CompletionVariant(PsiMethod.class, END_OF_BLOCK); - addKeywords(variant); - registerVariant(variant); - } - - { -// Keyword completion in returns !!!! - final CompletionVariant variant = new CompletionVariant(PsiMethod.class, new LeftNeighbour(new TextFilter(PsiKeyword.RETURN))); - variant.addCompletion(PsiKeyword.TRUE, TailType.NONE); - variant.addCompletion(PsiKeyword.FALSE, TailType.NONE); - registerVariant(variant); - } - - -// Catch/Finally completion - { - final ElementFilter position = AFTER_TRY_BLOCK; - - final CompletionVariant variant = new CompletionVariant(position); - variant.includeScopeClass(PsiCodeBlock.class, true); - variant.addCompletion(PsiKeyword.CATCH, TailTypes.CATCH_LPARENTH); - variant.addCompletion(PsiKeyword.FINALLY, TailTypes.FINALLY_LBRACE); - registerVariant(variant); - } - -// Catch/Finnaly completion - { - final ElementFilter position = new LeftNeighbour(new AndFilter( - new TextFilter("}"), - new ParentElementFilter(new AndFilter( - new LeftNeighbour(new NotFilter(new TextFilter(PsiKeyword.TRY))), - new OrFilter( - new ParentElementFilter(new ClassFilter(PsiTryStatement.class)), - new ParentElementFilter(new ClassFilter(PsiCatchSection.class))) - )))); - - final CompletionVariant variant = new CompletionVariant(position); - variant.includeScopeClass(PsiCodeBlock.class, false); - variant.addCompletion(PsiKeyword.CATCH, TailTypes.CATCH_LPARENTH); - variant.addCompletion(PsiKeyword.FINALLY, TailTypes.FINALLY_LBRACE); - registerVariant(variant); - } - -// Completion for else expression -// completion - { - final ElementFilter position = new LeftNeighbour( - new OrFilter( - new AndFilter(new TextFilter("}"),new ParentElementFilter(new ClassFilter(PsiIfStatement.class), 3)), - new AndFilter(new TextFilter(";"),new ParentElementFilter(new ClassFilter(PsiIfStatement.class), 2)) - )); - final CompletionVariant variant = new CompletionVariant(PsiMethod.class, position); - variant.addCompletion(PsiKeyword.ELSE); - - registerVariant(variant); - } - - { -// Class field completion - - final CompletionVariant variant = new CompletionVariant(PsiMethod.class, new LeftNeighbour( - new AndFilter( - new TextFilter("."), - new LeftNeighbour( - new OrFilter( - new ReferenceOnFilter(ElementClassFilter.CLASS), - new TextFilter(PRIMITIVE_TYPES), - new TextFilter("]")))))); - variant.includeScopeClass(PsiAnnotationParameterList.class); - variant.includeScopeClass(PsiVariable.class); - variant.excludeScopeClass(PsiJavaCodeReferenceCodeFragment.class); - variant.addCompletion(PsiKeyword.CLASS, TailType.NONE); - registerVariant(variant); - } - - { -// break completion - final CompletionVariant variant = new CompletionVariant(new AndFilter(END_OF_BLOCK, new OrFilter( - new ScopeFilter(new ClassFilter(PsiSwitchStatement.class)), - new InsideElementFilter(new ClassFilter(PsiBlockStatement.class))))); - - variant.includeScopeClass(PsiForStatement.class, false); - variant.includeScopeClass(PsiForeachStatement.class, false); - variant.includeScopeClass(PsiWhileStatement.class, false); - variant.includeScopeClass(PsiDoWhileStatement.class, false); - variant.includeScopeClass(PsiSwitchStatement.class, false); - variant.addCompletion(PsiKeyword.BREAK); - registerVariant(variant); - } - { -// continue completion - final CompletionVariant variant = new CompletionVariant(new AndFilter(END_OF_BLOCK, new InsideElementFilter(new ClassFilter(PsiBlockStatement.class)))); - variant.includeScopeClass(PsiForeachStatement.class, false); - variant.includeScopeClass(PsiForStatement.class, false); - variant.includeScopeClass(PsiWhileStatement.class, false); - variant.includeScopeClass(PsiDoWhileStatement.class, false); - - variant.addCompletion(PsiKeyword.CONTINUE); - registerVariant(variant); - } - - { - final CompletionVariant variant = new CompletionVariant( - new AndFilter( - END_OF_BLOCK, - new NotFilter(START_SWITCH), - new OrFilter( - new ParentElementFilter(new ClassFilter(PsiSwitchLabelStatement.class)), - new LeftNeighbour(new OrFilter( - new ParentElementFilter(new ClassFilter(PsiSwitchStatement.class), 2), - new AndFilter(new TextFilter(";", "}", ":"),new ParentElementFilter(new ClassFilter(PsiSwitchStatement.class), 3) - )))))); - variant.includeScopeClass(PsiElement.class, false); - variant.addCompletion(PsiKeyword.CASE, TailType.SPACE); - variant.addCompletion(PsiKeyword.DEFAULT, TailType.CASE_COLON); - registerVariant(variant); - } - - { - final CompletionVariant variant = new CompletionVariant(START_SWITCH); - variant.includeScopeClass(PsiElement.class, true); - variant.addCompletion(PsiKeyword.CASE, TailType.SPACE); - variant.addCompletion(PsiKeyword.DEFAULT, TailType.CASE_COLON); - registerVariant(variant); - } - - } - - private static void addPrimitiveTypes(CompletionVariant variant){ - addPrimitiveTypes(variant, CompletionVariant.DEFAULT_TAIL_TYPE); - } - - private static void addPrimitiveTypes(CompletionVariant variant, TailType tailType){ - variant.addCompletion(new String[]{ - PsiKeyword.SHORT, PsiKeyword.BOOLEAN, - PsiKeyword.DOUBLE, PsiKeyword.LONG, - PsiKeyword.INT, PsiKeyword.FLOAT, - PsiKeyword.CHAR, PsiKeyword.BYTE - }, tailType); - } - - private static void addKeywords(CompletionVariant variant){ - variant.addCompletion(PsiKeyword.SWITCH, TailTypes.SWITCH_LPARENTH); - variant.addCompletion(PsiKeyword.WHILE, TailTypes.WHILE_LPARENTH); - variant.addCompletion(PsiKeyword.DO, TailType.createSimpleTailType('{')); - variant.addCompletion(PsiKeyword.FOR, TailTypes.FOR_LPARENTH); - variant.addCompletion(PsiKeyword.IF, TailTypes.IF_LPARENTH); - variant.addCompletion(PsiKeyword.TRY, TailType.createSimpleTailType('{')); - variant.addCompletion(PsiKeyword.THROW, TailType.SPACE); - variant.addCompletion(PsiKeyword.RETURN, TailType.NONE); - variant.addCompletion(PsiKeyword.NEW, TailType.SPACE); - variant.addCompletion(PsiKeyword.ASSERT, TailType.SPACE); - variant.addCompletion(PsiKeyword.SYNCHRONIZED, TailTypes.SYNCHRONIZED_LPARENTH); - } - - @Override - public void fillCompletions(CompletionParameters parameters, CompletionResultSet result) { - final PsiElement position = parameters.getPosition(); - if (SUPER_OR_THIS_PATTERN.accepts(position)) { - if (AFTER_DOT.accepts(position) && !isInsideQualifierClass(position)) return; - - result.addElement(createKeyword(position, PsiKeyword.THIS)); - - final LookupItem superItem = (LookupItem)createKeyword(position, PsiKeyword.SUPER); - if (psiElement().afterLeaf(psiElement().withText("{").withSuperParent(2, psiMethod().constructor(true))).accepts(position)) { - final PsiMethod method = PsiTreeUtil.getParentOfType(position, PsiMethod.class, false, PsiClass.class); - assert method != null; - final boolean hasParams = superConstructorHasParameters(method); - superItem.setInsertHandler(new ParenthesesInsertHandler() { - @Override - protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) { - return hasParams; - } - - @Override - public void handleInsert(InsertionContext context, LookupElement item) { - super.handleInsert(context, item); - TailType.insertChar(context.getEditor(), context.getTailOffset(), ';'); - } - }); - } - - result.addElement(superItem); - } - - final ElementPattern exprKeywords = and( - psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent( - not( - or(psiElement(PsiTypeCastExpression.class), - psiElement(PsiSwitchLabelStatement.class), - psiElement(PsiExpressionStatement.class) - ) - ) - )), - not(psiElement().afterLeaf(".")) - ); - if (exprKeywords.accepts(position)) { - result.addElement(createKeyword(position, PsiKeyword.NEW)); - result.addElement(createKeyword(position, PsiKeyword.NULL)); - result.addElement(createKeyword(position, PsiKeyword.TRUE)); - result.addElement(createKeyword(position, PsiKeyword.FALSE)); - } - - if (START_FOR.accepts(position)) { - for (String primitiveType : PRIMITIVE_TYPES) { - if (!PsiKeyword.VOID.equals(primitiveType)) { - result.addElement(TailTypeDecorator.withTail(createKeyword(position, primitiveType), TailType.SPACE)); - } - result.addElement(TailTypeDecorator.withTail(createKeyword(position, PsiKeyword.FINAL), TailType.SPACE)); - } - } - - final ProcessingContext context = new ProcessingContext(); - if (psiElement().afterLeaf( - psiElement().withText(">").withParent( - psiElement(PsiTypeParameterList.class).withParent(PsiErrorElement.class).save("typeParameterList"))).accepts(position, context)) { - final PsiTypeParameterList list = (PsiTypeParameterList)context.get("typeParameterList"); - PsiElement current = list.getParent().getParent(); - if (current instanceof PsiField) { - current = current.getParent(); - } - if (current instanceof PsiClass) { - for (PsiTypeParameter typeParameter : list.getTypeParameters()) { - result.addElement(new JavaPsiClassReferenceElement(typeParameter)); - } - } - } - - } - - private static LookupElement createKeyword(PsiElement position, String keyword) { - return BasicExpressionCompletionContributor.createKeywordLookupItem(position, keyword); - } - - private static boolean isInsideQualifierClass(PsiElement position) { - if (position.getParent() instanceof PsiJavaCodeReferenceElement) { - final PsiElement qualifier = ((PsiJavaCodeReferenceElement)position.getParent()).getQualifier(); - if (qualifier instanceof PsiJavaCodeReferenceElement) { - final PsiElement qualifierClass = ((PsiJavaCodeReferenceElement)qualifier).resolve(); - if (qualifierClass instanceof PsiClass) { - PsiElement parent = position; - final PsiManager psiManager = position.getManager(); - while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) { - if (psiManager.areElementsEquivalent(parent, qualifierClass)) { - return true; - } - } - } - } - } - return false; - } - - private static boolean superConstructorHasParameters(PsiMethod method) { - final PsiClass psiClass = method.getContainingClass(); - if (psiClass == null) { - return false; - } - - final PsiClass superClass = psiClass.getSuperClass(); - if (superClass != null) { - for (final PsiMethod psiMethod : superClass.getConstructors()) { - final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); - if (resolveHelper.isAccessible(psiMethod, method, null) && psiMethod.getParameterList().getParameters().length > 0) { - return true; - } - } - } - return false; - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.TailTypes; +import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupItem; +import com.intellij.codeInsight.lookup.TailTypeDecorator; +import com.intellij.patterns.ElementPattern; +import com.intellij.patterns.PsiElementPattern; +import com.intellij.patterns.PsiJavaElementPattern; +import com.intellij.patterns.PsiJavaPatterns; +import com.intellij.psi.*; +import com.intellij.psi.filters.*; +import com.intellij.psi.filters.classes.EnumOrAnnotationTypeFilter; +import com.intellij.psi.filters.classes.InterfaceFilter; +import com.intellij.psi.filters.element.ReferenceOnFilter; +import com.intellij.psi.filters.position.*; +import com.intellij.psi.filters.types.TypeCodeFragmentIsVoidEnabledFilter; +import com.intellij.psi.impl.source.jsp.jspJava.JspClassLevelDeclarationStatement; +import com.intellij.psi.jsp.JspElementType; +import com.intellij.psi.scope.ElementClassFilter; +import com.intellij.psi.templateLanguages.OuterLanguageElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NonNls; + +import static com.intellij.patterns.PsiJavaPatterns.*; +import static com.intellij.patterns.StandardPatterns.not; + +public class JavaCompletionData extends JavaAwareCompletionData{ + + private static final @NonNls String[] ourBlockFinalizers = {"{", "}", ";", ":", "else"}; + private static final PsiElementPattern AFTER_DOT = psiElement().afterLeaf("."); + public static final LeftNeighbour INSTANCEOF_PLACE = new LeftNeighbour(new OrFilter( + new ReferenceOnFilter(new ClassFilter(PsiVariable.class)), + new TextFilter(PsiKeyword.THIS), + new AndFilter(new TextFilter(")"), new ParentElementFilter(new AndFilter( + new ClassFilter(PsiTypeCastExpression.class, false), + new OrFilter( + new ParentElementFilter(new ClassFilter(PsiExpression.class)), + new ClassFilter(PsiExpression.class))))), + new AndFilter(new TextFilter("]"), new ParentElementFilter(new ClassFilter(PsiArrayAccessExpression.class))))); + public static final PsiJavaElementPattern.Capture AFTER_FINAL = + PsiJavaPatterns.psiElement().afterLeaf(PsiKeyword.FINAL).inside(PsiDeclarationStatement.class); + public static final LeftNeighbour AFTER_TRY_BLOCK = new LeftNeighbour(new AndFilter( + new TextFilter("}"), + new ParentElementFilter(new AndFilter( + new LeftNeighbour(new TextFilter(PsiKeyword.TRY)), + new ParentElementFilter(new ClassFilter(PsiTryStatement.class))) +))); + public static final PsiJavaElementPattern.Capture INSIDE_PARAMETER_LIST = + PsiJavaPatterns.psiElement().withParent( + psiElement(PsiJavaCodeReferenceElement.class).withParent( + psiElement(PsiTypeElement.class).withParent(or(psiElement(PsiParameter.class), psiElement(PsiParameterList.class))))); + + private static final AndFilter START_OF_CODE_FRAGMENT = new AndFilter( + new ScopeFilter(new AndFilter( + new ClassFilter(JavaCodeFragment.class), + new ClassFilter(PsiExpressionCodeFragment.class, false), + new ClassFilter(PsiJavaCodeReferenceCodeFragment.class, false), + new ClassFilter(PsiTypeCodeFragment.class, false) + )), + new StartElementFilter() + ); + + static final ElementFilter END_OF_BLOCK = new OrFilter( + new AndFilter( + new LeftNeighbour( + new OrFilter( + new AndFilter ( + new TextFilter(ourBlockFinalizers), + new NotFilter ( + new SuperParentFilter(new ClassFilter(PsiAnnotation.class)) + ) + ), + new TextFilter("*/"), + new TokenTypeFilter(JspElementType.HOLDER_TEMPLATE_DATA), + new ClassFilter(OuterLanguageElement.class), + new AndFilter( + new TextFilter(")"), + new NotFilter( + new OrFilter( + new ParentElementFilter(new ClassFilter(PsiExpressionList.class)), + new ParentElementFilter(new ClassFilter(PsiParameterList.class)), + new ParentElementFilter(new ClassFilter(PsiTypeCastExpression.class)) + ) + ) + ))), + new NotFilter(new TextFilter(".")) + ), + START_OF_CODE_FRAGMENT + ); + + static final AndFilter START_SWITCH = new AndFilter(END_OF_BLOCK, new LeftNeighbour( + new AndFilter(new TextFilter("{"), new ParentElementFilter(new ClassFilter(PsiSwitchStatement.class), 2)))); + + private static final ElementPattern SUPER_OR_THIS_PATTERN = + and(JavaSmartCompletionContributor.INSIDE_EXPRESSION, + not(psiElement().afterLeaf(PsiKeyword.CASE)), + not(psiElement().afterLeaf(psiElement().withText(".").afterLeaf(PsiKeyword.THIS, PsiKeyword.SUPER))), + not(new FilterPattern(START_SWITCH))); + + + protected static final AndFilter CLASS_START = new AndFilter( + new OrFilter( + END_OF_BLOCK, + new PatternFilter(psiElement().afterLeaf( + or( + psiElement().withoutText(".").inside(psiElement(PsiModifierList.class)), + psiElement().isNull()))) + ), + new PatternFilter(not(psiElement().afterLeaf("@")))); + + private static final String[] PRIMITIVE_TYPES = new String[]{ + PsiKeyword.SHORT, PsiKeyword.BOOLEAN, + PsiKeyword.DOUBLE, PsiKeyword.LONG, + PsiKeyword.INT, PsiKeyword.FLOAT, + PsiKeyword.VOID, PsiKeyword.CHAR, PsiKeyword.BYTE + }; + + final static ElementFilter CLASS_BODY = new OrFilter( + new AfterElementFilter(new TextFilter("{")), + new ScopeFilter(new ClassFilter(JspClassLevelDeclarationStatement.class))); + public static final ElementPattern START_FOR = + psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for")).withParents(PsiJavaCodeReferenceElement.class, PsiExpressionStatement.class, PsiForStatement.class); + + public JavaCompletionData(){ + declareCompletionSpaces(); + + final CompletionVariant variant = new CompletionVariant(PsiMethod.class, + new PatternFilter(not(psiElement().afterLeaf("@", ".")))); + variant.includeScopeClass(PsiVariable.class); + variant.includeScopeClass(PsiClass.class); + variant.includeScopeClass(PsiFile.class); + variant.includeScopeClass(PsiParameterList.class); + + variant.addCompletion(new ModifierChooser()); + + registerVariant(variant); + + initVariantsInFileScope(); + initVariantsInClassScope(); + initVariantsInMethodScope(); + + defineScopeEquivalence(PsiMethod.class, PsiClassInitializer.class); + defineScopeEquivalence(PsiMethod.class, JavaCodeFragment.class); + } + + public static final AndFilter DECLARATION_START = new AndFilter( + CLASS_BODY, + new OrFilter( + END_OF_BLOCK, + new LeftNeighbour(new OrFilter( + new SuperParentFilter(new ClassFilter(PsiModifierList.class)), + new AndFilter (new TokenTypeFilter(JavaTokenType.GT), + new SuperParentFilter(new ClassFilter(PsiTypeParameterList.class))))) + ), + new PatternFilter(not(psiElement().afterLeaf("@", ".")))); + + private void declareCompletionSpaces() { + declareFinalScope(PsiFile.class); + + { + // Class body + final CompletionVariant variant = new CompletionVariant(CLASS_BODY); + variant.includeScopeClass(PsiClass.class, true); + registerVariant(variant); + } + { + // Method body + final CompletionVariant variant = new CompletionVariant(new AndFilter(new InsideElementFilter(new ClassFilter(PsiCodeBlock.class)), + new NotFilter(new InsideElementFilter(new ClassFilter(JspClassLevelDeclarationStatement.class))))); + variant.includeScopeClass(PsiMethod.class, true); + variant.includeScopeClass(PsiClassInitializer.class, true); + registerVariant(variant); + } + + { + // Field initializer + final CompletionVariant variant = new CompletionVariant(new AfterElementFilter(new TextFilter("="))); + variant.includeScopeClass(PsiField.class, true); + registerVariant(variant); + } + + declareFinalScope(PsiLiteralExpression.class); + declareFinalScope(PsiComment.class); + } + + protected void initVariantsInFileScope(){ +// package keyword completion + { + final CompletionVariant variant = new CompletionVariant(PsiJavaFile.class, new StartElementFilter()); + variant.addCompletion(PsiKeyword.PACKAGE); + registerVariant(variant); + } + +// import keyword completion + { + final CompletionVariant variant = new CompletionVariant(PsiJavaFile.class, new OrFilter( + new StartElementFilter(), + END_OF_BLOCK + )); + variant.addCompletion(PsiKeyword.IMPORT); + + registerVariant(variant); + } +// other in file scope + { + final CompletionVariant variant = new CompletionVariant(PsiJavaFile.class, CLASS_START); + variant.includeScopeClass(PsiClass.class); + + variant.addCompletion(PsiKeyword.CLASS); + variant.addCompletion(PsiKeyword.INTERFACE); + + registerVariant(variant); + } + + { + final CompletionVariant variant = new CompletionVariant(PsiTypeCodeFragment.class, new StartElementFilter()); + addPrimitiveTypes(variant, TailType.NONE); + final CompletionVariant variant1 = new CompletionVariant(PsiTypeCodeFragment.class, + new AndFilter( + new StartElementFilter(), + new TypeCodeFragmentIsVoidEnabledFilter() + ) + ); + variant1.addCompletion(PsiKeyword.VOID, TailType.NONE); + registerVariant(variant); + registerVariant(variant1); + + } + + } + + /** + * aClass == null for JspDeclaration scope + */ + protected void initVariantsInClassScope() { +// Completion for extends keyword +// position + { + final ElementFilter position = new AndFilter( + new NotFilter(CLASS_BODY), + new NotFilter(new AfterElementFilter(new ContentFilter(new TextFilter(PsiKeyword.EXTENDS)))), + new NotFilter(new AfterElementFilter(new ContentFilter(new TextFilter(PsiKeyword.IMPLEMENTS)))), + new NotFilter(new LeftNeighbour(new LeftNeighbour(new TextFilter("<", ",")))), + new NotFilter(new ScopeFilter(new EnumOrAnnotationTypeFilter())), + new LeftNeighbour(new OrFilter( + new ClassFilter(PsiIdentifier.class), + new TextFilter(">")))); +// completion + final CompletionVariant variant = new CompletionVariant(position); + variant.includeScopeClass(PsiClass.class, true); + variant.addCompletion(PsiKeyword.EXTENDS); + variant.excludeScopeClass(PsiAnonymousClass.class); + variant.excludeScopeClass(PsiTypeParameter.class); + + registerVariant(variant); + } +// Completion for implements keyword +// position + { + final ElementFilter position = new AndFilter( + new NotFilter(CLASS_BODY), + new NotFilter(new BeforeElementFilter(new ContentFilter(new TextFilter(PsiKeyword.EXTENDS)))), + new NotFilter(new AfterElementFilter(new ContentFilter(new TextFilter(PsiKeyword.IMPLEMENTS)))), + new NotFilter(new LeftNeighbour(new LeftNeighbour(new TextFilter("<", ",")))), + new LeftNeighbour(new OrFilter( + new ClassFilter(PsiIdentifier.class), + new TextFilter(">"))), + new NotFilter(new ScopeFilter(new InterfaceFilter()))); +// completion + final CompletionVariant variant = new CompletionVariant(position); + variant.includeScopeClass(PsiClass.class, true); + variant.addCompletion(PsiKeyword.IMPLEMENTS); + variant.excludeScopeClass(PsiAnonymousClass.class); + + registerVariant(variant); + } + + + { +// declaration start +// position + final CompletionVariant variant = new CompletionVariant(PsiClass.class, DECLARATION_START); + variant.includeScopeClass(JspClassLevelDeclarationStatement.class); + +// completion + addPrimitiveTypes(variant); + variant.addCompletion(PsiKeyword.VOID); + + registerVariant(variant); + } + + { + final CompletionVariant variant = new CompletionVariant(PsiElement.class, psiElement().afterLeaf( + psiElement(PsiIdentifier.class).afterLeaf( + psiElement().withText(string().oneOf(",", "<")).withParent(PsiTypeParameterList.class)))); + //variant.includeScopeClass(PsiClass.class, true); + variant.addCompletion(PsiKeyword.EXTENDS, TailType.SPACE); + registerVariant(variant); + } + } + + private void initVariantsInMethodScope() { + { +// parameters list completion + final CompletionVariant variant = new CompletionVariant(INSIDE_PARAMETER_LIST); + variant.includeScopeClass(PsiParameterList.class, true); + addPrimitiveTypes(variant); + registerVariant(variant); + } + +// Completion for classes in method throws section +// position + { + final ElementFilter position = new LeftNeighbour(new AndFilter( + new TextFilter(")"), + new ParentElementFilter(new ClassFilter(PsiParameterList.class)))); + +// completion + CompletionVariant variant = new CompletionVariant(PsiMethod.class, position); + variant.includeScopeClass(PsiClass.class); // for throws on separate line + variant.addCompletion(PsiKeyword.THROWS); + + registerVariant(variant); + +//in annotation methods + variant = new CompletionVariant(PsiAnnotationMethod.class, position); + variant.addCompletion(PsiKeyword.DEFAULT); + registerVariant(variant); + } + + { +// completion for declarations + final CompletionVariant variant = new CompletionVariant(new OrFilter(END_OF_BLOCK, new LeftNeighbour(new TextFilter(PsiKeyword.FINAL)))); + variant.includeScopeClass(PsiCodeBlock.class, false); + addPrimitiveTypes(variant); + variant.addCompletion(PsiKeyword.CLASS); + registerVariant(variant); + } + +// Completion in cast expressions + { + final CompletionVariant variant = new CompletionVariant(PsiMethod.class, new LeftNeighbour(new AndFilter( + new TextFilter("("), + new ParentElementFilter(new OrFilter( + new ClassFilter(PsiParenthesizedExpression.class), + new ClassFilter(PsiTypeCastExpression.class)))))); + addPrimitiveTypes(variant); + registerVariant(variant); + } + + { +// instanceof keyword + final ElementFilter position = INSTANCEOF_PLACE; + final CompletionVariant variant = new CompletionVariant(position); + variant.includeScopeClass(PsiExpression.class, true); + variant.includeScopeClass(PsiMethod.class); + variant.addCompletion(PsiKeyword.INSTANCEOF); + + registerVariant(variant); + } + + { +// after final keyword + final ElementFilter position = new PatternFilter(AFTER_FINAL); + final CompletionVariant variant = new CompletionVariant(position); + variant.includeScopeClass(PsiDeclarationStatement.class, true); + addPrimitiveTypes(variant); + + registerVariant(variant); + } + + + { +// Keyword completion in start of declaration + final CompletionVariant variant = new CompletionVariant(PsiMethod.class, END_OF_BLOCK); + addKeywords(variant); + registerVariant(variant); + } + + { +// Keyword completion in returns !!!! + final CompletionVariant variant = new CompletionVariant(PsiMethod.class, new LeftNeighbour(new TextFilter(PsiKeyword.RETURN))); + variant.addCompletion(PsiKeyword.TRUE, TailType.NONE); + variant.addCompletion(PsiKeyword.FALSE, TailType.NONE); + registerVariant(variant); + } + + +// Catch/Finally completion + { + final ElementFilter position = AFTER_TRY_BLOCK; + + final CompletionVariant variant = new CompletionVariant(position); + variant.includeScopeClass(PsiCodeBlock.class, true); + variant.addCompletion(PsiKeyword.CATCH, TailTypes.CATCH_LPARENTH); + variant.addCompletion(PsiKeyword.FINALLY, TailTypes.FINALLY_LBRACE); + registerVariant(variant); + } + +// Catch/Finnaly completion + { + final ElementFilter position = new LeftNeighbour(new AndFilter( + new TextFilter("}"), + new ParentElementFilter(new AndFilter( + new LeftNeighbour(new NotFilter(new TextFilter(PsiKeyword.TRY))), + new OrFilter( + new ParentElementFilter(new ClassFilter(PsiTryStatement.class)), + new ParentElementFilter(new ClassFilter(PsiCatchSection.class))) + )))); + + final CompletionVariant variant = new CompletionVariant(position); + variant.includeScopeClass(PsiCodeBlock.class, false); + variant.addCompletion(PsiKeyword.CATCH, TailTypes.CATCH_LPARENTH); + variant.addCompletion(PsiKeyword.FINALLY, TailTypes.FINALLY_LBRACE); + registerVariant(variant); + } + +// Completion for else expression +// completion + { + final ElementFilter position = new LeftNeighbour( + new OrFilter( + new AndFilter(new TextFilter("}"),new ParentElementFilter(new ClassFilter(PsiIfStatement.class), 3)), + new AndFilter(new TextFilter(";"),new ParentElementFilter(new ClassFilter(PsiIfStatement.class), 2)) + )); + final CompletionVariant variant = new CompletionVariant(PsiMethod.class, position); + variant.addCompletion(PsiKeyword.ELSE); + + registerVariant(variant); + } + + { +// Class field completion + + final CompletionVariant variant = new CompletionVariant(PsiMethod.class, new LeftNeighbour( + new AndFilter( + new TextFilter("."), + new LeftNeighbour( + new OrFilter( + new ReferenceOnFilter(ElementClassFilter.CLASS), + new TextFilter(PRIMITIVE_TYPES), + new TextFilter("]")))))); + variant.includeScopeClass(PsiAnnotationParameterList.class); + variant.includeScopeClass(PsiVariable.class); + variant.excludeScopeClass(PsiJavaCodeReferenceCodeFragment.class); + variant.addCompletion(PsiKeyword.CLASS, TailType.NONE); + registerVariant(variant); + } + + { +// break completion + final CompletionVariant variant = new CompletionVariant(new AndFilter(END_OF_BLOCK, new OrFilter( + new ScopeFilter(new ClassFilter(PsiSwitchStatement.class)), + new InsideElementFilter(new ClassFilter(PsiBlockStatement.class))))); + + variant.includeScopeClass(PsiForStatement.class, false); + variant.includeScopeClass(PsiForeachStatement.class, false); + variant.includeScopeClass(PsiWhileStatement.class, false); + variant.includeScopeClass(PsiDoWhileStatement.class, false); + variant.includeScopeClass(PsiSwitchStatement.class, false); + variant.addCompletion(PsiKeyword.BREAK); + registerVariant(variant); + } + { +// continue completion + final CompletionVariant variant = new CompletionVariant(new AndFilter(END_OF_BLOCK, new InsideElementFilter(new ClassFilter(PsiBlockStatement.class)))); + variant.includeScopeClass(PsiForeachStatement.class, false); + variant.includeScopeClass(PsiForStatement.class, false); + variant.includeScopeClass(PsiWhileStatement.class, false); + variant.includeScopeClass(PsiDoWhileStatement.class, false); + + variant.addCompletion(PsiKeyword.CONTINUE); + registerVariant(variant); + } + + { + final CompletionVariant variant = new CompletionVariant( + new AndFilter( + END_OF_BLOCK, + new NotFilter(START_SWITCH), + new OrFilter( + new ParentElementFilter(new ClassFilter(PsiSwitchLabelStatement.class)), + new LeftNeighbour(new OrFilter( + new ParentElementFilter(new ClassFilter(PsiSwitchStatement.class), 2), + new AndFilter(new TextFilter(";", "}", ":"),new ParentElementFilter(new ClassFilter(PsiSwitchStatement.class), 3) + )))))); + variant.includeScopeClass(PsiElement.class, false); + variant.addCompletion(PsiKeyword.CASE, TailType.SPACE); + variant.addCompletion(PsiKeyword.DEFAULT, TailType.CASE_COLON); + registerVariant(variant); + } + + { + final CompletionVariant variant = new CompletionVariant(START_SWITCH); + variant.includeScopeClass(PsiElement.class, true); + variant.addCompletion(PsiKeyword.CASE, TailType.SPACE); + variant.addCompletion(PsiKeyword.DEFAULT, TailType.CASE_COLON); + registerVariant(variant); + } + + } + + private static void addPrimitiveTypes(CompletionVariant variant){ + addPrimitiveTypes(variant, CompletionVariant.DEFAULT_TAIL_TYPE); + } + + private static void addPrimitiveTypes(CompletionVariant variant, TailType tailType){ + variant.addCompletion(new String[]{ + PsiKeyword.SHORT, PsiKeyword.BOOLEAN, + PsiKeyword.DOUBLE, PsiKeyword.LONG, + PsiKeyword.INT, PsiKeyword.FLOAT, + PsiKeyword.CHAR, PsiKeyword.BYTE + }, tailType); + } + + private static void addKeywords(CompletionVariant variant){ + variant.addCompletion(PsiKeyword.SWITCH, TailTypes.SWITCH_LPARENTH); + variant.addCompletion(PsiKeyword.WHILE, TailTypes.WHILE_LPARENTH); + variant.addCompletion(PsiKeyword.DO, TailType.createSimpleTailType('{')); + variant.addCompletion(PsiKeyword.FOR, TailTypes.FOR_LPARENTH); + variant.addCompletion(PsiKeyword.IF, TailTypes.IF_LPARENTH); + variant.addCompletion(PsiKeyword.TRY, TailType.createSimpleTailType('{')); + variant.addCompletion(PsiKeyword.THROW, TailType.SPACE); + variant.addCompletion(PsiKeyword.RETURN, TailType.NONE); + variant.addCompletion(PsiKeyword.NEW, TailType.SPACE); + variant.addCompletion(PsiKeyword.ASSERT, TailType.SPACE); + variant.addCompletion(PsiKeyword.SYNCHRONIZED, TailTypes.SYNCHRONIZED_LPARENTH); + } + + @Override + public void fillCompletions(CompletionParameters parameters, CompletionResultSet result) { + final PsiElement position = parameters.getPosition(); + if (SUPER_OR_THIS_PATTERN.accepts(position)) { + if (AFTER_DOT.accepts(position) && !isInsideQualifierClass(position)) return; + + result.addElement(createKeyword(position, PsiKeyword.THIS)); + + final LookupItem superItem = (LookupItem)createKeyword(position, PsiKeyword.SUPER); + if (psiElement().afterLeaf(psiElement().withText("{").withSuperParent(2, psiMethod().constructor(true))).accepts(position)) { + final PsiMethod method = PsiTreeUtil.getParentOfType(position, PsiMethod.class, false, PsiClass.class); + assert method != null; + final boolean hasParams = superConstructorHasParameters(method); + superItem.setInsertHandler(new ParenthesesInsertHandler() { + @Override + protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) { + return hasParams; + } + + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + super.handleInsert(context, item); + TailType.insertChar(context.getEditor(), context.getTailOffset(), ';'); + } + }); + } + + result.addElement(superItem); + } + + final ElementPattern exprKeywords = and( + psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent( + not( + or(psiElement(PsiTypeCastExpression.class), + psiElement(PsiSwitchLabelStatement.class), + psiElement(PsiExpressionStatement.class) + ) + ) + )), + not(psiElement().afterLeaf(".")) + ); + if (exprKeywords.accepts(position)) { + result.addElement(createKeyword(position, PsiKeyword.NEW)); + result.addElement(createKeyword(position, PsiKeyword.NULL)); + result.addElement(createKeyword(position, PsiKeyword.TRUE)); + result.addElement(createKeyword(position, PsiKeyword.FALSE)); + } + + if (START_FOR.accepts(position)) { + for (String primitiveType : PRIMITIVE_TYPES) { + if (!PsiKeyword.VOID.equals(primitiveType)) { + result.addElement(TailTypeDecorator.withTail(createKeyword(position, primitiveType), TailType.SPACE)); + } + result.addElement(TailTypeDecorator.withTail(createKeyword(position, PsiKeyword.FINAL), TailType.SPACE)); + } + } + + final ProcessingContext context = new ProcessingContext(); + if (psiElement().afterLeaf( + psiElement().withText(">").withParent( + psiElement(PsiTypeParameterList.class).withParent(PsiErrorElement.class).save("typeParameterList"))).accepts(position, context)) { + final PsiTypeParameterList list = (PsiTypeParameterList)context.get("typeParameterList"); + PsiElement current = list.getParent().getParent(); + if (current instanceof PsiField) { + current = current.getParent(); + } + if (current instanceof PsiClass) { + for (PsiTypeParameter typeParameter : list.getTypeParameters()) { + result.addElement(new JavaPsiClassReferenceElement(typeParameter)); + } + } + } + + } + + private static LookupElement createKeyword(PsiElement position, String keyword) { + return BasicExpressionCompletionContributor.createKeywordLookupItem(position, keyword); + } + + private static boolean isInsideQualifierClass(PsiElement position) { + if (position.getParent() instanceof PsiJavaCodeReferenceElement) { + final PsiElement qualifier = ((PsiJavaCodeReferenceElement)position.getParent()).getQualifier(); + if (qualifier instanceof PsiJavaCodeReferenceElement) { + final PsiElement qualifierClass = ((PsiJavaCodeReferenceElement)qualifier).resolve(); + if (qualifierClass instanceof PsiClass) { + PsiElement parent = position; + final PsiManager psiManager = position.getManager(); + while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) { + if (psiManager.areElementsEquivalent(parent, qualifierClass)) { + return true; + } + } + } + } + } + return false; + } + + private static boolean superConstructorHasParameters(PsiMethod method) { + final PsiClass psiClass = method.getContainingClass(); + if (psiClass == null) { + return false; + } + + final PsiClass superClass = psiClass.getSuperClass(); + if (superClass != null) { + for (final PsiMethod psiMethod : superClass.getConstructors()) { + final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); + if (resolveHelper.isAccessible(psiMethod, method, null) && psiMethod.getParameterList().getParameters().length > 0) { + return true; + } + } + } + return false; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaGlobalMemberNameCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaGlobalMemberNameCompletionContributor.java index 9a654da10c64..831ee149ab27 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaGlobalMemberNameCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaGlobalMemberNameCompletionContributor.java @@ -1,78 +1,78 @@ -package com.intellij.codeInsight.completion; - -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.VariableLookupItem; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.NotNull; - -/** - * @author peter - */ -public class JavaGlobalMemberNameCompletionContributor extends CompletionContributor { - - @Override - public void fillCompletionVariants(CompletionParameters parameters, final CompletionResultSet result) { - if (parameters.getCompletionType() != CompletionType.CLASS_NAME) { - return; - } - - final PrefixMatcher matcher = result.getPrefixMatcher(); - final String prefix = matcher.getPrefix(); - if (prefix.length() == 0 || !Character.isLowerCase(prefix.charAt(0))) { - return; - } - - final PsiElement position = parameters.getPosition(); - final PsiElement parent = position.getParent(); - if (!(parent instanceof PsiReferenceExpression)) { - return; - } - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)parent; - if (referenceExpression.isQualified()) { - return; - } - - completeStaticMembers(position).processStaticMethodsGlobally(result); - } - - public static StaticMemberProcessor completeStaticMembers(final PsiElement position) { - final StaticMemberProcessor processor = new StaticMemberProcessor(position) { - @NotNull - @Override - protected LookupElement createLookupElement(@NotNull PsiMember member, @NotNull final PsiClass containingClass, boolean shouldImport) { - if (member instanceof PsiMethod) { - final JavaMethodCallElement element = new JavaMethodCallElement((PsiMethod)member, true); - element.setShouldBeImported(shouldImport); - return element; - } - return new VariableLookupItem((PsiVariable)member) { - @Override - public void handleInsert(InsertionContext context) { - context.commitDocument(); - final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiReferenceExpression.class, false); - if (ref != null) { - ref.bindToElementViaStaticImport(containingClass); - PostprocessReformattingAspect.getInstance(ref.getProject()).doPostponedFormatting(); - } - super.handleInsert(context); - } - }; - } - - }; - final PsiFile file = position.getContainingFile(); - if (file instanceof PsiJavaFile) { - final PsiImportList importList = ((PsiJavaFile)file).getImportList(); - if (importList != null) { - for (PsiImportStaticStatement statement : importList.getImportStaticStatements()) { - processor.importMembersOf(statement.resolveTargetClass()); - } - } - } - - return processor; - } - -} +package com.intellij.codeInsight.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.VariableLookupItem; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +/** + * @author peter + */ +public class JavaGlobalMemberNameCompletionContributor extends CompletionContributor { + + @Override + public void fillCompletionVariants(CompletionParameters parameters, final CompletionResultSet result) { + if (parameters.getCompletionType() != CompletionType.CLASS_NAME) { + return; + } + + final PrefixMatcher matcher = result.getPrefixMatcher(); + final String prefix = matcher.getPrefix(); + if (prefix.length() == 0 || !Character.isLowerCase(prefix.charAt(0))) { + return; + } + + final PsiElement position = parameters.getPosition(); + final PsiElement parent = position.getParent(); + if (!(parent instanceof PsiReferenceExpression)) { + return; + } + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)parent; + if (referenceExpression.isQualified()) { + return; + } + + completeStaticMembers(position).processStaticMethodsGlobally(result); + } + + public static StaticMemberProcessor completeStaticMembers(final PsiElement position) { + final StaticMemberProcessor processor = new StaticMemberProcessor(position) { + @NotNull + @Override + protected LookupElement createLookupElement(@NotNull PsiMember member, @NotNull final PsiClass containingClass, boolean shouldImport) { + if (member instanceof PsiMethod) { + final JavaMethodCallElement element = new JavaMethodCallElement((PsiMethod)member, true); + element.setShouldBeImported(shouldImport); + return element; + } + return new VariableLookupItem((PsiVariable)member) { + @Override + public void handleInsert(InsertionContext context) { + context.commitDocument(); + final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiReferenceExpression.class, false); + if (ref != null) { + ref.bindToElementViaStaticImport(containingClass); + PostprocessReformattingAspect.getInstance(ref.getProject()).doPostponedFormatting(); + } + super.handleInsert(context); + } + }; + } + + }; + final PsiFile file = position.getContainingFile(); + if (file instanceof PsiJavaFile) { + final PsiImportList importList = ((PsiJavaFile)file).getImportList(); + if (importList != null) { + for (PsiImportStaticStatement statement : importList.getImportStaticStatements()) { + processor.importMembersOf(statement.resolveTargetClass()); + } + } + } + + return processor; + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ModifierChooser.java b/java/java-impl/src/com/intellij/codeInsight/completion/ModifierChooser.java index 89f73a759dac..4326012d0bb7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ModifierChooser.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ModifierChooser.java @@ -1,178 +1,178 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.psi.*; -import com.intellij.psi.filters.*; -import com.intellij.psi.filters.classes.InterfaceFilter; -import com.intellij.psi.impl.source.jsp.jspJava.JspClassLevelDeclarationStatement; -import com.intellij.psi.javadoc.PsiDocComment; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * Created by IntelliJ IDEA. - * User: ik - * Date: 17.02.2003 - * Time: 17:03:09 - * To change this template use Options | File Templates. - */ - -@SuppressWarnings({"HardCodedStringLiteral"}) -public class ModifierChooser - implements KeywordChooser{ - private static final Map myMap = new HashMap(); - - static { - myMap.put(new NotFilter(new InterfaceFilter()), new String[][]{ - new String[]{"public", "protected", "private"}, - new String[]{"static"}, - new String[]{"final", "abstract"}, - new String[]{"native"}, - new String[]{"synchronized"}, - new String[]{"strictfp"}, - new String[]{"volatile"}, - new String[]{"transient"} - }); - - myMap.put(new InterfaceFilter(), new String[][]{ - new String[]{"public", "protected"}, - new String[]{"static"}, - new String[]{"final"} - }); - - - myMap.put(new ClassFilter(PsiJavaFile.class), new String[][]{ - new String[]{"public"}, - new String[]{"final", "abstract"} - }); - - myMap.put(new OrFilter(new ClassFilter(PsiStatement.class), new ClassFilter(PsiCodeBlock.class)), new String[][]{ - new String[]{"final"} - }); - - myMap.put(new ClassFilter(PsiParameterList.class), new String[][]{ - new String[]{"final"} - }); - } - - public String[] getKeywords(CompletionContext context, PsiElement position){ - final List ret = new ArrayList(); - try{ - PsiElement scope; - - if(position == null) - scope = context.file; - else - scope = position.getParent(); - - final PsiModifierList list = getModifierList(position); - -scopes: - while (scope != null) { - for (final Object o : myMap.keySet()) { - final ElementFilter filter = (ElementFilter)o; - if (filter.isClassAcceptable(scope.getClass()) && filter.isAcceptable(scope, scope.getParent())) { - final String[][] keywordSets = myMap.get(filter); - for (int i = 0; i < keywordSets.length; i++) { - final String[] keywords = keywordSets[keywordSets.length - i - 1]; - boolean containModifierFlag = false; - if (list != null) { - for (@Modifier String keyword : keywords) { - if (list.hasModifierProperty(keyword)) { - containModifierFlag = true; - break; - } - } - } - if (!containModifierFlag) { - ContainerUtil.addAll(ret, keywords); - } - } - break scopes; - } - } - scope = scope.getParent(); - if (scope instanceof JspClassLevelDeclarationStatement) { - scope = scope.getContext(); - } - if (scope instanceof PsiDirectory) break; - } - } - catch(Exception e){} - return ArrayUtil.toStringArray(ret); - } - - private static PsiModifierList getModifierList(PsiElement element) - throws Exception{ - if(element == null){ - return null; - } - if(element.getParent() instanceof PsiModifierList) - return (PsiModifierList)element.getParent(); - - final PsiElement prev = FilterPositionUtil.searchNonSpaceNonCommentBack(element); - - if(prev != null) { - final PsiModifierList modifierList = PsiTreeUtil.getParentOfType(prev, PsiModifierList.class); - if(modifierList != null){ - return modifierList; - } - } - - PsiElement parent = element.getParent(); - while(parent != null && (parent instanceof PsiJavaCodeReferenceElement - || parent instanceof PsiErrorElement || parent instanceof PsiTypeElement - || parent instanceof PsiMethod || parent instanceof PsiVariable - || parent instanceof PsiDeclarationStatement || parent instanceof PsiImportList - || parent instanceof PsiDocComment - || element.getText().equals(parent.getText()))){ - parent = parent.getParent(); - if (parent instanceof JspClassLevelDeclarationStatement) { - parent = parent.getContext(); - } - } - - if(parent == null) throw new Exception(); - for (final Object o : myMap.keySet()) { - final ElementFilter filter = (ElementFilter)o; - if (filter.isClassAcceptable(parent.getClass()) && filter.isAcceptable(parent, parent.getParent())) { - if (parent instanceof PsiParameterList) { - if (prev == null || Arrays.asList(new String[]{"(", ","}).contains(prev.getText()) - || Arrays.asList(new String[]{"(", ","}).contains(element.getText())) { - return null; - } - } - else if (prev == null || JavaCompletionData.END_OF_BLOCK.isAcceptable(element, prev.getParent())) { - return null; - } - } - } - - throw new Exception("Can't find modifier list"); - } - - public String toString(){ - return "modifier-chooser"; - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.psi.*; +import com.intellij.psi.filters.*; +import com.intellij.psi.filters.classes.InterfaceFilter; +import com.intellij.psi.impl.source.jsp.jspJava.JspClassLevelDeclarationStatement; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * Created by IntelliJ IDEA. + * User: ik + * Date: 17.02.2003 + * Time: 17:03:09 + * To change this template use Options | File Templates. + */ + +@SuppressWarnings({"HardCodedStringLiteral"}) +public class ModifierChooser + implements KeywordChooser{ + private static final Map myMap = new HashMap(); + + static { + myMap.put(new NotFilter(new InterfaceFilter()), new String[][]{ + new String[]{"public", "protected", "private"}, + new String[]{"static"}, + new String[]{"final", "abstract"}, + new String[]{"native"}, + new String[]{"synchronized"}, + new String[]{"strictfp"}, + new String[]{"volatile"}, + new String[]{"transient"} + }); + + myMap.put(new InterfaceFilter(), new String[][]{ + new String[]{"public", "protected"}, + new String[]{"static"}, + new String[]{"final"} + }); + + + myMap.put(new ClassFilter(PsiJavaFile.class), new String[][]{ + new String[]{"public"}, + new String[]{"final", "abstract"} + }); + + myMap.put(new OrFilter(new ClassFilter(PsiStatement.class), new ClassFilter(PsiCodeBlock.class)), new String[][]{ + new String[]{"final"} + }); + + myMap.put(new ClassFilter(PsiParameterList.class), new String[][]{ + new String[]{"final"} + }); + } + + public String[] getKeywords(CompletionContext context, PsiElement position){ + final List ret = new ArrayList(); + try{ + PsiElement scope; + + if(position == null) + scope = context.file; + else + scope = position.getParent(); + + final PsiModifierList list = getModifierList(position); + +scopes: + while (scope != null) { + for (final Object o : myMap.keySet()) { + final ElementFilter filter = (ElementFilter)o; + if (filter.isClassAcceptable(scope.getClass()) && filter.isAcceptable(scope, scope.getParent())) { + final String[][] keywordSets = myMap.get(filter); + for (int i = 0; i < keywordSets.length; i++) { + final String[] keywords = keywordSets[keywordSets.length - i - 1]; + boolean containModifierFlag = false; + if (list != null) { + for (@Modifier String keyword : keywords) { + if (list.hasModifierProperty(keyword)) { + containModifierFlag = true; + break; + } + } + } + if (!containModifierFlag) { + ContainerUtil.addAll(ret, keywords); + } + } + break scopes; + } + } + scope = scope.getParent(); + if (scope instanceof JspClassLevelDeclarationStatement) { + scope = scope.getContext(); + } + if (scope instanceof PsiDirectory) break; + } + } + catch(Exception e){} + return ArrayUtil.toStringArray(ret); + } + + private static PsiModifierList getModifierList(PsiElement element) + throws Exception{ + if(element == null){ + return null; + } + if(element.getParent() instanceof PsiModifierList) + return (PsiModifierList)element.getParent(); + + final PsiElement prev = FilterPositionUtil.searchNonSpaceNonCommentBack(element); + + if(prev != null) { + final PsiModifierList modifierList = PsiTreeUtil.getParentOfType(prev, PsiModifierList.class); + if(modifierList != null){ + return modifierList; + } + } + + PsiElement parent = element.getParent(); + while(parent != null && (parent instanceof PsiJavaCodeReferenceElement + || parent instanceof PsiErrorElement || parent instanceof PsiTypeElement + || parent instanceof PsiMethod || parent instanceof PsiVariable + || parent instanceof PsiDeclarationStatement || parent instanceof PsiImportList + || parent instanceof PsiDocComment + || element.getText().equals(parent.getText()))){ + parent = parent.getParent(); + if (parent instanceof JspClassLevelDeclarationStatement) { + parent = parent.getContext(); + } + } + + if(parent == null) throw new Exception(); + for (final Object o : myMap.keySet()) { + final ElementFilter filter = (ElementFilter)o; + if (filter.isClassAcceptable(parent.getClass()) && filter.isAcceptable(parent, parent.getParent())) { + if (parent instanceof PsiParameterList) { + if (prev == null || Arrays.asList(new String[]{"(", ","}).contains(prev.getText()) + || Arrays.asList(new String[]{"(", ","}).contains(element.getText())) { + return null; + } + } + else if (prev == null || JavaCompletionData.END_OF_BLOCK.isAcceptable(element, prev.getParent())) { + return null; + } + } + } + + throw new Exception("Can't find modifier list"); + } + + public String toString(){ + return "modifier-chooser"; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java index 5b6144fa559b..2a7c843c2ae3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java @@ -1,831 +1,831 @@ -/* - * 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. - */ - -/* - * Checks and Highlights problems with classes - * User: cdr - * Date: Aug 19, 2002 - */ -package com.intellij.codeInsight.daemon.impl.analysis; - -import com.intellij.codeInsight.ClassUtil; -import com.intellij.codeInsight.ExceptionUtil; -import com.intellij.codeInsight.daemon.JavaErrorMessages; -import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.codeInsight.daemon.impl.HighlightInfoType; -import com.intellij.codeInsight.daemon.impl.RefCountHolder; -import com.intellij.codeInsight.daemon.impl.quickfix.*; -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.codeInsight.intention.QuickFixFactory; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.jsp.jspJava.JspClass; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -public class HighlightClassUtil { - public static final String INTERFACE_EXPECTED = JavaErrorMessages.message("interface.expected"); - public static final String NO_INTERFACE_EXPECTED = JavaErrorMessages.message("no.interface.expected"); - private static final String STATIC_DECLARATION_IN_INNER_CLASS = JavaErrorMessages.message("static.declaration.in.inner.class"); - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); - - /** - * new ref(...) or new ref(..) { ... } where ref is abstract class - */ - static HighlightInfo checkAbstractInstantiation(PsiJavaCodeReferenceElement ref) { - PsiElement parent = ref.getParent(); - HighlightInfo highlightInfo = null; - if (parent instanceof PsiNewExpression && !PsiUtilBase.hasErrorElementChild(parent)) { - if (((PsiNewExpression)parent).getType() instanceof PsiArrayType) return null; - PsiElement refElement = ref.resolve(); - if (refElement instanceof PsiClass) { - highlightInfo = checkInstantiationOfAbstractClass((PsiClass)refElement, ref); - } - } - else if (parent instanceof PsiAnonymousClass - && parent.getParent() instanceof PsiNewExpression - && !PsiUtilBase.hasErrorElementChild(parent.getParent())) { - PsiAnonymousClass aClass = (PsiAnonymousClass)parent; - highlightInfo = checkClassWithAbstractMethods(aClass, ref.getTextRange()); - } - return highlightInfo; - } - - static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, TextRange textRange) { - PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); - - if (abstractMethod == null || abstractMethod.getContainingClass() == null) { - return null; - } - String baseClassName = HighlightUtil.formatClass(aClass, false); - String methodName = HighlightUtil.formatMethod(abstractMethod); - String message = JavaErrorMessages.message("class.must.be.abstract", - baseClassName, - methodName, - HighlightUtil.formatClass(abstractMethod.getContainingClass(), false)); - - HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message); - if (ClassUtil.getAnyMethodToImplement(aClass) != null) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createImplementMethodsFix(aClass)); - } - if (!(aClass instanceof PsiAnonymousClass) - && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) { - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - } - return errorResult; - } - - static HighlightInfo checkClassMustBeAbstract(final PsiClass aClass, final TextRange textRange) { - if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass.getRBrace() == null - || aClass.isEnum() && hasEnumConstants(aClass) - ) { - return null; - } - return checkClassWithAbstractMethods(aClass, textRange); - } - - - public static HighlightInfo checkInstantiationOfAbstractClass(PsiClass aClass, PsiElement highlighElement) { - HighlightInfo errorResult = null; - if (aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { - String baseClassName = aClass.getName(); - String message = JavaErrorMessages.message("abstract.cannot.be.instantiated", baseClassName); - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, highlighElement, message); - if (!aClass.isInterface() && ClassUtil.getAnyAbstractMethod(aClass) == null) { - // suggest to make not abstract only if possible - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - } - } - return errorResult; - } - - private static boolean hasEnumConstants(PsiClass aClass) { - PsiField[] fields = aClass.getFields(); - for (PsiField field : fields) { - if (field instanceof PsiEnumConstant) return true; - } - return false; - } - - - static HighlightInfo checkDuplicateTopLevelClass(PsiClass aClass) { - if (!(aClass.getParent() instanceof PsiFile)) return null; - String qualifiedName = aClass.getQualifiedName(); - if (qualifiedName == null) return null; - int numOfClassesToFind = 2; - if (qualifiedName.contains("$")) { - qualifiedName = qualifiedName.replaceAll("\\$", "."); - numOfClassesToFind = 1; - } - PsiManager manager = aClass.getManager(); - Module module = ModuleUtil.findModuleForPsiElement(aClass); - if (module == null) return null; - - PsiClass[] classes = JavaPsiFacade.getInstance(aClass.getProject()).findClasses(qualifiedName, GlobalSearchScope.moduleScope(module)); - if (classes.length < numOfClassesToFind) return null; - String dupFileName = null; - for (PsiClass dupClass : classes) { - // do not use equals - if (dupClass != aClass) { - VirtualFile file = dupClass.getContainingFile().getVirtualFile(); - if (file != null && manager.isInProject(dupClass)) { - dupFileName = FileUtil.toSystemDependentName(file.getPath()); - break; - } - } - } - if (dupFileName == null) return null; - String message = JavaErrorMessages.message("duplicate.class.in.other.file", dupFileName); - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message); - } - - - static HighlightInfo checkDuplicateNestedClass(PsiClass aClass) { - if (aClass == null) return null; - PsiElement parent = aClass; - if (aClass.getParent() instanceof PsiDeclarationStatement) { - parent = aClass.getParent(); - } - String name = aClass.getName(); - if (name == null) return null; - boolean duplicateFound = false; - boolean checkSiblings = true; - while (parent != null) { - if (parent instanceof PsiFile) break; - PsiElement element = checkSiblings ? parent.getPrevSibling() : null; - if (element == null) { - element = parent.getParent(); - // JLS 14.3: - // The name of a local class C may not be redeclared - // as a local class of the directly enclosing method, constructor, or initializer block within the scope of C - // , or a compile-time error occurs. - // However, a local class declaration may be shadowed (?6.3.1) - // anywhere inside a class declaration nested within the local class declaration's scope. - if (element instanceof PsiMethod || element instanceof PsiClass) { - checkSiblings = false; - } - } - parent = element; - - if (element instanceof PsiDeclarationStatement) element = PsiTreeUtil.getChildOfType(element, PsiClass.class); - if (element instanceof PsiClass && name.equals(((PsiClass)element).getName())) { - duplicateFound = true; - break; - } - } - - if (duplicateFound) { - String message = JavaErrorMessages.message("duplicate.class", name); - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message); - } - return null; - } - - - - static HighlightInfo checkPublicClassInRightFile(PsiKeyword keyword, PsiModifierList psiModifierList) { - // todo most testcase classes located in wrong files - if (ApplicationManager.getApplication().isUnitTestMode()) return null; - if (new PsiMatcherImpl(keyword) - .dot(PsiMatchers.hasText(PsiModifier.PUBLIC)) - .parent(PsiMatchers.hasClass(PsiModifierList.class)) - .parent(PsiMatchers.hasClass(PsiClass.class)) - .parent(PsiMatchers.hasClass(PsiJavaFile.class)) - .getElement() == null) { - return null; - } - PsiClass aClass = (PsiClass)keyword.getParent().getParent(); - PsiJavaFile file = (PsiJavaFile)aClass.getContainingFile(); - VirtualFile virtualFile = file.getVirtualFile(); - HighlightInfo errorResult = null; - if (virtualFile != null && !aClass.getName().equals(virtualFile.getNameWithoutExtension()) && aClass.getNameIdentifier() != null) { - String message = JavaErrorMessages.message("public.class.should.be.named.after.file", aClass.getName()); - - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, aClass.getNameIdentifier(), message); - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(psiModifierList, PsiModifier.PUBLIC, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - PsiClass[] classes = file.getClasses(); - if (classes.length > 1) { - QuickFixAction.registerQuickFixAction(errorResult, new MoveClassToSeparateFileFix(aClass)); - } - for (PsiClass otherClass : classes) { - if (!otherClass.getManager().areElementsEquivalent(otherClass, aClass) && otherClass.hasModifierProperty(PsiModifier.PUBLIC) - && otherClass.getName().equals(virtualFile.getNameWithoutExtension())) { - return errorResult; - } - } - QuickFixAction.registerQuickFixAction(errorResult, new RenameFileFix(aClass.getName() + "." + StdFileTypes.JAVA.getDefaultExtension())); - QuickFixAction.registerQuickFixAction(errorResult, new RenameElementFix(aClass)); - } - return errorResult; - } - - - private static HighlightInfo checkStaticFieldDeclarationInInnerClass(PsiKeyword keyword) { - if (getEnclosingStaticClass(keyword, PsiField.class) == null) { - return null; - } - PsiField field = (PsiField)keyword.getParent().getParent(); - if (PsiUtilBase.hasErrorElementChild(field)) return null; - // except compile time constants - if (PsiUtil.isCompileTimeConstant(field)) { - return null; - } - HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - keyword, - STATIC_DECLARATION_IN_INNER_CLASS); - IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.STATIC, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix1); - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(field.getContainingClass(), PsiModifier.STATIC, true, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return errorResult; - } - - - private static HighlightInfo checkStaticMethodDeclarationInInnerClass(PsiKeyword keyword) { - if (getEnclosingStaticClass(keyword, PsiMethod.class) == null) { - return null; - } - PsiMethod method = (PsiMethod)keyword.getParent().getParent(); - if (PsiUtilBase.hasErrorElementChild(method)) return null; - HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - keyword, - STATIC_DECLARATION_IN_INNER_CLASS); - IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix1); - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix((PsiClass)keyword.getParent().getParent().getParent(), PsiModifier.STATIC, true, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return errorResult; - } - - - private static HighlightInfo checkStaticInitializerDeclarationInInnerClass(PsiKeyword keyword) { - if (getEnclosingStaticClass(keyword, PsiClassInitializer.class) == null) { - return null; - } - PsiClassInitializer initializer = (PsiClassInitializer)keyword.getParent().getParent(); - if (PsiUtilBase.hasErrorElementChild(initializer)) return null; - HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - keyword, - STATIC_DECLARATION_IN_INNER_CLASS); - IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(initializer, PsiModifier.STATIC, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix1); - PsiClass owner = (PsiClass)keyword.getParent().getParent().getParent(); - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(owner, PsiModifier.STATIC, true, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return errorResult; - } - - private static PsiElement getEnclosingStaticClass(PsiKeyword keyword, Class parentClass) { - return new PsiMatcherImpl(keyword) - .dot(PsiMatchers.hasText(PsiModifier.STATIC)) - .parent(PsiMatchers.hasClass(PsiModifierList.class)) - .parent(PsiMatchers.hasClass(parentClass)) - .parent(PsiMatchers.hasClass(PsiClass.class)) - .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, false)) - .parent(PsiMatchers.hasClass(new Class[]{PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class})) - .getElement(); - } - - - private static HighlightInfo checkStaticClassDeclarationInInnerClass(PsiKeyword keyword) { - // keyword points to 'class' or 'interface' or 'enum' - if (new PsiMatcherImpl(keyword) - .parent(PsiMatchers.hasClass(PsiClass.class)) - .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, true)) - .parent(PsiMatchers.hasClass(PsiClass.class)) - .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, false)) - .parent(PsiMatchers.hasClass(new Class[]{PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class})) - .getElement() == null) { - return null; - } - PsiClass aClass = (PsiClass)keyword.getParent(); - if (PsiUtilBase.hasErrorElementChild(aClass)) return null; - // highlight 'static' keyword if any, or class or interface if not - PsiElement context = null; - PsiModifierList modifierList = aClass.getModifierList(); - PsiElement[] children = modifierList.getChildren(); - for (PsiElement element : children) { - if (Comparing.equal(element.getText(), PsiModifier.STATIC)) { - context = element; - break; - } - } - TextRange textRange = context == null ? null : context.getTextRange(); - if (textRange == null) { - textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - } - HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, STATIC_DECLARATION_IN_INNER_CLASS); - if (context != keyword) { - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - } - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass.getContainingClass(), PsiModifier.STATIC, true, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return errorResult; - } - - - static HighlightInfo checkStaticDeclarationInInnerClass(PsiKeyword keyword) { - HighlightInfo errorResult = checkStaticFieldDeclarationInInnerClass(keyword); - if (errorResult != null) return errorResult; - errorResult = checkStaticMethodDeclarationInInnerClass(keyword); - if (errorResult != null) return errorResult; - errorResult = checkStaticClassDeclarationInInnerClass(keyword); - if (errorResult != null) return errorResult; - errorResult = checkStaticInitializerDeclarationInInnerClass(keyword); - if (errorResult != null) return errorResult; - return null; - } - - static HighlightInfo checkExtendsAllowed(PsiReferenceList list) { - if (list.getParent() instanceof PsiClass) { - PsiClass aClass = (PsiClass)list.getParent(); - if (aClass.isEnum()) { - boolean isExtends = list.equals(aClass.getExtendsList()); - if (isExtends) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, list, JavaErrorMessages.message("extends.after.enum")); - } - } - } - return null; - } - - static HighlightInfo checkImplementsAllowed(PsiReferenceList list) { - if (list.getParent() instanceof PsiClass) { - PsiClass aClass = (PsiClass)list.getParent(); - if (aClass.isInterface()) { - boolean isImplements = list.equals(aClass.getImplementsList()); - if (isImplements) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, list, JavaErrorMessages.message("implements.after.interface")); - } - } - } - return null; - } - - - static HighlightInfo checkExtendsClassAndImplementsInterface(PsiReferenceList referenceList, - JavaResolveResult resolveResult, - PsiJavaCodeReferenceElement ref) { - PsiClass aClass = (PsiClass)referenceList.getParent(); - boolean isImplements = referenceList.equals(aClass.getImplementsList()); - boolean isInterface = aClass.isInterface(); - if (isInterface && isImplements) return null; - boolean mustBeInterface = isImplements || isInterface; - HighlightInfo errorResult = null; - PsiClass extendFrom = (PsiClass)resolveResult.getElement(); - if (extendFrom.isInterface() != mustBeInterface) { - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - ref, - mustBeInterface ? INTERFACE_EXPECTED : NO_INTERFACE_EXPECTED); - PsiClassType type = - JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(ref); - QuickFixAction.registerQuickFixAction(errorResult, new ChangeExtendsToImplementsFix(aClass, type)); - } - return errorResult; - } - - - static HighlightInfo checkCannotInheritFromFinal(PsiClass superClass, PsiElement elementToHighlight) { - HighlightInfo errorResult = null; - if (superClass.hasModifierProperty(PsiModifier.FINAL) || superClass.isEnum()) { - String message = JavaErrorMessages.message("inheritance.from.final.class", superClass.getQualifiedName()); - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, message); - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(superClass, PsiModifier.FINAL, false, false); - QuickFixAction.registerQuickFixAction(errorResult, fix); - } - return errorResult; - } - - - static HighlightInfo checkAnonymousInheritFinal(PsiNewExpression expression) { - PsiAnonymousClass aClass = PsiTreeUtil.getChildOfType(expression, PsiAnonymousClass.class); - if (aClass == null) return null; - PsiClassType baseClassReference = aClass.getBaseClassType(); - PsiClass baseClass = baseClassReference.resolve(); - if (baseClass == null) return null; - return checkCannotInheritFromFinal(baseClass, aClass.getBaseClassReference()); - } - - - - - private static String checkDefaultConstructorThrowsException(PsiMethod constructor, @NotNull PsiClassType[] handledExceptions) { - PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes(); - List exceptions = new ArrayList(); - for (PsiClassType referencedType : referencedTypes) { - if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) { - exceptions.add(referencedType); - } - } - if (!exceptions.isEmpty()) { - return HighlightUtil.getUnhandledExceptionsDescriptor(exceptions); - } - return null; - } - - - public static HighlightInfo checkClassDoesNotCallSuperConstructorOrHandleExceptions(PsiClass aClass, - RefCountHolder refCountHolder, - final PsiResolveHelper resolveHelper) { - if (aClass.isEnum()) return null; - // check only no-ctr classes. Problem with specific constructor will be highlighted inside it - if (aClass.getConstructors().length != 0) return null; - // find no-args base class ctr - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, PsiClassType.EMPTY_ARRAY); - } - - public static HighlightInfo checkBaseClassDefaultConstructorProblem(PsiClass aClass, - RefCountHolder refCountHolder, - PsiResolveHelper resolveHelper, - TextRange textRange, - @NotNull PsiClassType[] handledExceptions) { - PsiClass baseClass = aClass.getSuperClass(); - if (baseClass == null) return null; - PsiMethod[] constructors = baseClass.getConstructors(); - if (constructors.length == 0) return null; - - for (PsiMethod constructor : constructors) { - if (resolveHelper.isAccessible(constructor, aClass, null)) { - if (constructor.getParameterList().getParametersCount() == 0 || - constructor.getParameterList().getParametersCount() == 1 && constructor.isVarArgs() - ) { - // it is an error if base ctr throws exceptions - String description = checkDefaultConstructorThrowsException(constructor, handledExceptions); - if (description != null) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); - } - if (refCountHolder != null) { - refCountHolder.registerLocallyReferenced(constructor); - } - return null; - } - } - } - - String description = JavaErrorMessages.message("no.default.constructor.available", HighlightUtil.formatClass(baseClass)); - - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); - QuickFixAction.registerQuickFixAction(info, new CreateConstructorMatchingSuperFix(aClass)); - - return info; - } - - - static HighlightInfo checkInterfaceCannotBeLocal(PsiClass aClass) { - if (PsiUtil.isLocalClass(aClass)) { - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - textRange, - JavaErrorMessages.message("interface.cannot.be.local")); - } - return null; - } - - - public static HighlightInfo checkCyclicInheritance(PsiClass aClass) { - PsiClass circularClass = getCircularClass(aClass, new HashSet()); - if (circularClass != null) { - String description = JavaErrorMessages.message("cyclic.inheritance", HighlightUtil.formatClass(circularClass)); - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); - } - return null; - } - - private static PsiClass getCircularClass(PsiClass aClass, Collection usedClasses) { - if (usedClasses.contains(aClass)) { - return aClass; - } - try { - usedClasses.add(aClass); - PsiClass[] superTypes = aClass.getSupers(); - for (PsiElement superType : superTypes) { - while (superType instanceof PsiClass) { - if (!"java.lang.Object".equals(((PsiClass)superType).getQualifiedName())) { - PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses); - if (circularClass != null) return circularClass; - } - // check class qualifier - superType = superType.getParent(); - } - } - } - finally { - usedClasses.remove(aClass); - } - return null; - } - - - public static HighlightInfo checkExtendsDuplicate(PsiJavaCodeReferenceElement element, PsiElement resolved) { - if (!(element.getParent() instanceof PsiReferenceList)) return null; - PsiReferenceList list = (PsiReferenceList)element.getParent(); - if (!(list.getParent() instanceof PsiClass)) return null; - if (!(resolved instanceof PsiClass)) return null; - PsiClass aClass = (PsiClass)resolved; - PsiClassType[] referencedTypes = list.getReferencedTypes(); - int dupCount = 0; - for (PsiClassType referencedType : referencedTypes) { - PsiClass resolvedElement = referencedType.resolve(); - if (resolvedElement != null && list.getManager().areElementsEquivalent(resolvedElement, aClass)) { - dupCount++; - } - } - if (dupCount > 1) { - String description = JavaErrorMessages.message("duplicate.class", HighlightUtil.formatClass(aClass)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, description); - } - return null; - } - - - public static HighlightInfo checkClassAlreadyImported(PsiClass aClass, PsiElement elementToHighlight) { - PsiFile file = aClass.getContainingFile(); - if (!(file instanceof PsiJavaFile)) return null; - PsiJavaFile javaFile = (PsiJavaFile)file; - // check only top-level classes conflicts - if (aClass.getParent() != javaFile) return null; - PsiImportList importList = javaFile.getImportList(); - if (importList == null) return null; - PsiImportStatementBase[] importStatements = importList.getAllImportStatements(); - for (PsiImportStatementBase importStatement : importStatements) { - if (importStatement.isOnDemand()) continue; - PsiElement resolved = importStatement.resolve(); - if (resolved instanceof PsiClass && !resolved.equals(aClass) && Comparing.equal(aClass.getName(), ((PsiClass)resolved).getName(), true)) { - String description = JavaErrorMessages.message("class.already.imported", HighlightUtil.formatClass(aClass, false)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - elementToHighlight, - description); - } - } - return null; - } - - - public static HighlightInfo checkClassExtendsOnlyOneClass(PsiReferenceList list) { - PsiClassType[] referencedTypes = list.getReferencedTypes(); - PsiElement parent = list.getParent(); - if (!(parent instanceof PsiClass)) return null; - - PsiClass aClass = (PsiClass)parent; - if (!aClass.isInterface() - && referencedTypes.length > 1 - && aClass.getExtendsList() == list) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - list, - JavaErrorMessages.message("class.cannot.extend.multiple.classes")); - } - - return null; - } - - - public static HighlightInfo checkThingNotAllowedInInterface(PsiElement element, PsiClass aClass) { - if (aClass == null || !aClass.isInterface()) return null; - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, JavaErrorMessages.message("not.allowed.in.interface")); - } - - - public static HighlightInfo checkQualifiedNewOfStaticClass(PsiNewExpression expression) { - PsiExpression qualifier = expression.getQualifier(); - if (qualifier == null) return null; - PsiType type = expression.getType(); - if (type instanceof PsiArrayType) { - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - expression, - JavaErrorMessages.message("invalid.qualified.new")); - QuickFixAction.registerQuickFixAction(info, new RemoveNewQualifierFix(expression, null)); - return info; - } - PsiClass aClass = PsiUtil.resolveClassInType(type); - if (aClass != null && aClass.hasModifierProperty(PsiModifier.STATIC)) { - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, - expression, - JavaErrorMessages.message("qualified.new.of.static.class")); - if (!aClass.isEnum()) { - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false); - QuickFixAction.registerQuickFixAction(info, fix); - } - QuickFixAction.registerQuickFixAction(info, new RemoveNewQualifierFix(expression, aClass)); - return info; - } - return null; - } - - - /** - * class c extends foreign.inner {} - * - * @param extendRef points to the class in the extends list - * @param resolved extendRef resolved - */ - public static HighlightInfo checkClassExtendsForeignInnerClass(PsiJavaCodeReferenceElement extendRef, PsiElement resolved) { - PsiElement parent = extendRef.getParent(); - if (!(parent instanceof PsiReferenceList)) { - return null; - } - PsiElement grand = parent.getParent(); - if (!(grand instanceof PsiClass)) { - return null; - } - PsiClass aClass = (PsiClass)grand; - if (aClass.getExtendsList() != parent || aClass instanceof PsiTypeParameter) { - return null; - } - if (!(resolved instanceof PsiClass)) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, extendRef, JavaErrorMessages.message("class.name.expected")); - } - PsiClass base = (PsiClass)resolved; - // must be inner class - if (!PsiUtil.isInnerClass(base)) return null; - PsiClass baseClass = base.getContainingClass(); - - if (!hasEnclosingInstanceInScope(baseClass, extendRef, true) && !qualifiedNewCalledInConstructors(aClass, baseClass)) { - String description = JavaErrorMessages.message("no.enclosing.instance.in.scope", HighlightUtil.formatClass(baseClass)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, extendRef, description); - } - - return null; - } - - private static boolean qualifiedNewCalledInConstructors(final PsiClass aClass, final PsiClass baseClass) { - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) return false; - for (PsiMethod constructor : constructors) { - PsiCodeBlock body = constructor.getBody(); - if (body == null) return false; - PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) return false; - PsiStatement firstStatement = statements[0]; - if (!(firstStatement instanceof PsiExpressionStatement)) return false; - PsiExpression expression = ((PsiExpressionStatement)firstStatement).getExpression(); - if (!HighlightUtil.isSuperOrThisMethodCall(expression)) return false; - PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; - if (PsiKeyword.THIS.equals(methodCallExpression.getMethodExpression().getReferenceName())) continue; - PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); - PsiExpression qualifierExpression = referenceExpression.getQualifierExpression(); - if (!(qualifierExpression instanceof PsiReferenceExpression)) return false; - PsiType type = qualifierExpression.getType(); - if (!(type instanceof PsiClassType)) return false; - PsiClass resolved = ((PsiClassType)type).resolve(); - if (resolved != baseClass) return false; - } - return true; - } - - - static HighlightInfo checkExternalizableHasPublicNoArgsConstructor(PsiClass aClass, PsiElement context) { - if (!isExternalizable(aClass) || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { - return null; - } - PsiMethod[] constructors = aClass.getConstructors(); - boolean hasPublicNoArgsConstructor = constructors.length == 0; - for (PsiMethod constructor : constructors) { - if (constructor.getParameterList().getParametersCount() == 0 && constructor.hasModifierProperty(PsiModifier.PUBLIC)) { - hasPublicNoArgsConstructor = true; - break; - } - } - if (!hasPublicNoArgsConstructor) { - HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WARNING, - context, - JavaErrorMessages.message("externalizable.class.should.have.public.constructor")); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddDefaultConstructorFix(aClass)); - return highlightInfo; - } - return null; - } - - private static boolean isExternalizable(PsiClass aClass) { - PsiManager manager = aClass.getManager(); - PsiClass externalizableClass = - JavaPsiFacade.getInstance(manager.getProject()).findClass("java.io.Externalizable", aClass.getResolveScope()); - return externalizableClass != null && aClass.isInheritor(externalizableClass, true); - } - - public static boolean hasEnclosingInstanceInScope(PsiClass aClass, PsiElement scope, final boolean isSuperClassAccepted) { - PsiManager manager = aClass.getManager(); - PsiElement place = scope; - while (place != null && place != aClass && !(place instanceof PsiFile)) { - if (place instanceof PsiClass) { - if (isSuperClassAccepted) { - if (InheritanceUtil.isInheritorOrSelf((PsiClass)place, aClass, true)) return true; - } - else { - if (manager.areElementsEquivalent(place, aClass)) return true; - } - } - if (place instanceof PsiModifierListOwner && ((PsiModifierListOwner)place).hasModifierProperty(PsiModifier.STATIC)) { - return false; - } - place = place.getParent(); - } - return place == aClass; - } - - @Nullable - public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiNewExpression expression) { - PsiType type = expression.getType(); - if (type == null || type instanceof PsiArrayType || type instanceof PsiPrimitiveType) return null; - PsiClass aClass = PsiUtil.resolveClassInType(type); - if (aClass == null) return null; - if (aClass instanceof PsiAnonymousClass) { - aClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); - if (aClass == null) return null; - } - - if (!PsiUtil.isInnerClass(aClass)) return null; - PsiClass outerClass = aClass.getContainingClass(); - if (outerClass == null) return null; - - PsiElement placeToSearchEnclosingFrom; - PsiExpression qualifier = expression.getQualifier(); - if (qualifier != null) { - PsiType qtype = qualifier.getType(); - placeToSearchEnclosingFrom = PsiUtil.resolveClassInType(qtype); - } - else { - placeToSearchEnclosingFrom = expression; - } - - if (outerClass instanceof JspClass - || hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true)) return null; - return reportIllegalEnclosingUsage(placeToSearchEnclosingFrom, aClass, outerClass, expression); - } - - public static HighlightInfo checkSuperQualifierType(PsiMethodCallExpression superCall) { - if (!HighlightUtil.isSuperMethodCall(superCall)) return null; - PsiMethod ctr = PsiTreeUtil.getParentOfType(superCall, PsiMethod.class, true, PsiMember.class); - if (ctr == null) return null; - PsiClass targetClass = ctr.getContainingClass().getSuperClass(); - if (targetClass == null) return null; - PsiExpression qualifier = superCall.getMethodExpression().getQualifierExpression(); - if (qualifier != null && PsiUtil.isInnerClass(targetClass)) { - PsiClass outerClass = targetClass.getContainingClass(); - PsiClassType outerType = JavaPsiFacade.getInstance(superCall.getProject()).getElementFactory().createType(outerClass); - return HighlightUtil.checkAssignability(outerType, null, qualifier, qualifier); - } - return null; - } - - @Nullable - public static HighlightInfo reportIllegalEnclosingUsage(PsiElement place, - PsiClass aClass, PsiClass outerClass, - PsiElement elementToHighlight) { - if (outerClass != null && !PsiTreeUtil.isContextAncestor(outerClass, place, false)) { - String description = JavaErrorMessages.message("is.not.an.enclosing.class", HighlightUtil.formatClass(outerClass)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, description); - } - PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, outerClass); - if (staticParent != null) { - String description = JavaErrorMessages.message("cannot.be.referenced.from.static.context", - outerClass == null ? "" : HighlightUtil.formatClass(outerClass) + "." + PsiKeyword.THIS); - HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, description); - // make context not static or referenced class static - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false); - QuickFixAction.registerQuickFixAction(highlightInfo, fix); - if (aClass != null && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, aClass.getModifierList()) == null) { - IntentionAction fix2 = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, true, false); - QuickFixAction.registerQuickFixAction(highlightInfo, fix2); - } - return highlightInfo; - } - return null; - } - -} +/* + * 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. + */ + +/* + * Checks and Highlights problems with classes + * User: cdr + * Date: Aug 19, 2002 + */ +package com.intellij.codeInsight.daemon.impl.analysis; + +import com.intellij.codeInsight.ClassUtil; +import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInsight.daemon.JavaErrorMessages; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.RefCountHolder; +import com.intellij.codeInsight.daemon.impl.quickfix.*; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.jsp.jspJava.JspClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +public class HighlightClassUtil { + public static final String INTERFACE_EXPECTED = JavaErrorMessages.message("interface.expected"); + public static final String NO_INTERFACE_EXPECTED = JavaErrorMessages.message("no.interface.expected"); + private static final String STATIC_DECLARATION_IN_INNER_CLASS = JavaErrorMessages.message("static.declaration.in.inner.class"); + private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); + + /** + * new ref(...) or new ref(..) { ... } where ref is abstract class + */ + static HighlightInfo checkAbstractInstantiation(PsiJavaCodeReferenceElement ref) { + PsiElement parent = ref.getParent(); + HighlightInfo highlightInfo = null; + if (parent instanceof PsiNewExpression && !PsiUtilBase.hasErrorElementChild(parent)) { + if (((PsiNewExpression)parent).getType() instanceof PsiArrayType) return null; + PsiElement refElement = ref.resolve(); + if (refElement instanceof PsiClass) { + highlightInfo = checkInstantiationOfAbstractClass((PsiClass)refElement, ref); + } + } + else if (parent instanceof PsiAnonymousClass + && parent.getParent() instanceof PsiNewExpression + && !PsiUtilBase.hasErrorElementChild(parent.getParent())) { + PsiAnonymousClass aClass = (PsiAnonymousClass)parent; + highlightInfo = checkClassWithAbstractMethods(aClass, ref.getTextRange()); + } + return highlightInfo; + } + + static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, TextRange textRange) { + PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); + + if (abstractMethod == null || abstractMethod.getContainingClass() == null) { + return null; + } + String baseClassName = HighlightUtil.formatClass(aClass, false); + String methodName = HighlightUtil.formatMethod(abstractMethod); + String message = JavaErrorMessages.message("class.must.be.abstract", + baseClassName, + methodName, + HighlightUtil.formatClass(abstractMethod.getContainingClass(), false)); + + HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message); + if (ClassUtil.getAnyMethodToImplement(aClass) != null) { + QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createImplementMethodsFix(aClass)); + } + if (!(aClass instanceof PsiAnonymousClass) + && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) { + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + } + return errorResult; + } + + static HighlightInfo checkClassMustBeAbstract(final PsiClass aClass, final TextRange textRange) { + if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass.getRBrace() == null + || aClass.isEnum() && hasEnumConstants(aClass) + ) { + return null; + } + return checkClassWithAbstractMethods(aClass, textRange); + } + + + public static HighlightInfo checkInstantiationOfAbstractClass(PsiClass aClass, PsiElement highlighElement) { + HighlightInfo errorResult = null; + if (aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { + String baseClassName = aClass.getName(); + String message = JavaErrorMessages.message("abstract.cannot.be.instantiated", baseClassName); + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, highlighElement, message); + if (!aClass.isInterface() && ClassUtil.getAnyAbstractMethod(aClass) == null) { + // suggest to make not abstract only if possible + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + } + } + return errorResult; + } + + private static boolean hasEnumConstants(PsiClass aClass) { + PsiField[] fields = aClass.getFields(); + for (PsiField field : fields) { + if (field instanceof PsiEnumConstant) return true; + } + return false; + } + + + static HighlightInfo checkDuplicateTopLevelClass(PsiClass aClass) { + if (!(aClass.getParent() instanceof PsiFile)) return null; + String qualifiedName = aClass.getQualifiedName(); + if (qualifiedName == null) return null; + int numOfClassesToFind = 2; + if (qualifiedName.contains("$")) { + qualifiedName = qualifiedName.replaceAll("\\$", "."); + numOfClassesToFind = 1; + } + PsiManager manager = aClass.getManager(); + Module module = ModuleUtil.findModuleForPsiElement(aClass); + if (module == null) return null; + + PsiClass[] classes = JavaPsiFacade.getInstance(aClass.getProject()).findClasses(qualifiedName, GlobalSearchScope.moduleScope(module)); + if (classes.length < numOfClassesToFind) return null; + String dupFileName = null; + for (PsiClass dupClass : classes) { + // do not use equals + if (dupClass != aClass) { + VirtualFile file = dupClass.getContainingFile().getVirtualFile(); + if (file != null && manager.isInProject(dupClass)) { + dupFileName = FileUtil.toSystemDependentName(file.getPath()); + break; + } + } + } + if (dupFileName == null) return null; + String message = JavaErrorMessages.message("duplicate.class.in.other.file", dupFileName); + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message); + } + + + static HighlightInfo checkDuplicateNestedClass(PsiClass aClass) { + if (aClass == null) return null; + PsiElement parent = aClass; + if (aClass.getParent() instanceof PsiDeclarationStatement) { + parent = aClass.getParent(); + } + String name = aClass.getName(); + if (name == null) return null; + boolean duplicateFound = false; + boolean checkSiblings = true; + while (parent != null) { + if (parent instanceof PsiFile) break; + PsiElement element = checkSiblings ? parent.getPrevSibling() : null; + if (element == null) { + element = parent.getParent(); + // JLS 14.3: + // The name of a local class C may not be redeclared + // as a local class of the directly enclosing method, constructor, or initializer block within the scope of C + // , or a compile-time error occurs. + // However, a local class declaration may be shadowed (?6.3.1) + // anywhere inside a class declaration nested within the local class declaration's scope. + if (element instanceof PsiMethod || element instanceof PsiClass) { + checkSiblings = false; + } + } + parent = element; + + if (element instanceof PsiDeclarationStatement) element = PsiTreeUtil.getChildOfType(element, PsiClass.class); + if (element instanceof PsiClass && name.equals(((PsiClass)element).getName())) { + duplicateFound = true; + break; + } + } + + if (duplicateFound) { + String message = JavaErrorMessages.message("duplicate.class", name); + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message); + } + return null; + } + + + + static HighlightInfo checkPublicClassInRightFile(PsiKeyword keyword, PsiModifierList psiModifierList) { + // todo most testcase classes located in wrong files + if (ApplicationManager.getApplication().isUnitTestMode()) return null; + if (new PsiMatcherImpl(keyword) + .dot(PsiMatchers.hasText(PsiModifier.PUBLIC)) + .parent(PsiMatchers.hasClass(PsiModifierList.class)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .parent(PsiMatchers.hasClass(PsiJavaFile.class)) + .getElement() == null) { + return null; + } + PsiClass aClass = (PsiClass)keyword.getParent().getParent(); + PsiJavaFile file = (PsiJavaFile)aClass.getContainingFile(); + VirtualFile virtualFile = file.getVirtualFile(); + HighlightInfo errorResult = null; + if (virtualFile != null && !aClass.getName().equals(virtualFile.getNameWithoutExtension()) && aClass.getNameIdentifier() != null) { + String message = JavaErrorMessages.message("public.class.should.be.named.after.file", aClass.getName()); + + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, aClass.getNameIdentifier(), message); + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(psiModifierList, PsiModifier.PUBLIC, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + PsiClass[] classes = file.getClasses(); + if (classes.length > 1) { + QuickFixAction.registerQuickFixAction(errorResult, new MoveClassToSeparateFileFix(aClass)); + } + for (PsiClass otherClass : classes) { + if (!otherClass.getManager().areElementsEquivalent(otherClass, aClass) && otherClass.hasModifierProperty(PsiModifier.PUBLIC) + && otherClass.getName().equals(virtualFile.getNameWithoutExtension())) { + return errorResult; + } + } + QuickFixAction.registerQuickFixAction(errorResult, new RenameFileFix(aClass.getName() + "." + StdFileTypes.JAVA.getDefaultExtension())); + QuickFixAction.registerQuickFixAction(errorResult, new RenameElementFix(aClass)); + } + return errorResult; + } + + + private static HighlightInfo checkStaticFieldDeclarationInInnerClass(PsiKeyword keyword) { + if (getEnclosingStaticClass(keyword, PsiField.class) == null) { + return null; + } + PsiField field = (PsiField)keyword.getParent().getParent(); + if (PsiUtilBase.hasErrorElementChild(field)) return null; + // except compile time constants + if (PsiUtil.isCompileTimeConstant(field)) { + return null; + } + HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + keyword, + STATIC_DECLARATION_IN_INNER_CLASS); + IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.STATIC, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix1); + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(field.getContainingClass(), PsiModifier.STATIC, true, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + return errorResult; + } + + + private static HighlightInfo checkStaticMethodDeclarationInInnerClass(PsiKeyword keyword) { + if (getEnclosingStaticClass(keyword, PsiMethod.class) == null) { + return null; + } + PsiMethod method = (PsiMethod)keyword.getParent().getParent(); + if (PsiUtilBase.hasErrorElementChild(method)) return null; + HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + keyword, + STATIC_DECLARATION_IN_INNER_CLASS); + IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix1); + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix((PsiClass)keyword.getParent().getParent().getParent(), PsiModifier.STATIC, true, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + return errorResult; + } + + + private static HighlightInfo checkStaticInitializerDeclarationInInnerClass(PsiKeyword keyword) { + if (getEnclosingStaticClass(keyword, PsiClassInitializer.class) == null) { + return null; + } + PsiClassInitializer initializer = (PsiClassInitializer)keyword.getParent().getParent(); + if (PsiUtilBase.hasErrorElementChild(initializer)) return null; + HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + keyword, + STATIC_DECLARATION_IN_INNER_CLASS); + IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(initializer, PsiModifier.STATIC, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix1); + PsiClass owner = (PsiClass)keyword.getParent().getParent().getParent(); + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(owner, PsiModifier.STATIC, true, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + return errorResult; + } + + private static PsiElement getEnclosingStaticClass(PsiKeyword keyword, Class parentClass) { + return new PsiMatcherImpl(keyword) + .dot(PsiMatchers.hasText(PsiModifier.STATIC)) + .parent(PsiMatchers.hasClass(PsiModifierList.class)) + .parent(PsiMatchers.hasClass(parentClass)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, false)) + .parent(PsiMatchers.hasClass(new Class[]{PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class})) + .getElement(); + } + + + private static HighlightInfo checkStaticClassDeclarationInInnerClass(PsiKeyword keyword) { + // keyword points to 'class' or 'interface' or 'enum' + if (new PsiMatcherImpl(keyword) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, true)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, false)) + .parent(PsiMatchers.hasClass(new Class[]{PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class})) + .getElement() == null) { + return null; + } + PsiClass aClass = (PsiClass)keyword.getParent(); + if (PsiUtilBase.hasErrorElementChild(aClass)) return null; + // highlight 'static' keyword if any, or class or interface if not + PsiElement context = null; + PsiModifierList modifierList = aClass.getModifierList(); + PsiElement[] children = modifierList.getChildren(); + for (PsiElement element : children) { + if (Comparing.equal(element.getText(), PsiModifier.STATIC)) { + context = element; + break; + } + } + TextRange textRange = context == null ? null : context.getTextRange(); + if (textRange == null) { + textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + } + HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, STATIC_DECLARATION_IN_INNER_CLASS); + if (context != keyword) { + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + } + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass.getContainingClass(), PsiModifier.STATIC, true, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + return errorResult; + } + + + static HighlightInfo checkStaticDeclarationInInnerClass(PsiKeyword keyword) { + HighlightInfo errorResult = checkStaticFieldDeclarationInInnerClass(keyword); + if (errorResult != null) return errorResult; + errorResult = checkStaticMethodDeclarationInInnerClass(keyword); + if (errorResult != null) return errorResult; + errorResult = checkStaticClassDeclarationInInnerClass(keyword); + if (errorResult != null) return errorResult; + errorResult = checkStaticInitializerDeclarationInInnerClass(keyword); + if (errorResult != null) return errorResult; + return null; + } + + static HighlightInfo checkExtendsAllowed(PsiReferenceList list) { + if (list.getParent() instanceof PsiClass) { + PsiClass aClass = (PsiClass)list.getParent(); + if (aClass.isEnum()) { + boolean isExtends = list.equals(aClass.getExtendsList()); + if (isExtends) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, list, JavaErrorMessages.message("extends.after.enum")); + } + } + } + return null; + } + + static HighlightInfo checkImplementsAllowed(PsiReferenceList list) { + if (list.getParent() instanceof PsiClass) { + PsiClass aClass = (PsiClass)list.getParent(); + if (aClass.isInterface()) { + boolean isImplements = list.equals(aClass.getImplementsList()); + if (isImplements) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, list, JavaErrorMessages.message("implements.after.interface")); + } + } + } + return null; + } + + + static HighlightInfo checkExtendsClassAndImplementsInterface(PsiReferenceList referenceList, + JavaResolveResult resolveResult, + PsiJavaCodeReferenceElement ref) { + PsiClass aClass = (PsiClass)referenceList.getParent(); + boolean isImplements = referenceList.equals(aClass.getImplementsList()); + boolean isInterface = aClass.isInterface(); + if (isInterface && isImplements) return null; + boolean mustBeInterface = isImplements || isInterface; + HighlightInfo errorResult = null; + PsiClass extendFrom = (PsiClass)resolveResult.getElement(); + if (extendFrom.isInterface() != mustBeInterface) { + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + ref, + mustBeInterface ? INTERFACE_EXPECTED : NO_INTERFACE_EXPECTED); + PsiClassType type = + JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(ref); + QuickFixAction.registerQuickFixAction(errorResult, new ChangeExtendsToImplementsFix(aClass, type)); + } + return errorResult; + } + + + static HighlightInfo checkCannotInheritFromFinal(PsiClass superClass, PsiElement elementToHighlight) { + HighlightInfo errorResult = null; + if (superClass.hasModifierProperty(PsiModifier.FINAL) || superClass.isEnum()) { + String message = JavaErrorMessages.message("inheritance.from.final.class", superClass.getQualifiedName()); + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, message); + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(superClass, PsiModifier.FINAL, false, false); + QuickFixAction.registerQuickFixAction(errorResult, fix); + } + return errorResult; + } + + + static HighlightInfo checkAnonymousInheritFinal(PsiNewExpression expression) { + PsiAnonymousClass aClass = PsiTreeUtil.getChildOfType(expression, PsiAnonymousClass.class); + if (aClass == null) return null; + PsiClassType baseClassReference = aClass.getBaseClassType(); + PsiClass baseClass = baseClassReference.resolve(); + if (baseClass == null) return null; + return checkCannotInheritFromFinal(baseClass, aClass.getBaseClassReference()); + } + + + + + private static String checkDefaultConstructorThrowsException(PsiMethod constructor, @NotNull PsiClassType[] handledExceptions) { + PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes(); + List exceptions = new ArrayList(); + for (PsiClassType referencedType : referencedTypes) { + if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) { + exceptions.add(referencedType); + } + } + if (!exceptions.isEmpty()) { + return HighlightUtil.getUnhandledExceptionsDescriptor(exceptions); + } + return null; + } + + + public static HighlightInfo checkClassDoesNotCallSuperConstructorOrHandleExceptions(PsiClass aClass, + RefCountHolder refCountHolder, + final PsiResolveHelper resolveHelper) { + if (aClass.isEnum()) return null; + // check only no-ctr classes. Problem with specific constructor will be highlighted inside it + if (aClass.getConstructors().length != 0) return null; + // find no-args base class ctr + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, PsiClassType.EMPTY_ARRAY); + } + + public static HighlightInfo checkBaseClassDefaultConstructorProblem(PsiClass aClass, + RefCountHolder refCountHolder, + PsiResolveHelper resolveHelper, + TextRange textRange, + @NotNull PsiClassType[] handledExceptions) { + PsiClass baseClass = aClass.getSuperClass(); + if (baseClass == null) return null; + PsiMethod[] constructors = baseClass.getConstructors(); + if (constructors.length == 0) return null; + + for (PsiMethod constructor : constructors) { + if (resolveHelper.isAccessible(constructor, aClass, null)) { + if (constructor.getParameterList().getParametersCount() == 0 || + constructor.getParameterList().getParametersCount() == 1 && constructor.isVarArgs() + ) { + // it is an error if base ctr throws exceptions + String description = checkDefaultConstructorThrowsException(constructor, handledExceptions); + if (description != null) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); + } + if (refCountHolder != null) { + refCountHolder.registerLocallyReferenced(constructor); + } + return null; + } + } + } + + String description = JavaErrorMessages.message("no.default.constructor.available", HighlightUtil.formatClass(baseClass)); + + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); + QuickFixAction.registerQuickFixAction(info, new CreateConstructorMatchingSuperFix(aClass)); + + return info; + } + + + static HighlightInfo checkInterfaceCannotBeLocal(PsiClass aClass) { + if (PsiUtil.isLocalClass(aClass)) { + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + textRange, + JavaErrorMessages.message("interface.cannot.be.local")); + } + return null; + } + + + public static HighlightInfo checkCyclicInheritance(PsiClass aClass) { + PsiClass circularClass = getCircularClass(aClass, new HashSet()); + if (circularClass != null) { + String description = JavaErrorMessages.message("cyclic.inheritance", HighlightUtil.formatClass(circularClass)); + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); + } + return null; + } + + private static PsiClass getCircularClass(PsiClass aClass, Collection usedClasses) { + if (usedClasses.contains(aClass)) { + return aClass; + } + try { + usedClasses.add(aClass); + PsiClass[] superTypes = aClass.getSupers(); + for (PsiElement superType : superTypes) { + while (superType instanceof PsiClass) { + if (!"java.lang.Object".equals(((PsiClass)superType).getQualifiedName())) { + PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses); + if (circularClass != null) return circularClass; + } + // check class qualifier + superType = superType.getParent(); + } + } + } + finally { + usedClasses.remove(aClass); + } + return null; + } + + + public static HighlightInfo checkExtendsDuplicate(PsiJavaCodeReferenceElement element, PsiElement resolved) { + if (!(element.getParent() instanceof PsiReferenceList)) return null; + PsiReferenceList list = (PsiReferenceList)element.getParent(); + if (!(list.getParent() instanceof PsiClass)) return null; + if (!(resolved instanceof PsiClass)) return null; + PsiClass aClass = (PsiClass)resolved; + PsiClassType[] referencedTypes = list.getReferencedTypes(); + int dupCount = 0; + for (PsiClassType referencedType : referencedTypes) { + PsiClass resolvedElement = referencedType.resolve(); + if (resolvedElement != null && list.getManager().areElementsEquivalent(resolvedElement, aClass)) { + dupCount++; + } + } + if (dupCount > 1) { + String description = JavaErrorMessages.message("duplicate.class", HighlightUtil.formatClass(aClass)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, description); + } + return null; + } + + + public static HighlightInfo checkClassAlreadyImported(PsiClass aClass, PsiElement elementToHighlight) { + PsiFile file = aClass.getContainingFile(); + if (!(file instanceof PsiJavaFile)) return null; + PsiJavaFile javaFile = (PsiJavaFile)file; + // check only top-level classes conflicts + if (aClass.getParent() != javaFile) return null; + PsiImportList importList = javaFile.getImportList(); + if (importList == null) return null; + PsiImportStatementBase[] importStatements = importList.getAllImportStatements(); + for (PsiImportStatementBase importStatement : importStatements) { + if (importStatement.isOnDemand()) continue; + PsiElement resolved = importStatement.resolve(); + if (resolved instanceof PsiClass && !resolved.equals(aClass) && Comparing.equal(aClass.getName(), ((PsiClass)resolved).getName(), true)) { + String description = JavaErrorMessages.message("class.already.imported", HighlightUtil.formatClass(aClass, false)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + elementToHighlight, + description); + } + } + return null; + } + + + public static HighlightInfo checkClassExtendsOnlyOneClass(PsiReferenceList list) { + PsiClassType[] referencedTypes = list.getReferencedTypes(); + PsiElement parent = list.getParent(); + if (!(parent instanceof PsiClass)) return null; + + PsiClass aClass = (PsiClass)parent; + if (!aClass.isInterface() + && referencedTypes.length > 1 + && aClass.getExtendsList() == list) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + list, + JavaErrorMessages.message("class.cannot.extend.multiple.classes")); + } + + return null; + } + + + public static HighlightInfo checkThingNotAllowedInInterface(PsiElement element, PsiClass aClass) { + if (aClass == null || !aClass.isInterface()) return null; + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, JavaErrorMessages.message("not.allowed.in.interface")); + } + + + public static HighlightInfo checkQualifiedNewOfStaticClass(PsiNewExpression expression) { + PsiExpression qualifier = expression.getQualifier(); + if (qualifier == null) return null; + PsiType type = expression.getType(); + if (type instanceof PsiArrayType) { + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + expression, + JavaErrorMessages.message("invalid.qualified.new")); + QuickFixAction.registerQuickFixAction(info, new RemoveNewQualifierFix(expression, null)); + return info; + } + PsiClass aClass = PsiUtil.resolveClassInType(type); + if (aClass != null && aClass.hasModifierProperty(PsiModifier.STATIC)) { + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, + expression, + JavaErrorMessages.message("qualified.new.of.static.class")); + if (!aClass.isEnum()) { + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false); + QuickFixAction.registerQuickFixAction(info, fix); + } + QuickFixAction.registerQuickFixAction(info, new RemoveNewQualifierFix(expression, aClass)); + return info; + } + return null; + } + + + /** + * class c extends foreign.inner {} + * + * @param extendRef points to the class in the extends list + * @param resolved extendRef resolved + */ + public static HighlightInfo checkClassExtendsForeignInnerClass(PsiJavaCodeReferenceElement extendRef, PsiElement resolved) { + PsiElement parent = extendRef.getParent(); + if (!(parent instanceof PsiReferenceList)) { + return null; + } + PsiElement grand = parent.getParent(); + if (!(grand instanceof PsiClass)) { + return null; + } + PsiClass aClass = (PsiClass)grand; + if (aClass.getExtendsList() != parent || aClass instanceof PsiTypeParameter) { + return null; + } + if (!(resolved instanceof PsiClass)) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, extendRef, JavaErrorMessages.message("class.name.expected")); + } + PsiClass base = (PsiClass)resolved; + // must be inner class + if (!PsiUtil.isInnerClass(base)) return null; + PsiClass baseClass = base.getContainingClass(); + + if (!hasEnclosingInstanceInScope(baseClass, extendRef, true) && !qualifiedNewCalledInConstructors(aClass, baseClass)) { + String description = JavaErrorMessages.message("no.enclosing.instance.in.scope", HighlightUtil.formatClass(baseClass)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, extendRef, description); + } + + return null; + } + + private static boolean qualifiedNewCalledInConstructors(final PsiClass aClass, final PsiClass baseClass) { + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) return false; + for (PsiMethod constructor : constructors) { + PsiCodeBlock body = constructor.getBody(); + if (body == null) return false; + PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) return false; + PsiStatement firstStatement = statements[0]; + if (!(firstStatement instanceof PsiExpressionStatement)) return false; + PsiExpression expression = ((PsiExpressionStatement)firstStatement).getExpression(); + if (!HighlightUtil.isSuperOrThisMethodCall(expression)) return false; + PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; + if (PsiKeyword.THIS.equals(methodCallExpression.getMethodExpression().getReferenceName())) continue; + PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); + PsiExpression qualifierExpression = referenceExpression.getQualifierExpression(); + if (!(qualifierExpression instanceof PsiReferenceExpression)) return false; + PsiType type = qualifierExpression.getType(); + if (!(type instanceof PsiClassType)) return false; + PsiClass resolved = ((PsiClassType)type).resolve(); + if (resolved != baseClass) return false; + } + return true; + } + + + static HighlightInfo checkExternalizableHasPublicNoArgsConstructor(PsiClass aClass, PsiElement context) { + if (!isExternalizable(aClass) || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { + return null; + } + PsiMethod[] constructors = aClass.getConstructors(); + boolean hasPublicNoArgsConstructor = constructors.length == 0; + for (PsiMethod constructor : constructors) { + if (constructor.getParameterList().getParametersCount() == 0 && constructor.hasModifierProperty(PsiModifier.PUBLIC)) { + hasPublicNoArgsConstructor = true; + break; + } + } + if (!hasPublicNoArgsConstructor) { + HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WARNING, + context, + JavaErrorMessages.message("externalizable.class.should.have.public.constructor")); + QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddDefaultConstructorFix(aClass)); + return highlightInfo; + } + return null; + } + + private static boolean isExternalizable(PsiClass aClass) { + PsiManager manager = aClass.getManager(); + PsiClass externalizableClass = + JavaPsiFacade.getInstance(manager.getProject()).findClass("java.io.Externalizable", aClass.getResolveScope()); + return externalizableClass != null && aClass.isInheritor(externalizableClass, true); + } + + public static boolean hasEnclosingInstanceInScope(PsiClass aClass, PsiElement scope, final boolean isSuperClassAccepted) { + PsiManager manager = aClass.getManager(); + PsiElement place = scope; + while (place != null && place != aClass && !(place instanceof PsiFile)) { + if (place instanceof PsiClass) { + if (isSuperClassAccepted) { + if (InheritanceUtil.isInheritorOrSelf((PsiClass)place, aClass, true)) return true; + } + else { + if (manager.areElementsEquivalent(place, aClass)) return true; + } + } + if (place instanceof PsiModifierListOwner && ((PsiModifierListOwner)place).hasModifierProperty(PsiModifier.STATIC)) { + return false; + } + place = place.getParent(); + } + return place == aClass; + } + + @Nullable + public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiNewExpression expression) { + PsiType type = expression.getType(); + if (type == null || type instanceof PsiArrayType || type instanceof PsiPrimitiveType) return null; + PsiClass aClass = PsiUtil.resolveClassInType(type); + if (aClass == null) return null; + if (aClass instanceof PsiAnonymousClass) { + aClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); + if (aClass == null) return null; + } + + if (!PsiUtil.isInnerClass(aClass)) return null; + PsiClass outerClass = aClass.getContainingClass(); + if (outerClass == null) return null; + + PsiElement placeToSearchEnclosingFrom; + PsiExpression qualifier = expression.getQualifier(); + if (qualifier != null) { + PsiType qtype = qualifier.getType(); + placeToSearchEnclosingFrom = PsiUtil.resolveClassInType(qtype); + } + else { + placeToSearchEnclosingFrom = expression; + } + + if (outerClass instanceof JspClass + || hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true)) return null; + return reportIllegalEnclosingUsage(placeToSearchEnclosingFrom, aClass, outerClass, expression); + } + + public static HighlightInfo checkSuperQualifierType(PsiMethodCallExpression superCall) { + if (!HighlightUtil.isSuperMethodCall(superCall)) return null; + PsiMethod ctr = PsiTreeUtil.getParentOfType(superCall, PsiMethod.class, true, PsiMember.class); + if (ctr == null) return null; + PsiClass targetClass = ctr.getContainingClass().getSuperClass(); + if (targetClass == null) return null; + PsiExpression qualifier = superCall.getMethodExpression().getQualifierExpression(); + if (qualifier != null && PsiUtil.isInnerClass(targetClass)) { + PsiClass outerClass = targetClass.getContainingClass(); + PsiClassType outerType = JavaPsiFacade.getInstance(superCall.getProject()).getElementFactory().createType(outerClass); + return HighlightUtil.checkAssignability(outerType, null, qualifier, qualifier); + } + return null; + } + + @Nullable + public static HighlightInfo reportIllegalEnclosingUsage(PsiElement place, + PsiClass aClass, PsiClass outerClass, + PsiElement elementToHighlight) { + if (outerClass != null && !PsiTreeUtil.isContextAncestor(outerClass, place, false)) { + String description = JavaErrorMessages.message("is.not.an.enclosing.class", HighlightUtil.formatClass(outerClass)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, description); + } + PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, outerClass); + if (staticParent != null) { + String description = JavaErrorMessages.message("cannot.be.referenced.from.static.context", + outerClass == null ? "" : HighlightUtil.formatClass(outerClass) + "." + PsiKeyword.THIS); + HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, description); + // make context not static or referenced class static + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false); + QuickFixAction.registerQuickFixAction(highlightInfo, fix); + if (aClass != null && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, aClass.getModifierList()) == null) { + IntentionAction fix2 = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, true, false); + QuickFixAction.registerQuickFixAction(highlightInfo, fix2); + } + return highlightInfo; + } + return null; + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 49425ec220b9..f399888ce64e 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -1,1985 +1,1985 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: cdr - * Date: Jul 30, 2002 - */ -package com.intellij.codeInsight.daemon.impl.analysis; - -import com.intellij.codeInsight.ExceptionUtil; -import com.intellij.codeInsight.daemon.JavaErrorMessages; -import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.codeInsight.daemon.impl.HighlightInfoType; -import com.intellij.codeInsight.daemon.impl.quickfix.*; -import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation; -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.codeInsight.intention.QuickFixFactory; -import com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider; -import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; -import com.intellij.lang.StdLanguages; -import com.intellij.lang.findUsages.LanguageFindUsages; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod; -import com.intellij.psi.javadoc.PsiDocComment; -import com.intellij.psi.jsp.JspFile; -import com.intellij.psi.scope.processor.VariablesNotProcessor; -import com.intellij.psi.scope.util.PsiScopesUtil; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.*; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.xml.util.XmlStringUtil; -import gnu.trove.THashMap; -import gnu.trove.THashSet; -import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class HighlightUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil"); - private static final Map> ourInterfaceIncompatibleModifiers; - private static final Map> ourMethodIncompatibleModifiers; - private static final Map> ourFieldIncompatibleModifiers; - private static final Map> ourClassIncompatibleModifiers; - private static final Map> ourClassInitializerIncompatibleModifiers; - private static final Set ourConstructorNotAllowedModifiers; - - @NonNls private static final String SERIAL_VERSION_UID_FIELD_NAME = "serialVersionUID"; - @NonNls private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); - - private HighlightUtil() { - } - - static { - ourClassIncompatibleModifiers = new THashMap>(8); - Set modifiers = new THashSet(1); - modifiers.add(PsiModifier.FINAL); - ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, modifiers); - modifiers = new THashSet(1); - modifiers.add(PsiModifier.ABSTRACT); - ourClassIncompatibleModifiers.put(PsiModifier.FINAL, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourClassIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourClassIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PROTECTED); - ourClassIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PRIVATE); - ourClassIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); - ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.emptySet()); - ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - ourInterfaceIncompatibleModifiers = new THashMap>(7); - ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Collections.emptySet()); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PROTECTED); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); - ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.emptySet()); - ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - ourMethodIncompatibleModifiers = new THashMap>(10); - modifiers = new THashSet(6); - modifiers.addAll(Arrays.asList(PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, - PsiModifier.SYNCHRONIZED)); - ourMethodIncompatibleModifiers.put(PsiModifier.ABSTRACT, modifiers); - modifiers = new THashSet(2); - modifiers.add(PsiModifier.ABSTRACT); - modifiers.add(PsiModifier.STRICTFP); - ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourMethodIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); - modifiers = new THashSet(4); - modifiers.add(PsiModifier.ABSTRACT); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourMethodIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PROTECTED); - ourMethodIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PRIVATE); - ourMethodIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); - modifiers = new THashSet(1); - modifiers.add(PsiModifier.ABSTRACT); - ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, modifiers); - ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, modifiers); - ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, modifiers); - ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, modifiers); - ourFieldIncompatibleModifiers = new THashMap>(8); - modifiers = new THashSet(1); - modifiers.add(PsiModifier.VOLATILE); - ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourFieldIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PUBLIC); - modifiers.add(PsiModifier.PROTECTED); - ourFieldIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PROTECTED); - ourFieldIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); - modifiers = new THashSet(3); - modifiers.add(PsiModifier.PACKAGE_LOCAL); - modifiers.add(PsiModifier.PRIVATE); - modifiers.add(PsiModifier.PUBLIC); - ourFieldIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); - ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Collections.emptySet()); - modifiers = new THashSet(1); - modifiers.add(PsiModifier.FINAL); - ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, modifiers); - - ourClassInitializerIncompatibleModifiers = new THashMap>(1); - ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - - ourConstructorNotAllowedModifiers = new THashSet(6); - ourConstructorNotAllowedModifiers.add(PsiModifier.ABSTRACT); - ourConstructorNotAllowedModifiers.add(PsiModifier.STATIC); - ourConstructorNotAllowedModifiers.add(PsiModifier.NATIVE); - ourConstructorNotAllowedModifiers.add(PsiModifier.FINAL); - ourConstructorNotAllowedModifiers.add(PsiModifier.STRICTFP); - ourConstructorNotAllowedModifiers.add(PsiModifier.SYNCHRONIZED); - } - - @Nullable - public static String getIncompatibleModifier(String modifier, - PsiModifierList modifierList, - Map> incompatibleModifiersHash) { - if (modifierList == null) return null; - // modifier is always incompatible with itself - PsiElement[] modifiers = modifierList.getChildren(); - int modifierCount = 0; - for (PsiElement otherModifier : modifiers) { - if (Comparing.equal(modifier, otherModifier.getText(), true)) modifierCount++; - } - if (modifierCount > 1) { - return modifier; - } - - Set incompatibles = incompatibleModifiersHash.get(modifier); - if (incompatibles == null) return null; - for (@Modifier String incompatible : incompatibles) { - if (modifierList.hasModifierProperty(incompatible)) { - return incompatible; - } - } - return null; - } - - /** - * make element protected/package local/public suggestion - */ - static void registerAccessQuickFixAction(PsiMember refElement, - PsiJavaCodeReferenceElement place, - HighlightInfo errorResult, - final PsiElement fileResolveScope) { - if (refElement instanceof PsiCompiledElement) return; - PsiModifierList modifierList = refElement.getModifierList(); - if (modifierList == null) return; - - PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); - if (packageLocalClassInTheMiddle != null) { - IntentionAction fix = - QUICK_FIX_FACTORY.createModifierListFix(packageLocalClassInTheMiddle, PsiModifier.PUBLIC, true, true); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return; - } - - try { - Project project = refElement.getProject(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); - modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); - @Modifier String minModifier = PsiModifier.PACKAGE_LOCAL; - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { - minModifier = PsiModifier.PROTECTED; - } - if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { - minModifier = PsiModifier.PUBLIC; - } - String[] modifiers = {PsiModifier.PACKAGE_LOCAL, PsiModifier.PROTECTED, PsiModifier.PUBLIC,}; - PsiClass accessObjectClass = null; - PsiElement qualifier = place.getQualifier(); - if (qualifier instanceof PsiExpression) { - accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass((PsiExpression)qualifier).getElement(); - } - for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { - @Modifier String modifier = modifiers[i]; - modifierListCopy.setModifierProperty(modifier, true); - if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(refElement, modifier, true, true); - TextRange fixRange = new TextRange(errorResult.startOffset, errorResult.endOffset); - PsiElement ref = place.getReferenceNameElement(); - if (ref != null) { - fixRange = fixRange.union(ref.getTextRange()); - } - QuickFixAction.registerQuickFixAction(errorResult, fixRange, fix, null); - } - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - @Nullable - private static PsiClass getPackageLocalClassInTheMiddle(PsiJavaCodeReferenceElement place) { - if (place instanceof PsiReferenceExpression) { - // check for package local classes in the middle - PsiReferenceExpression expression = (PsiReferenceExpression)place; - while (true) { - PsiElement resolved = expression.resolve(); - if (resolved instanceof PsiField) { - PsiField field = (PsiField)resolved; - PsiClass aClass = field.getContainingClass(); - if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && - !JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, place)) { - - return aClass; - } - } - PsiExpression qualifier = expression.getQualifierExpression(); - if (!(qualifier instanceof PsiReferenceExpression)) break; - expression = (PsiReferenceExpression)qualifier; - } - } - return null; - } - - - @Nullable - static HighlightInfo checkInstanceOfApplicable(PsiInstanceOfExpression expression) { - PsiExpression operand = expression.getOperand(); - PsiTypeElement typeElement = expression.getCheckType(); - if (typeElement == null) return null; - PsiType checkType = typeElement.getType(); - PsiType operandType = operand.getType(); - if (operandType == null) return null; - if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) - || TypeConversionUtil.isPrimitiveAndNotNull(checkType) - || !TypeConversionUtil.areTypesConvertible(operandType, checkType)) { - String message = JavaErrorMessages.message("inconvertible.type.cast", formatType(operandType), formatType(checkType)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); - } - return null; - } - - - @Nullable - static HighlightInfo checkInconvertibleTypeCast(PsiTypeCastExpression expression) { - PsiExpression operand = expression.getOperand(); - PsiType castType = expression.getCastType().getType(); - PsiType operandType = operand == null ? null : operand.getType(); - if (operandType != null && !TypeConversionUtil.areTypesConvertible(operandType, castType)) { - String message = JavaErrorMessages.message("inconvertible.type.cast", formatType(operandType), formatType(castType)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); - } - return null; - } - - - static HighlightInfo checkVariableExpected(PsiExpression expression) { - PsiExpression lValue; - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; - lValue = assignment.getLExpression(); - } - else if (PsiUtil.isIncrementDecrementOperation(expression)) { - lValue = expression instanceof PsiPostfixExpression - ? ((PsiPostfixExpression)expression).getOperand() - : ((PsiPrefixExpression)expression).getOperand(); - } - else { - lValue = null; - } - HighlightInfo errorResult = null; - if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, lValue, JavaErrorMessages.message("variable.expected")); - } - - return errorResult; - } - - - @Nullable - static HighlightInfo checkAssignmentOperatorApplicable(PsiAssignmentExpression assignment) { - PsiJavaToken operationSign = assignment.getOperationSign(); - IElementType eqOpSign = operationSign.getTokenType(); - IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); - if (opSign == null) return null; - HighlightInfo errorResult = null; - if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, assignment.getLExpression(), assignment.getRExpression(), true)) { - String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); - String message = JavaErrorMessages.message("binary.operator.not.applicable", operatorText, - formatType(assignment.getLExpression().getType()), - formatType(assignment.getRExpression().getType())); - - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, assignment, message); - } - return errorResult; - } - - - @Nullable - static HighlightInfo checkAssignmentCompatibleTypes(PsiAssignmentExpression assignment) { - if (!"=".equals(assignment.getOperationSign().getText())) return null; - PsiExpression lExpr = assignment.getLExpression(); - PsiExpression rExpr = assignment.getRExpression(); - if (rExpr == null) return null; - PsiType lType = lExpr.getType(); - PsiType rType = rExpr.getType(); - if (rType == null) return null; - - HighlightInfo highlightInfo = checkAssignability(lType, rType, rExpr, assignment); - if (highlightInfo != null) { - PsiVariable leftVar = null; - if (lExpr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression)lExpr).resolve(); - if (element instanceof PsiVariable) { - leftVar = (PsiVariable)element; - } - } - if (leftVar != null) { - registerChangeVariableTypeFixes(leftVar, rType, highlightInfo); - } - } - return highlightInfo; - } - - private static boolean isCastIntentionApplicable(PsiExpression expression, PsiType toType) { - while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { - if (expression instanceof PsiTypeCastExpression) { - expression = ((PsiTypeCastExpression)expression).getOperand(); - } - if (expression instanceof PsiParenthesizedExpression) { - expression = ((PsiParenthesizedExpression)expression).getExpression(); - } - } - if (expression == null) return false; - PsiType rType = expression.getType(); - return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); - } - - - @Nullable - static HighlightInfo checkVariableInitializerType(PsiVariable variable) { - PsiExpression initializer = variable.getInitializer(); - // array initalizer checked in checkArrayInitializerApplicable - if (initializer == null || initializer instanceof PsiArrayInitializerExpression) return null; - PsiType lType = variable.getType(); - PsiType rType = initializer.getType(); - int start = variable.getTypeElement().getTextRange().getStartOffset(); - int end = variable.getTextRange().getEndOffset(); - HighlightInfo highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end)); - if (highlightInfo != null) { - registerChangeVariableTypeFixes(variable, rType, highlightInfo); - } - return highlightInfo; - } - - @Nullable - static HighlightInfo checkAssignability(PsiType lType, PsiType rType, PsiExpression expression, PsiElement elementToHighlight) { - TextRange textRange = elementToHighlight.getTextRange(); - return checkAssignability(lType, rType, expression, textRange); - } - - @Nullable - public static HighlightInfo checkAssignability(@Nullable PsiType lType, @Nullable PsiType rType, @Nullable PsiExpression expression, TextRange textRange) { - if (lType == rType) return null; - if (expression == null) { - if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) return null; - } - else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { - if (lType == null || rType == null) return null; - return GenericsHighlightUtil.checkRawToGenericAssignment(lType, rType, expression); - } - if (rType == null) { - rType = expression.getType(); - } - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange); - if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, new AddTypeCastFix(lType, expression)); - } - if (expression != null && lType != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, new WrapExpressionFix(lType, expression)); - } - ChangeNewOperatorTypeFix.register(highlightInfo, expression, lType); - return highlightInfo; - } - - - @Nullable - static HighlightInfo checkReturnStatementType(PsiReturnStatement statement) { - PsiMethod method = null; - PsiElement parent = statement.getParent(); - while (true) { - if (parent instanceof PsiFile) break; - if (parent instanceof PsiClassInitializer) break; - if (parent instanceof PsiMethod) { - method = (PsiMethod)parent; - break; - } - parent = parent.getParent(); - } - String description; - int navigationShift = 0; - HighlightInfo errorResult = null; - if (method == null && !(parent instanceof JspFile)) { - description = JavaErrorMessages.message("return.outside.method"); - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); - } - else { - PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; - boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); - final PsiExpression returnValue = statement.getReturnValue(); - if (returnValue != null) { - PsiType valueType = returnValue.getType(); - if (isMethodVoid) { - description = JavaErrorMessages.message("return.from.void.method"); - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); - if (valueType != null) { - IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true); - QuickFixAction.registerQuickFixAction(errorResult, fix); - } - } - else { - errorResult = checkAssignability(returnType, valueType, returnValue, statement); - if (errorResult != null && valueType != null) { - IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true); - QuickFixAction.registerQuickFixAction(errorResult, fix); - if (returnType instanceof PsiArrayType && TypeConversionUtil.isAssignable(((PsiArrayType)returnType).getComponentType(), valueType)) { - QuickFixAction.registerQuickFixAction(errorResult, new SurroundWithArrayFix(null){ - @Override - protected PsiExpression getExpression(final PsiElement element) { - return returnValue.isValid() ? returnValue : null; - } - }); - } - } - } - navigationShift = returnValue.getStartOffsetInParent(); - } - else { - if (!isMethodVoid) { - description = JavaErrorMessages.message("missing.return.value"); - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); - IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true); - QuickFixAction.registerQuickFixAction(errorResult, fix); - navigationShift = PsiKeyword.RETURN.length(); - } - } - } - if (errorResult != null) { - errorResult.navigationShift = navigationShift; - } - return errorResult; - } - - public static String getUnhandledExceptionsDescriptor(Collection unhandledExceptions) { - StringBuilder exceptionsText = new StringBuilder(); - for (PsiClassType unhandledException : unhandledExceptions) { - if (exceptionsText.length() != 0) exceptionsText.append(", "); - exceptionsText.append(formatType(unhandledException)); - } - - return JavaErrorMessages.message("unhandled.exceptions", exceptionsText.toString(), unhandledExceptions.size()); - } - - - @Nullable - static HighlightInfo checkVariableAlreadyDefined(PsiVariable variable) { - if (variable instanceof ExternallyDefinedPsiElement) return null; - boolean isIncorrect = false; - PsiIdentifier identifier = variable.getNameIdentifier(); - String name = variable.getName(); - if (variable instanceof PsiLocalVariable || - variable instanceof PsiParameter && ((PsiParameter)variable).getDeclarationScope() instanceof PsiCatchSection || - variable instanceof PsiParameter && ((PsiParameter)variable).getDeclarationScope() instanceof PsiForeachStatement) { - PsiElement scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class); - VariablesNotProcessor proc = new VariablesNotProcessor(variable, false){ - protected boolean check(final PsiVariable var, final ResolveState state) { - return (var instanceof PsiLocalVariable || var instanceof PsiParameter) && super.check(var, state); - } - }; - PsiScopesUtil.treeWalkUp(proc, identifier, scope); - if (proc.size() > 0) { - isIncorrect = true; - } - } - else if (variable instanceof PsiField) { - PsiField field = (PsiField)variable; - PsiClass aClass = field.getContainingClass(); - if (aClass == null) return null; - PsiField fieldByName = aClass.findFieldByName(name, false); - if (fieldByName != null && fieldByName != field) { - isIncorrect = true; - } - } - else { - PsiElement scope = variable.getParent(); - PsiElement[] children = scope.getChildren(); - for (PsiElement child : children) { - if (child instanceof PsiVariable) { - if (child.equals(variable)) continue; - if (name.equals(((PsiVariable)child).getName())) { - isIncorrect = true; - break; - } - } - } - } - - if (isIncorrect) { - String description = JavaErrorMessages.message("variable.already.defined", name); - HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, identifier, description); - QuickFixAction.registerQuickFixAction(highlightInfo, new ReuseVariableDeclarationFix(variable, identifier)); - return highlightInfo; - } - return null; - } - - @NotNull - public static String formatClass(@NotNull PsiClass aClass) { - return formatClass(aClass, true); - } - - @NotNull - public static String formatClass(@NotNull PsiClass aClass, boolean fqn) { - return PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME | - PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0)); - } - - @NotNull - public static String formatMethod(@NotNull PsiMethod method) { - return PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, - PsiFormatUtilBase.SHOW_TYPE); - } - - @NotNull - public static String formatType(@Nullable PsiType type) { - if (type == null) return PsiKeyword.NULL; - String text = type.getInternalCanonicalText(); - return text == null ? PsiKeyword.NULL : text; - } - - - public static HighlightInfo checkUnhandledExceptions(PsiElement element, TextRange fixRange) { - List unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); - HighlightInfo errorResult = null; - if (!unhandledExceptions.isEmpty()) { - if (fixRange == null) { - fixRange = element.getTextRange(); - } - HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); - if (highlightType == null) return null; - errorResult = HighlightInfo.createHighlightInfo(highlightType, fixRange, getUnhandledExceptionsDescriptor(unhandledExceptions)); - QuickFixAction.registerQuickFixAction(errorResult, new AddExceptionToCatchFix()); - QuickFixAction.registerQuickFixAction(errorResult, new AddExceptionToThrowsFix(element)); - QuickFixAction.registerQuickFixAction(errorResult, new SurroundWithTryCatchFix(element)); - if (unhandledExceptions.size() == 1) { - QuickFixAction.registerQuickFixAction(errorResult, new GeneralizeCatchFix(element, unhandledExceptions.get(0))); - } - } - return errorResult; - } - - private static HighlightInfoType getUnhandledExceptionHighlightType(final PsiElement element) { - if (!JspPsiUtil.isInJspFile(element)) { - return HighlightInfoType.UNHANDLED_EXCEPTION; - } - PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); - if (!(targetMethod instanceof JspHolderMethod)) return HighlightInfoType.UNHANDLED_EXCEPTION; - // ignore JSP top level errors - it handled by UnhandledExceptionInJSP inspection - return null; - } - - - @Nullable - static HighlightInfo checkBreakOutsideLoop(PsiBreakStatement statement) { - if (statement.getLabelIdentifier() == null) { - if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { - return HighlightInfo - .createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("break.outside.switch.or.loop")); - } - } - else { - // todo labeled - } - return null; - } - - - @Nullable - static HighlightInfo checkContinueOutsideLoop(PsiContinueStatement statement) { - if (statement.getLabelIdentifier() == null) { - if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("continue.outside.loop")); - } - } - else { - PsiStatement exitedStatement = statement.findContinuedStatement(); - if (exitedStatement == null) return null; - if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) && - !(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof PsiForeachStatement)) { - String description = JavaErrorMessages.message("not.loop.label", statement.getLabelIdentifier().getText()); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); - } - } - return null; - } - - - static HighlightInfo checkIllegalModifierCombination(PsiKeyword keyword, PsiModifierList modifierList) { - @Modifier String modifier = keyword.getText(); - String incompatible = getIncompatibleModifier(modifier, modifierList); - - HighlightInfo highlightInfo = null; - if (incompatible != null) { - String message = JavaErrorMessages.message("incompatible.modifiers", modifier, incompatible); - - highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, keyword, message); - - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); - } - return highlightInfo; - } - - @Nullable - private static Map> getIncompatibleModifierMap(PsiModifierList modifierList) { - PsiElement parent = modifierList.getParent(); - if (parent == null || PsiUtilBase.hasErrorElementChild(parent)) return null; - return parent instanceof PsiClass - ? ((PsiClass)parent).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers - : parent instanceof PsiMethod - ? ourMethodIncompatibleModifiers - : parent instanceof PsiVariable - ? ourFieldIncompatibleModifiers - : parent instanceof PsiClassInitializer ? ourClassInitializerIncompatibleModifiers : null; - } - - @Nullable - public static String getIncompatibleModifier(String modifier, PsiModifierList modifierList) { - PsiElement parent = modifierList.getParent(); - if (parent == null || PsiUtilBase.hasErrorElementChild(parent)) return null; - final Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList); - if (incompatibleModifierMap == null) return null; - return getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap); - } - - - @Nullable - public static HighlightInfo checkNotAllowedModifier(PsiKeyword keyword, PsiModifierList modifierList) { - PsiElement modifierOwner = modifierList.getParent(); - if (modifierOwner == null) return null; - if (PsiUtilBase.hasErrorElementChild(modifierOwner)) return null; - @Modifier String modifier = keyword.getText(); - final Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList); - if (incompatibleModifierMap == null) return null; - Set incompatibles = incompatibleModifierMap.get(modifier); - PsiElement modifierOwnerParent = modifierOwner instanceof PsiMember ? ((PsiMember)modifierOwner).getContainingClass() : modifierOwner.getParent(); - if (modifierOwnerParent == null) modifierOwnerParent = modifierOwner.getParent(); - boolean isAllowed = true; - if (modifierOwner instanceof PsiClass) { - PsiClass aClass = (PsiClass)modifierOwner; - if (aClass.isInterface()) { - if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || - PsiModifier.PACKAGE_LOCAL.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass; - } - } - else { - if (PsiModifier.PUBLIC.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiJavaFile || modifierOwnerParent instanceof PsiClass; - } - else if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || - PsiModifier.PACKAGE_LOCAL.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass; - } - - if (aClass.isEnum()) { - isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); - } - } - } - else if (modifierOwner instanceof PsiMethod) { - PsiMethod method = (PsiMethod)modifierOwner; - isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); - PsiClass containingClass = method.getContainingClass(); - if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() && - containingClass != null && containingClass.isEnum()) { - isAllowed = false; - } - - if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || - PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { - isAllowed &= modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); - } - } - else if (modifierOwner instanceof PsiField) { - if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || - PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); - } - } - else if (modifierOwner instanceof PsiClassInitializer) { - isAllowed = PsiModifier.STATIC.equals(modifier); - } - else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { - isAllowed = PsiModifier.FINAL.equals(modifier); - } - - isAllowed &= incompatibles != null; - if (!isAllowed) { - String message = JavaErrorMessages.message("modifier.not.allowed", modifier); - - HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, keyword, message); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); - return highlightInfo; - } - return null; - } - - - @Nullable - static HighlightInfo checkLiteralExpressionParsingError(PsiLiteralExpression expression) { - String error = expression.getParsingError(); - if (error != null) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, error); - } - return null; - } - - - @Nullable - static HighlightInfo checkMustBeBoolean(PsiExpression expr) { - PsiElement parent = expr.getParent(); - if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || - parent instanceof PsiForStatement && expr.equals(((PsiForStatement)parent).getCondition()) || - parent instanceof PsiDoWhileStatement && expr.equals(((PsiDoWhileStatement)parent).getCondition())) { - if (expr.getNextSibling() instanceof PsiErrorElement) return null; - - PsiType type = expr.getType(); - if (!TypeConversionUtil.isBooleanType(type)) { - final HighlightInfo info = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange()); - if (expr instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) expr; - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && PsiType.VOID.equals(method.getReturnType())) { - IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.BOOLEAN, true); - QuickFixAction.registerQuickFixAction(info, fix); - } - } - return info; - } - } - return null; - } - - - @Nullable - static HighlightInfo checkExceptionThrownInTry(PsiParameter parameter) { - PsiElement declarationScope = parameter.getDeclarationScope(); - if (!(declarationScope instanceof PsiCatchSection)) return null; - PsiTryStatement statement = ((PsiCatchSection)declarationScope).getTryStatement(); - Collection classes = ExceptionUtil.collectUnhandledExceptions(statement.getTryBlock(), statement.getTryBlock()); - - PsiType caughtType = parameter.getType(); - if (!(caughtType instanceof PsiClassType)) return null; - if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)caughtType)) return null; - - for (PsiClassType exceptionType : classes) { - if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) return null; - } - - String description = JavaErrorMessages.message("exception.never.thrown.try", formatType(caughtType)); - HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, parameter, description); - - QuickFixAction.registerQuickFixAction(errorResult, new DeleteCatchFix(parameter)); - return errorResult; - } - - - @Nullable - static HighlightInfo checkNotAStatement(PsiStatement statement) { - if (!PsiUtil.isStatement(statement) && !PsiUtilBase.hasErrorElementChild(statement)) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("not.a.statement")); - } - return null; - } - - - public static HighlightInfo checkSwitchSelectorType(PsiSwitchStatement statement) { - PsiExpression expression = statement.getExpression(); - HighlightInfo errorResult = null; - if (expression != null && expression.getType() != null) { - PsiType type = expression.getType(); - if (!isValidTypeForSwitchSelector(type, expression)) { - String message = - JavaErrorMessages.message("incompatible.types", JavaErrorMessages.message("valid.switch.selector.types"), formatType(type)); - errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); - if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { - QuickFixAction.registerQuickFixAction(errorResult, new AddTypeCastFix(PsiType.INT, expression)); - } - } - } - return errorResult; - } - - private static boolean isValidTypeForSwitchSelector(PsiType type, PsiExpression expression) { - if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) return true; - if (type instanceof PsiClassType) { - PsiClass psiClass = ((PsiClassType)type).resolve(); - if (psiClass == null) return false; - if (psiClass.isEnum()) { - return true; - } - if (PsiUtil.isLanguageLevel7OrHigher(expression)) { - return Comparing.strEqual(psiClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING); - } - } - return false; - } - - - @Nullable - static HighlightInfo checkBinaryOperatorApplicable(PsiBinaryExpression expression) { - PsiExpression lOperand = expression.getLOperand(); - PsiExpression rOperand = expression.getROperand(); - PsiJavaToken operationSign = expression.getOperationSign(); - if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign.getTokenType(), lOperand, rOperand, false)) { - String message = JavaErrorMessages - .message("binary.operator.not.applicable", operationSign.getText(), formatType(lOperand.getType()), formatType(rOperand.getType())); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); - } - return null; - } - - - @Nullable - public static HighlightInfo checkUnaryOperatorApplicable(PsiJavaToken token, PsiExpression expression) { - if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { - PsiType type = expression.getType(); - if (type == null) return null; - String message = JavaErrorMessages.message("unary.operator.not.applicable", token.getText(), formatType(type)); - - PsiElement parentExpr = token.getParent(); - HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, parentExpr, message); - if (parentExpr instanceof PsiPrefixExpression && token.getTokenType() == JavaTokenType.EXCL) { - QuickFixAction.registerQuickFixAction(highlightInfo, new NegationBroadScopeFix((PsiPrefixExpression)parentExpr)); - } - return highlightInfo; - } - return null; - } - - @Nullable - public static HighlightInfo checkThisOrSuperExpressionInIllegalContext(PsiExpression expr, @Nullable PsiJavaCodeReferenceElement qualifier) { - if (expr instanceof PsiSuperExpression && !(expr.getParent() instanceof PsiReferenceExpression)) { - // like in 'Object o = super;' - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expr.getTextRange().getEndOffset(), - expr.getTextRange().getEndOffset() + 1, - JavaErrorMessages.message("dot.expected.after.super.or.this")); - } - PsiElement resolved = null; - PsiClass aClass = qualifier == null ? PsiTreeUtil.getParentOfType(expr, PsiClass.class) : (resolved = qualifier.resolve()) instanceof PsiClass ? (PsiClass)resolved : null; - if (resolved != null && !(resolved instanceof PsiClass)) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, qualifier, JavaErrorMessages.message("class.expected")); - } - if (aClass == null) return null; - if (qualifier != null && aClass.isInterface()) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, qualifier, HighlightClassUtil.NO_INTERFACE_EXPECTED); - } - if (!HighlightClassUtil.hasEnclosingInstanceInScope(aClass, expr, false)) { - return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); - } - - return null; - - } - - static String buildProblemWithStaticDescription(PsiElement refElement) { - String type = LanguageFindUsages.INSTANCE.forLanguage(StdLanguages.JAVA).getType(refElement); - String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); - return JavaErrorMessages.message("non.static.symbol.referenced.from.static.context", type, name); - } - - static void registerStaticProblemQuickFixAction(@NotNull PsiElement refElement, HighlightInfo errorResult, PsiJavaCodeReferenceElement place) { - if (refElement instanceof PsiModifierListOwner) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix((PsiModifierListOwner)refElement, PsiModifier.STATIC, true, false)); - } - // make context non static - PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); - if (staticParent != null && isInstanceReference(place)) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false)); - } - } - - private static boolean isInstanceReference(PsiJavaCodeReferenceElement place) { - PsiElement qualifier = place.getQualifier(); - if (qualifier == null) return true; - if (!(qualifier instanceof PsiJavaCodeReferenceElement)) return false; - PsiElement q = ((PsiReference)qualifier).resolve(); - if (q instanceof PsiClass) return false; - if (q != null) return true; - String qname = ((PsiJavaCodeReferenceElement)qualifier).getQualifiedName(); - return qname == null || !Character.isLowerCase(qname.charAt(0)); - } - - static String buildProblemWithAccessDescription(PsiJavaCodeReferenceElement reference, JavaResolveResult result) { - PsiModifierListOwner refElement = (PsiModifierListOwner)result.getElement(); - String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - - if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { - String containerName = HighlightMessageUtil.getSymbolName(refElement.getParent(), result.getSubstitutor()); - return JavaErrorMessages.message("private.symbol", symbolName, containerName); - } - else { - if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { - String containerName = HighlightMessageUtil.getSymbolName(refElement.getParent(), result.getSubstitutor()); - return JavaErrorMessages.message("protected.symbol", symbolName, containerName); - } - else { - PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); - if (packageLocalClass != null) { - refElement = packageLocalClass; - symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - } - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { - String containerName = HighlightMessageUtil.getSymbolName(refElement.getParent(), result.getSubstitutor()); - return JavaErrorMessages.message("package.local.symbol", symbolName, containerName); - } - else { - String containerName = HighlightMessageUtil.getSymbolName( - refElement instanceof PsiTypeParameter ? refElement.getParent().getParent() : refElement.getParent(), result.getSubstitutor()); - return JavaErrorMessages.message("visibility.access.problem", symbolName, containerName); - } - } - } - } - - @Nullable - static HighlightInfo checkValidArrayAccessExpression(PsiExpression arrayExpression, PsiExpression indexExpression) { - PsiType arrayExpressionType = arrayExpression == null ? null : arrayExpression.getType(); - if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { - String description = JavaErrorMessages.message("array.type.expected", formatType(arrayExpressionType)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, arrayExpression, description); - } - return checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression); - } - - - @Nullable - public static HighlightInfo checkCatchParameterIsThrowable(PsiParameter parameter) { - if (parameter.getDeclarationScope() instanceof PsiCatchSection) { - PsiType type = parameter.getType(); - return checkMustBeThrowable(type, parameter, true); - } - return null; - } - - public static void checkArrayInitalizer(final PsiExpression initializer, final HighlightInfoHolder holder) { - if (! (initializer instanceof PsiArrayInitializerExpression)) return; - - final PsiType arrayInitializerType = initializer.getType(); - if (! (arrayInitializerType instanceof PsiArrayType)) return; - - final PsiType componentType = ((PsiArrayType) arrayInitializerType).getComponentType(); - final PsiArrayInitializerExpression arrayInitializer = (PsiArrayInitializerExpression) initializer; - - boolean arrayTypeFixChecked = false; - VariableArrayTypeFix fix = null; - - final PsiExpression[] initializers = arrayInitializer.getInitializers(); - for (PsiExpression expression : initializers) { - final HighlightInfo info = checkArrayInitalizerCompatibleTypes(expression, componentType); - if (info != null) { - holder.add(info); - - if (!arrayTypeFixChecked) { - final PsiType checkResult = sameType(initializers); - fix = checkResult != null ? new VariableArrayTypeFix(arrayInitializer, checkResult) : null; - arrayTypeFixChecked = true; - } - if (fix != null) { - QuickFixAction.registerQuickFixAction(info, fix); - } - } - } - } - - @Nullable - private static PsiType getArrayInitializerType(final PsiArrayInitializerExpression element) { - final PsiType typeCheckResult = sameType(element.getInitializers()); - if (typeCheckResult != null) { - return typeCheckResult.createArrayType(); - } - return null; - } - - private static PsiType sameType(PsiExpression[] expressions) { - PsiType type = null; - for (PsiExpression expression : expressions) { - final PsiType currentType; - if (expression instanceof PsiArrayInitializerExpression) { - currentType = getArrayInitializerType((PsiArrayInitializerExpression)expression); - } - else { - currentType = expression.getType(); - } - if (type == null) { - type = currentType; - } - else if (!type.equals(currentType)) { - return null; - } - } - return type; - } - - @Nullable - private static HighlightInfo checkArrayInitalizerCompatibleTypes(PsiExpression initializer, final PsiType componentType) { - PsiType initializerType = initializer.getType(); - if (initializerType == null) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, initializer, - JavaErrorMessages.message("illegal.initializer", formatType(componentType))); - } - PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; - return checkAssignability(componentType, initializerType, expression, initializer); - } - - @Nullable - public static HighlightInfo checkExpressionRequired(PsiReferenceExpression expression) { - if (expression.getNextSibling() instanceof PsiErrorElement) return null; - PsiElement resolved = expression.advancedResolve(true).getElement(); - if (resolved == null) return null; - PsiElement parent = expression.getParent(); - // String.class or String() are both correct - if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) return null; - if (resolved instanceof PsiVariable) return null; - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, JavaErrorMessages.message("expression.expected")); - } - - - @Nullable - public static HighlightInfo checkArrayInitializerApplicable(PsiArrayInitializerExpression expression) { - /* - JLS 10.6 Array Initializers - An array initializer may be specified in a declaration, or as part of an array creation expression - */ - PsiElement parent = expression.getParent(); - if (parent instanceof PsiVariable) { - PsiVariable variable = (PsiVariable)parent; - if (variable.getType() instanceof PsiArrayType) return null; - } - else if (parent instanceof PsiNewExpression) { - return null; - } - else if (parent instanceof PsiArrayInitializerExpression) { - return null; - } - HighlightInfo info = - HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, JavaErrorMessages.message("expression.expected")); - QuickFixAction.registerQuickFixAction(info, new AddNewArrayExpressionFix(expression)); - - return info; - } - - - @Nullable - public static HighlightInfo checkCaseStatement(PsiSwitchLabelStatement statement) { - PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement(); - if (switchStatement == null) { - return HighlightInfo - .createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("case.statement.outside.switch")); - } - if (switchStatement.getBody() == null) return null; - PsiExpression switchExpression = switchStatement.getExpression(); - PsiType switchType = switchExpression == null ? PsiType.INT : switchExpression.getType(); - // check constant expression - PsiExpression caseValue = statement.getCaseValue(); - - // Every case constant expression associated with a switch statement must be assignable ($5.2) to the type of the switch Expression. - if (caseValue != null && switchExpression != null) { - HighlightInfo highlightInfo = checkAssignability(switchType, caseValue.getType(), caseValue, caseValue); - if (highlightInfo != null) return highlightInfo; - } - Object value = null; - - boolean isEnumSwitch = false; - if (!statement.isDefaultCase() && caseValue != null) { - if (caseValue instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression)caseValue).resolve(); - if (element instanceof PsiEnumConstant) { - isEnumSwitch = true; - value = ((PsiEnumConstant)element).getName(); - if (!(((PsiReferenceExpression)caseValue).getQualifier() == null)) { - String message = JavaErrorMessages.message("qualified.enum.constant.in.switch"); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, caseValue, message); - } - } - } - if (!isEnumSwitch) { - value = ConstantExpressionUtil.computeCastTo(caseValue, switchType); - } - if (value == null) { - return HighlightInfo - .createHighlightInfo(HighlightInfoType.ERROR, caseValue, JavaErrorMessages.message("constant.expression.required")); - } - } - - // check duplicate - PsiStatement[] statements = switchStatement.getBody().getStatements(); - for (PsiStatement st : statements) { - if (st == statement) continue; - if (!(st instanceof PsiSwitchLabelStatement)) continue; - PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement)st; - if (labelStatement.isDefaultCase() != statement.isDefaultCase()) continue; - PsiExpression caseExpr = labelStatement.getCaseValue(); - if (isEnumSwitch && caseExpr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression)caseExpr).resolve(); - if (!(element instanceof PsiEnumConstant && Comparing.equal(((PsiEnumConstant)element).getName(), value))) continue; - } - else { - // not assignable error already caught - if (!TypeConversionUtil.areTypesAssignmentCompatible(switchType, caseExpr)) continue; - if (!Comparing.equal(ConstantExpressionUtil.computeCastTo(caseExpr, switchType), value)) continue; - } - String description = statement.isDefaultCase() - ? JavaErrorMessages.message("duplicate.default.switch.label") - : JavaErrorMessages.message("duplicate.switch.label", value); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, value == null ? statement : caseValue, description); - } - - // must be followed with colon - PsiElement lastChild = statement.getLastChild(); - while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { - lastChild = lastChild.getPrevSibling(); - } - if (!(lastChild instanceof PsiJavaToken && ((PsiJavaToken)lastChild).getTokenType() == JavaTokenType.COLON)) { - int start = statement.getTextRange().getEndOffset(); - int end = statement.getTextRange().getEndOffset() + 1; - String description = JavaErrorMessages.message("switch.colon.expected.after.case.label"); - CharSequence chars = statement.getContainingFile().getViewProvider().getContents(); - boolean isAfterEndOfLine = end >= chars.length() || chars.charAt(start) == '\n' || chars.charAt(start) == '\r'; - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, null,start, end, description, description,isAfterEndOfLine, null); - } - return null; - } - - - /** - * see JLS 8.3.2.3 - */ - @Nullable - public static HighlightInfo checkIllegalForwardReferenceToField(PsiReferenceExpression expression, PsiField referencedField) { - PsiClass containingClass = referencedField.getContainingClass(); - if (containingClass == null) return null; - if (expression.getContainingFile() != referencedField.getContainingFile()) return null; - if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) return null; - // only simple reference can be illegal - if (expression.getQualifierExpression() != null) return null; - PsiField initField = findEnclosingFieldInitializer(expression); - PsiClassInitializer classInitializer = findParentClassInitializer(expression); - if (initField == null && classInitializer == null) return null; - // instance initializers may access static fields - boolean isStaticClassInitializer = classInitializer != null && classInitializer.hasModifierProperty(PsiModifier.STATIC); - boolean isStaticInitField = initField != null && initField.hasModifierProperty(PsiModifier.STATIC); - boolean inStaticContext = isStaticInitField || isStaticClassInitializer; - if (!inStaticContext && referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; - if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) return null; - if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { - return null; - } - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, JavaErrorMessages.message("illegal.forward.reference")); - } - - /** - * @return field that has initializer with this element as subexpression or null if not found - */ - @Nullable - static PsiField findEnclosingFieldInitializer(PsiElement element) { - while (element != null) { - PsiElement parent = element.getParent(); - if (parent instanceof PsiField) { - PsiField field = (PsiField)parent; - if (element == field.getInitializer()) return field; - if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) return field; - } - if (element instanceof PsiClass || element instanceof PsiMethod) return null; - element = parent; - } - return null; - } - - @Nullable - private static PsiClassInitializer findParentClassInitializer(PsiElement element) { - while (element != null) { - if (element instanceof PsiClassInitializer) return (PsiClassInitializer)element; - if (element instanceof PsiClass || element instanceof PsiMethod) return null; - element = element.getParent(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkIllegalType(PsiTypeElement typeElement) { - if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) return null; - - if (PsiUtil.isInsideJavadocComment(typeElement)) return null; - - PsiType type = typeElement.getType(); - PsiType componentType = type.getDeepComponentType(); - if (componentType instanceof PsiClassType) { - PsiClass aClass = PsiUtil.resolveClassInType(componentType); - if (aClass == null) { - String canonicalText = type.getCanonicalText(); - String description = JavaErrorMessages.message("unknown.class", canonicalText); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, typeElement, description); - } - } - return null; - } - - - @Nullable - public static HighlightInfo checkIllegalVoidType(PsiKeyword type) { - if (!PsiKeyword.VOID.equals(type.getText())) return null; - - PsiElement parent = type.getParent(); - if (parent instanceof PsiTypeElement) { - PsiElement typeOwner = parent.getParent(); - if (typeOwner instanceof PsiMethod) { - if (((PsiMethod)typeOwner).getReturnTypeElement() == parent) return null; - } - else if (typeOwner instanceof PsiClassObjectAccessExpression && - TypeConversionUtil.isVoidType(((PsiClassObjectAccessExpression)typeOwner).getOperand().getType())) { - // like in Class c = void.class; - return null; - } - else if (typeOwner != null && PsiUtilBase.hasErrorElementChild(typeOwner)) { - // do not highlight incomplete declarations - return null; - } - else if (typeOwner instanceof JavaCodeFragment) { - if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) return null; - } - } - - String description = JavaErrorMessages.message("illegal.type.void"); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, type, description); - } - - @Nullable - public static HighlightInfo checkMemberReferencedBeforeConstructorCalled(PsiElement expression, PsiElement resolved) { - PsiClass referencedClass; - @NonNls String resolvedName; - PsiType type; - if (expression instanceof PsiJavaCodeReferenceElement) { - // redirected ctr - if (PsiKeyword.THIS.equals(((PsiJavaCodeReferenceElement)expression).getReferenceName()) - && resolved instanceof PsiMethod - && ((PsiMethod)resolved).isConstructor()) return null; - PsiElement qualifier = ((PsiJavaCodeReferenceElement)expression).getQualifier(); - type = qualifier instanceof PsiExpression ? ((PsiExpression)qualifier).getType() : null; - referencedClass = PsiUtil.resolveClassInType(type); - - boolean isSuperCall = isSuperMethodCall(expression.getParent()); - if (resolved == null && isSuperCall) { - if (qualifier instanceof PsiReferenceExpression) { - resolved = ((PsiReferenceExpression)qualifier).resolve(); - expression = qualifier; - type = ((PsiReferenceExpression)qualifier).getType(); - referencedClass = PsiUtil.resolveClassInType(type); - } - else if (qualifier instanceof PsiThisExpression || qualifier == null) { - resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); - expression = qualifier == null ? expression : qualifier; - if (resolved instanceof PsiMethod) { - referencedClass = ((PsiMethod)resolved).getContainingClass(); - } - } - } - if (resolved instanceof PsiField) { - PsiField referencedField = (PsiField)resolved; - if (referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; - resolvedName = PsiFormatUtil.formatVariable(referencedField, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); - referencedClass = referencedField.getContainingClass(); - } - else if (resolved instanceof PsiMethod) { - PsiMethod method = (PsiMethod)resolved; - if (method.hasModifierProperty(PsiModifier.STATIC)) return null; - PsiElement nameElement = expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement(); - String name = nameElement == null ? null : nameElement.getText(); - if (isSuperCall) { - if (referencedClass == null) return null; - if (qualifier == null) { - PsiClass superClass = referencedClass.getSuperClass(); - if (superClass != null - && PsiUtil.isInnerClass(superClass) - && InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) { - // by default super() is considered this. - qualified - resolvedName = PsiKeyword.THIS; - } - else { - return null; - } - } - else { - resolvedName = qualifier.getText(); - } - } - else if (PsiKeyword.THIS.equals(name)) { - resolvedName = PsiKeyword.THIS; - } - else { - resolvedName = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | - PsiFormatUtilBase.SHOW_NAME, 0); - if (referencedClass == null) referencedClass = method.getContainingClass(); - } - } - else if (resolved instanceof PsiClass) { - PsiClass aClass = (PsiClass)resolved; - if (aClass.hasModifierProperty(PsiModifier.STATIC)) return null; - referencedClass = aClass.getContainingClass(); - if (referencedClass == null) return null; - resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); - } - else { - return null; - } - } - else if (expression instanceof PsiThisExpression) { - PsiThisExpression thisExpression = (PsiThisExpression)expression; - type = thisExpression.getType(); - referencedClass = PsiUtil.resolveClassInType(type); - if (thisExpression.getQualifier() != null) { - resolvedName = referencedClass == null - ? null - : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME) + ".this"; - } - else { - resolvedName = "this"; - } - } - else { - return null; - } - if (referencedClass == null) return null; - return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName); - } - - private static HighlightInfo checkReferenceToOurInstanceInsideThisOrSuper(final PsiElement expression, - final PsiClass referencedClass, - final String resolvedName) { - if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) return null; - PsiElement element = expression.getParent(); - while (element != null) { - // check if expression inside super()/this() call - if (isSuperOrThisMethodCall(element)) { - PsiElement parentClass = new PsiMatcherImpl(element) - .parent(PsiMatchers.hasClass(PsiExpressionStatement.class)) - .parent(PsiMatchers.hasClass(PsiCodeBlock.class)) - .parent(PsiMatchers.hasClass(PsiMethod.class)) - .dot(PsiMatchers.isConstructor(true)) - .parent(PsiMatchers.hasClass(PsiClass.class)) - .getElement(); - if (parentClass == null) { - return null; - } - - // only this class/superclasses instance methods are not allowed to call - PsiClass aClass = (PsiClass)parentClass; - if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) return null; - // field or method should be declared in this class or super - if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) return null; - // and point to our instance - if (expression instanceof PsiReferenceExpression && - !thisOrSuperReference(((PsiReferenceExpression)expression).getQualifierExpression(), aClass)) { - return null; - } - return createMemberReferencedError(resolvedName, expression.getTextRange()); - } - element = element.getParent(); - } - return null; - } - - private static HighlightInfo createMemberReferencedError(@NonNls final String resolvedName, TextRange textRange) { - String description = JavaErrorMessages.message("member.referenced.before.constructor.called", resolvedName); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); - } - - public static HighlightInfo checkImplicitThisReferenceBeforeSuper(PsiClass aClass) { - if (aClass instanceof PsiAnonymousClass) return null; - PsiClass superClass = aClass.getSuperClass(); - if (superClass == null || !PsiUtil.isInnerClass(superClass)) return null; - PsiClass outerClass = superClass.getContainingClass(); - if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { - return null; - } - // 'this' can be used as an (implicit) super() qualifier - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) { - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return createMemberReferencedError(aClass.getName()+".this", range); - } - for (PsiMethod constructor : constructors) { - if (!isSuperCalledInConstructor(constructor)) { - return createMemberReferencedError(aClass.getName()+".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor)); - } - } - return null; - } - - private static boolean isSuperCalledInConstructor(final PsiMethod constructor) { - final PsiCodeBlock body = constructor.getBody(); - if (body == null) return false; - final PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) return false; - final PsiStatement statement = statements[0]; - final PsiElement element = new PsiMatcherImpl(statement) - .dot(PsiMatchers.hasClass(PsiExpressionStatement.class)) - .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) - .firstChild(PsiMatchers.hasClass(PsiReferenceExpression.class)) - .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) - .dot(PsiMatchers.hasText(PsiKeyword.SUPER)) - .getElement(); - return element != null; - } - - private static String getMethodExpressionName(PsiElement element) { - if (!(element instanceof PsiMethodCallExpression)) return null; - PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)element).getMethodExpression(); - return methodExpression.getReferenceName(); - } - public static boolean isSuperOrThisMethodCall(PsiElement element) { - String name = getMethodExpressionName(element); - return PsiKeyword.SUPER.equals(name) || PsiKeyword.THIS.equals(name); - } - public static boolean isSuperMethodCall(PsiElement element) { - String name = getMethodExpressionName(element); - return PsiKeyword.SUPER.equals(name); - } - - private static boolean thisOrSuperReference(PsiExpression qualifierExpression, PsiClass aClass) { - if (qualifierExpression == null) return true; - PsiJavaCodeReferenceElement qualifier; - if (qualifierExpression instanceof PsiThisExpression) { - qualifier = ((PsiThisExpression)qualifierExpression).getQualifier(); - } - else if (qualifierExpression instanceof PsiSuperExpression) { - qualifier = ((PsiSuperExpression)qualifierExpression).getQualifier(); - } - else { - return false; - } - if (qualifier == null) return true; - PsiElement resolved = qualifier.resolve(); - return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true); - } - - - @Nullable - public static HighlightInfo checkLabelWithoutStatement(PsiLabeledStatement statement) { - if (statement.getStatement() == null) { - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("label.without.statement")); - } - return null; - } - - - @Nullable - public static HighlightInfo checkLabelAlreadyInUse(PsiLabeledStatement statement) { - PsiIdentifier identifier = statement.getLabelIdentifier(); - String text = identifier.getText(); - PsiElement element = statement; - while (element != null) { - if (element instanceof PsiMethod || element instanceof PsiClass) break; - if (element instanceof PsiLabeledStatement && element != statement && - Comparing.equal(((PsiLabeledStatement)element).getLabelIdentifier().getText(), text)) { - String description = JavaErrorMessages.message("duplicate.label", text); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, identifier, description); - } - element = element.getParent(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkUnclosedComment(PsiComment comment) { - if (!(comment instanceof PsiDocComment) && !(comment.getTokenType() == JavaTokenType.C_STYLE_COMMENT)) return null; - if (!comment.getText().endsWith("*/")) { - int start = comment.getTextRange().getEndOffset() - 1; - int end = start + 1; - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, start, end, JavaErrorMessages.message("nonterminated.comment")); - } - return null; - } - - - @Nullable - public static HighlightInfo checkExceptionAlreadyCaught(PsiJavaCodeReferenceElement element, PsiElement resolved) { - if (!(resolved instanceof PsiClass)) return null; - PsiClass catchClass = (PsiClass)resolved; - if (!(element.getParent() instanceof PsiTypeElement)) return null; - PsiElement catchParameter = element.getParent().getParent(); - if (!(catchParameter instanceof PsiParameter) || !(((PsiParameter)catchParameter).getDeclarationScope() instanceof PsiCatchSection)) { - return null; - } - PsiCatchSection catchSection = (PsiCatchSection)((PsiParameter)catchParameter).getDeclarationScope(); - PsiTryStatement statement = catchSection.getTryStatement(); - PsiCatchSection[] catchSections = statement.getCatchSections(); - int i = ArrayUtil.find(catchSections, catchSection); - for (i--; i >= 0; i--) { - PsiCatchSection section = catchSections[i]; - PsiType type = section.getCatchType(); - PsiClass upCatchClass = PsiUtil.resolveClassInType(type); - if (upCatchClass == null) continue; - if (InheritanceUtil.isInheritorOrSelf(catchClass, upCatchClass, true)) { - String description = JavaErrorMessages - .message("exception.already.caught", PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | - PsiFormatUtilBase.SHOW_FQ_NAME)); - HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, description); - QuickFixAction.registerQuickFixAction(highlightInfo, new MoveCatchUpFix(catchSection, section)); - QuickFixAction.registerQuickFixAction(highlightInfo, new DeleteCatchFix((PsiParameter)catchParameter)); - return highlightInfo; - } - } - return null; - } - - - @Nullable - public static HighlightInfo checkTernaryOperatorConditionIsBoolean(PsiExpression expression) { - if (expression.getParent() instanceof PsiConditionalExpression && - ((PsiConditionalExpression)expression.getParent()).getCondition() == expression && expression.getType() != null && - !TypeConversionUtil.isBooleanType(expression.getType())) { - PsiType foundType = expression.getType(); - return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, foundType, expression.getTextRange()); - } - return null; - } - - - @Nullable - public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(PsiStatement statement) { - if (!(statement instanceof PsiSwitchLabelStatement) && statement.getParent() instanceof PsiCodeBlock && - statement.getParent().getParent() instanceof PsiSwitchStatement && - ((PsiCodeBlock)statement.getParent()).getStatements().length != 0 && - statement == ((PsiCodeBlock)statement.getParent()).getStatements()[0]) { - return HighlightInfo - .createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("statement.must.be.prepended.with.case.label")); - } - return null; - } - - - @Nullable - public static HighlightInfo checkAssertOperatorTypes(PsiExpression expression) { - if (!(expression.getParent() instanceof PsiAssertStatement)) { - return null; - } - PsiAssertStatement assertStatement = (PsiAssertStatement)expression.getParent(); - PsiType type = expression.getType(); - if (type == null) return null; - if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { - // addTypeCast quickfix is not applicable here since no type can be cast to boolean - return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange()); - } - else if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { - String description = JavaErrorMessages.message("void.type.is.not.allowed"); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, description); - } - return null; - } - - - @Nullable - public static HighlightInfo checkSynchronizedExpressionType(PsiExpression expression) { - if (expression.getParent() instanceof PsiSynchronizedStatement) { - PsiType type = expression.getType(); - if (type == null) return null; - PsiSynchronizedStatement synchronizedStatement = (PsiSynchronizedStatement)expression.getParent(); - if (expression == synchronizedStatement.getLockExpression() && - (type instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(type))) { - PsiClassType objectType = PsiType.getJavaLangObject(expression.getManager(), expression.getResolveScope()); - return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange()); - } - } - return null; - } - - - @Nullable - public static HighlightInfo checkConditionalExpressionBranchTypesMatch(PsiExpression expression) { - if (!(expression.getParent() instanceof PsiConditionalExpression)) { - return null; - } - PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)expression.getParent(); - // check else branches only - if (conditionalExpression.getElseExpression() != expression) return null; - final PsiExpression thenExpression = conditionalExpression.getThenExpression(); - assert thenExpression != null; - PsiType thenType = thenExpression.getType(); - PsiType elseType = expression.getType(); - if (thenType == null || elseType == null) return null; - if (conditionalExpression.getType() == null) { - // cannot derive type of conditional expression - // elsetype will never be castable to thentype, so no quick fix here - return createIncompatibleTypeHighlightInfo(thenType, elseType, expression.getTextRange()); - } - return null; - } - - private static HighlightInfo createIncompatibleTypeHighlightInfo(final PsiType lType, final PsiType rType, final TextRange textRange) { - PsiType lType1 = lType; - PsiType rType1 = rType; - PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; - PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; - if (lType1 instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)lType1).resolveGenerics(); - lTypeSubstitutor = resolveResult.getSubstitutor(); - PsiClass psiClass = resolveResult.getElement(); - if (psiClass instanceof PsiAnonymousClass) { - lType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); - resolveResult = ((PsiClassType)lType1).resolveGenerics(); - lTypeSubstitutor = resolveResult.getSubstitutor(); - psiClass = resolveResult.getElement(); - } - lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); - } - PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; - PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; - if (rType1 instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)rType1).resolveGenerics(); - rTypeSubstitutor = resolveResult.getSubstitutor(); - PsiClass psiClass = resolveResult.getElement(); - if (psiClass instanceof PsiAnonymousClass) { - rType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); - resolveResult = ((PsiClassType)rType1).resolveGenerics(); - rTypeSubstitutor = resolveResult.getSubstitutor(); - psiClass = resolveResult.getElement(); - } - rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); - } - - int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); - @Language("HTML") @NonNls String requiredRow = ""; - @Language("HTML") @NonNls String foundRow = ""; - for (int i = 0; i < typeParamColumns; i++) { - PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; - PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; - PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); - PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); - boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); - @NonNls String openBrace = i == 0 ? "<" : ""; - @NonNls String closeBrace = i == typeParamColumns - 1 ? ">" : ","; - requiredRow += "" + (lTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(lSubstitutedType, matches) + - (i < lTypeParams.length ? closeBrace : "") + ""; - foundRow += "" + (rTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(rSubstitutedType, matches) + - (i < rTypeParams.length ? closeBrace : "") + ""; - } - PsiType lRawType = lType1 instanceof PsiClassType ? ((PsiClassType)lType1).rawType() : lType1; - PsiType rRawType = rType1 instanceof PsiClassType ? ((PsiClassType)rType1).rawType() : rType1; - boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); - - String toolTip = JavaErrorMessages.message("incompatible.types.html.tooltip", - redIfNotMatch(lRawType, assignable), requiredRow, - redIfNotMatch(rRawType, assignable), foundRow); - - String description = JavaErrorMessages.message("incompatible.types", formatType(lType1), formatType(rType1)); - - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, null, textRange.getStartOffset(), textRange.getEndOffset(), - description, toolTip); - } - - @Nullable - public static HighlightInfo checkSingleImportClassConflict(PsiImportStatement statement, - Map> singleImportedClasses) { - if (statement.isOnDemand()) return null; - PsiElement element = statement.resolve(); - if (element instanceof PsiClass) { - String name = ((PsiClass)element).getName(); - Pair imported = singleImportedClasses.get(name); - PsiClass importedClass = imported == null ? null : imported.getSecond(); - if (importedClass != null && !element.getManager().areElementsEquivalent(importedClass, element)) { - String description = JavaErrorMessages.message("single.import.class.conflict", formatClass(importedClass)); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); - } - singleImportedClasses.put(name, Pair.create(statement, (PsiClass)element)); - } - return null; - } - - - @NonNls - private static String redIfNotMatch(PsiType type, boolean matches) { - if (matches) return getFQName(type, false); - return "" + getFQName(type, true) + ""; - } - - private static String getFQName(PsiType type, boolean longName) { - if (type == null) return ""; - return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); - } - - - @Nullable - public static HighlightInfo checkMustBeThrowable(PsiType type, PsiElement context, boolean addCastIntention) { - if (type == null) return null; - PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); - PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", context.getResolveScope()); - if (!TypeConversionUtil.isAssignable(throwable, type)) { - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange()); - if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) { - if (context instanceof PsiExpression) { - QuickFixAction.registerQuickFixAction(highlightInfo, new AddTypeCastFix(throwable, (PsiExpression)context)); - } - } - return highlightInfo; - } - return null; - } - - - @Nullable - private static HighlightInfo checkMustBeThrowable(PsiClass aClass, PsiElement context) { - if (aClass == null) return null; - PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); - return checkMustBeThrowable(type, context, false); - } - - - @Nullable - public static HighlightInfo checkLabelDefined(PsiIdentifier labelIdentifier, PsiStatement exitedStatement) { - if (labelIdentifier == null) return null; - String label = labelIdentifier.getText(); - if (label == null) return null; - if (exitedStatement == null) { - String message = JavaErrorMessages.message("unresolved.label", label); - return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, labelIdentifier, message); - } - return null; - } - - - @Nullable - public static HighlightInfo checkReference(PsiJavaCodeReferenceElement ref, JavaResolveResult result, PsiElement resolved) { - PsiElement refName = ref.getReferenceNameElement(); - - if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) return null; - HighlightInfo highlightInfo = checkMemberReferencedBeforeConstructorCalled(ref, resolved); - if (highlightInfo != null) return highlightInfo; - - PsiElement refParent = ref.getParent(); - if (refParent instanceof PsiMethodCallExpression) { - return null; // methods checked elsewhere - } - if (resolved == null) { - // do not highlight unknown packages - javac does not care about illegal package names - if (isInsidePackageStatement(refName)) return null; - if (result.isPackagePrefixPackageReference()) return null; - JavaResolveResult[] results = ref.multiResolve(true); - String description; - if (results.length > 1) { - String t1 = format(results[0].getElement()); - String t2 = format(results[1].getElement()); - description = JavaErrorMessages.message("ambiguous.reference", refName.getText(), t1, t2); - } - else { - description = JavaErrorMessages.message("cannot.resolve.symbol", refName.getText()); - } - - HighlightInfoType type = HighlightInfoType.WRONG_REF; - if (PsiUtil.isInsideJavadocComment(ref)) return null; - - HighlightInfo info = HighlightInfo.createHighlightInfo(type, refName, description); - - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, new QuickFixActionRegistrarImpl(info)); - return info; - } - - if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { - if (!result.isAccessible()) { - String description = buildProblemWithAccessDescription(ref, result); - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, ref.getReferenceNameElement(), description); - if (result.isStaticsScopeCorrect()) { - registerAccessQuickFixAction((PsiMember)resolved, ref, info, result.getCurrentFileResolveScope()); - if (ref instanceof PsiReferenceExpression) { - QuickFixAction.registerQuickFixAction(info, new RenameWrongRefFix((PsiReferenceExpression)ref)); - } - } - return info; - } - - if (!result.isStaticsScopeCorrect()) { - String description = buildProblemWithStaticDescription(resolved); - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, ref.getReferenceNameElement(), description); - registerStaticProblemQuickFixAction(resolved, info, ref); - if (ref instanceof PsiReferenceExpression) { - QuickFixAction.registerQuickFixAction(info, new RenameWrongRefFix((PsiReferenceExpression)ref)); - } - return info; - } - } - if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { - highlightInfo = HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref); - } - return highlightInfo; - } - - private static String format(PsiElement element) { - if (element instanceof PsiClass) return formatClass((PsiClass)element); - if (element instanceof PsiMethod) return formatMethod((PsiMethod)element); - return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); - } - - private static boolean isInsidePackageStatement(PsiElement element) { - while (element != null) { - if (element instanceof PsiPackageStatement) return true; - if (!(element instanceof PsiIdentifier) && !(element instanceof PsiJavaCodeReferenceElement)) return false; - element = element.getParent(); - } - return false; - } - - @Nullable - public static HighlightInfo checkElementInReferenceList(PsiJavaCodeReferenceElement ref, - PsiReferenceList referenceList, - JavaResolveResult resolveResult) { - PsiElement resolved = resolveResult.getElement(); - HighlightInfo highlightInfo = null; - PsiElement refGrandParent = referenceList.getParent(); - if (resolved instanceof PsiClass) { - PsiClass aClass = (PsiClass)resolved; - if (refGrandParent instanceof PsiClass) { - if (refGrandParent instanceof PsiTypeParameter) { - highlightInfo = GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, resolveResult, ref); - } - else { - highlightInfo = HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); - if (highlightInfo == null) { - highlightInfo = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); - } - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkCannotInheritFromEnum(aClass, ref); - } - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); - } - } - } - else if (refGrandParent instanceof PsiMethod && ((PsiMethod)refGrandParent).getThrowsList() == referenceList) { - highlightInfo = checkMustBeThrowable(aClass, ref); - } - } - else if (refGrandParent instanceof PsiMethod && referenceList == ((PsiMethod)refGrandParent).getThrowsList()) { - highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, ref, JavaErrorMessages.message("class.name.expected")); - } - return highlightInfo; - } - - - public static boolean isSerializable(PsiClass aClass) { - PsiManager manager = aClass.getManager(); - PsiClass serializableClass = JavaPsiFacade.getInstance(manager.getProject()).findClass("java.io.Serializable", aClass.getResolveScope()); - return serializableClass != null && aClass.isInheritor(serializableClass, true); - } - - public static boolean isSerializationImplicitlyUsedField(PsiField field) { - final String name = field.getName(); - if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) return false; - if (!field.hasModifierProperty(PsiModifier.STATIC)) return false; - PsiClass aClass = field.getContainingClass(); - return aClass == null || isSerializable(aClass); - } - - public static HighlightInfo checkClassReferenceAfterQualifier(final PsiReferenceExpression expression, final PsiElement resolved) { - if (!(resolved instanceof PsiClass)) return null; - final PsiExpression qualifier = expression.getQualifierExpression(); - if (qualifier == null) return null; - if (qualifier instanceof PsiReferenceExpression) { - PsiElement qualifierResolved = ((PsiReferenceExpression)qualifier).resolve(); - if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) return null; - } - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, qualifier, JavaErrorMessages.message("expected.class.or.package")); - QuickFixAction.registerQuickFixAction(info, new RemoveQualifierFix(qualifier, expression, (PsiClass)resolved)); - return info; - } - - public static void registerChangeVariableTypeFixes(PsiVariable parameter, PsiType itemType, HighlightInfo highlightInfo) { - for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { - for (IntentionAction action : fixProvider.getFixes(parameter, itemType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, action, null); - } - } - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: cdr + * Date: Jul 30, 2002 + */ +package com.intellij.codeInsight.daemon.impl.analysis; + +import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInsight.daemon.JavaErrorMessages; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.quickfix.*; +import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider; +import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; +import com.intellij.lang.StdLanguages; +import com.intellij.lang.findUsages.LanguageFindUsages; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.jsp.JspFile; +import com.intellij.psi.scope.processor.VariablesNotProcessor; +import com.intellij.psi.scope.util.PsiScopesUtil; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.xml.util.XmlStringUtil; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class HighlightUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil"); + private static final Map> ourInterfaceIncompatibleModifiers; + private static final Map> ourMethodIncompatibleModifiers; + private static final Map> ourFieldIncompatibleModifiers; + private static final Map> ourClassIncompatibleModifiers; + private static final Map> ourClassInitializerIncompatibleModifiers; + private static final Set ourConstructorNotAllowedModifiers; + + @NonNls private static final String SERIAL_VERSION_UID_FIELD_NAME = "serialVersionUID"; + @NonNls private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; + private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); + + private HighlightUtil() { + } + + static { + ourClassIncompatibleModifiers = new THashMap>(8); + Set modifiers = new THashSet(1); + modifiers.add(PsiModifier.FINAL); + ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, modifiers); + modifiers = new THashSet(1); + modifiers.add(PsiModifier.ABSTRACT); + ourClassIncompatibleModifiers.put(PsiModifier.FINAL, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourClassIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourClassIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PROTECTED); + ourClassIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PRIVATE); + ourClassIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); + ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.emptySet()); + ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); + ourInterfaceIncompatibleModifiers = new THashMap>(7); + ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Collections.emptySet()); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourInterfaceIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourInterfaceIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PROTECTED); + ourInterfaceIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + ourInterfaceIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); + ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.emptySet()); + ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); + ourMethodIncompatibleModifiers = new THashMap>(10); + modifiers = new THashSet(6); + modifiers.addAll(Arrays.asList(PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, + PsiModifier.SYNCHRONIZED)); + ourMethodIncompatibleModifiers.put(PsiModifier.ABSTRACT, modifiers); + modifiers = new THashSet(2); + modifiers.add(PsiModifier.ABSTRACT); + modifiers.add(PsiModifier.STRICTFP); + ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourMethodIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); + modifiers = new THashSet(4); + modifiers.add(PsiModifier.ABSTRACT); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourMethodIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PROTECTED); + ourMethodIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PRIVATE); + ourMethodIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); + modifiers = new THashSet(1); + modifiers.add(PsiModifier.ABSTRACT); + ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, modifiers); + ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, modifiers); + ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, modifiers); + ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, modifiers); + ourFieldIncompatibleModifiers = new THashMap>(8); + modifiers = new THashSet(1); + modifiers.add(PsiModifier.VOLATILE); + ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourFieldIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PUBLIC); + modifiers.add(PsiModifier.PROTECTED); + ourFieldIncompatibleModifiers.put(PsiModifier.PRIVATE, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PROTECTED); + ourFieldIncompatibleModifiers.put(PsiModifier.PUBLIC, modifiers); + modifiers = new THashSet(3); + modifiers.add(PsiModifier.PACKAGE_LOCAL); + modifiers.add(PsiModifier.PRIVATE); + modifiers.add(PsiModifier.PUBLIC); + ourFieldIncompatibleModifiers.put(PsiModifier.PROTECTED, modifiers); + ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); + ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Collections.emptySet()); + modifiers = new THashSet(1); + modifiers.add(PsiModifier.FINAL); + ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, modifiers); + + ourClassInitializerIncompatibleModifiers = new THashMap>(1); + ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); + + ourConstructorNotAllowedModifiers = new THashSet(6); + ourConstructorNotAllowedModifiers.add(PsiModifier.ABSTRACT); + ourConstructorNotAllowedModifiers.add(PsiModifier.STATIC); + ourConstructorNotAllowedModifiers.add(PsiModifier.NATIVE); + ourConstructorNotAllowedModifiers.add(PsiModifier.FINAL); + ourConstructorNotAllowedModifiers.add(PsiModifier.STRICTFP); + ourConstructorNotAllowedModifiers.add(PsiModifier.SYNCHRONIZED); + } + + @Nullable + public static String getIncompatibleModifier(String modifier, + PsiModifierList modifierList, + Map> incompatibleModifiersHash) { + if (modifierList == null) return null; + // modifier is always incompatible with itself + PsiElement[] modifiers = modifierList.getChildren(); + int modifierCount = 0; + for (PsiElement otherModifier : modifiers) { + if (Comparing.equal(modifier, otherModifier.getText(), true)) modifierCount++; + } + if (modifierCount > 1) { + return modifier; + } + + Set incompatibles = incompatibleModifiersHash.get(modifier); + if (incompatibles == null) return null; + for (@Modifier String incompatible : incompatibles) { + if (modifierList.hasModifierProperty(incompatible)) { + return incompatible; + } + } + return null; + } + + /** + * make element protected/package local/public suggestion + */ + static void registerAccessQuickFixAction(PsiMember refElement, + PsiJavaCodeReferenceElement place, + HighlightInfo errorResult, + final PsiElement fileResolveScope) { + if (refElement instanceof PsiCompiledElement) return; + PsiModifierList modifierList = refElement.getModifierList(); + if (modifierList == null) return; + + PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); + if (packageLocalClassInTheMiddle != null) { + IntentionAction fix = + QUICK_FIX_FACTORY.createModifierListFix(packageLocalClassInTheMiddle, PsiModifier.PUBLIC, true, true); + QuickFixAction.registerQuickFixAction(errorResult, fix); + return; + } + + try { + Project project = refElement.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); + modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); + @Modifier String minModifier = PsiModifier.PACKAGE_LOCAL; + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + minModifier = PsiModifier.PROTECTED; + } + if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { + minModifier = PsiModifier.PUBLIC; + } + String[] modifiers = {PsiModifier.PACKAGE_LOCAL, PsiModifier.PROTECTED, PsiModifier.PUBLIC,}; + PsiClass accessObjectClass = null; + PsiElement qualifier = place.getQualifier(); + if (qualifier instanceof PsiExpression) { + accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass((PsiExpression)qualifier).getElement(); + } + for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { + @Modifier String modifier = modifiers[i]; + modifierListCopy.setModifierProperty(modifier, true); + if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { + IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(refElement, modifier, true, true); + TextRange fixRange = new TextRange(errorResult.startOffset, errorResult.endOffset); + PsiElement ref = place.getReferenceNameElement(); + if (ref != null) { + fixRange = fixRange.union(ref.getTextRange()); + } + QuickFixAction.registerQuickFixAction(errorResult, fixRange, fix, null); + } + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + @Nullable + private static PsiClass getPackageLocalClassInTheMiddle(PsiJavaCodeReferenceElement place) { + if (place instanceof PsiReferenceExpression) { + // check for package local classes in the middle + PsiReferenceExpression expression = (PsiReferenceExpression)place; + while (true) { + PsiElement resolved = expression.resolve(); + if (resolved instanceof PsiField) { + PsiField field = (PsiField)resolved; + PsiClass aClass = field.getContainingClass(); + if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && + !JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, place)) { + + return aClass; + } + } + PsiExpression qualifier = expression.getQualifierExpression(); + if (!(qualifier instanceof PsiReferenceExpression)) break; + expression = (PsiReferenceExpression)qualifier; + } + } + return null; + } + + + @Nullable + static HighlightInfo checkInstanceOfApplicable(PsiInstanceOfExpression expression) { + PsiExpression operand = expression.getOperand(); + PsiTypeElement typeElement = expression.getCheckType(); + if (typeElement == null) return null; + PsiType checkType = typeElement.getType(); + PsiType operandType = operand.getType(); + if (operandType == null) return null; + if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) + || TypeConversionUtil.isPrimitiveAndNotNull(checkType) + || !TypeConversionUtil.areTypesConvertible(operandType, checkType)) { + String message = JavaErrorMessages.message("inconvertible.type.cast", formatType(operandType), formatType(checkType)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); + } + return null; + } + + + @Nullable + static HighlightInfo checkInconvertibleTypeCast(PsiTypeCastExpression expression) { + PsiExpression operand = expression.getOperand(); + PsiType castType = expression.getCastType().getType(); + PsiType operandType = operand == null ? null : operand.getType(); + if (operandType != null && !TypeConversionUtil.areTypesConvertible(operandType, castType)) { + String message = JavaErrorMessages.message("inconvertible.type.cast", formatType(operandType), formatType(castType)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); + } + return null; + } + + + static HighlightInfo checkVariableExpected(PsiExpression expression) { + PsiExpression lValue; + if (expression instanceof PsiAssignmentExpression) { + PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; + lValue = assignment.getLExpression(); + } + else if (PsiUtil.isIncrementDecrementOperation(expression)) { + lValue = expression instanceof PsiPostfixExpression + ? ((PsiPostfixExpression)expression).getOperand() + : ((PsiPrefixExpression)expression).getOperand(); + } + else { + lValue = null; + } + HighlightInfo errorResult = null; + if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, lValue, JavaErrorMessages.message("variable.expected")); + } + + return errorResult; + } + + + @Nullable + static HighlightInfo checkAssignmentOperatorApplicable(PsiAssignmentExpression assignment) { + PsiJavaToken operationSign = assignment.getOperationSign(); + IElementType eqOpSign = operationSign.getTokenType(); + IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); + if (opSign == null) return null; + HighlightInfo errorResult = null; + if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, assignment.getLExpression(), assignment.getRExpression(), true)) { + String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); + String message = JavaErrorMessages.message("binary.operator.not.applicable", operatorText, + formatType(assignment.getLExpression().getType()), + formatType(assignment.getRExpression().getType())); + + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, assignment, message); + } + return errorResult; + } + + + @Nullable + static HighlightInfo checkAssignmentCompatibleTypes(PsiAssignmentExpression assignment) { + if (!"=".equals(assignment.getOperationSign().getText())) return null; + PsiExpression lExpr = assignment.getLExpression(); + PsiExpression rExpr = assignment.getRExpression(); + if (rExpr == null) return null; + PsiType lType = lExpr.getType(); + PsiType rType = rExpr.getType(); + if (rType == null) return null; + + HighlightInfo highlightInfo = checkAssignability(lType, rType, rExpr, assignment); + if (highlightInfo != null) { + PsiVariable leftVar = null; + if (lExpr instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)lExpr).resolve(); + if (element instanceof PsiVariable) { + leftVar = (PsiVariable)element; + } + } + if (leftVar != null) { + registerChangeVariableTypeFixes(leftVar, rType, highlightInfo); + } + } + return highlightInfo; + } + + private static boolean isCastIntentionApplicable(PsiExpression expression, PsiType toType) { + while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { + if (expression instanceof PsiTypeCastExpression) { + expression = ((PsiTypeCastExpression)expression).getOperand(); + } + if (expression instanceof PsiParenthesizedExpression) { + expression = ((PsiParenthesizedExpression)expression).getExpression(); + } + } + if (expression == null) return false; + PsiType rType = expression.getType(); + return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); + } + + + @Nullable + static HighlightInfo checkVariableInitializerType(PsiVariable variable) { + PsiExpression initializer = variable.getInitializer(); + // array initalizer checked in checkArrayInitializerApplicable + if (initializer == null || initializer instanceof PsiArrayInitializerExpression) return null; + PsiType lType = variable.getType(); + PsiType rType = initializer.getType(); + int start = variable.getTypeElement().getTextRange().getStartOffset(); + int end = variable.getTextRange().getEndOffset(); + HighlightInfo highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end)); + if (highlightInfo != null) { + registerChangeVariableTypeFixes(variable, rType, highlightInfo); + } + return highlightInfo; + } + + @Nullable + static HighlightInfo checkAssignability(PsiType lType, PsiType rType, PsiExpression expression, PsiElement elementToHighlight) { + TextRange textRange = elementToHighlight.getTextRange(); + return checkAssignability(lType, rType, expression, textRange); + } + + @Nullable + public static HighlightInfo checkAssignability(@Nullable PsiType lType, @Nullable PsiType rType, @Nullable PsiExpression expression, TextRange textRange) { + if (lType == rType) return null; + if (expression == null) { + if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) return null; + } + else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { + if (lType == null || rType == null) return null; + return GenericsHighlightUtil.checkRawToGenericAssignment(lType, rType, expression); + } + if (rType == null) { + rType = expression.getType(); + } + HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange); + if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { + QuickFixAction.registerQuickFixAction(highlightInfo, new AddTypeCastFix(lType, expression)); + } + if (expression != null && lType != null) { + QuickFixAction.registerQuickFixAction(highlightInfo, new WrapExpressionFix(lType, expression)); + } + ChangeNewOperatorTypeFix.register(highlightInfo, expression, lType); + return highlightInfo; + } + + + @Nullable + static HighlightInfo checkReturnStatementType(PsiReturnStatement statement) { + PsiMethod method = null; + PsiElement parent = statement.getParent(); + while (true) { + if (parent instanceof PsiFile) break; + if (parent instanceof PsiClassInitializer) break; + if (parent instanceof PsiMethod) { + method = (PsiMethod)parent; + break; + } + parent = parent.getParent(); + } + String description; + int navigationShift = 0; + HighlightInfo errorResult = null; + if (method == null && !(parent instanceof JspFile)) { + description = JavaErrorMessages.message("return.outside.method"); + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); + } + else { + PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; + boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); + final PsiExpression returnValue = statement.getReturnValue(); + if (returnValue != null) { + PsiType valueType = returnValue.getType(); + if (isMethodVoid) { + description = JavaErrorMessages.message("return.from.void.method"); + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); + if (valueType != null) { + IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true); + QuickFixAction.registerQuickFixAction(errorResult, fix); + } + } + else { + errorResult = checkAssignability(returnType, valueType, returnValue, statement); + if (errorResult != null && valueType != null) { + IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true); + QuickFixAction.registerQuickFixAction(errorResult, fix); + if (returnType instanceof PsiArrayType && TypeConversionUtil.isAssignable(((PsiArrayType)returnType).getComponentType(), valueType)) { + QuickFixAction.registerQuickFixAction(errorResult, new SurroundWithArrayFix(null){ + @Override + protected PsiExpression getExpression(final PsiElement element) { + return returnValue.isValid() ? returnValue : null; + } + }); + } + } + } + navigationShift = returnValue.getStartOffsetInParent(); + } + else { + if (!isMethodVoid) { + description = JavaErrorMessages.message("missing.return.value"); + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); + IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true); + QuickFixAction.registerQuickFixAction(errorResult, fix); + navigationShift = PsiKeyword.RETURN.length(); + } + } + } + if (errorResult != null) { + errorResult.navigationShift = navigationShift; + } + return errorResult; + } + + public static String getUnhandledExceptionsDescriptor(Collection unhandledExceptions) { + StringBuilder exceptionsText = new StringBuilder(); + for (PsiClassType unhandledException : unhandledExceptions) { + if (exceptionsText.length() != 0) exceptionsText.append(", "); + exceptionsText.append(formatType(unhandledException)); + } + + return JavaErrorMessages.message("unhandled.exceptions", exceptionsText.toString(), unhandledExceptions.size()); + } + + + @Nullable + static HighlightInfo checkVariableAlreadyDefined(PsiVariable variable) { + if (variable instanceof ExternallyDefinedPsiElement) return null; + boolean isIncorrect = false; + PsiIdentifier identifier = variable.getNameIdentifier(); + String name = variable.getName(); + if (variable instanceof PsiLocalVariable || + variable instanceof PsiParameter && ((PsiParameter)variable).getDeclarationScope() instanceof PsiCatchSection || + variable instanceof PsiParameter && ((PsiParameter)variable).getDeclarationScope() instanceof PsiForeachStatement) { + PsiElement scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class); + VariablesNotProcessor proc = new VariablesNotProcessor(variable, false){ + protected boolean check(final PsiVariable var, final ResolveState state) { + return (var instanceof PsiLocalVariable || var instanceof PsiParameter) && super.check(var, state); + } + }; + PsiScopesUtil.treeWalkUp(proc, identifier, scope); + if (proc.size() > 0) { + isIncorrect = true; + } + } + else if (variable instanceof PsiField) { + PsiField field = (PsiField)variable; + PsiClass aClass = field.getContainingClass(); + if (aClass == null) return null; + PsiField fieldByName = aClass.findFieldByName(name, false); + if (fieldByName != null && fieldByName != field) { + isIncorrect = true; + } + } + else { + PsiElement scope = variable.getParent(); + PsiElement[] children = scope.getChildren(); + for (PsiElement child : children) { + if (child instanceof PsiVariable) { + if (child.equals(variable)) continue; + if (name.equals(((PsiVariable)child).getName())) { + isIncorrect = true; + break; + } + } + } + } + + if (isIncorrect) { + String description = JavaErrorMessages.message("variable.already.defined", name); + HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, identifier, description); + QuickFixAction.registerQuickFixAction(highlightInfo, new ReuseVariableDeclarationFix(variable, identifier)); + return highlightInfo; + } + return null; + } + + @NotNull + public static String formatClass(@NotNull PsiClass aClass) { + return formatClass(aClass, true); + } + + @NotNull + public static String formatClass(@NotNull PsiClass aClass, boolean fqn) { + return PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME | + PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0)); + } + + @NotNull + public static String formatMethod(@NotNull PsiMethod method) { + return PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, + PsiFormatUtilBase.SHOW_TYPE); + } + + @NotNull + public static String formatType(@Nullable PsiType type) { + if (type == null) return PsiKeyword.NULL; + String text = type.getInternalCanonicalText(); + return text == null ? PsiKeyword.NULL : text; + } + + + public static HighlightInfo checkUnhandledExceptions(PsiElement element, TextRange fixRange) { + List unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); + HighlightInfo errorResult = null; + if (!unhandledExceptions.isEmpty()) { + if (fixRange == null) { + fixRange = element.getTextRange(); + } + HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); + if (highlightType == null) return null; + errorResult = HighlightInfo.createHighlightInfo(highlightType, fixRange, getUnhandledExceptionsDescriptor(unhandledExceptions)); + QuickFixAction.registerQuickFixAction(errorResult, new AddExceptionToCatchFix()); + QuickFixAction.registerQuickFixAction(errorResult, new AddExceptionToThrowsFix(element)); + QuickFixAction.registerQuickFixAction(errorResult, new SurroundWithTryCatchFix(element)); + if (unhandledExceptions.size() == 1) { + QuickFixAction.registerQuickFixAction(errorResult, new GeneralizeCatchFix(element, unhandledExceptions.get(0))); + } + } + return errorResult; + } + + private static HighlightInfoType getUnhandledExceptionHighlightType(final PsiElement element) { + if (!JspPsiUtil.isInJspFile(element)) { + return HighlightInfoType.UNHANDLED_EXCEPTION; + } + PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); + if (!(targetMethod instanceof JspHolderMethod)) return HighlightInfoType.UNHANDLED_EXCEPTION; + // ignore JSP top level errors - it handled by UnhandledExceptionInJSP inspection + return null; + } + + + @Nullable + static HighlightInfo checkBreakOutsideLoop(PsiBreakStatement statement) { + if (statement.getLabelIdentifier() == null) { + if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { + return HighlightInfo + .createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("break.outside.switch.or.loop")); + } + } + else { + // todo labeled + } + return null; + } + + + @Nullable + static HighlightInfo checkContinueOutsideLoop(PsiContinueStatement statement) { + if (statement.getLabelIdentifier() == null) { + if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("continue.outside.loop")); + } + } + else { + PsiStatement exitedStatement = statement.findContinuedStatement(); + if (exitedStatement == null) return null; + if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) && + !(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof PsiForeachStatement)) { + String description = JavaErrorMessages.message("not.loop.label", statement.getLabelIdentifier().getText()); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); + } + } + return null; + } + + + static HighlightInfo checkIllegalModifierCombination(PsiKeyword keyword, PsiModifierList modifierList) { + @Modifier String modifier = keyword.getText(); + String incompatible = getIncompatibleModifier(modifier, modifierList); + + HighlightInfo highlightInfo = null; + if (incompatible != null) { + String message = JavaErrorMessages.message("incompatible.modifiers", modifier, incompatible); + + highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, keyword, message); + + QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); + } + return highlightInfo; + } + + @Nullable + private static Map> getIncompatibleModifierMap(PsiModifierList modifierList) { + PsiElement parent = modifierList.getParent(); + if (parent == null || PsiUtilBase.hasErrorElementChild(parent)) return null; + return parent instanceof PsiClass + ? ((PsiClass)parent).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers + : parent instanceof PsiMethod + ? ourMethodIncompatibleModifiers + : parent instanceof PsiVariable + ? ourFieldIncompatibleModifiers + : parent instanceof PsiClassInitializer ? ourClassInitializerIncompatibleModifiers : null; + } + + @Nullable + public static String getIncompatibleModifier(String modifier, PsiModifierList modifierList) { + PsiElement parent = modifierList.getParent(); + if (parent == null || PsiUtilBase.hasErrorElementChild(parent)) return null; + final Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList); + if (incompatibleModifierMap == null) return null; + return getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap); + } + + + @Nullable + public static HighlightInfo checkNotAllowedModifier(PsiKeyword keyword, PsiModifierList modifierList) { + PsiElement modifierOwner = modifierList.getParent(); + if (modifierOwner == null) return null; + if (PsiUtilBase.hasErrorElementChild(modifierOwner)) return null; + @Modifier String modifier = keyword.getText(); + final Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList); + if (incompatibleModifierMap == null) return null; + Set incompatibles = incompatibleModifierMap.get(modifier); + PsiElement modifierOwnerParent = modifierOwner instanceof PsiMember ? ((PsiMember)modifierOwner).getContainingClass() : modifierOwner.getParent(); + if (modifierOwnerParent == null) modifierOwnerParent = modifierOwner.getParent(); + boolean isAllowed = true; + if (modifierOwner instanceof PsiClass) { + PsiClass aClass = (PsiClass)modifierOwner; + if (aClass.isInterface()) { + if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || + PsiModifier.PACKAGE_LOCAL.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass; + } + } + else { + if (PsiModifier.PUBLIC.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiJavaFile || modifierOwnerParent instanceof PsiClass; + } + else if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || + PsiModifier.PACKAGE_LOCAL.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass; + } + + if (aClass.isEnum()) { + isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); + } + } + } + else if (modifierOwner instanceof PsiMethod) { + PsiMethod method = (PsiMethod)modifierOwner; + isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); + PsiClass containingClass = method.getContainingClass(); + if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() && + containingClass != null && containingClass.isEnum()) { + isAllowed = false; + } + + if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || + PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { + isAllowed &= modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); + } + } + else if (modifierOwner instanceof PsiField) { + if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || + PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); + } + } + else if (modifierOwner instanceof PsiClassInitializer) { + isAllowed = PsiModifier.STATIC.equals(modifier); + } + else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { + isAllowed = PsiModifier.FINAL.equals(modifier); + } + + isAllowed &= incompatibles != null; + if (!isAllowed) { + String message = JavaErrorMessages.message("modifier.not.allowed", modifier); + + HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, keyword, message); + QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); + return highlightInfo; + } + return null; + } + + + @Nullable + static HighlightInfo checkLiteralExpressionParsingError(PsiLiteralExpression expression) { + String error = expression.getParsingError(); + if (error != null) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, error); + } + return null; + } + + + @Nullable + static HighlightInfo checkMustBeBoolean(PsiExpression expr) { + PsiElement parent = expr.getParent(); + if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || + parent instanceof PsiForStatement && expr.equals(((PsiForStatement)parent).getCondition()) || + parent instanceof PsiDoWhileStatement && expr.equals(((PsiDoWhileStatement)parent).getCondition())) { + if (expr.getNextSibling() instanceof PsiErrorElement) return null; + + PsiType type = expr.getType(); + if (!TypeConversionUtil.isBooleanType(type)) { + final HighlightInfo info = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange()); + if (expr instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) expr; + final PsiMethod method = methodCall.resolveMethod(); + if (method != null && PsiType.VOID.equals(method.getReturnType())) { + IntentionAction fix = QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.BOOLEAN, true); + QuickFixAction.registerQuickFixAction(info, fix); + } + } + return info; + } + } + return null; + } + + + @Nullable + static HighlightInfo checkExceptionThrownInTry(PsiParameter parameter) { + PsiElement declarationScope = parameter.getDeclarationScope(); + if (!(declarationScope instanceof PsiCatchSection)) return null; + PsiTryStatement statement = ((PsiCatchSection)declarationScope).getTryStatement(); + Collection classes = ExceptionUtil.collectUnhandledExceptions(statement.getTryBlock(), statement.getTryBlock()); + + PsiType caughtType = parameter.getType(); + if (!(caughtType instanceof PsiClassType)) return null; + if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)caughtType)) return null; + + for (PsiClassType exceptionType : classes) { + if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) return null; + } + + String description = JavaErrorMessages.message("exception.never.thrown.try", formatType(caughtType)); + HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, parameter, description); + + QuickFixAction.registerQuickFixAction(errorResult, new DeleteCatchFix(parameter)); + return errorResult; + } + + + @Nullable + static HighlightInfo checkNotAStatement(PsiStatement statement) { + if (!PsiUtil.isStatement(statement) && !PsiUtilBase.hasErrorElementChild(statement)) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("not.a.statement")); + } + return null; + } + + + public static HighlightInfo checkSwitchSelectorType(PsiSwitchStatement statement) { + PsiExpression expression = statement.getExpression(); + HighlightInfo errorResult = null; + if (expression != null && expression.getType() != null) { + PsiType type = expression.getType(); + if (!isValidTypeForSwitchSelector(type, expression)) { + String message = + JavaErrorMessages.message("incompatible.types", JavaErrorMessages.message("valid.switch.selector.types"), formatType(type)); + errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); + if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { + QuickFixAction.registerQuickFixAction(errorResult, new AddTypeCastFix(PsiType.INT, expression)); + } + } + } + return errorResult; + } + + private static boolean isValidTypeForSwitchSelector(PsiType type, PsiExpression expression) { + if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) return true; + if (type instanceof PsiClassType) { + PsiClass psiClass = ((PsiClassType)type).resolve(); + if (psiClass == null) return false; + if (psiClass.isEnum()) { + return true; + } + if (PsiUtil.isLanguageLevel7OrHigher(expression)) { + return Comparing.strEqual(psiClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING); + } + } + return false; + } + + + @Nullable + static HighlightInfo checkBinaryOperatorApplicable(PsiBinaryExpression expression) { + PsiExpression lOperand = expression.getLOperand(); + PsiExpression rOperand = expression.getROperand(); + PsiJavaToken operationSign = expression.getOperationSign(); + if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign.getTokenType(), lOperand, rOperand, false)) { + String message = JavaErrorMessages + .message("binary.operator.not.applicable", operationSign.getText(), formatType(lOperand.getType()), formatType(rOperand.getType())); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, message); + } + return null; + } + + + @Nullable + public static HighlightInfo checkUnaryOperatorApplicable(PsiJavaToken token, PsiExpression expression) { + if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { + PsiType type = expression.getType(); + if (type == null) return null; + String message = JavaErrorMessages.message("unary.operator.not.applicable", token.getText(), formatType(type)); + + PsiElement parentExpr = token.getParent(); + HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, parentExpr, message); + if (parentExpr instanceof PsiPrefixExpression && token.getTokenType() == JavaTokenType.EXCL) { + QuickFixAction.registerQuickFixAction(highlightInfo, new NegationBroadScopeFix((PsiPrefixExpression)parentExpr)); + } + return highlightInfo; + } + return null; + } + + @Nullable + public static HighlightInfo checkThisOrSuperExpressionInIllegalContext(PsiExpression expr, @Nullable PsiJavaCodeReferenceElement qualifier) { + if (expr instanceof PsiSuperExpression && !(expr.getParent() instanceof PsiReferenceExpression)) { + // like in 'Object o = super;' + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expr.getTextRange().getEndOffset(), + expr.getTextRange().getEndOffset() + 1, + JavaErrorMessages.message("dot.expected.after.super.or.this")); + } + PsiElement resolved = null; + PsiClass aClass = qualifier == null ? PsiTreeUtil.getParentOfType(expr, PsiClass.class) : (resolved = qualifier.resolve()) instanceof PsiClass ? (PsiClass)resolved : null; + if (resolved != null && !(resolved instanceof PsiClass)) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, qualifier, JavaErrorMessages.message("class.expected")); + } + if (aClass == null) return null; + if (qualifier != null && aClass.isInterface()) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, qualifier, HighlightClassUtil.NO_INTERFACE_EXPECTED); + } + if (!HighlightClassUtil.hasEnclosingInstanceInScope(aClass, expr, false)) { + return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); + } + + return null; + + } + + static String buildProblemWithStaticDescription(PsiElement refElement) { + String type = LanguageFindUsages.INSTANCE.forLanguage(StdLanguages.JAVA).getType(refElement); + String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); + return JavaErrorMessages.message("non.static.symbol.referenced.from.static.context", type, name); + } + + static void registerStaticProblemQuickFixAction(@NotNull PsiElement refElement, HighlightInfo errorResult, PsiJavaCodeReferenceElement place) { + if (refElement instanceof PsiModifierListOwner) { + QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix((PsiModifierListOwner)refElement, PsiModifier.STATIC, true, false)); + } + // make context non static + PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); + if (staticParent != null && isInstanceReference(place)) { + QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false)); + } + } + + private static boolean isInstanceReference(PsiJavaCodeReferenceElement place) { + PsiElement qualifier = place.getQualifier(); + if (qualifier == null) return true; + if (!(qualifier instanceof PsiJavaCodeReferenceElement)) return false; + PsiElement q = ((PsiReference)qualifier).resolve(); + if (q instanceof PsiClass) return false; + if (q != null) return true; + String qname = ((PsiJavaCodeReferenceElement)qualifier).getQualifiedName(); + return qname == null || !Character.isLowerCase(qname.charAt(0)); + } + + static String buildProblemWithAccessDescription(PsiJavaCodeReferenceElement reference, JavaResolveResult result) { + PsiModifierListOwner refElement = (PsiModifierListOwner)result.getElement(); + String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + + if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { + String containerName = HighlightMessageUtil.getSymbolName(refElement.getParent(), result.getSubstitutor()); + return JavaErrorMessages.message("private.symbol", symbolName, containerName); + } + else { + if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { + String containerName = HighlightMessageUtil.getSymbolName(refElement.getParent(), result.getSubstitutor()); + return JavaErrorMessages.message("protected.symbol", symbolName, containerName); + } + else { + PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); + if (packageLocalClass != null) { + refElement = packageLocalClass; + symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + } + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { + String containerName = HighlightMessageUtil.getSymbolName(refElement.getParent(), result.getSubstitutor()); + return JavaErrorMessages.message("package.local.symbol", symbolName, containerName); + } + else { + String containerName = HighlightMessageUtil.getSymbolName( + refElement instanceof PsiTypeParameter ? refElement.getParent().getParent() : refElement.getParent(), result.getSubstitutor()); + return JavaErrorMessages.message("visibility.access.problem", symbolName, containerName); + } + } + } + } + + @Nullable + static HighlightInfo checkValidArrayAccessExpression(PsiExpression arrayExpression, PsiExpression indexExpression) { + PsiType arrayExpressionType = arrayExpression == null ? null : arrayExpression.getType(); + if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { + String description = JavaErrorMessages.message("array.type.expected", formatType(arrayExpressionType)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, arrayExpression, description); + } + return checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression); + } + + + @Nullable + public static HighlightInfo checkCatchParameterIsThrowable(PsiParameter parameter) { + if (parameter.getDeclarationScope() instanceof PsiCatchSection) { + PsiType type = parameter.getType(); + return checkMustBeThrowable(type, parameter, true); + } + return null; + } + + public static void checkArrayInitalizer(final PsiExpression initializer, final HighlightInfoHolder holder) { + if (! (initializer instanceof PsiArrayInitializerExpression)) return; + + final PsiType arrayInitializerType = initializer.getType(); + if (! (arrayInitializerType instanceof PsiArrayType)) return; + + final PsiType componentType = ((PsiArrayType) arrayInitializerType).getComponentType(); + final PsiArrayInitializerExpression arrayInitializer = (PsiArrayInitializerExpression) initializer; + + boolean arrayTypeFixChecked = false; + VariableArrayTypeFix fix = null; + + final PsiExpression[] initializers = arrayInitializer.getInitializers(); + for (PsiExpression expression : initializers) { + final HighlightInfo info = checkArrayInitalizerCompatibleTypes(expression, componentType); + if (info != null) { + holder.add(info); + + if (!arrayTypeFixChecked) { + final PsiType checkResult = sameType(initializers); + fix = checkResult != null ? new VariableArrayTypeFix(arrayInitializer, checkResult) : null; + arrayTypeFixChecked = true; + } + if (fix != null) { + QuickFixAction.registerQuickFixAction(info, fix); + } + } + } + } + + @Nullable + private static PsiType getArrayInitializerType(final PsiArrayInitializerExpression element) { + final PsiType typeCheckResult = sameType(element.getInitializers()); + if (typeCheckResult != null) { + return typeCheckResult.createArrayType(); + } + return null; + } + + private static PsiType sameType(PsiExpression[] expressions) { + PsiType type = null; + for (PsiExpression expression : expressions) { + final PsiType currentType; + if (expression instanceof PsiArrayInitializerExpression) { + currentType = getArrayInitializerType((PsiArrayInitializerExpression)expression); + } + else { + currentType = expression.getType(); + } + if (type == null) { + type = currentType; + } + else if (!type.equals(currentType)) { + return null; + } + } + return type; + } + + @Nullable + private static HighlightInfo checkArrayInitalizerCompatibleTypes(PsiExpression initializer, final PsiType componentType) { + PsiType initializerType = initializer.getType(); + if (initializerType == null) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, initializer, + JavaErrorMessages.message("illegal.initializer", formatType(componentType))); + } + PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; + return checkAssignability(componentType, initializerType, expression, initializer); + } + + @Nullable + public static HighlightInfo checkExpressionRequired(PsiReferenceExpression expression) { + if (expression.getNextSibling() instanceof PsiErrorElement) return null; + PsiElement resolved = expression.advancedResolve(true).getElement(); + if (resolved == null) return null; + PsiElement parent = expression.getParent(); + // String.class or String() are both correct + if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) return null; + if (resolved instanceof PsiVariable) return null; + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, JavaErrorMessages.message("expression.expected")); + } + + + @Nullable + public static HighlightInfo checkArrayInitializerApplicable(PsiArrayInitializerExpression expression) { + /* + JLS 10.6 Array Initializers + An array initializer may be specified in a declaration, or as part of an array creation expression + */ + PsiElement parent = expression.getParent(); + if (parent instanceof PsiVariable) { + PsiVariable variable = (PsiVariable)parent; + if (variable.getType() instanceof PsiArrayType) return null; + } + else if (parent instanceof PsiNewExpression) { + return null; + } + else if (parent instanceof PsiArrayInitializerExpression) { + return null; + } + HighlightInfo info = + HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, JavaErrorMessages.message("expression.expected")); + QuickFixAction.registerQuickFixAction(info, new AddNewArrayExpressionFix(expression)); + + return info; + } + + + @Nullable + public static HighlightInfo checkCaseStatement(PsiSwitchLabelStatement statement) { + PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement(); + if (switchStatement == null) { + return HighlightInfo + .createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("case.statement.outside.switch")); + } + if (switchStatement.getBody() == null) return null; + PsiExpression switchExpression = switchStatement.getExpression(); + PsiType switchType = switchExpression == null ? PsiType.INT : switchExpression.getType(); + // check constant expression + PsiExpression caseValue = statement.getCaseValue(); + + // Every case constant expression associated with a switch statement must be assignable ($5.2) to the type of the switch Expression. + if (caseValue != null && switchExpression != null) { + HighlightInfo highlightInfo = checkAssignability(switchType, caseValue.getType(), caseValue, caseValue); + if (highlightInfo != null) return highlightInfo; + } + Object value = null; + + boolean isEnumSwitch = false; + if (!statement.isDefaultCase() && caseValue != null) { + if (caseValue instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)caseValue).resolve(); + if (element instanceof PsiEnumConstant) { + isEnumSwitch = true; + value = ((PsiEnumConstant)element).getName(); + if (!(((PsiReferenceExpression)caseValue).getQualifier() == null)) { + String message = JavaErrorMessages.message("qualified.enum.constant.in.switch"); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, caseValue, message); + } + } + } + if (!isEnumSwitch) { + value = ConstantExpressionUtil.computeCastTo(caseValue, switchType); + } + if (value == null) { + return HighlightInfo + .createHighlightInfo(HighlightInfoType.ERROR, caseValue, JavaErrorMessages.message("constant.expression.required")); + } + } + + // check duplicate + PsiStatement[] statements = switchStatement.getBody().getStatements(); + for (PsiStatement st : statements) { + if (st == statement) continue; + if (!(st instanceof PsiSwitchLabelStatement)) continue; + PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement)st; + if (labelStatement.isDefaultCase() != statement.isDefaultCase()) continue; + PsiExpression caseExpr = labelStatement.getCaseValue(); + if (isEnumSwitch && caseExpr instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)caseExpr).resolve(); + if (!(element instanceof PsiEnumConstant && Comparing.equal(((PsiEnumConstant)element).getName(), value))) continue; + } + else { + // not assignable error already caught + if (!TypeConversionUtil.areTypesAssignmentCompatible(switchType, caseExpr)) continue; + if (!Comparing.equal(ConstantExpressionUtil.computeCastTo(caseExpr, switchType), value)) continue; + } + String description = statement.isDefaultCase() + ? JavaErrorMessages.message("duplicate.default.switch.label") + : JavaErrorMessages.message("duplicate.switch.label", value); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, value == null ? statement : caseValue, description); + } + + // must be followed with colon + PsiElement lastChild = statement.getLastChild(); + while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { + lastChild = lastChild.getPrevSibling(); + } + if (!(lastChild instanceof PsiJavaToken && ((PsiJavaToken)lastChild).getTokenType() == JavaTokenType.COLON)) { + int start = statement.getTextRange().getEndOffset(); + int end = statement.getTextRange().getEndOffset() + 1; + String description = JavaErrorMessages.message("switch.colon.expected.after.case.label"); + CharSequence chars = statement.getContainingFile().getViewProvider().getContents(); + boolean isAfterEndOfLine = end >= chars.length() || chars.charAt(start) == '\n' || chars.charAt(start) == '\r'; + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, null,start, end, description, description,isAfterEndOfLine, null); + } + return null; + } + + + /** + * see JLS 8.3.2.3 + */ + @Nullable + public static HighlightInfo checkIllegalForwardReferenceToField(PsiReferenceExpression expression, PsiField referencedField) { + PsiClass containingClass = referencedField.getContainingClass(); + if (containingClass == null) return null; + if (expression.getContainingFile() != referencedField.getContainingFile()) return null; + if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) return null; + // only simple reference can be illegal + if (expression.getQualifierExpression() != null) return null; + PsiField initField = findEnclosingFieldInitializer(expression); + PsiClassInitializer classInitializer = findParentClassInitializer(expression); + if (initField == null && classInitializer == null) return null; + // instance initializers may access static fields + boolean isStaticClassInitializer = classInitializer != null && classInitializer.hasModifierProperty(PsiModifier.STATIC); + boolean isStaticInitField = initField != null && initField.hasModifierProperty(PsiModifier.STATIC); + boolean inStaticContext = isStaticInitField || isStaticClassInitializer; + if (!inStaticContext && referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; + if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) return null; + if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { + return null; + } + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, JavaErrorMessages.message("illegal.forward.reference")); + } + + /** + * @return field that has initializer with this element as subexpression or null if not found + */ + @Nullable + static PsiField findEnclosingFieldInitializer(PsiElement element) { + while (element != null) { + PsiElement parent = element.getParent(); + if (parent instanceof PsiField) { + PsiField field = (PsiField)parent; + if (element == field.getInitializer()) return field; + if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) return field; + } + if (element instanceof PsiClass || element instanceof PsiMethod) return null; + element = parent; + } + return null; + } + + @Nullable + private static PsiClassInitializer findParentClassInitializer(PsiElement element) { + while (element != null) { + if (element instanceof PsiClassInitializer) return (PsiClassInitializer)element; + if (element instanceof PsiClass || element instanceof PsiMethod) return null; + element = element.getParent(); + } + return null; + } + + + @Nullable + public static HighlightInfo checkIllegalType(PsiTypeElement typeElement) { + if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) return null; + + if (PsiUtil.isInsideJavadocComment(typeElement)) return null; + + PsiType type = typeElement.getType(); + PsiType componentType = type.getDeepComponentType(); + if (componentType instanceof PsiClassType) { + PsiClass aClass = PsiUtil.resolveClassInType(componentType); + if (aClass == null) { + String canonicalText = type.getCanonicalText(); + String description = JavaErrorMessages.message("unknown.class", canonicalText); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, typeElement, description); + } + } + return null; + } + + + @Nullable + public static HighlightInfo checkIllegalVoidType(PsiKeyword type) { + if (!PsiKeyword.VOID.equals(type.getText())) return null; + + PsiElement parent = type.getParent(); + if (parent instanceof PsiTypeElement) { + PsiElement typeOwner = parent.getParent(); + if (typeOwner instanceof PsiMethod) { + if (((PsiMethod)typeOwner).getReturnTypeElement() == parent) return null; + } + else if (typeOwner instanceof PsiClassObjectAccessExpression && + TypeConversionUtil.isVoidType(((PsiClassObjectAccessExpression)typeOwner).getOperand().getType())) { + // like in Class c = void.class; + return null; + } + else if (typeOwner != null && PsiUtilBase.hasErrorElementChild(typeOwner)) { + // do not highlight incomplete declarations + return null; + } + else if (typeOwner instanceof JavaCodeFragment) { + if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) return null; + } + } + + String description = JavaErrorMessages.message("illegal.type.void"); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, type, description); + } + + @Nullable + public static HighlightInfo checkMemberReferencedBeforeConstructorCalled(PsiElement expression, PsiElement resolved) { + PsiClass referencedClass; + @NonNls String resolvedName; + PsiType type; + if (expression instanceof PsiJavaCodeReferenceElement) { + // redirected ctr + if (PsiKeyword.THIS.equals(((PsiJavaCodeReferenceElement)expression).getReferenceName()) + && resolved instanceof PsiMethod + && ((PsiMethod)resolved).isConstructor()) return null; + PsiElement qualifier = ((PsiJavaCodeReferenceElement)expression).getQualifier(); + type = qualifier instanceof PsiExpression ? ((PsiExpression)qualifier).getType() : null; + referencedClass = PsiUtil.resolveClassInType(type); + + boolean isSuperCall = isSuperMethodCall(expression.getParent()); + if (resolved == null && isSuperCall) { + if (qualifier instanceof PsiReferenceExpression) { + resolved = ((PsiReferenceExpression)qualifier).resolve(); + expression = qualifier; + type = ((PsiReferenceExpression)qualifier).getType(); + referencedClass = PsiUtil.resolveClassInType(type); + } + else if (qualifier instanceof PsiThisExpression || qualifier == null) { + resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); + expression = qualifier == null ? expression : qualifier; + if (resolved instanceof PsiMethod) { + referencedClass = ((PsiMethod)resolved).getContainingClass(); + } + } + } + if (resolved instanceof PsiField) { + PsiField referencedField = (PsiField)resolved; + if (referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; + resolvedName = PsiFormatUtil.formatVariable(referencedField, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); + referencedClass = referencedField.getContainingClass(); + } + else if (resolved instanceof PsiMethod) { + PsiMethod method = (PsiMethod)resolved; + if (method.hasModifierProperty(PsiModifier.STATIC)) return null; + PsiElement nameElement = expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement(); + String name = nameElement == null ? null : nameElement.getText(); + if (isSuperCall) { + if (referencedClass == null) return null; + if (qualifier == null) { + PsiClass superClass = referencedClass.getSuperClass(); + if (superClass != null + && PsiUtil.isInnerClass(superClass) + && InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) { + // by default super() is considered this. - qualified + resolvedName = PsiKeyword.THIS; + } + else { + return null; + } + } + else { + resolvedName = qualifier.getText(); + } + } + else if (PsiKeyword.THIS.equals(name)) { + resolvedName = PsiKeyword.THIS; + } + else { + resolvedName = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | + PsiFormatUtilBase.SHOW_NAME, 0); + if (referencedClass == null) referencedClass = method.getContainingClass(); + } + } + else if (resolved instanceof PsiClass) { + PsiClass aClass = (PsiClass)resolved; + if (aClass.hasModifierProperty(PsiModifier.STATIC)) return null; + referencedClass = aClass.getContainingClass(); + if (referencedClass == null) return null; + resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); + } + else { + return null; + } + } + else if (expression instanceof PsiThisExpression) { + PsiThisExpression thisExpression = (PsiThisExpression)expression; + type = thisExpression.getType(); + referencedClass = PsiUtil.resolveClassInType(type); + if (thisExpression.getQualifier() != null) { + resolvedName = referencedClass == null + ? null + : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME) + ".this"; + } + else { + resolvedName = "this"; + } + } + else { + return null; + } + if (referencedClass == null) return null; + return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName); + } + + private static HighlightInfo checkReferenceToOurInstanceInsideThisOrSuper(final PsiElement expression, + final PsiClass referencedClass, + final String resolvedName) { + if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) return null; + PsiElement element = expression.getParent(); + while (element != null) { + // check if expression inside super()/this() call + if (isSuperOrThisMethodCall(element)) { + PsiElement parentClass = new PsiMatcherImpl(element) + .parent(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .parent(PsiMatchers.hasClass(PsiCodeBlock.class)) + .parent(PsiMatchers.hasClass(PsiMethod.class)) + .dot(PsiMatchers.isConstructor(true)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .getElement(); + if (parentClass == null) { + return null; + } + + // only this class/superclasses instance methods are not allowed to call + PsiClass aClass = (PsiClass)parentClass; + if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) return null; + // field or method should be declared in this class or super + if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) return null; + // and point to our instance + if (expression instanceof PsiReferenceExpression && + !thisOrSuperReference(((PsiReferenceExpression)expression).getQualifierExpression(), aClass)) { + return null; + } + return createMemberReferencedError(resolvedName, expression.getTextRange()); + } + element = element.getParent(); + } + return null; + } + + private static HighlightInfo createMemberReferencedError(@NonNls final String resolvedName, TextRange textRange) { + String description = JavaErrorMessages.message("member.referenced.before.constructor.called", resolvedName); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description); + } + + public static HighlightInfo checkImplicitThisReferenceBeforeSuper(PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) return null; + PsiClass superClass = aClass.getSuperClass(); + if (superClass == null || !PsiUtil.isInnerClass(superClass)) return null; + PsiClass outerClass = superClass.getContainingClass(); + if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { + return null; + } + // 'this' can be used as an (implicit) super() qualifier + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) { + TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return createMemberReferencedError(aClass.getName()+".this", range); + } + for (PsiMethod constructor : constructors) { + if (!isSuperCalledInConstructor(constructor)) { + return createMemberReferencedError(aClass.getName()+".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor)); + } + } + return null; + } + + private static boolean isSuperCalledInConstructor(final PsiMethod constructor) { + final PsiCodeBlock body = constructor.getBody(); + if (body == null) return false; + final PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) return false; + final PsiStatement statement = statements[0]; + final PsiElement element = new PsiMatcherImpl(statement) + .dot(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiReferenceExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) + .dot(PsiMatchers.hasText(PsiKeyword.SUPER)) + .getElement(); + return element != null; + } + + private static String getMethodExpressionName(PsiElement element) { + if (!(element instanceof PsiMethodCallExpression)) return null; + PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)element).getMethodExpression(); + return methodExpression.getReferenceName(); + } + public static boolean isSuperOrThisMethodCall(PsiElement element) { + String name = getMethodExpressionName(element); + return PsiKeyword.SUPER.equals(name) || PsiKeyword.THIS.equals(name); + } + public static boolean isSuperMethodCall(PsiElement element) { + String name = getMethodExpressionName(element); + return PsiKeyword.SUPER.equals(name); + } + + private static boolean thisOrSuperReference(PsiExpression qualifierExpression, PsiClass aClass) { + if (qualifierExpression == null) return true; + PsiJavaCodeReferenceElement qualifier; + if (qualifierExpression instanceof PsiThisExpression) { + qualifier = ((PsiThisExpression)qualifierExpression).getQualifier(); + } + else if (qualifierExpression instanceof PsiSuperExpression) { + qualifier = ((PsiSuperExpression)qualifierExpression).getQualifier(); + } + else { + return false; + } + if (qualifier == null) return true; + PsiElement resolved = qualifier.resolve(); + return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true); + } + + + @Nullable + public static HighlightInfo checkLabelWithoutStatement(PsiLabeledStatement statement) { + if (statement.getStatement() == null) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("label.without.statement")); + } + return null; + } + + + @Nullable + public static HighlightInfo checkLabelAlreadyInUse(PsiLabeledStatement statement) { + PsiIdentifier identifier = statement.getLabelIdentifier(); + String text = identifier.getText(); + PsiElement element = statement; + while (element != null) { + if (element instanceof PsiMethod || element instanceof PsiClass) break; + if (element instanceof PsiLabeledStatement && element != statement && + Comparing.equal(((PsiLabeledStatement)element).getLabelIdentifier().getText(), text)) { + String description = JavaErrorMessages.message("duplicate.label", text); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, identifier, description); + } + element = element.getParent(); + } + return null; + } + + + @Nullable + public static HighlightInfo checkUnclosedComment(PsiComment comment) { + if (!(comment instanceof PsiDocComment) && !(comment.getTokenType() == JavaTokenType.C_STYLE_COMMENT)) return null; + if (!comment.getText().endsWith("*/")) { + int start = comment.getTextRange().getEndOffset() - 1; + int end = start + 1; + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, start, end, JavaErrorMessages.message("nonterminated.comment")); + } + return null; + } + + + @Nullable + public static HighlightInfo checkExceptionAlreadyCaught(PsiJavaCodeReferenceElement element, PsiElement resolved) { + if (!(resolved instanceof PsiClass)) return null; + PsiClass catchClass = (PsiClass)resolved; + if (!(element.getParent() instanceof PsiTypeElement)) return null; + PsiElement catchParameter = element.getParent().getParent(); + if (!(catchParameter instanceof PsiParameter) || !(((PsiParameter)catchParameter).getDeclarationScope() instanceof PsiCatchSection)) { + return null; + } + PsiCatchSection catchSection = (PsiCatchSection)((PsiParameter)catchParameter).getDeclarationScope(); + PsiTryStatement statement = catchSection.getTryStatement(); + PsiCatchSection[] catchSections = statement.getCatchSections(); + int i = ArrayUtil.find(catchSections, catchSection); + for (i--; i >= 0; i--) { + PsiCatchSection section = catchSections[i]; + PsiType type = section.getCatchType(); + PsiClass upCatchClass = PsiUtil.resolveClassInType(type); + if (upCatchClass == null) continue; + if (InheritanceUtil.isInheritorOrSelf(catchClass, upCatchClass, true)) { + String description = JavaErrorMessages + .message("exception.already.caught", PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | + PsiFormatUtilBase.SHOW_FQ_NAME)); + HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, description); + QuickFixAction.registerQuickFixAction(highlightInfo, new MoveCatchUpFix(catchSection, section)); + QuickFixAction.registerQuickFixAction(highlightInfo, new DeleteCatchFix((PsiParameter)catchParameter)); + return highlightInfo; + } + } + return null; + } + + + @Nullable + public static HighlightInfo checkTernaryOperatorConditionIsBoolean(PsiExpression expression) { + if (expression.getParent() instanceof PsiConditionalExpression && + ((PsiConditionalExpression)expression.getParent()).getCondition() == expression && expression.getType() != null && + !TypeConversionUtil.isBooleanType(expression.getType())) { + PsiType foundType = expression.getType(); + return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, foundType, expression.getTextRange()); + } + return null; + } + + + @Nullable + public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(PsiStatement statement) { + if (!(statement instanceof PsiSwitchLabelStatement) && statement.getParent() instanceof PsiCodeBlock && + statement.getParent().getParent() instanceof PsiSwitchStatement && + ((PsiCodeBlock)statement.getParent()).getStatements().length != 0 && + statement == ((PsiCodeBlock)statement.getParent()).getStatements()[0]) { + return HighlightInfo + .createHighlightInfo(HighlightInfoType.ERROR, statement, JavaErrorMessages.message("statement.must.be.prepended.with.case.label")); + } + return null; + } + + + @Nullable + public static HighlightInfo checkAssertOperatorTypes(PsiExpression expression) { + if (!(expression.getParent() instanceof PsiAssertStatement)) { + return null; + } + PsiAssertStatement assertStatement = (PsiAssertStatement)expression.getParent(); + PsiType type = expression.getType(); + if (type == null) return null; + if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { + // addTypeCast quickfix is not applicable here since no type can be cast to boolean + return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange()); + } + else if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { + String description = JavaErrorMessages.message("void.type.is.not.allowed"); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, description); + } + return null; + } + + + @Nullable + public static HighlightInfo checkSynchronizedExpressionType(PsiExpression expression) { + if (expression.getParent() instanceof PsiSynchronizedStatement) { + PsiType type = expression.getType(); + if (type == null) return null; + PsiSynchronizedStatement synchronizedStatement = (PsiSynchronizedStatement)expression.getParent(); + if (expression == synchronizedStatement.getLockExpression() && + (type instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(type))) { + PsiClassType objectType = PsiType.getJavaLangObject(expression.getManager(), expression.getResolveScope()); + return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange()); + } + } + return null; + } + + + @Nullable + public static HighlightInfo checkConditionalExpressionBranchTypesMatch(PsiExpression expression) { + if (!(expression.getParent() instanceof PsiConditionalExpression)) { + return null; + } + PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)expression.getParent(); + // check else branches only + if (conditionalExpression.getElseExpression() != expression) return null; + final PsiExpression thenExpression = conditionalExpression.getThenExpression(); + assert thenExpression != null; + PsiType thenType = thenExpression.getType(); + PsiType elseType = expression.getType(); + if (thenType == null || elseType == null) return null; + if (conditionalExpression.getType() == null) { + // cannot derive type of conditional expression + // elsetype will never be castable to thentype, so no quick fix here + return createIncompatibleTypeHighlightInfo(thenType, elseType, expression.getTextRange()); + } + return null; + } + + private static HighlightInfo createIncompatibleTypeHighlightInfo(final PsiType lType, final PsiType rType, final TextRange textRange) { + PsiType lType1 = lType; + PsiType rType1 = rType; + PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; + PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; + if (lType1 instanceof PsiClassType) { + PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)lType1).resolveGenerics(); + lTypeSubstitutor = resolveResult.getSubstitutor(); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass instanceof PsiAnonymousClass) { + lType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); + resolveResult = ((PsiClassType)lType1).resolveGenerics(); + lTypeSubstitutor = resolveResult.getSubstitutor(); + psiClass = resolveResult.getElement(); + } + lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); + } + PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; + PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; + if (rType1 instanceof PsiClassType) { + PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)rType1).resolveGenerics(); + rTypeSubstitutor = resolveResult.getSubstitutor(); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass instanceof PsiAnonymousClass) { + rType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); + resolveResult = ((PsiClassType)rType1).resolveGenerics(); + rTypeSubstitutor = resolveResult.getSubstitutor(); + psiClass = resolveResult.getElement(); + } + rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); + } + + int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); + @Language("HTML") @NonNls String requiredRow = ""; + @Language("HTML") @NonNls String foundRow = ""; + for (int i = 0; i < typeParamColumns; i++) { + PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; + PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; + PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); + PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); + boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); + @NonNls String openBrace = i == 0 ? "<" : ""; + @NonNls String closeBrace = i == typeParamColumns - 1 ? ">" : ","; + requiredRow += "" + (lTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(lSubstitutedType, matches) + + (i < lTypeParams.length ? closeBrace : "") + ""; + foundRow += "" + (rTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(rSubstitutedType, matches) + + (i < rTypeParams.length ? closeBrace : "") + ""; + } + PsiType lRawType = lType1 instanceof PsiClassType ? ((PsiClassType)lType1).rawType() : lType1; + PsiType rRawType = rType1 instanceof PsiClassType ? ((PsiClassType)rType1).rawType() : rType1; + boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); + + String toolTip = JavaErrorMessages.message("incompatible.types.html.tooltip", + redIfNotMatch(lRawType, assignable), requiredRow, + redIfNotMatch(rRawType, assignable), foundRow); + + String description = JavaErrorMessages.message("incompatible.types", formatType(lType1), formatType(rType1)); + + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, null, textRange.getStartOffset(), textRange.getEndOffset(), + description, toolTip); + } + + @Nullable + public static HighlightInfo checkSingleImportClassConflict(PsiImportStatement statement, + Map> singleImportedClasses) { + if (statement.isOnDemand()) return null; + PsiElement element = statement.resolve(); + if (element instanceof PsiClass) { + String name = ((PsiClass)element).getName(); + Pair imported = singleImportedClasses.get(name); + PsiClass importedClass = imported == null ? null : imported.getSecond(); + if (importedClass != null && !element.getManager().areElementsEquivalent(importedClass, element)) { + String description = JavaErrorMessages.message("single.import.class.conflict", formatClass(importedClass)); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, statement, description); + } + singleImportedClasses.put(name, Pair.create(statement, (PsiClass)element)); + } + return null; + } + + + @NonNls + private static String redIfNotMatch(PsiType type, boolean matches) { + if (matches) return getFQName(type, false); + return "" + getFQName(type, true) + ""; + } + + private static String getFQName(PsiType type, boolean longName) { + if (type == null) return ""; + return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); + } + + + @Nullable + public static HighlightInfo checkMustBeThrowable(PsiType type, PsiElement context, boolean addCastIntention) { + if (type == null) return null; + PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); + PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", context.getResolveScope()); + if (!TypeConversionUtil.isAssignable(throwable, type)) { + HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange()); + if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) { + if (context instanceof PsiExpression) { + QuickFixAction.registerQuickFixAction(highlightInfo, new AddTypeCastFix(throwable, (PsiExpression)context)); + } + } + return highlightInfo; + } + return null; + } + + + @Nullable + private static HighlightInfo checkMustBeThrowable(PsiClass aClass, PsiElement context) { + if (aClass == null) return null; + PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); + return checkMustBeThrowable(type, context, false); + } + + + @Nullable + public static HighlightInfo checkLabelDefined(PsiIdentifier labelIdentifier, PsiStatement exitedStatement) { + if (labelIdentifier == null) return null; + String label = labelIdentifier.getText(); + if (label == null) return null; + if (exitedStatement == null) { + String message = JavaErrorMessages.message("unresolved.label", label); + return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, labelIdentifier, message); + } + return null; + } + + + @Nullable + public static HighlightInfo checkReference(PsiJavaCodeReferenceElement ref, JavaResolveResult result, PsiElement resolved) { + PsiElement refName = ref.getReferenceNameElement(); + + if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) return null; + HighlightInfo highlightInfo = checkMemberReferencedBeforeConstructorCalled(ref, resolved); + if (highlightInfo != null) return highlightInfo; + + PsiElement refParent = ref.getParent(); + if (refParent instanceof PsiMethodCallExpression) { + return null; // methods checked elsewhere + } + if (resolved == null) { + // do not highlight unknown packages - javac does not care about illegal package names + if (isInsidePackageStatement(refName)) return null; + if (result.isPackagePrefixPackageReference()) return null; + JavaResolveResult[] results = ref.multiResolve(true); + String description; + if (results.length > 1) { + String t1 = format(results[0].getElement()); + String t2 = format(results[1].getElement()); + description = JavaErrorMessages.message("ambiguous.reference", refName.getText(), t1, t2); + } + else { + description = JavaErrorMessages.message("cannot.resolve.symbol", refName.getText()); + } + + HighlightInfoType type = HighlightInfoType.WRONG_REF; + if (PsiUtil.isInsideJavadocComment(ref)) return null; + + HighlightInfo info = HighlightInfo.createHighlightInfo(type, refName, description); + + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, new QuickFixActionRegistrarImpl(info)); + return info; + } + + if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { + if (!result.isAccessible()) { + String description = buildProblemWithAccessDescription(ref, result); + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, ref.getReferenceNameElement(), description); + if (result.isStaticsScopeCorrect()) { + registerAccessQuickFixAction((PsiMember)resolved, ref, info, result.getCurrentFileResolveScope()); + if (ref instanceof PsiReferenceExpression) { + QuickFixAction.registerQuickFixAction(info, new RenameWrongRefFix((PsiReferenceExpression)ref)); + } + } + return info; + } + + if (!result.isStaticsScopeCorrect()) { + String description = buildProblemWithStaticDescription(resolved); + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, ref.getReferenceNameElement(), description); + registerStaticProblemQuickFixAction(resolved, info, ref); + if (ref instanceof PsiReferenceExpression) { + QuickFixAction.registerQuickFixAction(info, new RenameWrongRefFix((PsiReferenceExpression)ref)); + } + return info; + } + } + if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { + highlightInfo = HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref); + } + return highlightInfo; + } + + private static String format(PsiElement element) { + if (element instanceof PsiClass) return formatClass((PsiClass)element); + if (element instanceof PsiMethod) return formatMethod((PsiMethod)element); + return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); + } + + private static boolean isInsidePackageStatement(PsiElement element) { + while (element != null) { + if (element instanceof PsiPackageStatement) return true; + if (!(element instanceof PsiIdentifier) && !(element instanceof PsiJavaCodeReferenceElement)) return false; + element = element.getParent(); + } + return false; + } + + @Nullable + public static HighlightInfo checkElementInReferenceList(PsiJavaCodeReferenceElement ref, + PsiReferenceList referenceList, + JavaResolveResult resolveResult) { + PsiElement resolved = resolveResult.getElement(); + HighlightInfo highlightInfo = null; + PsiElement refGrandParent = referenceList.getParent(); + if (resolved instanceof PsiClass) { + PsiClass aClass = (PsiClass)resolved; + if (refGrandParent instanceof PsiClass) { + if (refGrandParent instanceof PsiTypeParameter) { + highlightInfo = GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, resolveResult, ref); + } + else { + highlightInfo = HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); + if (highlightInfo == null) { + highlightInfo = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); + } + if (highlightInfo == null) { + highlightInfo = GenericsHighlightUtil.checkCannotInheritFromEnum(aClass, ref); + } + if (highlightInfo == null) { + highlightInfo = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); + } + } + } + else if (refGrandParent instanceof PsiMethod && ((PsiMethod)refGrandParent).getThrowsList() == referenceList) { + highlightInfo = checkMustBeThrowable(aClass, ref); + } + } + else if (refGrandParent instanceof PsiMethod && referenceList == ((PsiMethod)refGrandParent).getThrowsList()) { + highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, ref, JavaErrorMessages.message("class.name.expected")); + } + return highlightInfo; + } + + + public static boolean isSerializable(PsiClass aClass) { + PsiManager manager = aClass.getManager(); + PsiClass serializableClass = JavaPsiFacade.getInstance(manager.getProject()).findClass("java.io.Serializable", aClass.getResolveScope()); + return serializableClass != null && aClass.isInheritor(serializableClass, true); + } + + public static boolean isSerializationImplicitlyUsedField(PsiField field) { + final String name = field.getName(); + if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) return false; + if (!field.hasModifierProperty(PsiModifier.STATIC)) return false; + PsiClass aClass = field.getContainingClass(); + return aClass == null || isSerializable(aClass); + } + + public static HighlightInfo checkClassReferenceAfterQualifier(final PsiReferenceExpression expression, final PsiElement resolved) { + if (!(resolved instanceof PsiClass)) return null; + final PsiExpression qualifier = expression.getQualifierExpression(); + if (qualifier == null) return null; + if (qualifier instanceof PsiReferenceExpression) { + PsiElement qualifierResolved = ((PsiReferenceExpression)qualifier).resolve(); + if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) return null; + } + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, qualifier, JavaErrorMessages.message("expected.class.or.package")); + QuickFixAction.registerQuickFixAction(info, new RemoveQualifierFix(qualifier, expression, (PsiClass)resolved)); + return info; + } + + public static void registerChangeVariableTypeFixes(PsiVariable parameter, PsiType itemType, HighlightInfo highlightInfo) { + for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { + for (IntentionAction action : fixProvider.getFixes(parameter, itemType)) { + QuickFixAction.registerQuickFixAction(highlightInfo, action, null); + } + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorFromThisOrSuperFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorFromThisOrSuperFix.java index b08848801b07..feef7fce0cda 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorFromThisOrSuperFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorFromThisOrSuperFix.java @@ -1,150 +1,150 @@ -/* - * 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.codeInsight.daemon.impl.quickfix; - -import com.intellij.codeInsight.CodeInsightUtil; -import com.intellij.codeInsight.CodeInsightUtilBase; -import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInsight.template.Template; -import com.intellij.codeInsight.template.TemplateBuilderImpl; -import com.intellij.codeInsight.template.TemplateEditingAdapter; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.undo.UndoUtil; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ex.RangeMarkerEx; -import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.NonNls; - -import java.util.List; - -/** - * Created by IntelliJ IDEA. - * User: ven - * Date: May 12, 2003 - * Time: 6:41:19 PM - * To change this template use Options | File Templates. - */ -public abstract class CreateConstructorFromThisOrSuperFix extends CreateFromUsageBaseFix { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.CreateConstructorFromThisOrSuperFix"); - - protected PsiMethodCallExpression myMethodCall; - - public CreateConstructorFromThisOrSuperFix(PsiMethodCallExpression methodCall) { - myMethodCall = methodCall; - } - - @NonNls - protected abstract String getSyntheticMethodName (); - - protected boolean isAvailableImpl(int offset) { - PsiReferenceExpression ref = myMethodCall.getMethodExpression(); - if (!ref.getText().equals(getSyntheticMethodName())) return false; - - PsiMethod method = PsiTreeUtil.getParentOfType(myMethodCall, PsiMethod.class); - if (method == null || !method.isConstructor()) return false; - if (CreateMethodFromUsageFix.hasErrorsInArgumentList(myMethodCall)) return false; - List targetClasses = getTargetClasses(myMethodCall); - LOG.assertTrue(targetClasses.size() == 1); - - if (CreateFromUsageUtils.shouldShowTag(offset, ref.getReferenceNameElement(), myMethodCall)) { - setText(QuickFixBundle.message("create.constructor.text", targetClasses.get(0).getName())); - return true; - } - - return false; - } - - protected void invokeImpl(PsiClass targetClass) { - final PsiFile callSite = myMethodCall.getContainingFile(); - final Project project = myMethodCall.getProject(); - PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); - - IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace(); - - try { - PsiMethod constructor = elementFactory.createConstructor(); - constructor = (PsiMethod)targetClass.add(constructor); - - final TemplateBuilderImpl templateBuilder = new TemplateBuilderImpl(constructor); - CreateFromUsageUtils.setupMethodParameters(constructor, templateBuilder, myMethodCall.getArgumentList(), - getTargetSubstitutor(myMethodCall)); - - final PsiFile psiFile = myMethodCall.getContainingFile(); - - templateBuilder.setEndVariableAfter(constructor.getBody().getLBrace()); - final RangeMarker rangeMarker = psiFile.getViewProvider().getDocument().createRangeMarker(myMethodCall.getTextRange()); - - constructor = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(constructor); - - targetClass = constructor.getContainingClass(); - myMethodCall = CodeInsightUtil.findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), myMethodCall.getClass()); - rangeMarker.dispose(); - - Template template = templateBuilder.buildTemplate(); - final Editor editor = positionCursor(project, targetClass.getContainingFile(), targetClass); - final TextRange textRange = constructor.getTextRange(); - final PsiFile file = targetClass.getContainingFile(); - editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset()); - editor.getCaretModel().moveToOffset(textRange.getStartOffset()); - - - startTemplate(editor, template, project, new TemplateEditingAdapter() { - public void templateFinished(Template template, boolean brokenOff) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); - final int offset = editor.getCaretModel().getOffset(); - PsiMethod constructor = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethod.class, false); - CreateFromUsageUtils.setupMethodBody(constructor); - CreateFromUsageUtils.setupEditor(constructor, editor); - - UndoUtil.markPsiFileForUndo(callSite); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - }); - } - }); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - protected boolean isValidElement(PsiElement element) { - PsiMethodCallExpression methodCall = (PsiMethodCallExpression) element; - PsiMethod method = (PsiMethod) methodCall.getMethodExpression().resolve(); - PsiExpressionList argumentList = methodCall.getArgumentList(); - PsiClass targetClass = getTargetClasses(element).get(0); - - return !CreateFromUsageUtils.shouldCreateConstructor(targetClass, argumentList, method); - } - - protected PsiElement getElement() { - if (!myMethodCall.isValid() || !myMethodCall.getManager().isInProject(myMethodCall)) return null; - return myMethodCall; - } -} +/* + * 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.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.CodeInsightUtil; +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateBuilderImpl; +import com.intellij.codeInsight.template.TemplateEditingAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.undo.UndoUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.ex.RangeMarkerEx; +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NonNls; + +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: ven + * Date: May 12, 2003 + * Time: 6:41:19 PM + * To change this template use Options | File Templates. + */ +public abstract class CreateConstructorFromThisOrSuperFix extends CreateFromUsageBaseFix { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.CreateConstructorFromThisOrSuperFix"); + + protected PsiMethodCallExpression myMethodCall; + + public CreateConstructorFromThisOrSuperFix(PsiMethodCallExpression methodCall) { + myMethodCall = methodCall; + } + + @NonNls + protected abstract String getSyntheticMethodName (); + + protected boolean isAvailableImpl(int offset) { + PsiReferenceExpression ref = myMethodCall.getMethodExpression(); + if (!ref.getText().equals(getSyntheticMethodName())) return false; + + PsiMethod method = PsiTreeUtil.getParentOfType(myMethodCall, PsiMethod.class); + if (method == null || !method.isConstructor()) return false; + if (CreateMethodFromUsageFix.hasErrorsInArgumentList(myMethodCall)) return false; + List targetClasses = getTargetClasses(myMethodCall); + LOG.assertTrue(targetClasses.size() == 1); + + if (CreateFromUsageUtils.shouldShowTag(offset, ref.getReferenceNameElement(), myMethodCall)) { + setText(QuickFixBundle.message("create.constructor.text", targetClasses.get(0).getName())); + return true; + } + + return false; + } + + protected void invokeImpl(PsiClass targetClass) { + final PsiFile callSite = myMethodCall.getContainingFile(); + final Project project = myMethodCall.getProject(); + PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + + IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace(); + + try { + PsiMethod constructor = elementFactory.createConstructor(); + constructor = (PsiMethod)targetClass.add(constructor); + + final TemplateBuilderImpl templateBuilder = new TemplateBuilderImpl(constructor); + CreateFromUsageUtils.setupMethodParameters(constructor, templateBuilder, myMethodCall.getArgumentList(), + getTargetSubstitutor(myMethodCall)); + + final PsiFile psiFile = myMethodCall.getContainingFile(); + + templateBuilder.setEndVariableAfter(constructor.getBody().getLBrace()); + final RangeMarker rangeMarker = psiFile.getViewProvider().getDocument().createRangeMarker(myMethodCall.getTextRange()); + + constructor = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(constructor); + + targetClass = constructor.getContainingClass(); + myMethodCall = CodeInsightUtil.findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), myMethodCall.getClass()); + rangeMarker.dispose(); + + Template template = templateBuilder.buildTemplate(); + final Editor editor = positionCursor(project, targetClass.getContainingFile(), targetClass); + final TextRange textRange = constructor.getTextRange(); + final PsiFile file = targetClass.getContainingFile(); + editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset()); + editor.getCaretModel().moveToOffset(textRange.getStartOffset()); + + + startTemplate(editor, template, project, new TemplateEditingAdapter() { + public void templateFinished(Template template, boolean brokenOff) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + final int offset = editor.getCaretModel().getOffset(); + PsiMethod constructor = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethod.class, false); + CreateFromUsageUtils.setupMethodBody(constructor); + CreateFromUsageUtils.setupEditor(constructor, editor); + + UndoUtil.markPsiFileForUndo(callSite); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + }); + } + }); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + protected boolean isValidElement(PsiElement element) { + PsiMethodCallExpression methodCall = (PsiMethodCallExpression) element; + PsiMethod method = (PsiMethod) methodCall.getMethodExpression().resolve(); + PsiExpressionList argumentList = methodCall.getArgumentList(); + PsiClass targetClass = getTargetClasses(element).get(0); + + return !CreateFromUsageUtils.shouldCreateConstructor(targetClass, argumentList, method); + } + + protected PsiElement getElement() { + if (!myMethodCall.isValid() || !myMethodCall.getManager().isInProject(myMethodCall)) return null; + return myMethodCall; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java index ad3cbc5f4096..02870ac48ecc 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java @@ -1,287 +1,287 @@ -/* - * 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.codeInsight.daemon.impl.quickfix; - -import com.intellij.codeInsight.CodeInsightUtilBase; -import com.intellij.codeInsight.ExpectedTypeInfo; -import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; -import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.codeInsight.template.Template; -import com.intellij.codeInsight.template.TemplateBuilderImpl; -import com.intellij.codeInsight.template.TemplateEditingAdapter; -import com.intellij.lang.annotation.HighlightSeverity; -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.editor.RangeMarker; -import com.intellij.openapi.editor.ex.RangeMarkerEx; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.refactoring.util.FieldConflictsResolver; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.Processor; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @author Mike - */ -public class CreateMethodFromUsageFix extends CreateFromUsageBaseFix { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.CreateMethodFromUsageFix"); - - private final SmartPsiElementPointer myMethodCall; - - public CreateMethodFromUsageFix(PsiMethodCallExpression methodCall) { - myMethodCall = SmartPointerManager.getInstance(methodCall.getProject()).createSmartPsiElementPointer(methodCall); - } - - protected boolean isAvailableImpl(int offset) { - final PsiMethodCallExpression call = getMethodCall(); - if (call == null || !call.isValid()) return false; - PsiReferenceExpression ref = call.getMethodExpression(); - String name = ref.getReferenceName(); - - if (name == null || !JavaPsiFacade.getInstance(ref.getProject()).getNameHelper().isIdentifier(name)) return false; - if (hasErrorsInArgumentList(call)) return false; - setText(getDisplayString(name)); - return true; - } - - protected String getDisplayString(String name) { - return QuickFixBundle.message("create.method.from.usage.text", name); - } - - private static boolean isMethodSignatureExists(PsiMethodCallExpression call, PsiClass target) { - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList list = call.getArgumentList(); - PsiMethod[] methods = target.findMethodsByName(name, false); - for (PsiMethod method : methods) { - if (PsiUtil.isApplicable(method, PsiSubstitutor.EMPTY, list)) return true; - } - return false; - } - - static boolean hasErrorsInArgumentList(final PsiMethodCallExpression call) { - Project project = call.getProject(); - Document document = PsiDocumentManager.getInstance(project).getDocument(call.getContainingFile()); - if (document == null) return true; - - PsiExpressionList argumentList = call.getArgumentList(); - final TextRange argRange = argumentList.getTextRange(); - return !DaemonCodeAnalyzerImpl.processHighlights(document, project, HighlightSeverity.ERROR, - //strictly inside arg list - argRange.getStartOffset()+1, - argRange.getEndOffset()-1, new Processor() { - public boolean process(HighlightInfo info) { - return !(info.getActualStartOffset() > argRange.getStartOffset() && info.getActualEndOffset() < argRange.getEndOffset()); - } - }); - } - - protected PsiElement getElement() { - final PsiMethodCallExpression call = getMethodCall(); - if (call == null || !call.getManager().isInProject(call)) return null; - return call; - } - - @NotNull - protected List getTargetClasses(PsiElement element) { - List targets = super.getTargetClasses(element); - ArrayList result = new ArrayList(); - PsiMethodCallExpression call = getMethodCall(); - if (call == null) return Collections.emptyList(); - for (PsiClass target : targets) { - if (!isMethodSignatureExists(call, target)) { - result.add(target); - } - } - return result; - } - - protected void invokeImpl(final PsiClass targetClass) { - if (targetClass == null) return; - PsiMethodCallExpression expression = getMethodCall(); - if (expression == null) return; - final Project project = expression.getProject(); - PsiReferenceExpression ref = expression.getMethodExpression(); - - if (isValidElement(expression)) return; - - PsiClass parentClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class); - PsiMember enclosingContext = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, PsiField.class, PsiClassInitializer.class); - - String methodName = ref.getReferenceName(); - LOG.assertTrue(methodName != null); - - PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); - - PsiMethod method = factory.createMethod(methodName, PsiType.VOID); - - if (targetClass.equals(parentClass)) { - method = (PsiMethod)targetClass.addAfter(method, enclosingContext); - } - else { - PsiElement anchor = enclosingContext; - while (anchor != null && anchor.getParent() != null && !anchor.getParent().equals(targetClass)) { - anchor = anchor.getParent(); - } - if (anchor != null && anchor.getParent() == null) anchor = null; - if (anchor != null) { - method = (PsiMethod)targetClass.addAfter(method, anchor); - } - else { - method = (PsiMethod)targetClass.add(method); - } - } - - if (enclosingContext instanceof PsiMethod && methodName.equals(enclosingContext.getName()) && - PsiTreeUtil.isAncestor(targetClass, parentClass, true)) { - FieldConflictsResolver.qualifyReference(ref, method, null); - } - - PsiCodeBlock body = method.getBody(); - assert body != null; - if (shouldBeAbstract(targetClass)) { - body.delete(); - if (!targetClass.isInterface()) { - method.getModifierList().setModifierProperty(PsiModifier.ABSTRACT, true); - } - } - - setupVisibility(parentClass, targetClass, method.getModifierList()); - - expression = getMethodCall(); - LOG.assertTrue(expression.isValid()); - - if (shouldCreateStaticMember(expression.getMethodExpression(), targetClass) && !shouldBeAbstract(targetClass)) { - PsiUtil.setModifierProperty(method, PsiModifier.STATIC, true); - } - - final PsiElement context = PsiTreeUtil.getParentOfType(expression, PsiClass.class, PsiMethod.class); - - PsiExpression[] arguments = expression.getArgumentList().getExpressions(); - doCreate(targetClass, method, shouldBeAbstract(targetClass), - ContainerUtil.map2List(arguments, Pair.createFunction(null)), - getTargetSubstitutor(expression), - CreateFromUsageUtils.guessExpectedTypes(expression, true), - context); - } - - public static void doCreate(PsiClass targetClass, PsiMethod method, List> arguments, PsiSubstitutor substitutor, - ExpectedTypeInfo[] expectedTypes, @Nullable PsiElement context) { - doCreate(targetClass, method, shouldBeAbstractImpl(targetClass), arguments, substitutor, expectedTypes, context); - } - - private static void doCreate(PsiClass targetClass, - PsiMethod method, - boolean shouldBeAbstract, - List> arguments, - PsiSubstitutor substitutor, - ExpectedTypeInfo[] expectedTypes, - @Nullable PsiElement context) { - - method = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(method); - - if (method == null) { - return; - } - final Project project = targetClass.getProject(); - final PsiFile targetFile = targetClass.getContainingFile(); - Document document = PsiDocumentManager.getInstance(project).getDocument(targetFile); - - TemplateBuilderImpl builder = new TemplateBuilderImpl(method); - - CreateFromUsageUtils.setupMethodParameters(method, builder, context, substitutor, arguments); - new GuessTypeParameters(JavaPsiFacade.getInstance(project).getElementFactory()) - .setupTypeElement(method.getReturnTypeElement(), expectedTypes, substitutor, builder, context, targetClass); - PsiCodeBlock body = method.getBody(); - builder.setEndVariableAfter(shouldBeAbstract || body == null ? method : body.getLBrace()); - method = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(method); - if (method == null) return; - - assert document != null; - RangeMarker rangeMarker = document.createRangeMarker(method.getTextRange()); - final Editor newEditor = positionCursor(project, targetFile, method); - Template template = builder.buildTemplate(); - newEditor.getCaretModel().moveToOffset(rangeMarker.getStartOffset()); - newEditor.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset()); - rangeMarker.dispose(); - - if (!shouldBeAbstract) { - startTemplate(newEditor, template, project, new TemplateEditingAdapter() { - public void templateFinished(Template template, boolean brokenOff) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - PsiDocumentManager.getInstance(project).commitDocument(newEditor.getDocument()); - final int offset = newEditor.getCaretModel().getOffset(); - PsiMethod method = PsiTreeUtil.findElementOfClassAtOffset(targetFile, offset - 1, PsiMethod.class, false); - - if (method != null) { - try { - CreateFromUsageUtils.setupMethodBody(method); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - - CreateFromUsageUtils.setupEditor(method, newEditor); - } - } - }); - } - }); - } - else { - startTemplate(newEditor, template, project); - } - } - - protected boolean shouldBeAbstract(PsiClass targetClass) { - return shouldBeAbstractImpl(targetClass); - } - - private static boolean shouldBeAbstractImpl(PsiClass targetClass) { - return targetClass.isInterface(); - } - - protected boolean isValidElement(PsiElement element) { - PsiMethodCallExpression callExpression = (PsiMethodCallExpression) element; - PsiReferenceExpression referenceExpression = callExpression.getMethodExpression(); - - return CreateFromUsageUtils.isValidMethodReference(referenceExpression, callExpression); - } - - @NotNull - public String getFamilyName() { - return QuickFixBundle.message("create.method.from.usage.family"); - } - - @Nullable - protected PsiMethodCallExpression getMethodCall() { - return (PsiMethodCallExpression)myMethodCall.getElement(); - } -} +/* + * 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.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.codeInsight.ExpectedTypeInfo; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateBuilderImpl; +import com.intellij.codeInsight.template.TemplateEditingAdapter; +import com.intellij.lang.annotation.HighlightSeverity; +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.editor.RangeMarker; +import com.intellij.openapi.editor.ex.RangeMarkerEx; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.util.FieldConflictsResolver; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Mike + */ +public class CreateMethodFromUsageFix extends CreateFromUsageBaseFix { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.CreateMethodFromUsageFix"); + + private final SmartPsiElementPointer myMethodCall; + + public CreateMethodFromUsageFix(PsiMethodCallExpression methodCall) { + myMethodCall = SmartPointerManager.getInstance(methodCall.getProject()).createSmartPsiElementPointer(methodCall); + } + + protected boolean isAvailableImpl(int offset) { + final PsiMethodCallExpression call = getMethodCall(); + if (call == null || !call.isValid()) return false; + PsiReferenceExpression ref = call.getMethodExpression(); + String name = ref.getReferenceName(); + + if (name == null || !JavaPsiFacade.getInstance(ref.getProject()).getNameHelper().isIdentifier(name)) return false; + if (hasErrorsInArgumentList(call)) return false; + setText(getDisplayString(name)); + return true; + } + + protected String getDisplayString(String name) { + return QuickFixBundle.message("create.method.from.usage.text", name); + } + + private static boolean isMethodSignatureExists(PsiMethodCallExpression call, PsiClass target) { + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList list = call.getArgumentList(); + PsiMethod[] methods = target.findMethodsByName(name, false); + for (PsiMethod method : methods) { + if (PsiUtil.isApplicable(method, PsiSubstitutor.EMPTY, list)) return true; + } + return false; + } + + static boolean hasErrorsInArgumentList(final PsiMethodCallExpression call) { + Project project = call.getProject(); + Document document = PsiDocumentManager.getInstance(project).getDocument(call.getContainingFile()); + if (document == null) return true; + + PsiExpressionList argumentList = call.getArgumentList(); + final TextRange argRange = argumentList.getTextRange(); + return !DaemonCodeAnalyzerImpl.processHighlights(document, project, HighlightSeverity.ERROR, + //strictly inside arg list + argRange.getStartOffset()+1, + argRange.getEndOffset()-1, new Processor() { + public boolean process(HighlightInfo info) { + return !(info.getActualStartOffset() > argRange.getStartOffset() && info.getActualEndOffset() < argRange.getEndOffset()); + } + }); + } + + protected PsiElement getElement() { + final PsiMethodCallExpression call = getMethodCall(); + if (call == null || !call.getManager().isInProject(call)) return null; + return call; + } + + @NotNull + protected List getTargetClasses(PsiElement element) { + List targets = super.getTargetClasses(element); + ArrayList result = new ArrayList(); + PsiMethodCallExpression call = getMethodCall(); + if (call == null) return Collections.emptyList(); + for (PsiClass target : targets) { + if (!isMethodSignatureExists(call, target)) { + result.add(target); + } + } + return result; + } + + protected void invokeImpl(final PsiClass targetClass) { + if (targetClass == null) return; + PsiMethodCallExpression expression = getMethodCall(); + if (expression == null) return; + final Project project = expression.getProject(); + PsiReferenceExpression ref = expression.getMethodExpression(); + + if (isValidElement(expression)) return; + + PsiClass parentClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class); + PsiMember enclosingContext = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, PsiField.class, PsiClassInitializer.class); + + String methodName = ref.getReferenceName(); + LOG.assertTrue(methodName != null); + + PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + + PsiMethod method = factory.createMethod(methodName, PsiType.VOID); + + if (targetClass.equals(parentClass)) { + method = (PsiMethod)targetClass.addAfter(method, enclosingContext); + } + else { + PsiElement anchor = enclosingContext; + while (anchor != null && anchor.getParent() != null && !anchor.getParent().equals(targetClass)) { + anchor = anchor.getParent(); + } + if (anchor != null && anchor.getParent() == null) anchor = null; + if (anchor != null) { + method = (PsiMethod)targetClass.addAfter(method, anchor); + } + else { + method = (PsiMethod)targetClass.add(method); + } + } + + if (enclosingContext instanceof PsiMethod && methodName.equals(enclosingContext.getName()) && + PsiTreeUtil.isAncestor(targetClass, parentClass, true)) { + FieldConflictsResolver.qualifyReference(ref, method, null); + } + + PsiCodeBlock body = method.getBody(); + assert body != null; + if (shouldBeAbstract(targetClass)) { + body.delete(); + if (!targetClass.isInterface()) { + method.getModifierList().setModifierProperty(PsiModifier.ABSTRACT, true); + } + } + + setupVisibility(parentClass, targetClass, method.getModifierList()); + + expression = getMethodCall(); + LOG.assertTrue(expression.isValid()); + + if (shouldCreateStaticMember(expression.getMethodExpression(), targetClass) && !shouldBeAbstract(targetClass)) { + PsiUtil.setModifierProperty(method, PsiModifier.STATIC, true); + } + + final PsiElement context = PsiTreeUtil.getParentOfType(expression, PsiClass.class, PsiMethod.class); + + PsiExpression[] arguments = expression.getArgumentList().getExpressions(); + doCreate(targetClass, method, shouldBeAbstract(targetClass), + ContainerUtil.map2List(arguments, Pair.createFunction(null)), + getTargetSubstitutor(expression), + CreateFromUsageUtils.guessExpectedTypes(expression, true), + context); + } + + public static void doCreate(PsiClass targetClass, PsiMethod method, List> arguments, PsiSubstitutor substitutor, + ExpectedTypeInfo[] expectedTypes, @Nullable PsiElement context) { + doCreate(targetClass, method, shouldBeAbstractImpl(targetClass), arguments, substitutor, expectedTypes, context); + } + + private static void doCreate(PsiClass targetClass, + PsiMethod method, + boolean shouldBeAbstract, + List> arguments, + PsiSubstitutor substitutor, + ExpectedTypeInfo[] expectedTypes, + @Nullable PsiElement context) { + + method = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(method); + + if (method == null) { + return; + } + final Project project = targetClass.getProject(); + final PsiFile targetFile = targetClass.getContainingFile(); + Document document = PsiDocumentManager.getInstance(project).getDocument(targetFile); + + TemplateBuilderImpl builder = new TemplateBuilderImpl(method); + + CreateFromUsageUtils.setupMethodParameters(method, builder, context, substitutor, arguments); + new GuessTypeParameters(JavaPsiFacade.getInstance(project).getElementFactory()) + .setupTypeElement(method.getReturnTypeElement(), expectedTypes, substitutor, builder, context, targetClass); + PsiCodeBlock body = method.getBody(); + builder.setEndVariableAfter(shouldBeAbstract || body == null ? method : body.getLBrace()); + method = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(method); + if (method == null) return; + + assert document != null; + RangeMarker rangeMarker = document.createRangeMarker(method.getTextRange()); + final Editor newEditor = positionCursor(project, targetFile, method); + Template template = builder.buildTemplate(); + newEditor.getCaretModel().moveToOffset(rangeMarker.getStartOffset()); + newEditor.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset()); + rangeMarker.dispose(); + + if (!shouldBeAbstract) { + startTemplate(newEditor, template, project, new TemplateEditingAdapter() { + public void templateFinished(Template template, boolean brokenOff) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + PsiDocumentManager.getInstance(project).commitDocument(newEditor.getDocument()); + final int offset = newEditor.getCaretModel().getOffset(); + PsiMethod method = PsiTreeUtil.findElementOfClassAtOffset(targetFile, offset - 1, PsiMethod.class, false); + + if (method != null) { + try { + CreateFromUsageUtils.setupMethodBody(method); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + + CreateFromUsageUtils.setupEditor(method, newEditor); + } + } + }); + } + }); + } + else { + startTemplate(newEditor, template, project); + } + } + + protected boolean shouldBeAbstract(PsiClass targetClass) { + return shouldBeAbstractImpl(targetClass); + } + + private static boolean shouldBeAbstractImpl(PsiClass targetClass) { + return targetClass.isInterface(); + } + + protected boolean isValidElement(PsiElement element) { + PsiMethodCallExpression callExpression = (PsiMethodCallExpression) element; + PsiReferenceExpression referenceExpression = callExpression.getMethodExpression(); + + return CreateFromUsageUtils.isValidMethodReference(referenceExpression, callExpression); + } + + @NotNull + public String getFamilyName() { + return QuickFixBundle.message("create.method.from.usage.family"); + } + + @Nullable + protected PsiMethodCallExpression getMethodCall() { + return (PsiMethodCallExpression)myMethodCall.getElement(); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java index e4ed1c823495..f786be7e5c9a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java @@ -1,740 +1,740 @@ -/* - * 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.codeInsight.generation; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.CodeInsightActionHandler; -import com.intellij.codeInsight.CodeInsightBundle; -import com.intellij.codeInsight.MethodImplementor; -import com.intellij.codeInsight.intention.AddAnnotationFix; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.ide.fileTemplates.FileTemplate; -import com.intellij.ide.fileTemplates.FileTemplateManager; -import com.intellij.ide.fileTemplates.FileTemplateUtil; -import com.intellij.ide.fileTemplates.JavaTemplateUtil; -import com.intellij.ide.util.MemberChooser; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ScrollType; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.impl.source.jsp.jspJava.JspClass; -import com.intellij.psi.infos.CandidateInfo; -import com.intellij.psi.javadoc.PsiDocComment; -import com.intellij.psi.util.*; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.util.*; - -public class OverrideImplementUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.OverrideImplementUtil"); - - @NonNls private static final String PROP_COMBINED_OVERRIDE_IMPLEMENT = "OverrideImplement.combined"; - - private OverrideImplementUtil() { - } - - @NotNull - public static Collection getMethodsToOverrideImplement(PsiClass aClass, boolean toImplement) { - return getMapToOverrideImplement(aClass, toImplement).values(); - } - - @NotNull - public static Collection getMethodSignaturesToImplement(@NotNull PsiClass aClass) { - return getMapToOverrideImplement(aClass, true).keySet(); - } - - @NotNull - public static Collection getMethodSignaturesToOverride(@NotNull PsiClass aClass) { - return getMapToOverrideImplement(aClass, false).keySet(); - } - - @NotNull - private static Map getMapToOverrideImplement(PsiClass aClass, boolean toImplement) { - Map abstracts = new LinkedHashMap(); - Map finals = new LinkedHashMap(); - Map concretes = new LinkedHashMap(); - - Collection allMethodSigs = aClass.getVisibleSignatures(); - PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper(); - for (HierarchicalMethodSignature signature : allMethodSigs) { - PsiMethod method = signature.getMethod(); - LOG.assertTrue(method.isValid()); - - if (method.hasModifierProperty(PsiModifier.STATIC) || !resolveHelper.isAccessible(method, aClass, aClass)) continue; - PsiClass hisClass = method.getContainingClass(); - if (hisClass == null) continue; - //Filter non-immediate super constructors - if (method.isConstructor() && (!aClass.isInheritor(hisClass, false) || aClass instanceof PsiAnonymousClass || aClass.isEnum())) { - continue; - } - - if (MethodSignatureUtil.findMethodBySignature(aClass, signature, false) != null) continue; - - if (method.hasModifierProperty(PsiModifier.FINAL)) { - finals.put(signature, method); - continue; - } - - Map map = hisClass.isInterface() || method.hasModifierProperty(PsiModifier.ABSTRACT) ? abstracts : concretes; - PsiMethod other = map.get(signature); - if (other == null || preferLeftForImplement(method, other)) { - map.put(signature, method); - } - } - - final Map result = new TreeMap(new MethodSignatureComparator()); - if (toImplement || aClass.isInterface()) { - collectMethodsToImplement(aClass, abstracts, finals, concretes, result); - } - else { - for (Map.Entry entry : concretes.entrySet()) { - MethodSignature signature = entry.getKey(); - PsiMethod concrete = entry.getValue(); - if (finals.get(signature) == null) { - PsiMethod abstractOne = abstracts.get(signature); - if (abstractOne == null || !abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true) || - CommonClassNames.JAVA_LANG_OBJECT.equals(concrete.getContainingClass().getQualifiedName())) { - PsiSubstitutor subst = GenerateMembersUtil.correctSubstitutor(concrete, signature.getSubstitutor()); - CandidateInfo info = new CandidateInfo(concrete, subst); - result.put(signature, info); - } - } - } - } - - return result; - } - - public static void collectMethodsToImplement(PsiClass aClass, - Map abstracts, - Map finals, - Map concretes, - Map result) { - for (Map.Entry entry : abstracts.entrySet()) { - MethodSignature signature = entry.getKey(); - PsiMethod abstractOne = entry.getValue(); - PsiMethod concrete = concretes.get(signature); - if (concrete == null - || PsiUtil.getAccessLevel(concrete.getModifierList()) < PsiUtil.getAccessLevel(abstractOne.getModifierList()) - || !abstractOne.getContainingClass().isInterface() && abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true)) { - if (finals.get(signature) == null) { - PsiSubstitutor subst = GenerateMembersUtil.correctSubstitutor(abstractOne, signature.getSubstitutor()); - CandidateInfo info = new CandidateInfo(abstractOne, subst); - result.put(signature, info); - } - } - } - - for (final MethodImplementor implementor : getImplementors()) { - for (final PsiMethod method : implementor.getMethodsToImplement(aClass)) { - MethodSignature signature = MethodSignatureUtil.createMethodSignature(method.getName(), method.getParameterList(), - method.getTypeParameterList(), PsiSubstitutor.EMPTY); - CandidateInfo info = new CandidateInfo(method, PsiSubstitutor.EMPTY); - result.put(signature, info); - } - } - } - - private static boolean preferLeftForImplement(PsiMethod left, PsiMethod right) { - if (PsiUtil.getAccessLevel(left.getModifierList()) > PsiUtil.getAccessLevel(right.getModifierList())) return true; - if (!left.getContainingClass().isInterface()) return true; - if (!right.getContainingClass().isInterface()) return false; - // implement annotated method - PsiAnnotation[] leftAnnotations = left.getModifierList().getAnnotations(); - PsiAnnotation[] rightAnnotations = right.getModifierList().getAnnotations(); - return leftAnnotations.length > rightAnnotations.length; - } - - private static MethodImplementor[] getImplementors() { - return Extensions.getExtensions(MethodImplementor.EXTENSION_POINT_NAME); - } - - /** - * generate methods (with bodies) corresponding to given method declaration - * there are maybe two method implementations for one declaration - * (e.g. EJB' create() -> ejbCreate(), ejbPostCreate() ) - * @param aClass context for method implementations - * @param method method to override or implement - * @param toCopyJavaDoc true if copy JavaDoc from method declaration - * @return list of method prototypes - */ - @NotNull - public static Collection overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) throws IncorrectOperationException { - final PsiClass containingClass = method.getContainingClass(); - PsiSubstitutor substitutor = aClass.isInheritor(containingClass, true) ? - TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY) : PsiSubstitutor.EMPTY; - return overrideOrImplementMethod(aClass, method, substitutor, toCopyJavaDoc, true); - } - - public static boolean isInsertOverride(PsiMethod superMethod, PsiClass targetClass) { - if (superMethod.isConstructor()) { - return false; - } - if (!CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION - || !PsiUtil.isLanguageLevel5OrHigher(targetClass)) { - return false; - } - if (PsiUtil.isLanguageLevel6OrHigher(targetClass)) return true; - if (targetClass.isInterface()) return true; - PsiClass superClass = superMethod.getContainingClass(); - return !superClass.isInterface(); - } - - @NotNull - private static Collection overrideOrImplementMethod(PsiClass aClass, - PsiMethod method, - PsiSubstitutor substitutor, - boolean toCopyJavaDoc, - boolean insertOverrideIfPossible) throws IncorrectOperationException { - if (!method.isValid() || !substitutor.isValid()) return Collections.emptyList(); - - List results = new ArrayList(); - for (final MethodImplementor implementor : getImplementors()) { - ContainerUtil.addAll(results, implementor.createImplementationPrototypes(aClass, method)); - } - if (results.isEmpty()) { - PsiMethod method1 = GenerateMembersUtil.substituteGenericMethod(method, substitutor); - - PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); - PsiMethod result = (PsiMethod)factory.createClass("Dummy").add(method1); - if (result instanceof PsiAnnotationMethod) { - PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod)result).getDefaultValue(); - if (defaultValue != null) { - PsiElement defaultKeyword = defaultValue; - while (!(defaultKeyword instanceof PsiKeyword) && defaultKeyword != null) { - defaultKeyword = defaultKeyword.getPrevSibling(); - } - if (defaultKeyword == null) defaultKeyword = defaultValue; - defaultValue.getParent().deleteChildRange(defaultKeyword, defaultValue); - } - } - results.add(result); - } - - for (Iterator iterator = results.iterator(); iterator.hasNext();) { - PsiMethod result = iterator.next(); - PsiUtil.setModifierProperty(result, PsiModifier.ABSTRACT, aClass.isInterface()); - PsiUtil.setModifierProperty(result, PsiModifier.NATIVE, false); - - if (!toCopyJavaDoc){ - PsiDocComment comment = result.getDocComment(); - if (comment != null){ - comment.delete(); - } - } - - annotateOnOverrideImplement(result, aClass, method, insertOverrideIfPossible); - - final PsiCodeBlock body = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlockFromText("{}", null); - PsiCodeBlock oldbody = result.getBody(); - if (oldbody != null){ - oldbody.replace(body); - } - else{ - result.add(body); - } - - setupMethodBody(result, method, aClass); - - // probably, it's better to reformat the whole method - it can go from other style sources - final Project project = method.getProject(); - CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project); - boolean keepBreaks = settings.KEEP_LINE_BREAKS; - settings.KEEP_LINE_BREAKS = false; - result = (PsiMethod)JavaCodeStyleManager.getInstance(project).shortenClassReferences(result); - result = (PsiMethod)codeStyleManager.reformat(result); - settings.KEEP_LINE_BREAKS = keepBreaks; - - if (aClass.findMethodBySignature(result, false) != null) { - iterator.remove(); - } - } - - return results; - } - - public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden) { - annotateOnOverrideImplement(method, targetClass, overridden, - CodeStyleSettingsManager.getSettings(method.getProject()).INSERT_OVERRIDE_ANNOTATION); - } - - public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden, boolean insertOverride) { - if (insertOverride && isInsertOverride(overridden, targetClass)) { - annotate(method, Override.class.getName()); - } - for (OverrideImplementsAnnotationsHandler each : Extensions.getExtensions(OverrideImplementsAnnotationsHandler.EP_NAME)) { - for (String annotation : each.getAnnotations()) { - if (AnnotationUtil.isAnnotated(overridden, annotation, false)) { - annotate(method, annotation, each.annotationsToRemove(annotation)); - } - } - } - } - - public static void annotate(PsiMethod result, String fqn, String... annosToRemove) throws IncorrectOperationException { - Project project = result.getProject(); - AddAnnotationFix fix = new AddAnnotationFix(fqn, result, annosToRemove); - if (fix.isAvailable(project, null, result.getContainingFile())) { - fix.invoke(project, null, result.getContainingFile()); - } - } - - public static boolean isOverridable(PsiMethod method) { - return !method.isConstructor() - && !method.hasModifierProperty(PsiModifier.STATIC) - && !method.hasModifierProperty(PsiModifier.FINAL) - && !method.hasModifierProperty(PsiModifier.PRIVATE); - } - - @NotNull - public static List> overrideOrImplementMethods(PsiClass aClass, - Collection candidates, - boolean toCopyJavaDoc, - boolean toInsertAtOverride) - throws IncorrectOperationException { - List candidateInfos = ContainerUtil.map2List(candidates, new Function() { - public CandidateInfo fun(final PsiMethodMember s) { - return new CandidateInfo(s.getElement(), s.getSubstitutor()); - } - }); - final List methods = overrideOrImplementMethodCandidates(aClass, candidateInfos, toCopyJavaDoc, toInsertAtOverride); - return convert2GenerationInfos(methods); - } - - @NotNull - public static List overrideOrImplementMethodCandidates(PsiClass aClass, - Collection candidates, - boolean toCopyJavaDoc, - boolean insertOverrideWherePossible) throws IncorrectOperationException { - List result = new ArrayList(); - for (CandidateInfo candidateInfo : candidates) { - result.addAll(overrideOrImplementMethod(aClass, (PsiMethod)candidateInfo.getElement(), candidateInfo.getSubstitutor(), - toCopyJavaDoc, insertOverrideWherePossible)); - } - return result; - } - - public static List> convert2GenerationInfos(final Collection methods) { - return ContainerUtil.map2List(methods, new Function>() { - public PsiGenerationInfo fun(final PsiMethod s) { - return new PsiGenerationInfo(s); - } - }); - } - - @NotNull - public static String callSuper (PsiMethod superMethod, PsiMethod overriding) { - @NonNls StringBuilder buffer = new StringBuilder(); - if (!superMethod.isConstructor() && superMethod.getReturnType() != PsiType.VOID) { - buffer.append("return "); - } - buffer.append("super"); - PsiParameter[] parms = overriding.getParameterList().getParameters(); - if (!superMethod.isConstructor()){ - buffer.append("."); - buffer.append(superMethod.getName()); - } - buffer.append("("); - for (int i = 0; i < parms.length; i++) { - String name = parms[i].getName(); - if (i > 0) buffer.append(","); - buffer.append(name); - } - buffer.append(")"); - return buffer.toString(); - } - - public static void setupMethodBody(PsiMethod result, PsiMethod originalMethod, PsiClass targetClass) throws IncorrectOperationException { - String templName = originalMethod.hasModifierProperty(PsiModifier.ABSTRACT) ? - JavaTemplateUtil.TEMPLATE_IMPLEMENTED_METHOD_BODY : JavaTemplateUtil.TEMPLATE_OVERRIDDEN_METHOD_BODY; - FileTemplate template = FileTemplateManager.getInstance().getCodeTemplate(templName); - setupMethodBody(result, originalMethod, targetClass, template); - } - - public static void setupMethodBody(final PsiMethod result, final PsiMethod originalMethod, final PsiClass targetClass, - final FileTemplate template) throws IncorrectOperationException { - if (targetClass.isInterface()) { - final PsiCodeBlock body = result.getBody(); - if (body != null) body.delete(); - } - - FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(template.getExtension()); - PsiType returnType = result.getReturnType(); - if (returnType == null) { - returnType = PsiType.VOID; - } - Properties properties = new Properties(); - properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnType.getPresentableText()); - properties.setProperty(FileTemplate.ATTRIBUTE_DEFAULT_RETURN_VALUE, PsiTypesUtil.getDefaultValueOfType(returnType)); - properties.setProperty(FileTemplate.ATTRIBUTE_CALL_SUPER, callSuper(originalMethod, result)); - JavaTemplateUtil.setClassAndMethodNameProperties(properties, targetClass, result); - - PsiElementFactory factory = JavaPsiFacade.getInstance(originalMethod.getProject()).getElementFactory(); - @NonNls String methodText; - try { - String bodyText = template.getText(properties); - if (!"".equals(bodyText)) bodyText += "\n"; - methodText = "void foo () {\n" + bodyText + "}"; - methodText = FileTemplateUtil.indent(methodText, result.getProject(), fileType); - } catch (Exception e) { - throw new IncorrectOperationException("Failed to parse file template",e); - } - if (methodText != null) { - PsiMethod m; - try { - m = factory.createMethodFromText(methodText, originalMethod); - } - catch (IncorrectOperationException e) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - Messages.showErrorDialog(CodeInsightBundle.message("override.implement.broken.file.template.message"), - CodeInsightBundle.message("override.implement.broken.file.template.title")); - } - }); - return; - } - PsiCodeBlock oldBody = result.getBody(); - if (oldBody != null) { - oldBody.replace(m.getBody()); - } - } - } - - public static void chooseAndOverrideMethods(Project project, Editor editor, PsiClass aClass){ - FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.overrideimplement"); - chooseAndOverrideOrImplementMethods(project, editor, aClass, false); - } - - public static void chooseAndImplementMethods(Project project, Editor editor, PsiClass aClass){ - FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.overrideimplement"); - chooseAndOverrideOrImplementMethods(project, editor, aClass, true); - } - - private static void chooseAndOverrideOrImplementMethods(final Project project, - final Editor editor, - final PsiClass aClass, - final boolean toImplement){ - LOG.assertTrue(aClass.isValid()); - ApplicationManager.getApplication().assertReadAccessAllowed(); - - Collection candidates = getMethodsToOverrideImplement(aClass, toImplement); - Collection secondary = toImplement ? Collections.emptyList() : getMethodsToOverrideImplement(aClass, true); - - final MemberChooser chooser = showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary); - if (chooser == null) return; - - final List selectedElements = chooser.getSelectedElements(); - if (selectedElements == null || selectedElements.isEmpty()) return; - - new WriteCommandAction(project, aClass.getContainingFile()) { - protected void run(final Result result) throws Throwable { - overrideOrImplementMethodsInRightPlace(editor, aClass, selectedElements, chooser.isCopyJavadoc(), chooser.isInsertOverrideAnnotation()); - } - }.execute(); - } - - @Nullable - public static MemberChooser showOverrideImplementChooser(Editor editor, - final PsiElement aClass, - final boolean toImplement, - Collection candidates, - Collection secondary) { - Project project = aClass.getProject(); - if (candidates.isEmpty() && secondary.isEmpty()) return null; - - final PsiMethodMember[] onlyPrimary = convertToMethodMembers(candidates); - final PsiMethodMember[] all = ArrayUtil.mergeArrays(onlyPrimary, convertToMethodMembers(secondary), PsiMethodMember.class); - - final String toMerge = PropertiesComponent.getInstance(project).getValue(PROP_COMBINED_OVERRIDE_IMPLEMENT); - final Ref merge = Ref.create(!"false".equals(toMerge)); - - final boolean isAll = merge.get().booleanValue(); - final MemberChooser chooser = new MemberChooser(isAll ? all : onlyPrimary, false, true, project, - PsiUtil.isLanguageLevel5OrHigher(aClass)) { - - @Override - protected void fillToolbarActions(DefaultActionGroup group) { - super.fillToolbarActions(group); - if (toImplement) return; - - final ToggleAction mergeAction = new ToggleAction("Show methods to implement", "Show methods to implement", IconLoader.getIcon("/general/show_to_implement.png")) { - @Override - public boolean isSelected(AnActionEvent e) { - return merge.get().booleanValue(); - } - - @Override - public void setSelected(AnActionEvent e, boolean state) { - merge.set(state); - resetElements(state ? all : onlyPrimary); - setTitle(getChooserTitle(false, merge)); - } - }; - mergeAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.ALT_MASK)), myTree); - group.add(mergeAction); - } - }; - chooser.setTitle(getChooserTitle(toImplement, merge)); - registerHandlerForComplementaryAction(project, editor, aClass, toImplement, chooser); - - chooser.setCopyJavadocVisible(true); - - if (toImplement) { - chooser.selectElements(isAll ? all : onlyPrimary); - } - - chooser.show(); - if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null; - - PropertiesComponent.getInstance(project).setValue(PROP_COMBINED_OVERRIDE_IMPLEMENT, merge.get().toString()); - return chooser; - } - - private static String getChooserTitle(boolean toImplement, Ref merge) { - return toImplement - ? CodeInsightBundle.message("methods.to.implement.chooser.title") - : merge.get().booleanValue() - ? CodeInsightBundle.message("methods.to.override.implement.chooser.title") - : CodeInsightBundle.message("methods.to.override.chooser.title"); - } - - private static PsiMethodMember[] convertToMethodMembers(Collection candidates) { - return ContainerUtil.map2Array(candidates, PsiMethodMember.class, new Function() { - public PsiMethodMember fun(final CandidateInfo s) { - return new PsiMethodMember(s); - } - }); - } - - private static void registerHandlerForComplementaryAction(final Project project, final Editor editor, final PsiElement aClass, - final boolean toImplement, - final MemberChooser chooser) { - final JComponent preferredFocusedComponent = chooser.getPreferredFocusedComponent(); - final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); - - @NonNls final String s = toImplement ? "OverrideMethods" : "ImplementMethods"; - final Shortcut[] shortcuts = keymap.getShortcuts(s); - - if (shortcuts.length > 0 && shortcuts[0] instanceof KeyboardShortcut) { - preferredFocusedComponent.getInputMap().put( - ((KeyboardShortcut)shortcuts[0]).getFirstKeyStroke(), s - ); - - preferredFocusedComponent.getActionMap().put( - s, - new AbstractAction() { - public void actionPerformed(final ActionEvent e) { - chooser.close(DialogWrapper.CANCEL_EXIT_CODE); - - // invoke later in order to close previous modal dialog - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - final CodeInsightActionHandler handler = toImplement ? new OverrideMethodsHandler(): new ImplementMethodsHandler(); - handler.invoke(project, editor, aClass.getContainingFile()); - } - }); - } - } - ); - } - } - - public static void overrideOrImplementMethodsInRightPlace(Editor editor, - PsiClass aClass, - Collection candidates, - boolean copyJavadoc, - boolean insertOverrideWherePossible) { - try{ - int offset = editor.getCaretModel().getOffset(); - if (aClass.getLBrace() == null) { - PsiClass psiClass = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createClass("X"); - aClass.addRangeAfter(psiClass.getLBrace(), psiClass.getRBrace(), aClass.getLastChild()); - } - - int lbraceOffset = aClass.getLBrace().getTextOffset(); - List> resultMembers; - if (offset <= lbraceOffset || aClass.isEnum()) { - resultMembers = new ArrayList>(); - for (PsiMethodMember candidate : candidates) { - Collection prototypes = overrideOrImplementMethod(aClass, candidate.getElement(), candidate.getSubstitutor(), - copyJavadoc, insertOverrideWherePossible); - for (PsiMethod prototype : prototypes) { - PsiElement anchor = getDefaultAnchorToOverrideOrImplement(aClass, candidate.getElement(), candidate.getSubstitutor()); - PsiElement result = GenerateMembersUtil.insert(aClass, prototype, anchor, true); - resultMembers.add(new PsiGenerationInfo((PsiMethod)result)); - } - } - } - else{ - List> prototypes = overrideOrImplementMethods(aClass, candidates, copyJavadoc, insertOverrideWherePossible); - resultMembers = GenerateMembersUtil.insertMembersAtOffset(aClass.getContainingFile(), offset, prototypes); - } - - if (!resultMembers.isEmpty()) { - GenerateMembersUtil.positionCaret(editor, resultMembers.get(0).getPsiMember(), true); - } - } - catch(IncorrectOperationException e){ - LOG.error(e); - } - } - - @Nullable - public static PsiElement getDefaultAnchorToOverrideOrImplement(PsiClass aClass, PsiMethod baseMethod, PsiSubstitutor substitutor){ - PsiMethod prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(baseMethod, PsiMethod.class); - while(prevBaseMethod != null) { - String name = prevBaseMethod.isConstructor() ? aClass.getName() : prevBaseMethod.getName(); - //Happens when aClass instanceof PsiAnonymousClass - if (name != null) { - MethodSignature signature = MethodSignatureUtil.createMethodSignature(name, prevBaseMethod.getParameterList(), prevBaseMethod.getTypeParameterList(), substitutor); - PsiMethod prevMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); - if (prevMethod != null){ - return prevMethod.getNextSibling(); - } - } - prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(prevBaseMethod, PsiMethod.class); - } - - PsiMethod nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(baseMethod, PsiMethod.class); - while(nextBaseMethod != null) { - String name = nextBaseMethod.isConstructor() ? aClass.getName() : nextBaseMethod.getName(); - if (name != null) { - MethodSignature signature = MethodSignatureUtil.createMethodSignature(name, nextBaseMethod.getParameterList(), nextBaseMethod.getTypeParameterList(), substitutor); - PsiMethod nextMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); - if (nextMethod != null){ - return nextMethod; - } - } - nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(nextBaseMethod, PsiMethod.class); - } - - return null; - } - - public static void overrideOrImplement(PsiClass psiClass, @NotNull PsiMethod baseMethod) throws IncorrectOperationException { - FileEditorManager fileEditorManager = FileEditorManager.getInstance(baseMethod.getProject()); - - List> prototypes = convert2GenerationInfos(overrideOrImplementMethod(psiClass, baseMethod, false)); - if (prototypes.isEmpty()) return; - - PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseMethod.getContainingClass(), psiClass, PsiSubstitutor.EMPTY); - PsiElement anchor = getDefaultAnchorToOverrideOrImplement(psiClass, baseMethod, substitutor); - List> results = GenerateMembersUtil.insertMembersBeforeAnchor(psiClass, anchor, prototypes); - - PsiFile psiFile = psiClass.getContainingFile(); - Editor editor = fileEditorManager.openTextEditor(new OpenFileDescriptor(psiFile.getProject(), psiFile.getVirtualFile()), false); - if (editor == null) return; - - GenerateMembersUtil.positionCaret(editor, results.get(0).getPsiMember(), true); - editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); - } - - public static PsiClass getContextClass(Project project, Editor editor, PsiFile file, boolean allowInterface) { - PsiDocumentManager.getInstance(project).commitAllDocuments(); - - int offset = editor.getCaretModel().getOffset(); - PsiElement element = file.findElementAt(offset); - do { - element = PsiTreeUtil.getParentOfType(element, PsiClass.class); - } - while (element instanceof PsiTypeParameter); - - final PsiClass aClass = (PsiClass)element; - if (aClass instanceof JspClass) return null; - return aClass == null || - !allowInterface && aClass.isInterface() ? null : aClass; - } - - private static PsiSubstitutor getContextSubstitutor(PsiClass aClass) { - if (aClass instanceof PsiAnonymousClass) { - return ((PsiAnonymousClass)aClass).getBaseClassType().resolveGenerics().getSubstitutor(); - } - - return PsiSubstitutor.EMPTY; - } - - public static void overrideOrImplementMethodsInRightPlace(Editor editor1, PsiClass aClass, Collection members, boolean copyJavadoc) { - boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; - overrideOrImplementMethodsInRightPlace(editor1, aClass, members, copyJavadoc, insert); - } - - public static List overrideOrImplementMethodCandidates(PsiClass aClass, Collection candidatesToImplement, - boolean copyJavadoc) throws IncorrectOperationException { - boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; - return overrideOrImplementMethodCandidates(aClass, candidatesToImplement, copyJavadoc, insert); - } - - public static class MethodSignatureComparator implements Comparator { - // signatures should appear in the order of declaration - public int compare(MethodSignature o1, MethodSignature o2) { - if (o1 instanceof MethodSignatureBackedByPsiMethod && o2 instanceof MethodSignatureBackedByPsiMethod) { - PsiMethod m1 = ((MethodSignatureBackedByPsiMethod)o1).getMethod(); - PsiMethod m2 = ((MethodSignatureBackedByPsiMethod)o2).getMethod(); - PsiElement p1 = m1.getParent(); - PsiElement p2 = m2.getParent(); - if (p1 instanceof PsiClass && p2 instanceof PsiClass) { - final PsiClass c1 = (PsiClass)p1; - final PsiClass c2 = (PsiClass)p2; - - if (c1 == c2) { - final List methods = Arrays.asList(c1.getMethods()); - return methods.indexOf(m1) - methods.indexOf(m2); - } - - if (c1.isInheritor(c2, true)) return -1; - if (c2.isInheritor(c1, true)) return 1; - - return StringUtil.notNullize(c1.getName()).compareTo(StringUtil.notNullize(c2.getName())); - } - return m1.getTextOffset() - m2.getTextOffset(); - } - return 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.codeInsight.generation; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.CodeInsightActionHandler; +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.MethodImplementor; +import com.intellij.codeInsight.intention.AddAnnotationFix; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.ide.fileTemplates.JavaTemplateUtil; +import com.intellij.ide.util.MemberChooser; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.impl.source.jsp.jspJava.JspClass; +import com.intellij.psi.infos.CandidateInfo; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.*; + +public class OverrideImplementUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.OverrideImplementUtil"); + + @NonNls private static final String PROP_COMBINED_OVERRIDE_IMPLEMENT = "OverrideImplement.combined"; + + private OverrideImplementUtil() { + } + + @NotNull + public static Collection getMethodsToOverrideImplement(PsiClass aClass, boolean toImplement) { + return getMapToOverrideImplement(aClass, toImplement).values(); + } + + @NotNull + public static Collection getMethodSignaturesToImplement(@NotNull PsiClass aClass) { + return getMapToOverrideImplement(aClass, true).keySet(); + } + + @NotNull + public static Collection getMethodSignaturesToOverride(@NotNull PsiClass aClass) { + return getMapToOverrideImplement(aClass, false).keySet(); + } + + @NotNull + private static Map getMapToOverrideImplement(PsiClass aClass, boolean toImplement) { + Map abstracts = new LinkedHashMap(); + Map finals = new LinkedHashMap(); + Map concretes = new LinkedHashMap(); + + Collection allMethodSigs = aClass.getVisibleSignatures(); + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper(); + for (HierarchicalMethodSignature signature : allMethodSigs) { + PsiMethod method = signature.getMethod(); + LOG.assertTrue(method.isValid()); + + if (method.hasModifierProperty(PsiModifier.STATIC) || !resolveHelper.isAccessible(method, aClass, aClass)) continue; + PsiClass hisClass = method.getContainingClass(); + if (hisClass == null) continue; + //Filter non-immediate super constructors + if (method.isConstructor() && (!aClass.isInheritor(hisClass, false) || aClass instanceof PsiAnonymousClass || aClass.isEnum())) { + continue; + } + + if (MethodSignatureUtil.findMethodBySignature(aClass, signature, false) != null) continue; + + if (method.hasModifierProperty(PsiModifier.FINAL)) { + finals.put(signature, method); + continue; + } + + Map map = hisClass.isInterface() || method.hasModifierProperty(PsiModifier.ABSTRACT) ? abstracts : concretes; + PsiMethod other = map.get(signature); + if (other == null || preferLeftForImplement(method, other)) { + map.put(signature, method); + } + } + + final Map result = new TreeMap(new MethodSignatureComparator()); + if (toImplement || aClass.isInterface()) { + collectMethodsToImplement(aClass, abstracts, finals, concretes, result); + } + else { + for (Map.Entry entry : concretes.entrySet()) { + MethodSignature signature = entry.getKey(); + PsiMethod concrete = entry.getValue(); + if (finals.get(signature) == null) { + PsiMethod abstractOne = abstracts.get(signature); + if (abstractOne == null || !abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true) || + CommonClassNames.JAVA_LANG_OBJECT.equals(concrete.getContainingClass().getQualifiedName())) { + PsiSubstitutor subst = GenerateMembersUtil.correctSubstitutor(concrete, signature.getSubstitutor()); + CandidateInfo info = new CandidateInfo(concrete, subst); + result.put(signature, info); + } + } + } + } + + return result; + } + + public static void collectMethodsToImplement(PsiClass aClass, + Map abstracts, + Map finals, + Map concretes, + Map result) { + for (Map.Entry entry : abstracts.entrySet()) { + MethodSignature signature = entry.getKey(); + PsiMethod abstractOne = entry.getValue(); + PsiMethod concrete = concretes.get(signature); + if (concrete == null + || PsiUtil.getAccessLevel(concrete.getModifierList()) < PsiUtil.getAccessLevel(abstractOne.getModifierList()) + || !abstractOne.getContainingClass().isInterface() && abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true)) { + if (finals.get(signature) == null) { + PsiSubstitutor subst = GenerateMembersUtil.correctSubstitutor(abstractOne, signature.getSubstitutor()); + CandidateInfo info = new CandidateInfo(abstractOne, subst); + result.put(signature, info); + } + } + } + + for (final MethodImplementor implementor : getImplementors()) { + for (final PsiMethod method : implementor.getMethodsToImplement(aClass)) { + MethodSignature signature = MethodSignatureUtil.createMethodSignature(method.getName(), method.getParameterList(), + method.getTypeParameterList(), PsiSubstitutor.EMPTY); + CandidateInfo info = new CandidateInfo(method, PsiSubstitutor.EMPTY); + result.put(signature, info); + } + } + } + + private static boolean preferLeftForImplement(PsiMethod left, PsiMethod right) { + if (PsiUtil.getAccessLevel(left.getModifierList()) > PsiUtil.getAccessLevel(right.getModifierList())) return true; + if (!left.getContainingClass().isInterface()) return true; + if (!right.getContainingClass().isInterface()) return false; + // implement annotated method + PsiAnnotation[] leftAnnotations = left.getModifierList().getAnnotations(); + PsiAnnotation[] rightAnnotations = right.getModifierList().getAnnotations(); + return leftAnnotations.length > rightAnnotations.length; + } + + private static MethodImplementor[] getImplementors() { + return Extensions.getExtensions(MethodImplementor.EXTENSION_POINT_NAME); + } + + /** + * generate methods (with bodies) corresponding to given method declaration + * there are maybe two method implementations for one declaration + * (e.g. EJB' create() -> ejbCreate(), ejbPostCreate() ) + * @param aClass context for method implementations + * @param method method to override or implement + * @param toCopyJavaDoc true if copy JavaDoc from method declaration + * @return list of method prototypes + */ + @NotNull + public static Collection overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) throws IncorrectOperationException { + final PsiClass containingClass = method.getContainingClass(); + PsiSubstitutor substitutor = aClass.isInheritor(containingClass, true) ? + TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY) : PsiSubstitutor.EMPTY; + return overrideOrImplementMethod(aClass, method, substitutor, toCopyJavaDoc, true); + } + + public static boolean isInsertOverride(PsiMethod superMethod, PsiClass targetClass) { + if (superMethod.isConstructor()) { + return false; + } + if (!CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION + || !PsiUtil.isLanguageLevel5OrHigher(targetClass)) { + return false; + } + if (PsiUtil.isLanguageLevel6OrHigher(targetClass)) return true; + if (targetClass.isInterface()) return true; + PsiClass superClass = superMethod.getContainingClass(); + return !superClass.isInterface(); + } + + @NotNull + private static Collection overrideOrImplementMethod(PsiClass aClass, + PsiMethod method, + PsiSubstitutor substitutor, + boolean toCopyJavaDoc, + boolean insertOverrideIfPossible) throws IncorrectOperationException { + if (!method.isValid() || !substitutor.isValid()) return Collections.emptyList(); + + List results = new ArrayList(); + for (final MethodImplementor implementor : getImplementors()) { + ContainerUtil.addAll(results, implementor.createImplementationPrototypes(aClass, method)); + } + if (results.isEmpty()) { + PsiMethod method1 = GenerateMembersUtil.substituteGenericMethod(method, substitutor); + + PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); + PsiMethod result = (PsiMethod)factory.createClass("Dummy").add(method1); + if (result instanceof PsiAnnotationMethod) { + PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod)result).getDefaultValue(); + if (defaultValue != null) { + PsiElement defaultKeyword = defaultValue; + while (!(defaultKeyword instanceof PsiKeyword) && defaultKeyword != null) { + defaultKeyword = defaultKeyword.getPrevSibling(); + } + if (defaultKeyword == null) defaultKeyword = defaultValue; + defaultValue.getParent().deleteChildRange(defaultKeyword, defaultValue); + } + } + results.add(result); + } + + for (Iterator iterator = results.iterator(); iterator.hasNext();) { + PsiMethod result = iterator.next(); + PsiUtil.setModifierProperty(result, PsiModifier.ABSTRACT, aClass.isInterface()); + PsiUtil.setModifierProperty(result, PsiModifier.NATIVE, false); + + if (!toCopyJavaDoc){ + PsiDocComment comment = result.getDocComment(); + if (comment != null){ + comment.delete(); + } + } + + annotateOnOverrideImplement(result, aClass, method, insertOverrideIfPossible); + + final PsiCodeBlock body = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlockFromText("{}", null); + PsiCodeBlock oldbody = result.getBody(); + if (oldbody != null){ + oldbody.replace(body); + } + else{ + result.add(body); + } + + setupMethodBody(result, method, aClass); + + // probably, it's better to reformat the whole method - it can go from other style sources + final Project project = method.getProject(); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project); + boolean keepBreaks = settings.KEEP_LINE_BREAKS; + settings.KEEP_LINE_BREAKS = false; + result = (PsiMethod)JavaCodeStyleManager.getInstance(project).shortenClassReferences(result); + result = (PsiMethod)codeStyleManager.reformat(result); + settings.KEEP_LINE_BREAKS = keepBreaks; + + if (aClass.findMethodBySignature(result, false) != null) { + iterator.remove(); + } + } + + return results; + } + + public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden) { + annotateOnOverrideImplement(method, targetClass, overridden, + CodeStyleSettingsManager.getSettings(method.getProject()).INSERT_OVERRIDE_ANNOTATION); + } + + public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden, boolean insertOverride) { + if (insertOverride && isInsertOverride(overridden, targetClass)) { + annotate(method, Override.class.getName()); + } + for (OverrideImplementsAnnotationsHandler each : Extensions.getExtensions(OverrideImplementsAnnotationsHandler.EP_NAME)) { + for (String annotation : each.getAnnotations()) { + if (AnnotationUtil.isAnnotated(overridden, annotation, false)) { + annotate(method, annotation, each.annotationsToRemove(annotation)); + } + } + } + } + + public static void annotate(PsiMethod result, String fqn, String... annosToRemove) throws IncorrectOperationException { + Project project = result.getProject(); + AddAnnotationFix fix = new AddAnnotationFix(fqn, result, annosToRemove); + if (fix.isAvailable(project, null, result.getContainingFile())) { + fix.invoke(project, null, result.getContainingFile()); + } + } + + public static boolean isOverridable(PsiMethod method) { + return !method.isConstructor() + && !method.hasModifierProperty(PsiModifier.STATIC) + && !method.hasModifierProperty(PsiModifier.FINAL) + && !method.hasModifierProperty(PsiModifier.PRIVATE); + } + + @NotNull + public static List> overrideOrImplementMethods(PsiClass aClass, + Collection candidates, + boolean toCopyJavaDoc, + boolean toInsertAtOverride) + throws IncorrectOperationException { + List candidateInfos = ContainerUtil.map2List(candidates, new Function() { + public CandidateInfo fun(final PsiMethodMember s) { + return new CandidateInfo(s.getElement(), s.getSubstitutor()); + } + }); + final List methods = overrideOrImplementMethodCandidates(aClass, candidateInfos, toCopyJavaDoc, toInsertAtOverride); + return convert2GenerationInfos(methods); + } + + @NotNull + public static List overrideOrImplementMethodCandidates(PsiClass aClass, + Collection candidates, + boolean toCopyJavaDoc, + boolean insertOverrideWherePossible) throws IncorrectOperationException { + List result = new ArrayList(); + for (CandidateInfo candidateInfo : candidates) { + result.addAll(overrideOrImplementMethod(aClass, (PsiMethod)candidateInfo.getElement(), candidateInfo.getSubstitutor(), + toCopyJavaDoc, insertOverrideWherePossible)); + } + return result; + } + + public static List> convert2GenerationInfos(final Collection methods) { + return ContainerUtil.map2List(methods, new Function>() { + public PsiGenerationInfo fun(final PsiMethod s) { + return new PsiGenerationInfo(s); + } + }); + } + + @NotNull + public static String callSuper (PsiMethod superMethod, PsiMethod overriding) { + @NonNls StringBuilder buffer = new StringBuilder(); + if (!superMethod.isConstructor() && superMethod.getReturnType() != PsiType.VOID) { + buffer.append("return "); + } + buffer.append("super"); + PsiParameter[] parms = overriding.getParameterList().getParameters(); + if (!superMethod.isConstructor()){ + buffer.append("."); + buffer.append(superMethod.getName()); + } + buffer.append("("); + for (int i = 0; i < parms.length; i++) { + String name = parms[i].getName(); + if (i > 0) buffer.append(","); + buffer.append(name); + } + buffer.append(")"); + return buffer.toString(); + } + + public static void setupMethodBody(PsiMethod result, PsiMethod originalMethod, PsiClass targetClass) throws IncorrectOperationException { + String templName = originalMethod.hasModifierProperty(PsiModifier.ABSTRACT) ? + JavaTemplateUtil.TEMPLATE_IMPLEMENTED_METHOD_BODY : JavaTemplateUtil.TEMPLATE_OVERRIDDEN_METHOD_BODY; + FileTemplate template = FileTemplateManager.getInstance().getCodeTemplate(templName); + setupMethodBody(result, originalMethod, targetClass, template); + } + + public static void setupMethodBody(final PsiMethod result, final PsiMethod originalMethod, final PsiClass targetClass, + final FileTemplate template) throws IncorrectOperationException { + if (targetClass.isInterface()) { + final PsiCodeBlock body = result.getBody(); + if (body != null) body.delete(); + } + + FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(template.getExtension()); + PsiType returnType = result.getReturnType(); + if (returnType == null) { + returnType = PsiType.VOID; + } + Properties properties = new Properties(); + properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnType.getPresentableText()); + properties.setProperty(FileTemplate.ATTRIBUTE_DEFAULT_RETURN_VALUE, PsiTypesUtil.getDefaultValueOfType(returnType)); + properties.setProperty(FileTemplate.ATTRIBUTE_CALL_SUPER, callSuper(originalMethod, result)); + JavaTemplateUtil.setClassAndMethodNameProperties(properties, targetClass, result); + + PsiElementFactory factory = JavaPsiFacade.getInstance(originalMethod.getProject()).getElementFactory(); + @NonNls String methodText; + try { + String bodyText = template.getText(properties); + if (!"".equals(bodyText)) bodyText += "\n"; + methodText = "void foo () {\n" + bodyText + "}"; + methodText = FileTemplateUtil.indent(methodText, result.getProject(), fileType); + } catch (Exception e) { + throw new IncorrectOperationException("Failed to parse file template",e); + } + if (methodText != null) { + PsiMethod m; + try { + m = factory.createMethodFromText(methodText, originalMethod); + } + catch (IncorrectOperationException e) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + Messages.showErrorDialog(CodeInsightBundle.message("override.implement.broken.file.template.message"), + CodeInsightBundle.message("override.implement.broken.file.template.title")); + } + }); + return; + } + PsiCodeBlock oldBody = result.getBody(); + if (oldBody != null) { + oldBody.replace(m.getBody()); + } + } + } + + public static void chooseAndOverrideMethods(Project project, Editor editor, PsiClass aClass){ + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.overrideimplement"); + chooseAndOverrideOrImplementMethods(project, editor, aClass, false); + } + + public static void chooseAndImplementMethods(Project project, Editor editor, PsiClass aClass){ + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.overrideimplement"); + chooseAndOverrideOrImplementMethods(project, editor, aClass, true); + } + + private static void chooseAndOverrideOrImplementMethods(final Project project, + final Editor editor, + final PsiClass aClass, + final boolean toImplement){ + LOG.assertTrue(aClass.isValid()); + ApplicationManager.getApplication().assertReadAccessAllowed(); + + Collection candidates = getMethodsToOverrideImplement(aClass, toImplement); + Collection secondary = toImplement ? Collections.emptyList() : getMethodsToOverrideImplement(aClass, true); + + final MemberChooser chooser = showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary); + if (chooser == null) return; + + final List selectedElements = chooser.getSelectedElements(); + if (selectedElements == null || selectedElements.isEmpty()) return; + + new WriteCommandAction(project, aClass.getContainingFile()) { + protected void run(final Result result) throws Throwable { + overrideOrImplementMethodsInRightPlace(editor, aClass, selectedElements, chooser.isCopyJavadoc(), chooser.isInsertOverrideAnnotation()); + } + }.execute(); + } + + @Nullable + public static MemberChooser showOverrideImplementChooser(Editor editor, + final PsiElement aClass, + final boolean toImplement, + Collection candidates, + Collection secondary) { + Project project = aClass.getProject(); + if (candidates.isEmpty() && secondary.isEmpty()) return null; + + final PsiMethodMember[] onlyPrimary = convertToMethodMembers(candidates); + final PsiMethodMember[] all = ArrayUtil.mergeArrays(onlyPrimary, convertToMethodMembers(secondary), PsiMethodMember.class); + + final String toMerge = PropertiesComponent.getInstance(project).getValue(PROP_COMBINED_OVERRIDE_IMPLEMENT); + final Ref merge = Ref.create(!"false".equals(toMerge)); + + final boolean isAll = merge.get().booleanValue(); + final MemberChooser chooser = new MemberChooser(isAll ? all : onlyPrimary, false, true, project, + PsiUtil.isLanguageLevel5OrHigher(aClass)) { + + @Override + protected void fillToolbarActions(DefaultActionGroup group) { + super.fillToolbarActions(group); + if (toImplement) return; + + final ToggleAction mergeAction = new ToggleAction("Show methods to implement", "Show methods to implement", IconLoader.getIcon("/general/show_to_implement.png")) { + @Override + public boolean isSelected(AnActionEvent e) { + return merge.get().booleanValue(); + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + merge.set(state); + resetElements(state ? all : onlyPrimary); + setTitle(getChooserTitle(false, merge)); + } + }; + mergeAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.ALT_MASK)), myTree); + group.add(mergeAction); + } + }; + chooser.setTitle(getChooserTitle(toImplement, merge)); + registerHandlerForComplementaryAction(project, editor, aClass, toImplement, chooser); + + chooser.setCopyJavadocVisible(true); + + if (toImplement) { + chooser.selectElements(isAll ? all : onlyPrimary); + } + + chooser.show(); + if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null; + + PropertiesComponent.getInstance(project).setValue(PROP_COMBINED_OVERRIDE_IMPLEMENT, merge.get().toString()); + return chooser; + } + + private static String getChooserTitle(boolean toImplement, Ref merge) { + return toImplement + ? CodeInsightBundle.message("methods.to.implement.chooser.title") + : merge.get().booleanValue() + ? CodeInsightBundle.message("methods.to.override.implement.chooser.title") + : CodeInsightBundle.message("methods.to.override.chooser.title"); + } + + private static PsiMethodMember[] convertToMethodMembers(Collection candidates) { + return ContainerUtil.map2Array(candidates, PsiMethodMember.class, new Function() { + public PsiMethodMember fun(final CandidateInfo s) { + return new PsiMethodMember(s); + } + }); + } + + private static void registerHandlerForComplementaryAction(final Project project, final Editor editor, final PsiElement aClass, + final boolean toImplement, + final MemberChooser chooser) { + final JComponent preferredFocusedComponent = chooser.getPreferredFocusedComponent(); + final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); + + @NonNls final String s = toImplement ? "OverrideMethods" : "ImplementMethods"; + final Shortcut[] shortcuts = keymap.getShortcuts(s); + + if (shortcuts.length > 0 && shortcuts[0] instanceof KeyboardShortcut) { + preferredFocusedComponent.getInputMap().put( + ((KeyboardShortcut)shortcuts[0]).getFirstKeyStroke(), s + ); + + preferredFocusedComponent.getActionMap().put( + s, + new AbstractAction() { + public void actionPerformed(final ActionEvent e) { + chooser.close(DialogWrapper.CANCEL_EXIT_CODE); + + // invoke later in order to close previous modal dialog + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final CodeInsightActionHandler handler = toImplement ? new OverrideMethodsHandler(): new ImplementMethodsHandler(); + handler.invoke(project, editor, aClass.getContainingFile()); + } + }); + } + } + ); + } + } + + public static void overrideOrImplementMethodsInRightPlace(Editor editor, + PsiClass aClass, + Collection candidates, + boolean copyJavadoc, + boolean insertOverrideWherePossible) { + try{ + int offset = editor.getCaretModel().getOffset(); + if (aClass.getLBrace() == null) { + PsiClass psiClass = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createClass("X"); + aClass.addRangeAfter(psiClass.getLBrace(), psiClass.getRBrace(), aClass.getLastChild()); + } + + int lbraceOffset = aClass.getLBrace().getTextOffset(); + List> resultMembers; + if (offset <= lbraceOffset || aClass.isEnum()) { + resultMembers = new ArrayList>(); + for (PsiMethodMember candidate : candidates) { + Collection prototypes = overrideOrImplementMethod(aClass, candidate.getElement(), candidate.getSubstitutor(), + copyJavadoc, insertOverrideWherePossible); + for (PsiMethod prototype : prototypes) { + PsiElement anchor = getDefaultAnchorToOverrideOrImplement(aClass, candidate.getElement(), candidate.getSubstitutor()); + PsiElement result = GenerateMembersUtil.insert(aClass, prototype, anchor, true); + resultMembers.add(new PsiGenerationInfo((PsiMethod)result)); + } + } + } + else{ + List> prototypes = overrideOrImplementMethods(aClass, candidates, copyJavadoc, insertOverrideWherePossible); + resultMembers = GenerateMembersUtil.insertMembersAtOffset(aClass.getContainingFile(), offset, prototypes); + } + + if (!resultMembers.isEmpty()) { + GenerateMembersUtil.positionCaret(editor, resultMembers.get(0).getPsiMember(), true); + } + } + catch(IncorrectOperationException e){ + LOG.error(e); + } + } + + @Nullable + public static PsiElement getDefaultAnchorToOverrideOrImplement(PsiClass aClass, PsiMethod baseMethod, PsiSubstitutor substitutor){ + PsiMethod prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(baseMethod, PsiMethod.class); + while(prevBaseMethod != null) { + String name = prevBaseMethod.isConstructor() ? aClass.getName() : prevBaseMethod.getName(); + //Happens when aClass instanceof PsiAnonymousClass + if (name != null) { + MethodSignature signature = MethodSignatureUtil.createMethodSignature(name, prevBaseMethod.getParameterList(), prevBaseMethod.getTypeParameterList(), substitutor); + PsiMethod prevMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); + if (prevMethod != null){ + return prevMethod.getNextSibling(); + } + } + prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(prevBaseMethod, PsiMethod.class); + } + + PsiMethod nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(baseMethod, PsiMethod.class); + while(nextBaseMethod != null) { + String name = nextBaseMethod.isConstructor() ? aClass.getName() : nextBaseMethod.getName(); + if (name != null) { + MethodSignature signature = MethodSignatureUtil.createMethodSignature(name, nextBaseMethod.getParameterList(), nextBaseMethod.getTypeParameterList(), substitutor); + PsiMethod nextMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); + if (nextMethod != null){ + return nextMethod; + } + } + nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(nextBaseMethod, PsiMethod.class); + } + + return null; + } + + public static void overrideOrImplement(PsiClass psiClass, @NotNull PsiMethod baseMethod) throws IncorrectOperationException { + FileEditorManager fileEditorManager = FileEditorManager.getInstance(baseMethod.getProject()); + + List> prototypes = convert2GenerationInfos(overrideOrImplementMethod(psiClass, baseMethod, false)); + if (prototypes.isEmpty()) return; + + PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseMethod.getContainingClass(), psiClass, PsiSubstitutor.EMPTY); + PsiElement anchor = getDefaultAnchorToOverrideOrImplement(psiClass, baseMethod, substitutor); + List> results = GenerateMembersUtil.insertMembersBeforeAnchor(psiClass, anchor, prototypes); + + PsiFile psiFile = psiClass.getContainingFile(); + Editor editor = fileEditorManager.openTextEditor(new OpenFileDescriptor(psiFile.getProject(), psiFile.getVirtualFile()), false); + if (editor == null) return; + + GenerateMembersUtil.positionCaret(editor, results.get(0).getPsiMember(), true); + editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); + } + + public static PsiClass getContextClass(Project project, Editor editor, PsiFile file, boolean allowInterface) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + do { + element = PsiTreeUtil.getParentOfType(element, PsiClass.class); + } + while (element instanceof PsiTypeParameter); + + final PsiClass aClass = (PsiClass)element; + if (aClass instanceof JspClass) return null; + return aClass == null || + !allowInterface && aClass.isInterface() ? null : aClass; + } + + private static PsiSubstitutor getContextSubstitutor(PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) { + return ((PsiAnonymousClass)aClass).getBaseClassType().resolveGenerics().getSubstitutor(); + } + + return PsiSubstitutor.EMPTY; + } + + public static void overrideOrImplementMethodsInRightPlace(Editor editor1, PsiClass aClass, Collection members, boolean copyJavadoc) { + boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; + overrideOrImplementMethodsInRightPlace(editor1, aClass, members, copyJavadoc, insert); + } + + public static List overrideOrImplementMethodCandidates(PsiClass aClass, Collection candidatesToImplement, + boolean copyJavadoc) throws IncorrectOperationException { + boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; + return overrideOrImplementMethodCandidates(aClass, candidatesToImplement, copyJavadoc, insert); + } + + public static class MethodSignatureComparator implements Comparator { + // signatures should appear in the order of declaration + public int compare(MethodSignature o1, MethodSignature o2) { + if (o1 instanceof MethodSignatureBackedByPsiMethod && o2 instanceof MethodSignatureBackedByPsiMethod) { + PsiMethod m1 = ((MethodSignatureBackedByPsiMethod)o1).getMethod(); + PsiMethod m2 = ((MethodSignatureBackedByPsiMethod)o2).getMethod(); + PsiElement p1 = m1.getParent(); + PsiElement p2 = m2.getParent(); + if (p1 instanceof PsiClass && p2 instanceof PsiClass) { + final PsiClass c1 = (PsiClass)p1; + final PsiClass c2 = (PsiClass)p2; + + if (c1 == c2) { + final List methods = Arrays.asList(c1.getMethods()); + return methods.indexOf(m1) - methods.indexOf(m2); + } + + if (c1.isInheritor(c2, true)) return -1; + if (c2.isInheritor(c1, true)) return 1; + + return StringUtil.notNullize(c1.getName()).compareTo(StringUtil.notNullize(c2.getName())); + } + return m1.getTextOffset() - m2.getTextOffset(); + } + return 0; + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java index a2fa99d656e2..16a5b37803f9 100644 --- a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java @@ -1,625 +1,625 @@ -/* - * 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.codeInspection.inferNullity; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.intention.AddAnnotationFix; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.searches.OverridingMethodsSearch; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Query; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.HashSet; - -public class NullityInferrer { - private static final Logger LOG = Logger.getInstance("#" + NullityInferrer.class.getName()); - - private static final int MAX_PASSES = 10; - private int numAnnotationsAdded = 0; - private final HashSet> myNotNullSet = - new HashSet>(); - private final HashSet> myNullableSet = - new HashSet>(); - private boolean myAnnotateLocalVariables; - private SmartPointerManager myPointerManager; - - - public NullityInferrer(boolean annotateLocalVariables, Project project) { - myAnnotateLocalVariables = annotateLocalVariables; - myPointerManager = SmartPointerManager.getInstance(project); - } - - private boolean expressionIsNeverNull(@Nullable PsiExpression expression) { - if (expression == null) { - return false; - } - final ExpressionIsNeverNullVisitor visitor = new ExpressionIsNeverNullVisitor(); - expression.accept(visitor); - return visitor.isNeverNull(); - } - - protected boolean expressionIsSometimesNull(@Nullable PsiExpression expression) { - if (expression == null) { - return false; - } - final ExpressionIsSometimesNullVisitor visitor = new ExpressionIsSometimesNullVisitor(); - expression.accept(visitor); - return visitor.isSometimesNull(); - } - - private boolean methodNeverReturnsNull(@NotNull PsiMethod method) { - final MethodNeverReturnsNullVisitor visitor = new MethodNeverReturnsNullVisitor(); - method.accept(visitor); - return visitor.getNeverReturnsNull(); - } - - private boolean variableNeverAssignedNull(@NotNull PsiVariable variable) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer != null) { - if (!expressionIsNeverNull(initializer)) { - return false; - } - } - else if (!variable.hasModifierProperty(PsiModifier.FINAL)) { - return false; - } - final Query references = ReferencesSearch.search(variable); - for (final PsiReference reference : references) { - final PsiElement element = reference.getElement(); - if (!(element instanceof PsiReferenceExpression)) { - continue; - } - final PsiElement parent = element.getParent(); - if (!(parent instanceof PsiAssignmentExpression)) { - continue; - } - final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent; - if (assignment.getLExpression().equals(element) && - !expressionIsNeverNull(assignment.getRExpression())) { - return false; - } - } - return true; - } - - private boolean variableSometimesAssignedNull(@NotNull PsiVariable variable) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer != null && expressionIsSometimesNull(initializer)) { - return true; - } - final Query references = ReferencesSearch.search(variable); - for (final PsiReference reference : references) { - final PsiElement element = reference.getElement(); - if (!(element instanceof PsiReferenceExpression)) { - continue; - } - final PsiElement parent = element.getParent(); - if (!(parent instanceof PsiAssignmentExpression)) { - continue; - } - final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent; - if (assignment.getLExpression().equals(element) && expressionIsSometimesNull(assignment.getRExpression())) { - return true; - } - } - return false; - } - - public void collect(@NotNull PsiFile file) { - int prevNumAnnotationsAdded; - int pass = 0; - do { - final NullityInferrerVisitor visitor = new NullityInferrerVisitor(); - prevNumAnnotationsAdded = numAnnotationsAdded; - file.accept(visitor); - pass++; - } - while (prevNumAnnotationsAdded < numAnnotationsAdded && pass < MAX_PASSES); - } - - public void apply(Project project) { - for (SmartPsiElementPointer pointer : myNullableSet) { - final PsiModifierListOwner element = pointer.getElement(); - if (element != null) { - if (shouldIgnore(element)) continue; - new AddAnnotationFix(AnnotationUtil.NULLABLE, element, AnnotationUtil.NOT_NULL).invoke(project, null, element.getContainingFile()); - } - } - - for (SmartPsiElementPointer pointer : myNotNullSet) { - final PsiModifierListOwner element = pointer.getElement(); - if (element != null) { - if (shouldIgnore(element)) continue; - new AddAnnotationFix(AnnotationUtil.NOT_NULL, element, AnnotationUtil.NULLABLE).invoke(project, null, element.getContainingFile()); - } - } - } - - private boolean shouldIgnore(PsiModifierListOwner element) { - if (!myAnnotateLocalVariables){ - if (element instanceof PsiLocalVariable) return true; - if (element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiForeachStatement) return true; - } - return false; - } - - private void registerNullableAnnotation(@NotNull PsiModifierListOwner method) { - registerAnnotation(method, true); - } - - private void registerNotNullAnnotation(@NotNull PsiModifierListOwner method) { - registerAnnotation(method, false); - } - - private void registerAnnotation(@NotNull PsiModifierListOwner method, boolean isNullable) { - final SmartPsiElementPointer methodPointer = myPointerManager.createLazyPointer(method); - if (isNullable) { - myNullableSet.add(methodPointer); - } - else { - myNotNullSet.add(methodPointer); - } - numAnnotationsAdded++; - } - - private class ExpressionIsNeverNullVisitor extends JavaElementVisitor { - private boolean neverNull = true; - - @Override - public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) { - neverNull = !"null".equals(expression.getText()); - } - - @Override - public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { - neverNull = expressionIsNeverNull(expression.getRExpression()); - } - - @Override - public void visitAssertStatement(PsiAssertStatement statement) { - } - - @Override - public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) { - final PsiExpression condition = expression.getCondition(); - final PsiExpression thenExpression = expression.getThenExpression(); - final PsiExpression elseExpression = expression.getElseExpression(); - if (canTrunkImpossibleBrunch(condition, elseExpression)) { - neverNull = expressionIsNeverNull(thenExpression); - return; - } - - neverNull = expressionIsNeverNull(thenExpression) || - expressionIsNeverNull(elseExpression); - } - - @Override - public void visitParenthesizedExpression(@NotNull PsiParenthesizedExpression expression) { - neverNull = expressionIsNeverNull(expression.getExpression()); - } - - @Override - public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { - neverNull = false; - } - - @Override - public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) { - final PsiElement referent = expression.resolve(); - if (referent instanceof PsiVariable) { - final PsiVariable var = (PsiVariable)referent; - if (var instanceof PsiEnumConstant || isNotNull(var)) { - neverNull = true; - return; - } - } - neverNull = false; - } - - @Override - public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { - final PsiMethod method = expression.resolveMethod(); - if (method == null) { - neverNull = false; - } else { - neverNull = isNotNull(method); - } - } - - private boolean isNeverNull() { - return neverNull; - } - } - - private static boolean trunkImpossibleBrunch(PsiExpression condition, - PsiExpression elseExpression, - PsiExpression rOperand, - PsiExpression lOperand) { - if (rOperand instanceof PsiLiteralExpression && "null".equals(rOperand.getText())) { - if (lOperand instanceof PsiReferenceExpression) { - final PsiElement resolve = ((PsiReferenceExpression)lOperand).resolve(); - if (resolve instanceof PsiVariable) { - if (((PsiBinaryExpression)condition).getOperationTokenType() == JavaTokenType.EQEQ) { - if (elseExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)elseExpression).resolve() == resolve) { - return true; - } - } - } - } - } - return false; - } - - private static boolean canTrunkImpossibleBrunch(PsiExpression condition, PsiExpression elseExpression) { - if (condition instanceof PsiBinaryExpression) { - final PsiExpression rOperand = ((PsiBinaryExpression)condition).getROperand(); - final PsiExpression lOperand = ((PsiBinaryExpression)condition).getLOperand(); - if (trunkImpossibleBrunch(condition, elseExpression, rOperand, lOperand) || - trunkImpossibleBrunch(condition, elseExpression, lOperand, rOperand)) { - return true; - } - } - return false; - } - - private class ExpressionIsSometimesNullVisitor extends JavaRecursiveElementWalkingVisitor{ - private boolean sometimesNull = false; - - @Override - public void visitElement(PsiElement element) { - if (sometimesNull) return; - super.visitElement(element); - } - - @Override - public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) { - sometimesNull = "null".equals(expression.getText()); - } - - @Override - public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { - sometimesNull = expressionIsSometimesNull(expression.getRExpression()); - } - - @Override - public void visitAssertStatement(PsiAssertStatement statement) { - } - - @Override - public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) { - final PsiExpression condition = expression.getCondition(); - final PsiExpression thenExpression = expression.getThenExpression(); - final PsiExpression elseExpression = expression.getElseExpression(); - if (canTrunkImpossibleBrunch(condition, elseExpression)) { - sometimesNull = expressionIsSometimesNull(thenExpression); - return; - } - - sometimesNull = expressionIsSometimesNull(thenExpression) || - expressionIsSometimesNull(elseExpression); - } - - @Override - public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) { - final PsiElement referent = expression.resolve(); - if (referent instanceof PsiVariable) { - final PsiVariable var = (PsiVariable)referent; - if (isNullable(var)) { - sometimesNull = true; - } - } - } - - @Override - public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { - final PsiMethod method = expression.resolveMethod(); - if (method != null) { - sometimesNull = isNullable(method); - } - } - - private boolean isSometimesNull() { - return sometimesNull; - } - } - - private class MethodNeverReturnsNullVisitor extends JavaRecursiveElementWalkingVisitor { - private boolean neverReturnsNull = true; - - @Override - public void visitClass(PsiClass aClass) { - //so as not to drill into anonymous classes - } - - @Override - public void visitReturnStatement(@NotNull PsiReturnStatement statement) { - super.visitReturnStatement(statement); - final PsiExpression value = statement.getReturnValue(); - if (expressionIsNeverNull(value)) { - return; - } - if (value instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)value; - - //if it's a recursive call, don't throw the red flag - final PsiMethod method = methodCallExpression.resolveMethod(); - final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(value, PsiMethod.class); - if (method != null && method.equals(containingMethod)) { - return; - } - } - neverReturnsNull = false; - } - - private boolean getNeverReturnsNull() { - return neverReturnsNull; - } - } - - private boolean isNotNull(PsiModifierListOwner owner) { - if (AnnotationUtil.isNotNull(owner)) { - return true; - } - final SmartPsiElementPointer pointer = myPointerManager.createLazyPointer(owner); - return myNotNullSet.contains(pointer); - } - - private boolean isNullable(PsiModifierListOwner owner) { - if (AnnotationUtil.isNullable(owner)) { - return true; - } - final SmartPsiElementPointer pointer = myPointerManager.createLazyPointer(owner); - return myNullableSet.contains(pointer); - } - - private class NullityInferrerVisitor extends JavaRecursiveElementWalkingVisitor{ - - @Override - public void visitMethod(@NotNull PsiMethod method) { - super.visitMethod(method); - if (method.isConstructor() || method.getReturnType() instanceof PsiPrimitiveType) { - return; - } - final Collection overridingMethods = OverridingMethodsSearch.search(method).findAll(); - for (final PsiMethod overridingMethod : overridingMethods) { - if (isNullable(overridingMethod)) { - registerNullableAnnotation(method); - return; - } - } - if (!AnnotationUtil.isAnnotated(method, AnnotationUtil.NOT_NULL, false) && - AnnotationUtil.isAnnotated(method, AnnotationUtil.NOT_NULL, true)) { - registerNotNullAnnotation(method); - return; - } - if (isNotNull(method) || isNullable(method)) { - return; - } - final PsiCodeBlock body = method.getBody(); - if (body != null) { - final boolean[] sometimesReturnsNull = new boolean[1]; - body.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitClass(PsiClass aClass) { - } - - @Override - public void visitElement(PsiElement element) { - if (sometimesReturnsNull[0]) return; - super.visitElement(element); - } - - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - super.visitReturnStatement(statement); - final PsiExpression value = statement.getReturnValue(); - if (expressionIsSometimesNull(value)) { - sometimesReturnsNull[0] = true; - } - } - }); - if (sometimesReturnsNull[0]) { - registerNullableAnnotation(method); - return; - } - } - - - if (methodNeverReturnsNull(method)) { - for (final PsiMethod overridingMethod : overridingMethods) { - if (!isNotNull(overridingMethod)) { - return; - } - } - //and check that all of the submethods are not nullable - registerNotNullAnnotation(method); - } - } - - - @Override - public void visitLocalVariable(@NotNull PsiLocalVariable variable) { - super.visitLocalVariable(variable); - if (variable.getType() instanceof PsiPrimitiveType || - isNotNull(variable) || isNullable(variable)) { - return; - } - - if (variableNeverAssignedNull(variable)) { - registerNotNullAnnotation(variable); - } - if (variableSometimesAssignedNull(variable)) { - registerNullableAnnotation(variable); - } - } - - - @Override - public void visitParameter(@NotNull PsiParameter parameter) { - super.visitParameter(parameter); - if (parameter.getType() instanceof PsiPrimitiveType || - isNotNull(parameter) || isNullable(parameter)) { - return; - } - final PsiElement grandParent = parameter.getDeclarationScope(); - if (grandParent instanceof PsiMethod) { - final PsiMethod method = (PsiMethod)grandParent; - if (method.getBody() != null) { - - for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(method))) { - final PsiElement place = reference.getElement(); - if (place instanceof PsiReferenceExpression) { - final PsiReferenceExpression expr = (PsiReferenceExpression)place; - final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class); - if (processParameter(parameter, expr, parent)) return; - if (isNotNull(method)) { - PsiElement toReturn = parent; - if (parent instanceof PsiConditionalExpression && - ((PsiConditionalExpression)parent).getCondition() != expr) { //todo check conditional operations - toReturn = parent.getParent(); - } - if (toReturn instanceof PsiReturnStatement) { - registerNotNullAnnotation(parameter); - return; - } - } - } - } - } - } - else if (grandParent instanceof PsiForeachStatement) { - for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(grandParent))) { - final PsiElement place = reference.getElement(); - if (place instanceof PsiReferenceExpression) { - final PsiReferenceExpression expr = (PsiReferenceExpression)place; - final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class); - if (processParameter(parameter, expr, parent)) return; - } - } - } - else { - if (variableNeverAssignedNull(parameter)) { - registerNotNullAnnotation(parameter); - } - if (variableSometimesAssignedNull(parameter)) { - registerNullableAnnotation(parameter); - } - } - } - - private boolean processParameter(PsiParameter parameter, PsiReferenceExpression expr, PsiElement parent) { - if (PsiUtil.isAccessedForWriting(expr)) return true; - if (parent instanceof PsiBinaryExpression) { //todo check if comparison operation - PsiExpression opposite = null; - final PsiExpression lOperand = ((PsiBinaryExpression)parent).getLOperand(); - final PsiExpression rOperand = ((PsiBinaryExpression)parent).getROperand(); - if (lOperand == expr) { - opposite = rOperand; - } - else if (rOperand == expr) { - opposite = lOperand; - } - if (opposite != null && opposite.getType() == PsiType.NULL) { - if (parent.getParent() instanceof PsiAssertStatement) { - if (((PsiBinaryExpression)parent).getOperationTokenType() == JavaTokenType.NE) { - registerNotNullAnnotation(parameter); - return true; - } - } - registerNullableAnnotation(parameter); - return true; - } - } - else if (parent instanceof PsiReferenceExpression) { - if (((PsiReferenceExpression)parent).getQualifierExpression() == expr) { - registerNotNullAnnotation(parameter); - return true; - } - } - else if (parent instanceof PsiAssignmentExpression) { - if (((PsiAssignmentExpression)parent).getRExpression() == expr) { - final PsiExpression expression = ((PsiAssignmentExpression)parent).getLExpression(); - if (expression instanceof PsiReferenceExpression) { - final PsiElement resolve = ((PsiReferenceExpression)expression).resolve(); - if (resolve instanceof PsiVariable) { - final PsiVariable localVar = (PsiVariable)resolve; - if (isNotNull(localVar)) { - registerNotNullAnnotation(parameter); - return true; - } - } - } - } - } else if (parent instanceof PsiForeachStatement) { - if (((PsiForeachStatement)parent).getIteratedValue() == expr) { - registerNotNullAnnotation(parameter); - return true; - } - } - - final PsiCall call = PsiTreeUtil.getParentOfType(expr, PsiCall.class); - if (call != null) { - final PsiExpressionList argumentList = call.getArgumentList(); - if (argumentList != null) { - final PsiExpression[] args = argumentList.getExpressions(); - int idx = ArrayUtil.find(args, expr); - if (idx >= 0) { - final PsiMethod resolvedMethod = call.resolveMethod(); - if (resolvedMethod != null) { - final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters(); - if (idx < parameters.length) { //not vararg - final PsiParameter resolvedToParam = parameters[idx]; - if (isNotNull(resolvedToParam) && !resolvedToParam.isVarArgs()) { - registerNotNullAnnotation(parameter); - return true; - } - } - } - } - } - } - return false; - } - - @Override - public void visitField(@NotNull PsiField field) { - super.visitField(field); - if (field instanceof PsiEnumConstant) { - return; - } - if (field.getType() instanceof PsiPrimitiveType || - isNotNull(field) || isNullable(field)) { - return; - } - - if (variableNeverAssignedNull(field)) { - registerNotNullAnnotation(field); - } - if (variableSometimesAssignedNull(field)) { - registerNullableAnnotation(field); - } - } - } -} +/* + * 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.codeInspection.inferNullity; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.intention.AddAnnotationFix; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.OverridingMethodsSearch; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Query; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashSet; + +public class NullityInferrer { + private static final Logger LOG = Logger.getInstance("#" + NullityInferrer.class.getName()); + + private static final int MAX_PASSES = 10; + private int numAnnotationsAdded = 0; + private final HashSet> myNotNullSet = + new HashSet>(); + private final HashSet> myNullableSet = + new HashSet>(); + private boolean myAnnotateLocalVariables; + private SmartPointerManager myPointerManager; + + + public NullityInferrer(boolean annotateLocalVariables, Project project) { + myAnnotateLocalVariables = annotateLocalVariables; + myPointerManager = SmartPointerManager.getInstance(project); + } + + private boolean expressionIsNeverNull(@Nullable PsiExpression expression) { + if (expression == null) { + return false; + } + final ExpressionIsNeverNullVisitor visitor = new ExpressionIsNeverNullVisitor(); + expression.accept(visitor); + return visitor.isNeverNull(); + } + + protected boolean expressionIsSometimesNull(@Nullable PsiExpression expression) { + if (expression == null) { + return false; + } + final ExpressionIsSometimesNullVisitor visitor = new ExpressionIsSometimesNullVisitor(); + expression.accept(visitor); + return visitor.isSometimesNull(); + } + + private boolean methodNeverReturnsNull(@NotNull PsiMethod method) { + final MethodNeverReturnsNullVisitor visitor = new MethodNeverReturnsNullVisitor(); + method.accept(visitor); + return visitor.getNeverReturnsNull(); + } + + private boolean variableNeverAssignedNull(@NotNull PsiVariable variable) { + final PsiExpression initializer = variable.getInitializer(); + if (initializer != null) { + if (!expressionIsNeverNull(initializer)) { + return false; + } + } + else if (!variable.hasModifierProperty(PsiModifier.FINAL)) { + return false; + } + final Query references = ReferencesSearch.search(variable); + for (final PsiReference reference : references) { + final PsiElement element = reference.getElement(); + if (!(element instanceof PsiReferenceExpression)) { + continue; + } + final PsiElement parent = element.getParent(); + if (!(parent instanceof PsiAssignmentExpression)) { + continue; + } + final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent; + if (assignment.getLExpression().equals(element) && + !expressionIsNeverNull(assignment.getRExpression())) { + return false; + } + } + return true; + } + + private boolean variableSometimesAssignedNull(@NotNull PsiVariable variable) { + final PsiExpression initializer = variable.getInitializer(); + if (initializer != null && expressionIsSometimesNull(initializer)) { + return true; + } + final Query references = ReferencesSearch.search(variable); + for (final PsiReference reference : references) { + final PsiElement element = reference.getElement(); + if (!(element instanceof PsiReferenceExpression)) { + continue; + } + final PsiElement parent = element.getParent(); + if (!(parent instanceof PsiAssignmentExpression)) { + continue; + } + final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent; + if (assignment.getLExpression().equals(element) && expressionIsSometimesNull(assignment.getRExpression())) { + return true; + } + } + return false; + } + + public void collect(@NotNull PsiFile file) { + int prevNumAnnotationsAdded; + int pass = 0; + do { + final NullityInferrerVisitor visitor = new NullityInferrerVisitor(); + prevNumAnnotationsAdded = numAnnotationsAdded; + file.accept(visitor); + pass++; + } + while (prevNumAnnotationsAdded < numAnnotationsAdded && pass < MAX_PASSES); + } + + public void apply(Project project) { + for (SmartPsiElementPointer pointer : myNullableSet) { + final PsiModifierListOwner element = pointer.getElement(); + if (element != null) { + if (shouldIgnore(element)) continue; + new AddAnnotationFix(AnnotationUtil.NULLABLE, element, AnnotationUtil.NOT_NULL).invoke(project, null, element.getContainingFile()); + } + } + + for (SmartPsiElementPointer pointer : myNotNullSet) { + final PsiModifierListOwner element = pointer.getElement(); + if (element != null) { + if (shouldIgnore(element)) continue; + new AddAnnotationFix(AnnotationUtil.NOT_NULL, element, AnnotationUtil.NULLABLE).invoke(project, null, element.getContainingFile()); + } + } + } + + private boolean shouldIgnore(PsiModifierListOwner element) { + if (!myAnnotateLocalVariables){ + if (element instanceof PsiLocalVariable) return true; + if (element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiForeachStatement) return true; + } + return false; + } + + private void registerNullableAnnotation(@NotNull PsiModifierListOwner method) { + registerAnnotation(method, true); + } + + private void registerNotNullAnnotation(@NotNull PsiModifierListOwner method) { + registerAnnotation(method, false); + } + + private void registerAnnotation(@NotNull PsiModifierListOwner method, boolean isNullable) { + final SmartPsiElementPointer methodPointer = myPointerManager.createLazyPointer(method); + if (isNullable) { + myNullableSet.add(methodPointer); + } + else { + myNotNullSet.add(methodPointer); + } + numAnnotationsAdded++; + } + + private class ExpressionIsNeverNullVisitor extends JavaElementVisitor { + private boolean neverNull = true; + + @Override + public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) { + neverNull = !"null".equals(expression.getText()); + } + + @Override + public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { + neverNull = expressionIsNeverNull(expression.getRExpression()); + } + + @Override + public void visitAssertStatement(PsiAssertStatement statement) { + } + + @Override + public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) { + final PsiExpression condition = expression.getCondition(); + final PsiExpression thenExpression = expression.getThenExpression(); + final PsiExpression elseExpression = expression.getElseExpression(); + if (canTrunkImpossibleBrunch(condition, elseExpression)) { + neverNull = expressionIsNeverNull(thenExpression); + return; + } + + neverNull = expressionIsNeverNull(thenExpression) || + expressionIsNeverNull(elseExpression); + } + + @Override + public void visitParenthesizedExpression(@NotNull PsiParenthesizedExpression expression) { + neverNull = expressionIsNeverNull(expression.getExpression()); + } + + @Override + public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { + neverNull = false; + } + + @Override + public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) { + final PsiElement referent = expression.resolve(); + if (referent instanceof PsiVariable) { + final PsiVariable var = (PsiVariable)referent; + if (var instanceof PsiEnumConstant || isNotNull(var)) { + neverNull = true; + return; + } + } + neverNull = false; + } + + @Override + public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + neverNull = false; + } else { + neverNull = isNotNull(method); + } + } + + private boolean isNeverNull() { + return neverNull; + } + } + + private static boolean trunkImpossibleBrunch(PsiExpression condition, + PsiExpression elseExpression, + PsiExpression rOperand, + PsiExpression lOperand) { + if (rOperand instanceof PsiLiteralExpression && "null".equals(rOperand.getText())) { + if (lOperand instanceof PsiReferenceExpression) { + final PsiElement resolve = ((PsiReferenceExpression)lOperand).resolve(); + if (resolve instanceof PsiVariable) { + if (((PsiBinaryExpression)condition).getOperationTokenType() == JavaTokenType.EQEQ) { + if (elseExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)elseExpression).resolve() == resolve) { + return true; + } + } + } + } + } + return false; + } + + private static boolean canTrunkImpossibleBrunch(PsiExpression condition, PsiExpression elseExpression) { + if (condition instanceof PsiBinaryExpression) { + final PsiExpression rOperand = ((PsiBinaryExpression)condition).getROperand(); + final PsiExpression lOperand = ((PsiBinaryExpression)condition).getLOperand(); + if (trunkImpossibleBrunch(condition, elseExpression, rOperand, lOperand) || + trunkImpossibleBrunch(condition, elseExpression, lOperand, rOperand)) { + return true; + } + } + return false; + } + + private class ExpressionIsSometimesNullVisitor extends JavaRecursiveElementWalkingVisitor{ + private boolean sometimesNull = false; + + @Override + public void visitElement(PsiElement element) { + if (sometimesNull) return; + super.visitElement(element); + } + + @Override + public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) { + sometimesNull = "null".equals(expression.getText()); + } + + @Override + public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { + sometimesNull = expressionIsSometimesNull(expression.getRExpression()); + } + + @Override + public void visitAssertStatement(PsiAssertStatement statement) { + } + + @Override + public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) { + final PsiExpression condition = expression.getCondition(); + final PsiExpression thenExpression = expression.getThenExpression(); + final PsiExpression elseExpression = expression.getElseExpression(); + if (canTrunkImpossibleBrunch(condition, elseExpression)) { + sometimesNull = expressionIsSometimesNull(thenExpression); + return; + } + + sometimesNull = expressionIsSometimesNull(thenExpression) || + expressionIsSometimesNull(elseExpression); + } + + @Override + public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) { + final PsiElement referent = expression.resolve(); + if (referent instanceof PsiVariable) { + final PsiVariable var = (PsiVariable)referent; + if (isNullable(var)) { + sometimesNull = true; + } + } + } + + @Override + public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { + final PsiMethod method = expression.resolveMethod(); + if (method != null) { + sometimesNull = isNullable(method); + } + } + + private boolean isSometimesNull() { + return sometimesNull; + } + } + + private class MethodNeverReturnsNullVisitor extends JavaRecursiveElementWalkingVisitor { + private boolean neverReturnsNull = true; + + @Override + public void visitClass(PsiClass aClass) { + //so as not to drill into anonymous classes + } + + @Override + public void visitReturnStatement(@NotNull PsiReturnStatement statement) { + super.visitReturnStatement(statement); + final PsiExpression value = statement.getReturnValue(); + if (expressionIsNeverNull(value)) { + return; + } + if (value instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)value; + + //if it's a recursive call, don't throw the red flag + final PsiMethod method = methodCallExpression.resolveMethod(); + final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(value, PsiMethod.class); + if (method != null && method.equals(containingMethod)) { + return; + } + } + neverReturnsNull = false; + } + + private boolean getNeverReturnsNull() { + return neverReturnsNull; + } + } + + private boolean isNotNull(PsiModifierListOwner owner) { + if (AnnotationUtil.isNotNull(owner)) { + return true; + } + final SmartPsiElementPointer pointer = myPointerManager.createLazyPointer(owner); + return myNotNullSet.contains(pointer); + } + + private boolean isNullable(PsiModifierListOwner owner) { + if (AnnotationUtil.isNullable(owner)) { + return true; + } + final SmartPsiElementPointer pointer = myPointerManager.createLazyPointer(owner); + return myNullableSet.contains(pointer); + } + + private class NullityInferrerVisitor extends JavaRecursiveElementWalkingVisitor{ + + @Override + public void visitMethod(@NotNull PsiMethod method) { + super.visitMethod(method); + if (method.isConstructor() || method.getReturnType() instanceof PsiPrimitiveType) { + return; + } + final Collection overridingMethods = OverridingMethodsSearch.search(method).findAll(); + for (final PsiMethod overridingMethod : overridingMethods) { + if (isNullable(overridingMethod)) { + registerNullableAnnotation(method); + return; + } + } + if (!AnnotationUtil.isAnnotated(method, AnnotationUtil.NOT_NULL, false) && + AnnotationUtil.isAnnotated(method, AnnotationUtil.NOT_NULL, true)) { + registerNotNullAnnotation(method); + return; + } + if (isNotNull(method) || isNullable(method)) { + return; + } + final PsiCodeBlock body = method.getBody(); + if (body != null) { + final boolean[] sometimesReturnsNull = new boolean[1]; + body.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitClass(PsiClass aClass) { + } + + @Override + public void visitElement(PsiElement element) { + if (sometimesReturnsNull[0]) return; + super.visitElement(element); + } + + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + super.visitReturnStatement(statement); + final PsiExpression value = statement.getReturnValue(); + if (expressionIsSometimesNull(value)) { + sometimesReturnsNull[0] = true; + } + } + }); + if (sometimesReturnsNull[0]) { + registerNullableAnnotation(method); + return; + } + } + + + if (methodNeverReturnsNull(method)) { + for (final PsiMethod overridingMethod : overridingMethods) { + if (!isNotNull(overridingMethod)) { + return; + } + } + //and check that all of the submethods are not nullable + registerNotNullAnnotation(method); + } + } + + + @Override + public void visitLocalVariable(@NotNull PsiLocalVariable variable) { + super.visitLocalVariable(variable); + if (variable.getType() instanceof PsiPrimitiveType || + isNotNull(variable) || isNullable(variable)) { + return; + } + + if (variableNeverAssignedNull(variable)) { + registerNotNullAnnotation(variable); + } + if (variableSometimesAssignedNull(variable)) { + registerNullableAnnotation(variable); + } + } + + + @Override + public void visitParameter(@NotNull PsiParameter parameter) { + super.visitParameter(parameter); + if (parameter.getType() instanceof PsiPrimitiveType || + isNotNull(parameter) || isNullable(parameter)) { + return; + } + final PsiElement grandParent = parameter.getDeclarationScope(); + if (grandParent instanceof PsiMethod) { + final PsiMethod method = (PsiMethod)grandParent; + if (method.getBody() != null) { + + for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(method))) { + final PsiElement place = reference.getElement(); + if (place instanceof PsiReferenceExpression) { + final PsiReferenceExpression expr = (PsiReferenceExpression)place; + final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class); + if (processParameter(parameter, expr, parent)) return; + if (isNotNull(method)) { + PsiElement toReturn = parent; + if (parent instanceof PsiConditionalExpression && + ((PsiConditionalExpression)parent).getCondition() != expr) { //todo check conditional operations + toReturn = parent.getParent(); + } + if (toReturn instanceof PsiReturnStatement) { + registerNotNullAnnotation(parameter); + return; + } + } + } + } + } + } + else if (grandParent instanceof PsiForeachStatement) { + for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(grandParent))) { + final PsiElement place = reference.getElement(); + if (place instanceof PsiReferenceExpression) { + final PsiReferenceExpression expr = (PsiReferenceExpression)place; + final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class); + if (processParameter(parameter, expr, parent)) return; + } + } + } + else { + if (variableNeverAssignedNull(parameter)) { + registerNotNullAnnotation(parameter); + } + if (variableSometimesAssignedNull(parameter)) { + registerNullableAnnotation(parameter); + } + } + } + + private boolean processParameter(PsiParameter parameter, PsiReferenceExpression expr, PsiElement parent) { + if (PsiUtil.isAccessedForWriting(expr)) return true; + if (parent instanceof PsiBinaryExpression) { //todo check if comparison operation + PsiExpression opposite = null; + final PsiExpression lOperand = ((PsiBinaryExpression)parent).getLOperand(); + final PsiExpression rOperand = ((PsiBinaryExpression)parent).getROperand(); + if (lOperand == expr) { + opposite = rOperand; + } + else if (rOperand == expr) { + opposite = lOperand; + } + if (opposite != null && opposite.getType() == PsiType.NULL) { + if (parent.getParent() instanceof PsiAssertStatement) { + if (((PsiBinaryExpression)parent).getOperationTokenType() == JavaTokenType.NE) { + registerNotNullAnnotation(parameter); + return true; + } + } + registerNullableAnnotation(parameter); + return true; + } + } + else if (parent instanceof PsiReferenceExpression) { + if (((PsiReferenceExpression)parent).getQualifierExpression() == expr) { + registerNotNullAnnotation(parameter); + return true; + } + } + else if (parent instanceof PsiAssignmentExpression) { + if (((PsiAssignmentExpression)parent).getRExpression() == expr) { + final PsiExpression expression = ((PsiAssignmentExpression)parent).getLExpression(); + if (expression instanceof PsiReferenceExpression) { + final PsiElement resolve = ((PsiReferenceExpression)expression).resolve(); + if (resolve instanceof PsiVariable) { + final PsiVariable localVar = (PsiVariable)resolve; + if (isNotNull(localVar)) { + registerNotNullAnnotation(parameter); + return true; + } + } + } + } + } else if (parent instanceof PsiForeachStatement) { + if (((PsiForeachStatement)parent).getIteratedValue() == expr) { + registerNotNullAnnotation(parameter); + return true; + } + } + + final PsiCall call = PsiTreeUtil.getParentOfType(expr, PsiCall.class); + if (call != null) { + final PsiExpressionList argumentList = call.getArgumentList(); + if (argumentList != null) { + final PsiExpression[] args = argumentList.getExpressions(); + int idx = ArrayUtil.find(args, expr); + if (idx >= 0) { + final PsiMethod resolvedMethod = call.resolveMethod(); + if (resolvedMethod != null) { + final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters(); + if (idx < parameters.length) { //not vararg + final PsiParameter resolvedToParam = parameters[idx]; + if (isNotNull(resolvedToParam) && !resolvedToParam.isVarArgs()) { + registerNotNullAnnotation(parameter); + return true; + } + } + } + } + } + } + return false; + } + + @Override + public void visitField(@NotNull PsiField field) { + super.visitField(field); + if (field instanceof PsiEnumConstant) { + return; + } + if (field.getType() instanceof PsiPrimitiveType || + isNotNull(field) || isNullable(field)) { + return; + } + + if (variableNeverAssignedNull(field)) { + registerNotNullAnnotation(field); + } + if (variableSometimesAssignedNull(field)) { + registerNullableAnnotation(field); + } + } + } +} diff --git a/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java b/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java index a14f74eca09a..db305fd58e56 100644 --- a/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java +++ b/java/java-impl/src/com/intellij/javadoc/JavadocConfiguration.java @@ -1,347 +1,347 @@ -/* - * 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.javadoc; - -import com.intellij.analysis.AnalysisScope; -import com.intellij.execution.CantRunException; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.*; -import com.intellij.execution.filters.RegexpFilter; -import com.intellij.execution.filters.TextConsoleBuilder; -import com.intellij.execution.filters.TextConsoleBuilderFactory; -import com.intellij.execution.process.OSProcessHandler; -import com.intellij.execution.process.ProcessAdapter; -import com.intellij.execution.process.ProcessEvent; -import com.intellij.execution.process.ProcessTerminatedListener; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.ide.BrowserUtil; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.JavaSdk; -import com.intellij.openapi.projectRoots.JavaSdkType; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.ex.PathUtilEx; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.util.PathUtil; -import com.intellij.util.PathsList; -import com.intellij.util.containers.HashSet; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.regex.Pattern; - -/** - * @author Eugene Zhuravlev - * Date: Apr 24, 2004 - */ -public class JavadocConfiguration implements ModuleRunProfile, JDOMExternalizable{ - public String OUTPUT_DIRECTORY; - public String OPTION_SCOPE = PsiKeyword.PROTECTED; - public boolean OPTION_HIERARCHY = true; - public boolean OPTION_NAVIGATOR = true; - public boolean OPTION_INDEX = true; - public boolean OPTION_SEPARATE_INDEX = true; - public boolean OPTION_DOCUMENT_TAG_USE = false; - public boolean OPTION_DOCUMENT_TAG_AUTHOR = false; - public boolean OPTION_DOCUMENT_TAG_VERSION = false; - public boolean OPTION_DOCUMENT_TAG_DEPRECATED = true; - public boolean OPTION_DEPRECATED_LIST = true; - public String OTHER_OPTIONS = ""; - public String HEAP_SIZE; - public String LOCALE; - public boolean OPEN_IN_BROWSER = true; - - private final Project myProject; - private AnalysisScope myGenerationScope; - private static final Logger LOGGER = Logger.getInstance("#" + JavadocConfiguration.class.getName()); - - public void setGenerationScope(AnalysisScope generationScope) { - myGenerationScope = generationScope; - } - - public JavadocConfiguration(Project project) { - myProject = project; - } - - public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException { - return new MyJavaCommandLineState(myProject, myGenerationScope); - } - - public String getName() { - return JavadocBundle.message("javadoc.settings.title"); - } - - public void checkConfiguration() throws RuntimeConfigurationException { - if (myGenerationScope == null) { - throw new RuntimeConfigurationError(JavadocBundle.message("javadoc.settings.not.specified")); - } - } - - public JavadocConfigurable createConfigurable() { - return new JavadocConfigurable(this); - } - - public Icon getIcon() { - return null; - } - - @NotNull - public Module[] getModules() { - return Module.EMPTY_ARRAY; - } - - public void readExternal(Element element) throws InvalidDataException { - DefaultJDOMExternalizer.readExternal(this, element); - } - - public void writeExternal(Element element) throws WriteExternalException { - DefaultJDOMExternalizer.writeExternal(this, element); - } - - private class MyJavaCommandLineState extends CommandLineState { - private final AnalysisScope myGenerationOptions; - private final Project myProject; - @NonNls private static final String INDEX_HTML = "index.html"; - - public MyJavaCommandLineState(Project project, AnalysisScope generationOptions) { - super(null); - myGenerationOptions = generationOptions; - myProject = project; - TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project); - builder.addFilter(new RegexpFilter(project, "$FILE_PATH$:$LINE$:[^\\^]+\\^")); - builder.addFilter(new RegexpFilter(project, "$FILE_PATH$:$LINE$: warning - .+$")); - setConsoleBuilder(builder); - } - - protected GeneralCommandLine createCommandLine() throws ExecutionException { - final GeneralCommandLine cmdLine = new GeneralCommandLine(); - final Sdk jdk = PathUtilEx.getAnyJdk(myProject); - setupExeParams(jdk, cmdLine); - setupProgramParameters(jdk, cmdLine); - return cmdLine; - } - - private void setupExeParams(final Sdk jdk, GeneralCommandLine cmdLine) throws ExecutionException { - final String jdkPath = jdk != null && jdk.getSdkType() instanceof JavaSdkType ? ((JavaSdkType)jdk.getSdkType()).getBinPath(jdk) : null; - if (jdkPath == null) { - throw new CantRunException(JavadocBundle.message("javadoc.generate.no.jdk.path")); - } - String versionString = jdk.getVersionString(); - if (HEAP_SIZE != null && HEAP_SIZE.trim().length() != 0) { - if (versionString.indexOf("1.1") > -1) { - cmdLine.getParametersList().prepend("-J-mx" + HEAP_SIZE + "m"); - } - else { - cmdLine.getParametersList().prepend("-J-Xmx" + HEAP_SIZE + "m"); - } - } - cmdLine.setWorkingDirectory(null); - @NonNls final String javadocExecutableName = File.separator + (SystemInfo.isWindows ? "javadoc.exe" : "javadoc"); - @NonNls String exePath = jdkPath.replace('/', File.separatorChar) + javadocExecutableName; - if (new File(exePath).exists()) { - cmdLine.setExePath(exePath); - } else { //try to use wrapper jdk - exePath = new File(jdkPath).getParent().replace('/', File.separatorChar) + javadocExecutableName; - if (!new File(exePath).exists()){ - final File parent = new File(System.getProperty("java.home")).getParentFile(); //try system jre - exePath = parent.getPath() + File.separator + "bin" + javadocExecutableName; - if (!new File(exePath).exists()){ - throw new CantRunException(JavadocBundle.message("javadoc.generate.no.jdk.path")); - } - } - cmdLine.setExePath(exePath); - } - } - - private void setupProgramParameters(final Sdk jdk, final GeneralCommandLine cmdLine) throws CantRunException { - @NonNls final ParametersList parameters = cmdLine.getParametersList(); - - if (LOCALE != null && LOCALE.length() > 0) { - parameters.add("-locale"); - parameters.add(LOCALE); - } - - if (OPTION_SCOPE != null) { - parameters.add("-" + OPTION_SCOPE); - } - - if (!OPTION_HIERARCHY) { - parameters.add("-notree"); - } - - if (!OPTION_NAVIGATOR) { - parameters.add("-nonavbar"); - } - - if (!OPTION_INDEX) { - parameters.add("-noindex"); - } - else if (OPTION_SEPARATE_INDEX) { - parameters.add("-splitindex"); - } - - if (OPTION_DOCUMENT_TAG_USE) { - parameters.add("-use"); - } - - if (OPTION_DOCUMENT_TAG_AUTHOR) { - parameters.add("-author"); - } - - if (OPTION_DOCUMENT_TAG_VERSION) { - parameters.add("-version"); - } - - if (!OPTION_DOCUMENT_TAG_DEPRECATED) { - parameters.add("-nodeprecated"); - } - else if (!OPTION_DEPRECATED_LIST) { - parameters.add("-nodeprecatedlist"); - } - - parameters.addParametersString(OTHER_OPTIONS); - - final PathsList classPath; - if (jdk.getSdkType() instanceof JavaSdk) { - classPath = OrderEnumerator.orderEntries(myProject).withoutSdk().withoutModuleSourceEntries().getPathsList(); - } - else { - //libraries are included into jdk - classPath = OrderEnumerator.orderEntries(myProject).withoutModuleSourceEntries().getPathsList(); - } - final String classPathString = classPath.getPathsString(); - if (classPathString.length() > 0) { - parameters.add("-classpath"); - parameters.add(classPathString); - } - - if (OUTPUT_DIRECTORY != null) { - parameters.add("-d"); - parameters.add(OUTPUT_DIRECTORY.replace('/', File.separatorChar)); - } - - try { - File sourcepathTempFile = FileUtil.createTempFile("javadoc", "args.txt"); - sourcepathTempFile.deleteOnExit(); - parameters.add("@" + sourcepathTempFile.getCanonicalPath()); - final PrintWriter writer = new PrintWriter(new FileWriter(sourcepathTempFile)); - try { - writer.println("-sourcepath"); - writer.println(OrderEnumerator.orderEntries(myProject).withoutSdk().withoutLibraries().getSourcePathsList().getPathsString()); - final Collection packages = new HashSet(); - final Collection sources = new HashSet(); - final Runnable findRunnable = new Runnable() { - public void run() { - myGenerationOptions.accept(new MyContentIterator(myProject, packages, sources)); - } - }; - if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(findRunnable, "Search for sources to generate javadoc in...", false, myProject)) { - return; - } - if (packages.size() + sources.size() == 0) { - throw new CantRunException(JavadocBundle.message("javadoc.generate.no.classes.in.selected.packages.error")); - } - writer.println(StringUtil.join(packages, " ")); - writer.println(StringUtil.join(sources, " ")); - } - finally { - writer.close(); - } - } - catch (IOException e) { - LOGGER.error(e); - } - } - - protected OSProcessHandler startProcess() throws ExecutionException { - final OSProcessHandler handler = JavaCommandLineStateUtil.startProcess(createCommandLine()); - ProcessTerminatedListener.attach(handler, myProject, JavadocBundle.message("javadoc.generate.exited")); - handler.addProcessListener(new ProcessAdapter() { - public void processTerminated(ProcessEvent event) { - if (OPEN_IN_BROWSER) { - File url = new File(OUTPUT_DIRECTORY, INDEX_HTML); - if (url.exists() && event.getExitCode() == 0) { - BrowserUtil.launchBrowser(url.getPath()); - } - } - } - }); - return handler; - } - } - - private static class MyContentIterator extends PsiRecursiveElementWalkingVisitor { - private final PsiManager myPsiManager; - private final Collection myPackages; - private final Collection mySourceFiles; - - public MyContentIterator(Project project, Collection packages, Collection sources) { - myPsiManager = PsiManager.getInstance(project); - myPackages = packages; - mySourceFiles = sources; - } - - @Override - public void visitFile(PsiFile file) { - final VirtualFile fileOrDir = file.getVirtualFile(); - if (fileOrDir == null) return; - if (!fileOrDir.isInLocalFileSystem()) return; - final Module module = ModuleUtil.findModuleForFile(fileOrDir, myPsiManager.getProject()); - if (file instanceof PsiJavaFile) { - final PsiJavaFile javaFile = (PsiJavaFile)file; - if (containsPackagePrefix(module, javaFile.getPackageName())) { - mySourceFiles.add(PathUtil.getLocalPath(javaFile.getVirtualFile())); - } - else { - myPackages.add(javaFile.getPackageName()); - } - } - } - - private static boolean containsPackagePrefix(Module module, String packageFQName) { - if (module == null) return false; - final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); - for (ContentEntry contentEntry : contentEntries) { - final SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); - for (SourceFolder sourceFolder : sourceFolders) { - final String packagePrefix = sourceFolder.getPackagePrefix(); - final int prefixLength = packagePrefix.length(); - if (prefixLength > 0 && packageFQName.startsWith(packagePrefix)) { - return true; - } - } - } - return false; - } - } -} +/* + * 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.javadoc; + +import com.intellij.analysis.AnalysisScope; +import com.intellij.execution.CantRunException; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.*; +import com.intellij.execution.filters.RegexpFilter; +import com.intellij.execution.filters.TextConsoleBuilder; +import com.intellij.execution.filters.TextConsoleBuilderFactory; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessTerminatedListener; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdk; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.ex.PathUtilEx; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.util.PathUtil; +import com.intellij.util.PathsList; +import com.intellij.util.containers.HashSet; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.regex.Pattern; + +/** + * @author Eugene Zhuravlev + * Date: Apr 24, 2004 + */ +public class JavadocConfiguration implements ModuleRunProfile, JDOMExternalizable{ + public String OUTPUT_DIRECTORY; + public String OPTION_SCOPE = PsiKeyword.PROTECTED; + public boolean OPTION_HIERARCHY = true; + public boolean OPTION_NAVIGATOR = true; + public boolean OPTION_INDEX = true; + public boolean OPTION_SEPARATE_INDEX = true; + public boolean OPTION_DOCUMENT_TAG_USE = false; + public boolean OPTION_DOCUMENT_TAG_AUTHOR = false; + public boolean OPTION_DOCUMENT_TAG_VERSION = false; + public boolean OPTION_DOCUMENT_TAG_DEPRECATED = true; + public boolean OPTION_DEPRECATED_LIST = true; + public String OTHER_OPTIONS = ""; + public String HEAP_SIZE; + public String LOCALE; + public boolean OPEN_IN_BROWSER = true; + + private final Project myProject; + private AnalysisScope myGenerationScope; + private static final Logger LOGGER = Logger.getInstance("#" + JavadocConfiguration.class.getName()); + + public void setGenerationScope(AnalysisScope generationScope) { + myGenerationScope = generationScope; + } + + public JavadocConfiguration(Project project) { + myProject = project; + } + + public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException { + return new MyJavaCommandLineState(myProject, myGenerationScope); + } + + public String getName() { + return JavadocBundle.message("javadoc.settings.title"); + } + + public void checkConfiguration() throws RuntimeConfigurationException { + if (myGenerationScope == null) { + throw new RuntimeConfigurationError(JavadocBundle.message("javadoc.settings.not.specified")); + } + } + + public JavadocConfigurable createConfigurable() { + return new JavadocConfigurable(this); + } + + public Icon getIcon() { + return null; + } + + @NotNull + public Module[] getModules() { + return Module.EMPTY_ARRAY; + } + + public void readExternal(Element element) throws InvalidDataException { + DefaultJDOMExternalizer.readExternal(this, element); + } + + public void writeExternal(Element element) throws WriteExternalException { + DefaultJDOMExternalizer.writeExternal(this, element); + } + + private class MyJavaCommandLineState extends CommandLineState { + private final AnalysisScope myGenerationOptions; + private final Project myProject; + @NonNls private static final String INDEX_HTML = "index.html"; + + public MyJavaCommandLineState(Project project, AnalysisScope generationOptions) { + super(null); + myGenerationOptions = generationOptions; + myProject = project; + TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project); + builder.addFilter(new RegexpFilter(project, "$FILE_PATH$:$LINE$:[^\\^]+\\^")); + builder.addFilter(new RegexpFilter(project, "$FILE_PATH$:$LINE$: warning - .+$")); + setConsoleBuilder(builder); + } + + protected GeneralCommandLine createCommandLine() throws ExecutionException { + final GeneralCommandLine cmdLine = new GeneralCommandLine(); + final Sdk jdk = PathUtilEx.getAnyJdk(myProject); + setupExeParams(jdk, cmdLine); + setupProgramParameters(jdk, cmdLine); + return cmdLine; + } + + private void setupExeParams(final Sdk jdk, GeneralCommandLine cmdLine) throws ExecutionException { + final String jdkPath = jdk != null && jdk.getSdkType() instanceof JavaSdkType ? ((JavaSdkType)jdk.getSdkType()).getBinPath(jdk) : null; + if (jdkPath == null) { + throw new CantRunException(JavadocBundle.message("javadoc.generate.no.jdk.path")); + } + String versionString = jdk.getVersionString(); + if (HEAP_SIZE != null && HEAP_SIZE.trim().length() != 0) { + if (versionString.indexOf("1.1") > -1) { + cmdLine.getParametersList().prepend("-J-mx" + HEAP_SIZE + "m"); + } + else { + cmdLine.getParametersList().prepend("-J-Xmx" + HEAP_SIZE + "m"); + } + } + cmdLine.setWorkingDirectory(null); + @NonNls final String javadocExecutableName = File.separator + (SystemInfo.isWindows ? "javadoc.exe" : "javadoc"); + @NonNls String exePath = jdkPath.replace('/', File.separatorChar) + javadocExecutableName; + if (new File(exePath).exists()) { + cmdLine.setExePath(exePath); + } else { //try to use wrapper jdk + exePath = new File(jdkPath).getParent().replace('/', File.separatorChar) + javadocExecutableName; + if (!new File(exePath).exists()){ + final File parent = new File(System.getProperty("java.home")).getParentFile(); //try system jre + exePath = parent.getPath() + File.separator + "bin" + javadocExecutableName; + if (!new File(exePath).exists()){ + throw new CantRunException(JavadocBundle.message("javadoc.generate.no.jdk.path")); + } + } + cmdLine.setExePath(exePath); + } + } + + private void setupProgramParameters(final Sdk jdk, final GeneralCommandLine cmdLine) throws CantRunException { + @NonNls final ParametersList parameters = cmdLine.getParametersList(); + + if (LOCALE != null && LOCALE.length() > 0) { + parameters.add("-locale"); + parameters.add(LOCALE); + } + + if (OPTION_SCOPE != null) { + parameters.add("-" + OPTION_SCOPE); + } + + if (!OPTION_HIERARCHY) { + parameters.add("-notree"); + } + + if (!OPTION_NAVIGATOR) { + parameters.add("-nonavbar"); + } + + if (!OPTION_INDEX) { + parameters.add("-noindex"); + } + else if (OPTION_SEPARATE_INDEX) { + parameters.add("-splitindex"); + } + + if (OPTION_DOCUMENT_TAG_USE) { + parameters.add("-use"); + } + + if (OPTION_DOCUMENT_TAG_AUTHOR) { + parameters.add("-author"); + } + + if (OPTION_DOCUMENT_TAG_VERSION) { + parameters.add("-version"); + } + + if (!OPTION_DOCUMENT_TAG_DEPRECATED) { + parameters.add("-nodeprecated"); + } + else if (!OPTION_DEPRECATED_LIST) { + parameters.add("-nodeprecatedlist"); + } + + parameters.addParametersString(OTHER_OPTIONS); + + final PathsList classPath; + if (jdk.getSdkType() instanceof JavaSdk) { + classPath = OrderEnumerator.orderEntries(myProject).withoutSdk().withoutModuleSourceEntries().getPathsList(); + } + else { + //libraries are included into jdk + classPath = OrderEnumerator.orderEntries(myProject).withoutModuleSourceEntries().getPathsList(); + } + final String classPathString = classPath.getPathsString(); + if (classPathString.length() > 0) { + parameters.add("-classpath"); + parameters.add(classPathString); + } + + if (OUTPUT_DIRECTORY != null) { + parameters.add("-d"); + parameters.add(OUTPUT_DIRECTORY.replace('/', File.separatorChar)); + } + + try { + File sourcepathTempFile = FileUtil.createTempFile("javadoc", "args.txt"); + sourcepathTempFile.deleteOnExit(); + parameters.add("@" + sourcepathTempFile.getCanonicalPath()); + final PrintWriter writer = new PrintWriter(new FileWriter(sourcepathTempFile)); + try { + writer.println("-sourcepath"); + writer.println(OrderEnumerator.orderEntries(myProject).withoutSdk().withoutLibraries().getSourcePathsList().getPathsString()); + final Collection packages = new HashSet(); + final Collection sources = new HashSet(); + final Runnable findRunnable = new Runnable() { + public void run() { + myGenerationOptions.accept(new MyContentIterator(myProject, packages, sources)); + } + }; + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(findRunnable, "Search for sources to generate javadoc in...", false, myProject)) { + return; + } + if (packages.size() + sources.size() == 0) { + throw new CantRunException(JavadocBundle.message("javadoc.generate.no.classes.in.selected.packages.error")); + } + writer.println(StringUtil.join(packages, " ")); + writer.println(StringUtil.join(sources, " ")); + } + finally { + writer.close(); + } + } + catch (IOException e) { + LOGGER.error(e); + } + } + + protected OSProcessHandler startProcess() throws ExecutionException { + final OSProcessHandler handler = JavaCommandLineStateUtil.startProcess(createCommandLine()); + ProcessTerminatedListener.attach(handler, myProject, JavadocBundle.message("javadoc.generate.exited")); + handler.addProcessListener(new ProcessAdapter() { + public void processTerminated(ProcessEvent event) { + if (OPEN_IN_BROWSER) { + File url = new File(OUTPUT_DIRECTORY, INDEX_HTML); + if (url.exists() && event.getExitCode() == 0) { + BrowserUtil.launchBrowser(url.getPath()); + } + } + } + }); + return handler; + } + } + + private static class MyContentIterator extends PsiRecursiveElementWalkingVisitor { + private final PsiManager myPsiManager; + private final Collection myPackages; + private final Collection mySourceFiles; + + public MyContentIterator(Project project, Collection packages, Collection sources) { + myPsiManager = PsiManager.getInstance(project); + myPackages = packages; + mySourceFiles = sources; + } + + @Override + public void visitFile(PsiFile file) { + final VirtualFile fileOrDir = file.getVirtualFile(); + if (fileOrDir == null) return; + if (!fileOrDir.isInLocalFileSystem()) return; + final Module module = ModuleUtil.findModuleForFile(fileOrDir, myPsiManager.getProject()); + if (file instanceof PsiJavaFile) { + final PsiJavaFile javaFile = (PsiJavaFile)file; + if (containsPackagePrefix(module, javaFile.getPackageName())) { + mySourceFiles.add(PathUtil.getLocalPath(javaFile.getVirtualFile())); + } + else { + myPackages.add(javaFile.getPackageName()); + } + } + } + + private static boolean containsPackagePrefix(Module module, String packageFQName) { + if (module == null) return false; + final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); + for (ContentEntry contentEntry : contentEntries) { + final SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); + for (SourceFolder sourceFolder : sourceFolders) { + final String packagePrefix = sourceFolder.getPackagePrefix(); + final int prefixLength = packagePrefix.length(); + if (prefixLength > 0 && packageFQName.startsWith(packagePrefix)) { + return true; + } + } + } + return false; + } + } +} diff --git a/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java b/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java index 67edaf54e6a3..561e249289cb 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java @@ -1,732 +1,732 @@ -/* - * 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.lang.java.parser; - -import com.intellij.codeInsight.daemon.JavaErrorMessages; -import com.intellij.lang.PsiBuilder; -import com.intellij.psi.JavaTokenType; -import com.intellij.psi.impl.source.tree.ElementType; -import com.intellij.psi.impl.source.tree.JavaElementType; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.ILazyParseableElementType; -import com.intellij.psi.tree.TokenSet; -import com.intellij.util.SmartList; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -import static com.intellij.lang.PsiBuilderUtil.*; -import static com.intellij.lang.java.parser.JavaParserUtil.*; - - -public class StatementParser { - private static final boolean DEEP_PARSE_BLOCKS_IN_STATEMENTS = false; - - private enum BraceMode { - TILL_FIRST, TILL_LAST - } - - private static final TokenSet TRY_CLOSERS_SET = TokenSet.create(JavaTokenType.CATCH_KEYWORD, JavaTokenType.FINALLY_KEYWORD); - - private StatementParser() { } - - @Nullable - public static PsiBuilder.Marker parseCodeBlock(final PsiBuilder builder) { - if (builder.getTokenType() != JavaTokenType.LBRACE) return null; - else if (DEEP_PARSE_BLOCKS_IN_STATEMENTS) return parseCodeBlockDeep(builder, false); - - final PsiBuilder.Marker codeBlock = builder.mark(); - builder.advanceLexer(); - - boolean greedyBlock = false; - int braceCount = 1; - while (true) { - final IElementType tokenType = builder.getTokenType(); - if (tokenType == null) { - greedyBlock = true; - break; - } - if (tokenType == JavaTokenType.LBRACE) { - braceCount++; - } - else if (tokenType == JavaTokenType.RBRACE) { - braceCount--; - } - builder.advanceLexer(); - - if (braceCount == 0) { - break; - } - else if (braceCount == 1 && (tokenType == JavaTokenType.SEMICOLON || tokenType == JavaTokenType.RBRACE)) { - final PsiBuilder.Marker position = builder.mark(); - final List list = new SmartList(); - while (true) { - final IElementType type = builder.getTokenType(); - if (ElementType.PRIMITIVE_TYPE_BIT_SET.contains(type) || ElementType.MODIFIER_BIT_SET.contains(type) || - type == JavaTokenType.IDENTIFIER || type == JavaTokenType.LT || type == JavaTokenType.GT || - type == JavaTokenType.GTGT || type == JavaTokenType.GTGTGT || type == JavaTokenType.COMMA || - type == JavaTokenType.DOT || type == JavaTokenType.EXTENDS_KEYWORD || type == JavaTokenType.IMPLEMENTS_KEYWORD) { - list.add(type); - builder.advanceLexer(); - } else { - break; - } - } - if (builder.getTokenType() == JavaTokenType.LPARENTH && list.size() >= 2) { - final IElementType last = list.get(list.size() - 1); - final IElementType prevLast = list.get(list.size() - 2); - if (last == JavaTokenType.IDENTIFIER && - (prevLast == JavaTokenType.IDENTIFIER || ElementType.PRIMITIVE_TYPE_BIT_SET.contains(prevLast))) { - position.rollbackTo(); - greedyBlock = true; - break; - } - } - position.drop(); - } - } - - codeBlock.collapse(JavaElementType.CODE_BLOCK); - if (greedyBlock) { - codeBlock.setCustomEdgeTokenBinders(null, GREEDY_RIGHT_EDGE_PROCESSOR); - } - return codeBlock; - } - - @Nullable - public static PsiBuilder.Marker parseCodeBlockDeep(final PsiBuilder builder, final boolean parseUntilEof) { - if (builder.getTokenType() != JavaTokenType.LBRACE) return null; - - final PsiBuilder.Marker codeBlock = builder.mark(); - builder.advanceLexer(); - - parseStatements(builder, (parseUntilEof ? BraceMode.TILL_LAST : BraceMode.TILL_FIRST)); - - final boolean greedyBlock = !expectOrError(builder, JavaTokenType.RBRACE, JavaErrorMessages.message("expected.rbrace")); - builder.getTokenType(); // eat spaces - - done(codeBlock, JavaElementType.CODE_BLOCK); - if (greedyBlock) { - codeBlock.setCustomEdgeTokenBinders(null, GREEDY_RIGHT_EDGE_PROCESSOR); - } - return codeBlock; - } - - public static void parseStatements(final PsiBuilder builder) { - parseStatements(builder, null); - } - - private static void parseStatements(final PsiBuilder builder, final BraceMode braceMode) { - while (builder.getTokenType() != null) { - final PsiBuilder.Marker statement = parseStatement(builder); - if (statement != null) continue; - - final IElementType tokenType = builder.getTokenType(); - if (tokenType == JavaTokenType.RBRACE) { - if (braceMode == BraceMode.TILL_FIRST) { - return; - } - else if (braceMode == BraceMode.TILL_LAST) { - if (nextTokenType(builder) == null) { - return; - } - } - } - - final PsiBuilder.Marker error = builder.mark(); - builder.advanceLexer(); - if (tokenType == JavaTokenType.ELSE_KEYWORD) { - error.error(JavaErrorMessages.message("else.without.if")); - } - else if (tokenType == JavaTokenType.CATCH_KEYWORD) { - error.error(JavaErrorMessages.message("catch.without.try")); - } - else if (tokenType == JavaTokenType.FINALLY_KEYWORD) { - error.error(JavaErrorMessages.message("finally.without.try")); - } - else { - error.error(JavaErrorMessages.message("unexpected.token")); - } - } - } - - @Nullable - private static PsiBuilder.Marker parseStatement(final PsiBuilder builder) { - final IElementType tokenType = builder.getTokenType(); - - // todo: custom parsers (?) - - if (tokenType == JavaTokenType.IF_KEYWORD) { - return parseIfStatement(builder); - } - else if (tokenType == JavaTokenType.WHILE_KEYWORD) { - return parseWhileStatement(builder); - } - else if (tokenType == JavaTokenType.FOR_KEYWORD) { - return parseForStatement(builder); - } - else if (tokenType == JavaTokenType.DO_KEYWORD) { - return parseDoWhileStatement(builder); - } - else if (tokenType == JavaTokenType.SWITCH_KEYWORD) { - return parseSwitchStatement(builder); - } - else if (tokenType == JavaTokenType.CASE_KEYWORD || tokenType == JavaTokenType.DEFAULT_KEYWORD) { - return parseSwitchLabelStatement(builder); - } - else if (tokenType == JavaTokenType.BREAK_KEYWORD) { - return parseBreakStatement(builder); - } - else if (tokenType == JavaTokenType.CONTINUE_KEYWORD) { - return parseContinueStatement(builder); - } - else if (tokenType == JavaTokenType.RETURN_KEYWORD) { - return parseReturnStatement(builder); - } - else if (tokenType == JavaTokenType.THROW_KEYWORD) { - return parseThrowStatement(builder); - } - else if (tokenType == JavaTokenType.SYNCHRONIZED_KEYWORD) { - return parseSynchronizedStatement(builder); - } - else if (tokenType == JavaTokenType.TRY_KEYWORD) { - return parseTryStatement(builder); - } - else if (tokenType == JavaTokenType.ASSERT_KEYWORD) { - return parseAssertStatement(builder); - } - else if (tokenType == JavaTokenType.LBRACE) { - return parseBlockStatement(builder); - } - else if (tokenType instanceof ILazyParseableElementType) { - builder.advanceLexer(); - return null; - } - else if (tokenType == JavaTokenType.SEMICOLON) { - final PsiBuilder.Marker empty = builder.mark(); - builder.advanceLexer(); - done(empty, JavaElementType.EMPTY_STATEMENT); - return empty; - } - else if (tokenType == JavaTokenType.IDENTIFIER || tokenType == JavaTokenType.AT) { - final PsiBuilder.Marker refPos = builder.mark(); - DeclarationParser.parseAnnotations(builder); - skipQualifiedName(builder); - final IElementType suspectedLT = builder.getTokenType(); - refPos.rollbackTo(); - if (suspectedLT == JavaTokenType.LT) { - final PsiBuilder.Marker declStatement = builder.mark(); - final PsiBuilder.Marker decl = DeclarationParser.parse(builder, DeclarationParser.Context.CODE_BLOCK); - if (decl == null) { - ReferenceParser.parseType(builder, 0); - error(builder, JavaErrorMessages.message("expected.identifier")); - } - done(declStatement, JavaElementType.DECLARATION_STATEMENT); - return declStatement; - } - } - - final PsiBuilder.Marker pos = builder.mark(); - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - - if (expr != null) { - int count = 1; - final PsiBuilder.Marker list = expr.precede(); - final PsiBuilder.Marker statement = list.precede(); - while (builder.getTokenType() == JavaTokenType.COMMA) { - final PsiBuilder.Marker commaPos = builder.mark(); - builder.advanceLexer(); - final PsiBuilder.Marker expr1 = ExpressionParser.parse(builder); - if (expr1 == null) { - commaPos.rollbackTo(); - break; - } - commaPos.drop(); - count++; - } - if (count > 1) { - pos.drop(); - done(list, JavaElementType.EXPRESSION_LIST); - semicolon(builder); - done(statement, JavaElementType.EXPRESSION_LIST_STATEMENT); - return statement; - } - if (exprType(expr) != JavaElementType.REFERENCE_EXPRESSION) { - drop(list, pos); - semicolon(builder); - done(statement, JavaElementType.EXPRESSION_STATEMENT); - return statement; - } - pos.rollbackTo(); - } - else { - pos.drop(); - } - - final PsiBuilder.Marker decl = DeclarationParser.parse(builder, DeclarationParser.Context.CODE_BLOCK); - if (decl != null) { - final PsiBuilder.Marker statement = decl.precede(); - done(statement, JavaElementType.DECLARATION_STATEMENT); - return statement; - } - - if (lookAhead(builder, JavaTokenType.IDENTIFIER, JavaTokenType.COLON)) { - final PsiBuilder.Marker statement = builder.mark(); - advance(builder, 2); - parseStatement(builder); - done(statement, JavaElementType.LABELED_STATEMENT); - return statement; - } - - if (expr != null) { - final PsiBuilder.Marker statement = builder.mark(); - ExpressionParser.parse(builder); - semicolon(builder); - done(statement, JavaElementType.EXPRESSION_STATEMENT); - return statement; - } - - return null; - } - - private static void skipQualifiedName(final PsiBuilder builder) { - if (!expect(builder, JavaTokenType.IDENTIFIER)) return; - while (lookAhead(builder, JavaTokenType.DOT, JavaTokenType.IDENTIFIER)) { - advance(builder, 2); - } - } - - @NotNull - private static PsiBuilder.Marker parseIfStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - if (!parseExpressionInParenth(builder)) { - done(statement, JavaElementType.IF_STATEMENT); - return statement; - } - - final PsiBuilder.Marker thenStatement = parseStatement(builder); - if (thenStatement == null) { - error(builder, JavaErrorMessages.message("expected.statement")); - done(statement, JavaElementType.IF_STATEMENT); - return statement; - } - - if (!expect(builder, JavaTokenType.ELSE_KEYWORD)) { - done(statement, JavaElementType.IF_STATEMENT); - return statement; - } - - final PsiBuilder.Marker elseStatement = parseStatement(builder); - if (elseStatement == null) { - error(builder, JavaErrorMessages.message("expected.statement")); - } - - done(statement, JavaElementType.IF_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseWhileStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - if (!parseExpressionInParenth(builder)) { - done(statement, JavaElementType.WHILE_STATEMENT); - return statement; - } - - final PsiBuilder.Marker bodyStatement = parseStatement(builder); - if (bodyStatement == null) { - error(builder, JavaErrorMessages.message("expected.statement")); - } - - done(statement, JavaElementType.WHILE_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseForStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - if (!expect(builder, JavaTokenType.LPARENTH)) { - error(builder, JavaErrorMessages.message("expected.lparen")); - done(statement, JavaElementType.FOR_STATEMENT); - return statement; - } - - final PsiBuilder.Marker afterParenth = builder.mark(); - final PsiBuilder.Marker param = DeclarationParser.parseParameter(builder, false); - if (param == null || JavaParserUtil.exprType(param) != JavaElementType.PARAMETER || builder.getTokenType() != JavaTokenType.COLON) { - afterParenth.rollbackTo(); - return parseForLoopFromInitialization(builder, statement); - } - else { - afterParenth.drop(); - return parseForEachFromColon(builder, statement); - } - } - - @NotNull - private static PsiBuilder.Marker parseForLoopFromInitialization(final PsiBuilder builder, final PsiBuilder.Marker statement) { - final PsiBuilder.Marker init = parseStatement(builder); - if (init == null){ - error(builder, JavaErrorMessages.message("expected.statement")); - if (!expect(builder, JavaTokenType.RPARENTH)) { - done(statement, JavaElementType.FOR_STATEMENT); - return statement; - } - } - else { - ExpressionParser.parse(builder); - if (!expect(builder, JavaTokenType.SEMICOLON)) { - error(builder, JavaErrorMessages.message("expected.semicolon")); - if (!expect(builder, JavaTokenType.RPARENTH)) { - done(statement, JavaElementType.FOR_STATEMENT); - return statement; - } - } - else { - parseExpressionOrExpressionList(builder); - if (!expect(builder, JavaTokenType.RPARENTH)) { - error(builder, JavaErrorMessages.message("expected.rparen")); - done(statement, JavaElementType.FOR_STATEMENT); - return statement; - } - } - } - - final PsiBuilder.Marker bodyStatement = parseStatement(builder); - if (bodyStatement == null) { - error(builder, JavaErrorMessages.message("expected.statement")); - } - - done(statement, JavaElementType.FOR_STATEMENT); - return statement; - } - - private static void parseExpressionOrExpressionList(final PsiBuilder builder) { - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null) return; - - final PsiBuilder.Marker expressionStatement; - if (builder.getTokenType() != JavaTokenType.COMMA) { - expressionStatement = expr.precede(); - done(expressionStatement, JavaElementType.EXPRESSION_STATEMENT); - } - else { - final PsiBuilder.Marker expressionList = expr.precede(); - expressionStatement = expressionList.precede(); - - do { - builder.advanceLexer(); - final PsiBuilder.Marker nextExpression = ExpressionParser.parse(builder); - if (nextExpression == null) { - error(builder, JavaErrorMessages.message("expected.expression")); - } - } - while (builder.getTokenType() == JavaTokenType.COMMA); - - done(expressionList, JavaElementType.EXPRESSION_LIST); - done(expressionStatement, JavaElementType.EXPRESSION_LIST_STATEMENT); - } - } - - @NotNull - private static PsiBuilder.Marker parseForEachFromColon(PsiBuilder builder, PsiBuilder.Marker statement) { - builder.advanceLexer(); - - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null) { - error(builder, JavaErrorMessages.message("expected.expression")); - } - - if (expect(builder, JavaTokenType.RPARENTH)) { - final PsiBuilder.Marker bodyStatement = parseStatement(builder); - if (bodyStatement == null) { - error(builder, JavaErrorMessages.message("expected.statement")); - } - } - else { - error(builder, JavaErrorMessages.message("expected.rparen")); - } - - done(statement, JavaElementType.FOREACH_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseDoWhileStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - final PsiBuilder.Marker body = parseStatement(builder); - if (body == null) { - error(builder, JavaErrorMessages.message("expected.statement")); - done(statement, JavaElementType.DO_WHILE_STATEMENT); - return statement; - } - - if (!expect(builder, JavaTokenType.WHILE_KEYWORD)) { - error(builder, JavaErrorMessages.message("expected.while")); - done(statement, JavaElementType.DO_WHILE_STATEMENT); - return statement; - } - - if (parseExpressionInParenth(builder)) { - semicolon(builder); - } - - done(statement, JavaElementType.DO_WHILE_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseSwitchStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - if (!parseExpressionInParenth(builder)) { - done(statement, JavaElementType.SWITCH_STATEMENT); - return statement; - } - - final PsiBuilder.Marker body = parseCodeBlock(builder); - if (body == null) { - error(builder, JavaErrorMessages.message("expected.lbrace")); - } - - done(statement, JavaElementType.SWITCH_STATEMENT); - return statement; - } - - @Nullable - private static PsiBuilder.Marker parseSwitchLabelStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - final boolean isCase = builder.getTokenType() == JavaTokenType.CASE_KEYWORD; - builder.advanceLexer(); - - if (isCase) { - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null) { - statement.rollbackTo(); - return null; - } - } - - expectOrError(builder, JavaTokenType.COLON, JavaErrorMessages.message("expected.colon")); - - done(statement, JavaElementType.SWITCH_LABEL_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseBreakStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - expect(builder, JavaTokenType.IDENTIFIER); - semicolon(builder); - done(statement, JavaElementType.BREAK_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseContinueStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - expect(builder, JavaTokenType.IDENTIFIER); - semicolon(builder); - done(statement, JavaElementType.CONTINUE_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseReturnStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - ExpressionParser.parse(builder); - - semicolon(builder); - done(statement, JavaElementType.RETURN_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseThrowStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null) { - error(builder, JavaErrorMessages.message("expected.expression")); - done(statement, JavaElementType.THROW_STATEMENT); - return statement; - } - - semicolon(builder); - done(statement, JavaElementType.THROW_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseSynchronizedStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - if (!parseExpressionInParenth(builder)) { - done(statement, JavaElementType.SYNCHRONIZED_STATEMENT); - return statement; - } - - final PsiBuilder.Marker body = parseCodeBlock(builder); - if (body == null) { - error(builder, JavaErrorMessages.message("expected.lbrace")); - } - - done(statement, JavaElementType.SYNCHRONIZED_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseTryStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - final PsiBuilder.Marker tryBlock = parseCodeBlock(builder); - if (tryBlock == null) { - error(builder, JavaErrorMessages.message("expected.lbrace")); - done(statement, JavaElementType.TRY_STATEMENT); - return statement; - } - - if (!TRY_CLOSERS_SET.contains(builder.getTokenType())) { - error(builder, JavaErrorMessages.message("expected.catch.or.finally")); - done(statement, JavaElementType.TRY_STATEMENT); - return statement; - } - - while (builder.getTokenType() == JavaTokenType.CATCH_KEYWORD) { - if (!parseCatchBlock(builder)) break; - } - - if (expect(builder, JavaTokenType.FINALLY_KEYWORD)) { - final PsiBuilder.Marker finallyBlock = parseCodeBlock(builder); - if (finallyBlock == null) { - error(builder, JavaErrorMessages.message("expected.lbrace")); - } - } - - done(statement, JavaElementType.TRY_STATEMENT); - return statement; - } - - private static boolean parseCatchBlock(final PsiBuilder builder) { - final PsiBuilder.Marker section = builder.mark(); - builder.advanceLexer(); - - if (!expect(builder, JavaTokenType.LPARENTH)) { - error(builder, JavaErrorMessages.message("expected.lparen")); - done(section, JavaElementType.CATCH_SECTION); - return false; - } - - final PsiBuilder.Marker param = DeclarationParser.parseParameter(builder, false); - if (param == null) { - error(builder, JavaErrorMessages.message("expected.parameter")); - } - - if (!expect(builder, JavaTokenType.RPARENTH)) { - error(builder, JavaErrorMessages.message("expected.rparen")); - done(section, JavaElementType.CATCH_SECTION); - return false; - } - - final PsiBuilder.Marker body = parseCodeBlock(builder); - if (body == null) { - error(builder, JavaErrorMessages.message("expected.lbrace")); - done(section, JavaElementType.CATCH_SECTION); - return false; - } - - done(section, JavaElementType.CATCH_SECTION); - return true; - } - - @NotNull - private static PsiBuilder.Marker parseAssertStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - builder.advanceLexer(); - - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null) { - error(builder, JavaErrorMessages.message("expected.boolean.expression")); - done(statement, JavaElementType.ASSERT_STATEMENT); - return statement; - } - - if (expect(builder, JavaTokenType.COLON)) { - final PsiBuilder.Marker expr2 = ExpressionParser.parse(builder); - if (expr2 == null) { - error(builder, JavaErrorMessages.message("expected.expression")); - done(statement, JavaElementType.ASSERT_STATEMENT); - return statement; - } - } - - semicolon(builder); - done(statement, JavaElementType.ASSERT_STATEMENT); - return statement; - } - - @NotNull - private static PsiBuilder.Marker parseBlockStatement(final PsiBuilder builder) { - final PsiBuilder.Marker statement = builder.mark(); - parseCodeBlock(builder); - done(statement, JavaElementType.BLOCK_STATEMENT); - return statement; - } - - private static boolean parseExpressionInParenth(final PsiBuilder builder) { - if (!expect(builder, JavaTokenType.LPARENTH)) { - error(builder, JavaErrorMessages.message("expected.lparen")); - return false; - } - - final PsiBuilder.Marker beforeExpr = builder.mark(); - final PsiBuilder.Marker expr = ExpressionParser.parse(builder); - if (expr == null || builder.getTokenType() == JavaTokenType.SEMICOLON) { - beforeExpr.rollbackTo(); - error(builder, JavaErrorMessages.message("expected.expression")); - if (builder.getTokenType() != JavaTokenType.RPARENTH) { - return false; - } - } - else { - beforeExpr.drop(); - if (builder.getTokenType() != JavaTokenType.RPARENTH) { - error(builder, JavaErrorMessages.message("expected.rparen")); - return false; - } - } - - builder.advanceLexer(); - return true; - } -} +/* + * 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.lang.java.parser; + +import com.intellij.codeInsight.daemon.JavaErrorMessages; +import com.intellij.lang.PsiBuilder; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.tree.ElementType; +import com.intellij.psi.impl.source.tree.JavaElementType; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.ILazyParseableElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.util.SmartList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static com.intellij.lang.PsiBuilderUtil.*; +import static com.intellij.lang.java.parser.JavaParserUtil.*; + + +public class StatementParser { + private static final boolean DEEP_PARSE_BLOCKS_IN_STATEMENTS = false; + + private enum BraceMode { + TILL_FIRST, TILL_LAST + } + + private static final TokenSet TRY_CLOSERS_SET = TokenSet.create(JavaTokenType.CATCH_KEYWORD, JavaTokenType.FINALLY_KEYWORD); + + private StatementParser() { } + + @Nullable + public static PsiBuilder.Marker parseCodeBlock(final PsiBuilder builder) { + if (builder.getTokenType() != JavaTokenType.LBRACE) return null; + else if (DEEP_PARSE_BLOCKS_IN_STATEMENTS) return parseCodeBlockDeep(builder, false); + + final PsiBuilder.Marker codeBlock = builder.mark(); + builder.advanceLexer(); + + boolean greedyBlock = false; + int braceCount = 1; + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (tokenType == null) { + greedyBlock = true; + break; + } + if (tokenType == JavaTokenType.LBRACE) { + braceCount++; + } + else if (tokenType == JavaTokenType.RBRACE) { + braceCount--; + } + builder.advanceLexer(); + + if (braceCount == 0) { + break; + } + else if (braceCount == 1 && (tokenType == JavaTokenType.SEMICOLON || tokenType == JavaTokenType.RBRACE)) { + final PsiBuilder.Marker position = builder.mark(); + final List list = new SmartList(); + while (true) { + final IElementType type = builder.getTokenType(); + if (ElementType.PRIMITIVE_TYPE_BIT_SET.contains(type) || ElementType.MODIFIER_BIT_SET.contains(type) || + type == JavaTokenType.IDENTIFIER || type == JavaTokenType.LT || type == JavaTokenType.GT || + type == JavaTokenType.GTGT || type == JavaTokenType.GTGTGT || type == JavaTokenType.COMMA || + type == JavaTokenType.DOT || type == JavaTokenType.EXTENDS_KEYWORD || type == JavaTokenType.IMPLEMENTS_KEYWORD) { + list.add(type); + builder.advanceLexer(); + } else { + break; + } + } + if (builder.getTokenType() == JavaTokenType.LPARENTH && list.size() >= 2) { + final IElementType last = list.get(list.size() - 1); + final IElementType prevLast = list.get(list.size() - 2); + if (last == JavaTokenType.IDENTIFIER && + (prevLast == JavaTokenType.IDENTIFIER || ElementType.PRIMITIVE_TYPE_BIT_SET.contains(prevLast))) { + position.rollbackTo(); + greedyBlock = true; + break; + } + } + position.drop(); + } + } + + codeBlock.collapse(JavaElementType.CODE_BLOCK); + if (greedyBlock) { + codeBlock.setCustomEdgeTokenBinders(null, GREEDY_RIGHT_EDGE_PROCESSOR); + } + return codeBlock; + } + + @Nullable + public static PsiBuilder.Marker parseCodeBlockDeep(final PsiBuilder builder, final boolean parseUntilEof) { + if (builder.getTokenType() != JavaTokenType.LBRACE) return null; + + final PsiBuilder.Marker codeBlock = builder.mark(); + builder.advanceLexer(); + + parseStatements(builder, (parseUntilEof ? BraceMode.TILL_LAST : BraceMode.TILL_FIRST)); + + final boolean greedyBlock = !expectOrError(builder, JavaTokenType.RBRACE, JavaErrorMessages.message("expected.rbrace")); + builder.getTokenType(); // eat spaces + + done(codeBlock, JavaElementType.CODE_BLOCK); + if (greedyBlock) { + codeBlock.setCustomEdgeTokenBinders(null, GREEDY_RIGHT_EDGE_PROCESSOR); + } + return codeBlock; + } + + public static void parseStatements(final PsiBuilder builder) { + parseStatements(builder, null); + } + + private static void parseStatements(final PsiBuilder builder, final BraceMode braceMode) { + while (builder.getTokenType() != null) { + final PsiBuilder.Marker statement = parseStatement(builder); + if (statement != null) continue; + + final IElementType tokenType = builder.getTokenType(); + if (tokenType == JavaTokenType.RBRACE) { + if (braceMode == BraceMode.TILL_FIRST) { + return; + } + else if (braceMode == BraceMode.TILL_LAST) { + if (nextTokenType(builder) == null) { + return; + } + } + } + + final PsiBuilder.Marker error = builder.mark(); + builder.advanceLexer(); + if (tokenType == JavaTokenType.ELSE_KEYWORD) { + error.error(JavaErrorMessages.message("else.without.if")); + } + else if (tokenType == JavaTokenType.CATCH_KEYWORD) { + error.error(JavaErrorMessages.message("catch.without.try")); + } + else if (tokenType == JavaTokenType.FINALLY_KEYWORD) { + error.error(JavaErrorMessages.message("finally.without.try")); + } + else { + error.error(JavaErrorMessages.message("unexpected.token")); + } + } + } + + @Nullable + private static PsiBuilder.Marker parseStatement(final PsiBuilder builder) { + final IElementType tokenType = builder.getTokenType(); + + // todo: custom parsers (?) + + if (tokenType == JavaTokenType.IF_KEYWORD) { + return parseIfStatement(builder); + } + else if (tokenType == JavaTokenType.WHILE_KEYWORD) { + return parseWhileStatement(builder); + } + else if (tokenType == JavaTokenType.FOR_KEYWORD) { + return parseForStatement(builder); + } + else if (tokenType == JavaTokenType.DO_KEYWORD) { + return parseDoWhileStatement(builder); + } + else if (tokenType == JavaTokenType.SWITCH_KEYWORD) { + return parseSwitchStatement(builder); + } + else if (tokenType == JavaTokenType.CASE_KEYWORD || tokenType == JavaTokenType.DEFAULT_KEYWORD) { + return parseSwitchLabelStatement(builder); + } + else if (tokenType == JavaTokenType.BREAK_KEYWORD) { + return parseBreakStatement(builder); + } + else if (tokenType == JavaTokenType.CONTINUE_KEYWORD) { + return parseContinueStatement(builder); + } + else if (tokenType == JavaTokenType.RETURN_KEYWORD) { + return parseReturnStatement(builder); + } + else if (tokenType == JavaTokenType.THROW_KEYWORD) { + return parseThrowStatement(builder); + } + else if (tokenType == JavaTokenType.SYNCHRONIZED_KEYWORD) { + return parseSynchronizedStatement(builder); + } + else if (tokenType == JavaTokenType.TRY_KEYWORD) { + return parseTryStatement(builder); + } + else if (tokenType == JavaTokenType.ASSERT_KEYWORD) { + return parseAssertStatement(builder); + } + else if (tokenType == JavaTokenType.LBRACE) { + return parseBlockStatement(builder); + } + else if (tokenType instanceof ILazyParseableElementType) { + builder.advanceLexer(); + return null; + } + else if (tokenType == JavaTokenType.SEMICOLON) { + final PsiBuilder.Marker empty = builder.mark(); + builder.advanceLexer(); + done(empty, JavaElementType.EMPTY_STATEMENT); + return empty; + } + else if (tokenType == JavaTokenType.IDENTIFIER || tokenType == JavaTokenType.AT) { + final PsiBuilder.Marker refPos = builder.mark(); + DeclarationParser.parseAnnotations(builder); + skipQualifiedName(builder); + final IElementType suspectedLT = builder.getTokenType(); + refPos.rollbackTo(); + if (suspectedLT == JavaTokenType.LT) { + final PsiBuilder.Marker declStatement = builder.mark(); + final PsiBuilder.Marker decl = DeclarationParser.parse(builder, DeclarationParser.Context.CODE_BLOCK); + if (decl == null) { + ReferenceParser.parseType(builder, 0); + error(builder, JavaErrorMessages.message("expected.identifier")); + } + done(declStatement, JavaElementType.DECLARATION_STATEMENT); + return declStatement; + } + } + + final PsiBuilder.Marker pos = builder.mark(); + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + + if (expr != null) { + int count = 1; + final PsiBuilder.Marker list = expr.precede(); + final PsiBuilder.Marker statement = list.precede(); + while (builder.getTokenType() == JavaTokenType.COMMA) { + final PsiBuilder.Marker commaPos = builder.mark(); + builder.advanceLexer(); + final PsiBuilder.Marker expr1 = ExpressionParser.parse(builder); + if (expr1 == null) { + commaPos.rollbackTo(); + break; + } + commaPos.drop(); + count++; + } + if (count > 1) { + pos.drop(); + done(list, JavaElementType.EXPRESSION_LIST); + semicolon(builder); + done(statement, JavaElementType.EXPRESSION_LIST_STATEMENT); + return statement; + } + if (exprType(expr) != JavaElementType.REFERENCE_EXPRESSION) { + drop(list, pos); + semicolon(builder); + done(statement, JavaElementType.EXPRESSION_STATEMENT); + return statement; + } + pos.rollbackTo(); + } + else { + pos.drop(); + } + + final PsiBuilder.Marker decl = DeclarationParser.parse(builder, DeclarationParser.Context.CODE_BLOCK); + if (decl != null) { + final PsiBuilder.Marker statement = decl.precede(); + done(statement, JavaElementType.DECLARATION_STATEMENT); + return statement; + } + + if (lookAhead(builder, JavaTokenType.IDENTIFIER, JavaTokenType.COLON)) { + final PsiBuilder.Marker statement = builder.mark(); + advance(builder, 2); + parseStatement(builder); + done(statement, JavaElementType.LABELED_STATEMENT); + return statement; + } + + if (expr != null) { + final PsiBuilder.Marker statement = builder.mark(); + ExpressionParser.parse(builder); + semicolon(builder); + done(statement, JavaElementType.EXPRESSION_STATEMENT); + return statement; + } + + return null; + } + + private static void skipQualifiedName(final PsiBuilder builder) { + if (!expect(builder, JavaTokenType.IDENTIFIER)) return; + while (lookAhead(builder, JavaTokenType.DOT, JavaTokenType.IDENTIFIER)) { + advance(builder, 2); + } + } + + @NotNull + private static PsiBuilder.Marker parseIfStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (!parseExpressionInParenth(builder)) { + done(statement, JavaElementType.IF_STATEMENT); + return statement; + } + + final PsiBuilder.Marker thenStatement = parseStatement(builder); + if (thenStatement == null) { + error(builder, JavaErrorMessages.message("expected.statement")); + done(statement, JavaElementType.IF_STATEMENT); + return statement; + } + + if (!expect(builder, JavaTokenType.ELSE_KEYWORD)) { + done(statement, JavaElementType.IF_STATEMENT); + return statement; + } + + final PsiBuilder.Marker elseStatement = parseStatement(builder); + if (elseStatement == null) { + error(builder, JavaErrorMessages.message("expected.statement")); + } + + done(statement, JavaElementType.IF_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseWhileStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (!parseExpressionInParenth(builder)) { + done(statement, JavaElementType.WHILE_STATEMENT); + return statement; + } + + final PsiBuilder.Marker bodyStatement = parseStatement(builder); + if (bodyStatement == null) { + error(builder, JavaErrorMessages.message("expected.statement")); + } + + done(statement, JavaElementType.WHILE_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseForStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (!expect(builder, JavaTokenType.LPARENTH)) { + error(builder, JavaErrorMessages.message("expected.lparen")); + done(statement, JavaElementType.FOR_STATEMENT); + return statement; + } + + final PsiBuilder.Marker afterParenth = builder.mark(); + final PsiBuilder.Marker param = DeclarationParser.parseParameter(builder, false); + if (param == null || JavaParserUtil.exprType(param) != JavaElementType.PARAMETER || builder.getTokenType() != JavaTokenType.COLON) { + afterParenth.rollbackTo(); + return parseForLoopFromInitialization(builder, statement); + } + else { + afterParenth.drop(); + return parseForEachFromColon(builder, statement); + } + } + + @NotNull + private static PsiBuilder.Marker parseForLoopFromInitialization(final PsiBuilder builder, final PsiBuilder.Marker statement) { + final PsiBuilder.Marker init = parseStatement(builder); + if (init == null){ + error(builder, JavaErrorMessages.message("expected.statement")); + if (!expect(builder, JavaTokenType.RPARENTH)) { + done(statement, JavaElementType.FOR_STATEMENT); + return statement; + } + } + else { + ExpressionParser.parse(builder); + if (!expect(builder, JavaTokenType.SEMICOLON)) { + error(builder, JavaErrorMessages.message("expected.semicolon")); + if (!expect(builder, JavaTokenType.RPARENTH)) { + done(statement, JavaElementType.FOR_STATEMENT); + return statement; + } + } + else { + parseExpressionOrExpressionList(builder); + if (!expect(builder, JavaTokenType.RPARENTH)) { + error(builder, JavaErrorMessages.message("expected.rparen")); + done(statement, JavaElementType.FOR_STATEMENT); + return statement; + } + } + } + + final PsiBuilder.Marker bodyStatement = parseStatement(builder); + if (bodyStatement == null) { + error(builder, JavaErrorMessages.message("expected.statement")); + } + + done(statement, JavaElementType.FOR_STATEMENT); + return statement; + } + + private static void parseExpressionOrExpressionList(final PsiBuilder builder) { + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + if (expr == null) return; + + final PsiBuilder.Marker expressionStatement; + if (builder.getTokenType() != JavaTokenType.COMMA) { + expressionStatement = expr.precede(); + done(expressionStatement, JavaElementType.EXPRESSION_STATEMENT); + } + else { + final PsiBuilder.Marker expressionList = expr.precede(); + expressionStatement = expressionList.precede(); + + do { + builder.advanceLexer(); + final PsiBuilder.Marker nextExpression = ExpressionParser.parse(builder); + if (nextExpression == null) { + error(builder, JavaErrorMessages.message("expected.expression")); + } + } + while (builder.getTokenType() == JavaTokenType.COMMA); + + done(expressionList, JavaElementType.EXPRESSION_LIST); + done(expressionStatement, JavaElementType.EXPRESSION_LIST_STATEMENT); + } + } + + @NotNull + private static PsiBuilder.Marker parseForEachFromColon(PsiBuilder builder, PsiBuilder.Marker statement) { + builder.advanceLexer(); + + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + if (expr == null) { + error(builder, JavaErrorMessages.message("expected.expression")); + } + + if (expect(builder, JavaTokenType.RPARENTH)) { + final PsiBuilder.Marker bodyStatement = parseStatement(builder); + if (bodyStatement == null) { + error(builder, JavaErrorMessages.message("expected.statement")); + } + } + else { + error(builder, JavaErrorMessages.message("expected.rparen")); + } + + done(statement, JavaElementType.FOREACH_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseDoWhileStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker body = parseStatement(builder); + if (body == null) { + error(builder, JavaErrorMessages.message("expected.statement")); + done(statement, JavaElementType.DO_WHILE_STATEMENT); + return statement; + } + + if (!expect(builder, JavaTokenType.WHILE_KEYWORD)) { + error(builder, JavaErrorMessages.message("expected.while")); + done(statement, JavaElementType.DO_WHILE_STATEMENT); + return statement; + } + + if (parseExpressionInParenth(builder)) { + semicolon(builder); + } + + done(statement, JavaElementType.DO_WHILE_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseSwitchStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (!parseExpressionInParenth(builder)) { + done(statement, JavaElementType.SWITCH_STATEMENT); + return statement; + } + + final PsiBuilder.Marker body = parseCodeBlock(builder); + if (body == null) { + error(builder, JavaErrorMessages.message("expected.lbrace")); + } + + done(statement, JavaElementType.SWITCH_STATEMENT); + return statement; + } + + @Nullable + private static PsiBuilder.Marker parseSwitchLabelStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + final boolean isCase = builder.getTokenType() == JavaTokenType.CASE_KEYWORD; + builder.advanceLexer(); + + if (isCase) { + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + if (expr == null) { + statement.rollbackTo(); + return null; + } + } + + expectOrError(builder, JavaTokenType.COLON, JavaErrorMessages.message("expected.colon")); + + done(statement, JavaElementType.SWITCH_LABEL_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseBreakStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + expect(builder, JavaTokenType.IDENTIFIER); + semicolon(builder); + done(statement, JavaElementType.BREAK_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseContinueStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + expect(builder, JavaTokenType.IDENTIFIER); + semicolon(builder); + done(statement, JavaElementType.CONTINUE_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseReturnStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + ExpressionParser.parse(builder); + + semicolon(builder); + done(statement, JavaElementType.RETURN_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseThrowStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + if (expr == null) { + error(builder, JavaErrorMessages.message("expected.expression")); + done(statement, JavaElementType.THROW_STATEMENT); + return statement; + } + + semicolon(builder); + done(statement, JavaElementType.THROW_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseSynchronizedStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (!parseExpressionInParenth(builder)) { + done(statement, JavaElementType.SYNCHRONIZED_STATEMENT); + return statement; + } + + final PsiBuilder.Marker body = parseCodeBlock(builder); + if (body == null) { + error(builder, JavaErrorMessages.message("expected.lbrace")); + } + + done(statement, JavaElementType.SYNCHRONIZED_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseTryStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker tryBlock = parseCodeBlock(builder); + if (tryBlock == null) { + error(builder, JavaErrorMessages.message("expected.lbrace")); + done(statement, JavaElementType.TRY_STATEMENT); + return statement; + } + + if (!TRY_CLOSERS_SET.contains(builder.getTokenType())) { + error(builder, JavaErrorMessages.message("expected.catch.or.finally")); + done(statement, JavaElementType.TRY_STATEMENT); + return statement; + } + + while (builder.getTokenType() == JavaTokenType.CATCH_KEYWORD) { + if (!parseCatchBlock(builder)) break; + } + + if (expect(builder, JavaTokenType.FINALLY_KEYWORD)) { + final PsiBuilder.Marker finallyBlock = parseCodeBlock(builder); + if (finallyBlock == null) { + error(builder, JavaErrorMessages.message("expected.lbrace")); + } + } + + done(statement, JavaElementType.TRY_STATEMENT); + return statement; + } + + private static boolean parseCatchBlock(final PsiBuilder builder) { + final PsiBuilder.Marker section = builder.mark(); + builder.advanceLexer(); + + if (!expect(builder, JavaTokenType.LPARENTH)) { + error(builder, JavaErrorMessages.message("expected.lparen")); + done(section, JavaElementType.CATCH_SECTION); + return false; + } + + final PsiBuilder.Marker param = DeclarationParser.parseParameter(builder, false); + if (param == null) { + error(builder, JavaErrorMessages.message("expected.parameter")); + } + + if (!expect(builder, JavaTokenType.RPARENTH)) { + error(builder, JavaErrorMessages.message("expected.rparen")); + done(section, JavaElementType.CATCH_SECTION); + return false; + } + + final PsiBuilder.Marker body = parseCodeBlock(builder); + if (body == null) { + error(builder, JavaErrorMessages.message("expected.lbrace")); + done(section, JavaElementType.CATCH_SECTION); + return false; + } + + done(section, JavaElementType.CATCH_SECTION); + return true; + } + + @NotNull + private static PsiBuilder.Marker parseAssertStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + if (expr == null) { + error(builder, JavaErrorMessages.message("expected.boolean.expression")); + done(statement, JavaElementType.ASSERT_STATEMENT); + return statement; + } + + if (expect(builder, JavaTokenType.COLON)) { + final PsiBuilder.Marker expr2 = ExpressionParser.parse(builder); + if (expr2 == null) { + error(builder, JavaErrorMessages.message("expected.expression")); + done(statement, JavaElementType.ASSERT_STATEMENT); + return statement; + } + } + + semicolon(builder); + done(statement, JavaElementType.ASSERT_STATEMENT); + return statement; + } + + @NotNull + private static PsiBuilder.Marker parseBlockStatement(final PsiBuilder builder) { + final PsiBuilder.Marker statement = builder.mark(); + parseCodeBlock(builder); + done(statement, JavaElementType.BLOCK_STATEMENT); + return statement; + } + + private static boolean parseExpressionInParenth(final PsiBuilder builder) { + if (!expect(builder, JavaTokenType.LPARENTH)) { + error(builder, JavaErrorMessages.message("expected.lparen")); + return false; + } + + final PsiBuilder.Marker beforeExpr = builder.mark(); + final PsiBuilder.Marker expr = ExpressionParser.parse(builder); + if (expr == null || builder.getTokenType() == JavaTokenType.SEMICOLON) { + beforeExpr.rollbackTo(); + error(builder, JavaErrorMessages.message("expected.expression")); + if (builder.getTokenType() != JavaTokenType.RPARENTH) { + return false; + } + } + else { + beforeExpr.drop(); + if (builder.getTokenType() != JavaTokenType.RPARENTH) { + error(builder, JavaErrorMessages.message("expected.rparen")); + return false; + } + } + + builder.advanceLexer(); + return true; + } +} diff --git a/java/java-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java b/java/java-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java index acac4fd6e7d4..50cb8ecb43f7 100644 --- a/java/java-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java +++ b/java/java-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java @@ -1,99 +1,99 @@ -/* - * 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.roots.impl; - -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.module.LanguageLevelUtil; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.newvfs.FileAttribute; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.messages.MessageBus; -import org.jetbrains.annotations.NotNull; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -/** - * @author Gregory.Shrago - */ -public class JavaLanguageLevelPusher implements FilePropertyPusher { - - public static void pushLanguageLevel(final Project project) { - PushedFilePropertiesUpdater.getInstance(project).pushAll(new JavaLanguageLevelPusher()); - } - - public void initExtra(Project project, MessageBus bus, Engine languageLevelUpdater) { - // nothing - } - - @NotNull - public Key getFileDataKey() { - return LanguageLevel.KEY; - } - - public boolean pushDirectoriesOnly() { - return true; - } - - public LanguageLevel getDefaultValue() { - return LanguageLevel.HIGHEST; - } - - public LanguageLevel getImmediateValue(Project project, VirtualFile file) { - return null; - } - - public LanguageLevel getImmediateValue(Module module) { - return LanguageLevelUtil.getEffectiveLanguageLevel(module); - } - - public boolean acceptsFile(VirtualFile file) { - return false; - } - - private static final FileAttribute PERSISTENCE = new FileAttribute("language_level_persistence", 1, true); - - public void persistAttribute(VirtualFile fileOrDir, LanguageLevel level) throws IOException { - final DataInputStream iStream = PERSISTENCE.readAttribute(fileOrDir); - if (iStream != null) { - try { - final int oldLevelOrdinal = iStream.readInt(); - if (oldLevelOrdinal == level.ordinal()) return; - } - finally { - iStream.close(); - } - } - - final DataOutputStream oStream = PERSISTENCE.writeAttribute(fileOrDir); - oStream.writeInt(level.ordinal()); - oStream.close(); - - for (VirtualFile child : fileOrDir.getChildren()) { - if (!child.isDirectory() && StdFileTypes.JAVA.equals(child.getFileType())) { - FileBasedIndex.getInstance().requestReindex(child); - } - } - } - - public void afterRootsChanged(Project project) { - } -} +/* + * 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.roots.impl; + +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.module.LanguageLevelUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.FileAttribute; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * @author Gregory.Shrago + */ +public class JavaLanguageLevelPusher implements FilePropertyPusher { + + public static void pushLanguageLevel(final Project project) { + PushedFilePropertiesUpdater.getInstance(project).pushAll(new JavaLanguageLevelPusher()); + } + + public void initExtra(Project project, MessageBus bus, Engine languageLevelUpdater) { + // nothing + } + + @NotNull + public Key getFileDataKey() { + return LanguageLevel.KEY; + } + + public boolean pushDirectoriesOnly() { + return true; + } + + public LanguageLevel getDefaultValue() { + return LanguageLevel.HIGHEST; + } + + public LanguageLevel getImmediateValue(Project project, VirtualFile file) { + return null; + } + + public LanguageLevel getImmediateValue(Module module) { + return LanguageLevelUtil.getEffectiveLanguageLevel(module); + } + + public boolean acceptsFile(VirtualFile file) { + return false; + } + + private static final FileAttribute PERSISTENCE = new FileAttribute("language_level_persistence", 1, true); + + public void persistAttribute(VirtualFile fileOrDir, LanguageLevel level) throws IOException { + final DataInputStream iStream = PERSISTENCE.readAttribute(fileOrDir); + if (iStream != null) { + try { + final int oldLevelOrdinal = iStream.readInt(); + if (oldLevelOrdinal == level.ordinal()) return; + } + finally { + iStream.close(); + } + } + + final DataOutputStream oStream = PERSISTENCE.writeAttribute(fileOrDir); + oStream.writeInt(level.ordinal()); + oStream.close(); + + for (VirtualFile child : fileOrDir.getChildren()) { + if (!child.isDirectory() && StdFileTypes.JAVA.equals(child.getFileType())) { + FileBasedIndex.getInstance().requestReindex(child); + } + } + } + + public void afterRootsChanged(Project project) { + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/compiled/ClsClassImpl.java b/java/java-impl/src/com/intellij/psi/impl/compiled/ClsClassImpl.java index 9b2f8060c711..c4615730b454 100644 --- a/java/java-impl/src/com/intellij/psi/impl/compiled/ClsClassImpl.java +++ b/java/java-impl/src/com/intellij/psi/impl/compiled/ClsClassImpl.java @@ -1,538 +1,538 @@ -/* - * 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.impl.compiled; - -import com.intellij.navigation.ItemPresentation; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.ui.Queryable; -import com.intellij.openapi.util.Pair; -import com.intellij.psi.*; -import com.intellij.psi.impl.InheritanceImplUtil; -import com.intellij.psi.impl.PsiClassImplUtil; -import com.intellij.psi.impl.PsiImplUtil; -import com.intellij.psi.impl.PsiSuperMethodImplUtil; -import com.intellij.psi.impl.java.stubs.JavaStubElementTypes; -import com.intellij.psi.impl.java.stubs.PsiClassStub; -import com.intellij.psi.impl.source.ClassInnerStuffCache; -import com.intellij.psi.impl.source.Constants; -import com.intellij.psi.impl.source.PsiClassImpl; -import com.intellij.psi.impl.source.SourceTreeToPsiMap; -import com.intellij.psi.impl.source.tree.TreeElement; -import com.intellij.psi.javadoc.PsiDocComment; -import com.intellij.psi.presentation.java.ClassPresentationUtil; -import com.intellij.psi.scope.PsiScopeProcessor; -import com.intellij.psi.search.SearchScope; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public class ClsClassImpl extends ClsRepositoryPsiElement> implements PsiClass, PsiQualifiedNamedElement, Queryable { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClsClassImpl"); - - private final ClassInnerStuffCache innersCache = new ClassInnerStuffCache(this); - private final PsiIdentifier myNameIdentifier; - private final PsiDocComment myDocComment; - - public ClsClassImpl(final PsiClassStub stub) { - super(stub); - myDocComment = isDeprecated() ? new ClsDocCommentImpl(this) : null; - myNameIdentifier = new ClsIdentifierImpl(this, getShortName()); - } - - @NotNull - public PsiElement[] getChildren() { - PsiIdentifier name = getNameIdentifier(); - PsiDocComment docComment = getDocComment(); - PsiModifierList modifierList = getModifierList(); - PsiReferenceList extendsList = getExtendsList(); - PsiReferenceList implementsList = getImplementsList(); - PsiField[] fields = getFields(); - PsiMethod[] methods = getMethods(); - PsiClass[] classes = getInnerClasses(); - - int count = - (docComment != null ? 1 : 0) - + 1 // modifierList - + 1 // name - + 1 // extends list - + 1 // implementsList - + fields.length - + methods.length - + classes.length; - PsiElement[] children = new PsiElement[count]; - - int offset = 0; - if (docComment != null) { - children[offset++] = docComment; - } - - children[offset++] = modifierList; - children[offset++] = name; - children[offset++] = extendsList; - children[offset++] = implementsList; - - System.arraycopy(fields, 0, children, offset, fields.length); - offset += fields.length; - System.arraycopy(methods, 0, children, offset, methods.length); - offset += methods.length; - System.arraycopy(classes, 0, children, offset, classes.length); - /*offset += classes.length;*/ - - return children; - } - - @NotNull - public PsiIdentifier getNameIdentifier() { - return myNameIdentifier; - } - - private String getShortName() { - String qName = getQualifiedName(); - String name = PsiNameHelper.getShortClassName(qName); - if (name.length() == 0) { - name = "_"; - } - return name; - } - - @NotNull - public String getName() { - return getStub().getName(); - } - - @NotNull - public PsiTypeParameterList getTypeParameterList() { - return getStub().findChildStubByType(JavaStubElementTypes.TYPE_PARAMETER_LIST).getPsi(); - } - - public boolean hasTypeParameters() { - return PsiImplUtil.hasTypeParameters(this); - } - - public PsiElement setName(@NotNull String name) throws IncorrectOperationException { - PsiImplUtil.setName(getNameIdentifier(), name); - return this; - } - - @NotNull - public String getQualifiedName() { - return getStub().getQualifiedName(); - } - - @NotNull - public PsiModifierList getModifierList() { - return getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST).getPsi(); - } - - public boolean hasModifierProperty(@NotNull String name) { - return getModifierList().hasModifierProperty(name); - } - - @NotNull - public PsiReferenceList getExtendsList() { - return getStub().findChildStubByType(JavaStubElementTypes.EXTENDS_LIST).getPsi(); - } - - - @NotNull - public PsiReferenceList getImplementsList() { - return getStub().findChildStubByType(JavaStubElementTypes.IMPLEMENTS_LIST).getPsi(); - } - - @NotNull - public PsiClassType[] getExtendsListTypes() { - return PsiClassImplUtil.getExtendsListTypes(this); - } - - @NotNull - public PsiClassType[] getImplementsListTypes() { - return PsiClassImplUtil.getImplementsListTypes(this); - } - - public PsiClass getSuperClass() { - return PsiClassImplUtil.getSuperClass(this); - } - - public PsiClass[] getInterfaces() { - return PsiClassImplUtil.getInterfaces(this); - } - - @NotNull - public PsiClass[] getSupers() { - return PsiClassImplUtil.getSupers(this); - } - - @NotNull - public PsiClassType[] getSuperTypes() { - return PsiClassImplUtil.getSuperTypes(this); - } - - public PsiClass getContainingClass() { - PsiElement parent = getParent(); - return parent instanceof PsiClass ? (PsiClass)parent : null; - } - - @NotNull - public Collection getVisibleSignatures() { - return PsiSuperMethodImplUtil.getVisibleSignatures(this); - } - - @NotNull - public PsiField[] getFields() { - return getStub().getChildrenByType(Constants.FIELD_BIT_SET, PsiField.ARRAY_FACTORY); - } - - @NotNull - public PsiMethod[] getMethods() { - return getStub().getChildrenByType(Constants.METHOD_BIT_SET, PsiMethod.ARRAY_FACTORY); - } - - @NotNull - public PsiMethod[] getConstructors() { - return PsiImplUtil.getConstructors(this); - } - - @NotNull - public PsiClass[] getInnerClasses() { - return getStub().getChildrenByType(JavaStubElementTypes.CLASS, ARRAY_FACTORY); - } - - @NotNull - public PsiClassInitializer[] getInitializers() { - return PsiClassInitializer.EMPTY_ARRAY; - } - - @NotNull - public PsiTypeParameter[] getTypeParameters() { - return PsiImplUtil.getTypeParameters(this); - } - - @NotNull - public PsiField[] getAllFields() { - return PsiClassImplUtil.getAllFields(this); - } - - @NotNull - public PsiMethod[] getAllMethods() { - return PsiClassImplUtil.getAllMethods(this); - } - - @NotNull - public PsiClass[] getAllInnerClasses() { - return PsiClassImplUtil.getAllInnerClasses(this); - } - - public PsiField findFieldByName(String name, boolean checkBases) { - return innersCache.findFieldByName(name, checkBases); - } - - public PsiMethod findMethodBySignature(PsiMethod patternMethod, boolean checkBases) { - return PsiClassImplUtil.findMethodBySignature(this, patternMethod, checkBases); - } - - @NotNull - public PsiMethod[] findMethodsBySignature(PsiMethod patternMethod, boolean checkBases) { - return PsiClassImplUtil.findMethodsBySignature(this, patternMethod, checkBases); - } - - @NotNull - public PsiMethod[] findMethodsByName(String name, boolean checkBases) { - return innersCache.findMethodsByName(name, checkBases); - } - - @NotNull - public List> findMethodsAndTheirSubstitutorsByName(String name, boolean checkBases) { - return PsiClassImplUtil.findMethodsAndTheirSubstitutorsByName(this, name, checkBases); - } - - @NotNull - public List> getAllMethodsAndTheirSubstitutors() { - return PsiClassImplUtil.getAllWithSubstitutorsByMap(this, PsiMethod.class); - } - - public PsiClass findInnerClassByName(String name, boolean checkBases) { - return innersCache.findInnerClassByName(name, checkBases); - } - - public boolean isDeprecated() { - return getStub().isDeprecated(); - } - - public String getSourceFileName() { - final String sfn = getStub().getSourceFileName(); - return sfn != null ? sfn : obtainSourceFileNameFromClassFileName(); - } - - @NonNls - private String obtainSourceFileNameFromClassFileName() { - final String name = getContainingFile().getName(); - int i = name.indexOf('$'); - if (i < 0) { - i = name.indexOf('.'); - if (i < 0) { - i = name.length(); - } - } - return name.substring(0, i) + ".java"; - } - - public PsiDocComment getDocComment() { - return myDocComment; - } - - public PsiJavaToken getLBrace() { - return null; - } - - public PsiJavaToken getRBrace() { - return null; - } - - public boolean isInterface() { - return getStub().isInterface(); - } - - public boolean isAnnotationType() { - return getStub().isAnnotationType(); - } - - public boolean isEnum() { - return getStub().isEnum(); - } - - public void appendMirrorText(final int indentLevel, @NonNls final StringBuffer buffer) { - ClsDocCommentImpl docComment = (ClsDocCommentImpl)getDocComment(); - if (docComment != null) { - docComment.appendMirrorText(indentLevel, buffer); - goNextLine(indentLevel, buffer); - } - ((ClsElementImpl)getModifierList()).appendMirrorText(indentLevel, buffer); - buffer.append(isEnum() ? "enum " : isAnnotationType() ? "@interface " : isInterface() ? "interface " : "class "); - ((ClsElementImpl)getNameIdentifier()).appendMirrorText(indentLevel, buffer); - ((ClsElementImpl)getTypeParameterList()).appendMirrorText(indentLevel, buffer); - buffer.append(' '); - if (!isEnum() && !isAnnotationType()) { - ((ClsElementImpl)getExtendsList()).appendMirrorText(indentLevel, buffer); - buffer.append(' '); - } - if (!isInterface()) { - ((ClsElementImpl)getImplementsList()).appendMirrorText(indentLevel, buffer); - } - buffer.append('{'); - final int newIndentLevel = indentLevel + getIndentSize(); - PsiField[] fields = getFields(); - if (fields.length > 0) { - goNextLine(newIndentLevel, buffer); - for (int i = 0; i < fields.length; i++) { - PsiField field = fields[i]; - ((ClsElementImpl)field).appendMirrorText(newIndentLevel, buffer); - if (field instanceof ClsEnumConstantImpl) { - if (i < fields.length - 1 && fields[i + 1] instanceof ClsEnumConstantImpl) { - buffer.append(", "); - } - else { - buffer.append(";"); - if (i < fields.length - 1) { - goNextLine(newIndentLevel, buffer); - } - } - } else if (i < fields.length - 1) { - goNextLine(newIndentLevel, buffer); - } - } - } - - PsiMethod[] methods = getMethods(); - if (methods.length > 0) { - goNextLine(newIndentLevel, buffer); - goNextLine(newIndentLevel, buffer); - for (int i = 0; i < methods.length; i++) { - PsiMethod method = methods[i]; - ((ClsElementImpl)method).appendMirrorText(newIndentLevel, buffer); - if (i < methods.length - 1) { - goNextLine(newIndentLevel, buffer); - goNextLine(newIndentLevel, buffer); - } - } - } - - PsiClass[] classes = getInnerClasses(); - if (classes.length > 0) { - goNextLine(newIndentLevel, buffer); - goNextLine(newIndentLevel, buffer); - for (int i = 0; i < classes.length; i++) { - PsiClass aClass = classes[i]; - ((ClsElementImpl)aClass).appendMirrorText(newIndentLevel, buffer); - if (i < classes.length - 1) { - goNextLine(newIndentLevel, buffer); - goNextLine(newIndentLevel, buffer); - } - } - } - goNextLine(indentLevel, buffer); - buffer.append('}'); - } - - public void setMirror(@NotNull TreeElement element) { - setMirrorCheckingType(element, null); - - PsiClass mirror = (PsiClass)SourceTreeToPsiMap.treeElementToPsi(element); - - final PsiDocComment docComment = getDocComment(); - if (docComment != null) { - ((ClsElementImpl)docComment).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getDocComment())); - } - ((ClsElementImpl)getModifierList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getModifierList())); - ((ClsElementImpl)getNameIdentifier()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getNameIdentifier())); - if (!isAnnotationType() && !isEnum()) { - ((ClsElementImpl)getExtendsList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getExtendsList())); - } - ((ClsElementImpl)getImplementsList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getImplementsList())); - ((ClsElementImpl)getTypeParameterList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getTypeParameterList())); - - PsiField[] fields = getFields(); - PsiField[] mirrorFields = mirror.getFields(); - if (LOG.assertTrue(fields.length == mirrorFields.length)) { - for (int i = 0; i < fields.length; i++) { - ((ClsElementImpl)fields[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorFields[i])); - } - } - - PsiMethod[] methods = getMethods(); - PsiMethod[] mirrorMethods = mirror.getMethods(); - if (LOG.assertTrue(methods.length == mirrorMethods.length)) { - for (int i = 0; i < methods.length; i++) { - ((ClsElementImpl)methods[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorMethods[i])); - } - } - - PsiClass[] classes = getInnerClasses(); - PsiClass[] mirrorClasses = mirror.getInnerClasses(); - if (LOG.assertTrue(classes.length == mirrorClasses.length)) { - for (int i = 0; i < classes.length; i++) { - ((ClsElementImpl)classes[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorClasses[i])); - } - } - } - - public void accept(@NotNull PsiElementVisitor visitor) { - if (visitor instanceof JavaElementVisitor) { - ((JavaElementVisitor)visitor).visitClass(this); - } - else { - visitor.visitElement(this); - } - } - - @NonNls - public String toString() { - return "PsiClass:" + getName(); - } - - public boolean processDeclarations(@NotNull PsiScopeProcessor processor, - @NotNull ResolveState state, - PsiElement lastParent, - @NotNull PsiElement place) { - return PsiClassImplUtil.processDeclarationsInClass(this, processor, state, null, lastParent, place, false); - } - - public PsiElement getScope() { - return getParent(); - } - - public boolean isInheritorDeep(PsiClass baseClass, PsiClass classToByPass) { - return InheritanceImplUtil.isInheritorDeep(this, baseClass, classToByPass); - } - - public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) { - return InheritanceImplUtil.isInheritor(this, baseClass, checkDeep); - } - - @Nullable - public PsiClass getSourceMirrorClass() { - PsiElement parent = getParent(); - final String name = getName(); - if (parent instanceof PsiFile) { - if (!(parent instanceof PsiClassOwner)) return null; - - PsiClassOwner fileNavigationElement = (PsiClassOwner)parent.getNavigationElement(); - for (PsiClass aClass : fileNavigationElement.getClasses()) { - if (name.equals(aClass.getName())) return aClass; - } - } - else if (parent != null) { - ClsClassImpl parentClass = (ClsClassImpl)parent; - PsiClass parentSourceMirror = parentClass.getSourceMirrorClass(); - if (parentSourceMirror == null) return null; - PsiClass[] innerClasses = parentSourceMirror.getInnerClasses(); - for (PsiClass innerClass : innerClasses) { - if (name.equals(innerClass.getName())) return innerClass; - } - } - else { - throw new PsiInvalidElementAccessException(this); - } - - return null; - } - - @NotNull - public PsiElement getNavigationElement() { - PsiClass aClass = getSourceMirrorClass(); - return aClass != null && aClass != this ? aClass.getNavigationElement() : this; - } - - public ItemPresentation getPresentation() { - return ClassPresentationUtil.getPresentation(this); - } - - public Icon getElementIcon(final int flags) { - return PsiClassImplUtil.getClassIcon(flags, this); - } - - @Override - public boolean isEquivalentTo(final PsiElement another) { - return PsiClassImplUtil.isClassEquivalentTo(this, another); - } - - @NotNull - public SearchScope getUseScope() { - return PsiClassImplUtil.getClassUseScope(this); - } - - public PsiQualifiedNamedElement getContainer() { - final PsiFile file = getContainingFile(); - final PsiDirectory dir; - return file == null ? null : (dir = file.getContainingDirectory()) == null - ? null : JavaDirectoryService.getInstance().getPackage(dir); - } - - public void putInfo(Map info) { - PsiClassImpl.putInfo(this, info); - } - - @Override - protected boolean isVisibilitySupported() { - return true; - } - -} +/* + * 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.impl.compiled; + +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.ui.Queryable; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.*; +import com.intellij.psi.impl.InheritanceImplUtil; +import com.intellij.psi.impl.PsiClassImplUtil; +import com.intellij.psi.impl.PsiImplUtil; +import com.intellij.psi.impl.PsiSuperMethodImplUtil; +import com.intellij.psi.impl.java.stubs.JavaStubElementTypes; +import com.intellij.psi.impl.java.stubs.PsiClassStub; +import com.intellij.psi.impl.source.ClassInnerStuffCache; +import com.intellij.psi.impl.source.Constants; +import com.intellij.psi.impl.source.PsiClassImpl; +import com.intellij.psi.impl.source.SourceTreeToPsiMap; +import com.intellij.psi.impl.source.tree.TreeElement; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.presentation.java.ClassPresentationUtil; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.search.SearchScope; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class ClsClassImpl extends ClsRepositoryPsiElement> implements PsiClass, PsiQualifiedNamedElement, Queryable { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClsClassImpl"); + + private final ClassInnerStuffCache innersCache = new ClassInnerStuffCache(this); + private final PsiIdentifier myNameIdentifier; + private final PsiDocComment myDocComment; + + public ClsClassImpl(final PsiClassStub stub) { + super(stub); + myDocComment = isDeprecated() ? new ClsDocCommentImpl(this) : null; + myNameIdentifier = new ClsIdentifierImpl(this, getShortName()); + } + + @NotNull + public PsiElement[] getChildren() { + PsiIdentifier name = getNameIdentifier(); + PsiDocComment docComment = getDocComment(); + PsiModifierList modifierList = getModifierList(); + PsiReferenceList extendsList = getExtendsList(); + PsiReferenceList implementsList = getImplementsList(); + PsiField[] fields = getFields(); + PsiMethod[] methods = getMethods(); + PsiClass[] classes = getInnerClasses(); + + int count = + (docComment != null ? 1 : 0) + + 1 // modifierList + + 1 // name + + 1 // extends list + + 1 // implementsList + + fields.length + + methods.length + + classes.length; + PsiElement[] children = new PsiElement[count]; + + int offset = 0; + if (docComment != null) { + children[offset++] = docComment; + } + + children[offset++] = modifierList; + children[offset++] = name; + children[offset++] = extendsList; + children[offset++] = implementsList; + + System.arraycopy(fields, 0, children, offset, fields.length); + offset += fields.length; + System.arraycopy(methods, 0, children, offset, methods.length); + offset += methods.length; + System.arraycopy(classes, 0, children, offset, classes.length); + /*offset += classes.length;*/ + + return children; + } + + @NotNull + public PsiIdentifier getNameIdentifier() { + return myNameIdentifier; + } + + private String getShortName() { + String qName = getQualifiedName(); + String name = PsiNameHelper.getShortClassName(qName); + if (name.length() == 0) { + name = "_"; + } + return name; + } + + @NotNull + public String getName() { + return getStub().getName(); + } + + @NotNull + public PsiTypeParameterList getTypeParameterList() { + return getStub().findChildStubByType(JavaStubElementTypes.TYPE_PARAMETER_LIST).getPsi(); + } + + public boolean hasTypeParameters() { + return PsiImplUtil.hasTypeParameters(this); + } + + public PsiElement setName(@NotNull String name) throws IncorrectOperationException { + PsiImplUtil.setName(getNameIdentifier(), name); + return this; + } + + @NotNull + public String getQualifiedName() { + return getStub().getQualifiedName(); + } + + @NotNull + public PsiModifierList getModifierList() { + return getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST).getPsi(); + } + + public boolean hasModifierProperty(@NotNull String name) { + return getModifierList().hasModifierProperty(name); + } + + @NotNull + public PsiReferenceList getExtendsList() { + return getStub().findChildStubByType(JavaStubElementTypes.EXTENDS_LIST).getPsi(); + } + + + @NotNull + public PsiReferenceList getImplementsList() { + return getStub().findChildStubByType(JavaStubElementTypes.IMPLEMENTS_LIST).getPsi(); + } + + @NotNull + public PsiClassType[] getExtendsListTypes() { + return PsiClassImplUtil.getExtendsListTypes(this); + } + + @NotNull + public PsiClassType[] getImplementsListTypes() { + return PsiClassImplUtil.getImplementsListTypes(this); + } + + public PsiClass getSuperClass() { + return PsiClassImplUtil.getSuperClass(this); + } + + public PsiClass[] getInterfaces() { + return PsiClassImplUtil.getInterfaces(this); + } + + @NotNull + public PsiClass[] getSupers() { + return PsiClassImplUtil.getSupers(this); + } + + @NotNull + public PsiClassType[] getSuperTypes() { + return PsiClassImplUtil.getSuperTypes(this); + } + + public PsiClass getContainingClass() { + PsiElement parent = getParent(); + return parent instanceof PsiClass ? (PsiClass)parent : null; + } + + @NotNull + public Collection getVisibleSignatures() { + return PsiSuperMethodImplUtil.getVisibleSignatures(this); + } + + @NotNull + public PsiField[] getFields() { + return getStub().getChildrenByType(Constants.FIELD_BIT_SET, PsiField.ARRAY_FACTORY); + } + + @NotNull + public PsiMethod[] getMethods() { + return getStub().getChildrenByType(Constants.METHOD_BIT_SET, PsiMethod.ARRAY_FACTORY); + } + + @NotNull + public PsiMethod[] getConstructors() { + return PsiImplUtil.getConstructors(this); + } + + @NotNull + public PsiClass[] getInnerClasses() { + return getStub().getChildrenByType(JavaStubElementTypes.CLASS, ARRAY_FACTORY); + } + + @NotNull + public PsiClassInitializer[] getInitializers() { + return PsiClassInitializer.EMPTY_ARRAY; + } + + @NotNull + public PsiTypeParameter[] getTypeParameters() { + return PsiImplUtil.getTypeParameters(this); + } + + @NotNull + public PsiField[] getAllFields() { + return PsiClassImplUtil.getAllFields(this); + } + + @NotNull + public PsiMethod[] getAllMethods() { + return PsiClassImplUtil.getAllMethods(this); + } + + @NotNull + public PsiClass[] getAllInnerClasses() { + return PsiClassImplUtil.getAllInnerClasses(this); + } + + public PsiField findFieldByName(String name, boolean checkBases) { + return innersCache.findFieldByName(name, checkBases); + } + + public PsiMethod findMethodBySignature(PsiMethod patternMethod, boolean checkBases) { + return PsiClassImplUtil.findMethodBySignature(this, patternMethod, checkBases); + } + + @NotNull + public PsiMethod[] findMethodsBySignature(PsiMethod patternMethod, boolean checkBases) { + return PsiClassImplUtil.findMethodsBySignature(this, patternMethod, checkBases); + } + + @NotNull + public PsiMethod[] findMethodsByName(String name, boolean checkBases) { + return innersCache.findMethodsByName(name, checkBases); + } + + @NotNull + public List> findMethodsAndTheirSubstitutorsByName(String name, boolean checkBases) { + return PsiClassImplUtil.findMethodsAndTheirSubstitutorsByName(this, name, checkBases); + } + + @NotNull + public List> getAllMethodsAndTheirSubstitutors() { + return PsiClassImplUtil.getAllWithSubstitutorsByMap(this, PsiMethod.class); + } + + public PsiClass findInnerClassByName(String name, boolean checkBases) { + return innersCache.findInnerClassByName(name, checkBases); + } + + public boolean isDeprecated() { + return getStub().isDeprecated(); + } + + public String getSourceFileName() { + final String sfn = getStub().getSourceFileName(); + return sfn != null ? sfn : obtainSourceFileNameFromClassFileName(); + } + + @NonNls + private String obtainSourceFileNameFromClassFileName() { + final String name = getContainingFile().getName(); + int i = name.indexOf('$'); + if (i < 0) { + i = name.indexOf('.'); + if (i < 0) { + i = name.length(); + } + } + return name.substring(0, i) + ".java"; + } + + public PsiDocComment getDocComment() { + return myDocComment; + } + + public PsiJavaToken getLBrace() { + return null; + } + + public PsiJavaToken getRBrace() { + return null; + } + + public boolean isInterface() { + return getStub().isInterface(); + } + + public boolean isAnnotationType() { + return getStub().isAnnotationType(); + } + + public boolean isEnum() { + return getStub().isEnum(); + } + + public void appendMirrorText(final int indentLevel, @NonNls final StringBuffer buffer) { + ClsDocCommentImpl docComment = (ClsDocCommentImpl)getDocComment(); + if (docComment != null) { + docComment.appendMirrorText(indentLevel, buffer); + goNextLine(indentLevel, buffer); + } + ((ClsElementImpl)getModifierList()).appendMirrorText(indentLevel, buffer); + buffer.append(isEnum() ? "enum " : isAnnotationType() ? "@interface " : isInterface() ? "interface " : "class "); + ((ClsElementImpl)getNameIdentifier()).appendMirrorText(indentLevel, buffer); + ((ClsElementImpl)getTypeParameterList()).appendMirrorText(indentLevel, buffer); + buffer.append(' '); + if (!isEnum() && !isAnnotationType()) { + ((ClsElementImpl)getExtendsList()).appendMirrorText(indentLevel, buffer); + buffer.append(' '); + } + if (!isInterface()) { + ((ClsElementImpl)getImplementsList()).appendMirrorText(indentLevel, buffer); + } + buffer.append('{'); + final int newIndentLevel = indentLevel + getIndentSize(); + PsiField[] fields = getFields(); + if (fields.length > 0) { + goNextLine(newIndentLevel, buffer); + for (int i = 0; i < fields.length; i++) { + PsiField field = fields[i]; + ((ClsElementImpl)field).appendMirrorText(newIndentLevel, buffer); + if (field instanceof ClsEnumConstantImpl) { + if (i < fields.length - 1 && fields[i + 1] instanceof ClsEnumConstantImpl) { + buffer.append(", "); + } + else { + buffer.append(";"); + if (i < fields.length - 1) { + goNextLine(newIndentLevel, buffer); + } + } + } else if (i < fields.length - 1) { + goNextLine(newIndentLevel, buffer); + } + } + } + + PsiMethod[] methods = getMethods(); + if (methods.length > 0) { + goNextLine(newIndentLevel, buffer); + goNextLine(newIndentLevel, buffer); + for (int i = 0; i < methods.length; i++) { + PsiMethod method = methods[i]; + ((ClsElementImpl)method).appendMirrorText(newIndentLevel, buffer); + if (i < methods.length - 1) { + goNextLine(newIndentLevel, buffer); + goNextLine(newIndentLevel, buffer); + } + } + } + + PsiClass[] classes = getInnerClasses(); + if (classes.length > 0) { + goNextLine(newIndentLevel, buffer); + goNextLine(newIndentLevel, buffer); + for (int i = 0; i < classes.length; i++) { + PsiClass aClass = classes[i]; + ((ClsElementImpl)aClass).appendMirrorText(newIndentLevel, buffer); + if (i < classes.length - 1) { + goNextLine(newIndentLevel, buffer); + goNextLine(newIndentLevel, buffer); + } + } + } + goNextLine(indentLevel, buffer); + buffer.append('}'); + } + + public void setMirror(@NotNull TreeElement element) { + setMirrorCheckingType(element, null); + + PsiClass mirror = (PsiClass)SourceTreeToPsiMap.treeElementToPsi(element); + + final PsiDocComment docComment = getDocComment(); + if (docComment != null) { + ((ClsElementImpl)docComment).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getDocComment())); + } + ((ClsElementImpl)getModifierList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getModifierList())); + ((ClsElementImpl)getNameIdentifier()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getNameIdentifier())); + if (!isAnnotationType() && !isEnum()) { + ((ClsElementImpl)getExtendsList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getExtendsList())); + } + ((ClsElementImpl)getImplementsList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getImplementsList())); + ((ClsElementImpl)getTypeParameterList()).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirror.getTypeParameterList())); + + PsiField[] fields = getFields(); + PsiField[] mirrorFields = mirror.getFields(); + if (LOG.assertTrue(fields.length == mirrorFields.length)) { + for (int i = 0; i < fields.length; i++) { + ((ClsElementImpl)fields[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorFields[i])); + } + } + + PsiMethod[] methods = getMethods(); + PsiMethod[] mirrorMethods = mirror.getMethods(); + if (LOG.assertTrue(methods.length == mirrorMethods.length)) { + for (int i = 0; i < methods.length; i++) { + ((ClsElementImpl)methods[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorMethods[i])); + } + } + + PsiClass[] classes = getInnerClasses(); + PsiClass[] mirrorClasses = mirror.getInnerClasses(); + if (LOG.assertTrue(classes.length == mirrorClasses.length)) { + for (int i = 0; i < classes.length; i++) { + ((ClsElementImpl)classes[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorClasses[i])); + } + } + } + + public void accept(@NotNull PsiElementVisitor visitor) { + if (visitor instanceof JavaElementVisitor) { + ((JavaElementVisitor)visitor).visitClass(this); + } + else { + visitor.visitElement(this); + } + } + + @NonNls + public String toString() { + return "PsiClass:" + getName(); + } + + public boolean processDeclarations(@NotNull PsiScopeProcessor processor, + @NotNull ResolveState state, + PsiElement lastParent, + @NotNull PsiElement place) { + return PsiClassImplUtil.processDeclarationsInClass(this, processor, state, null, lastParent, place, false); + } + + public PsiElement getScope() { + return getParent(); + } + + public boolean isInheritorDeep(PsiClass baseClass, PsiClass classToByPass) { + return InheritanceImplUtil.isInheritorDeep(this, baseClass, classToByPass); + } + + public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) { + return InheritanceImplUtil.isInheritor(this, baseClass, checkDeep); + } + + @Nullable + public PsiClass getSourceMirrorClass() { + PsiElement parent = getParent(); + final String name = getName(); + if (parent instanceof PsiFile) { + if (!(parent instanceof PsiClassOwner)) return null; + + PsiClassOwner fileNavigationElement = (PsiClassOwner)parent.getNavigationElement(); + for (PsiClass aClass : fileNavigationElement.getClasses()) { + if (name.equals(aClass.getName())) return aClass; + } + } + else if (parent != null) { + ClsClassImpl parentClass = (ClsClassImpl)parent; + PsiClass parentSourceMirror = parentClass.getSourceMirrorClass(); + if (parentSourceMirror == null) return null; + PsiClass[] innerClasses = parentSourceMirror.getInnerClasses(); + for (PsiClass innerClass : innerClasses) { + if (name.equals(innerClass.getName())) return innerClass; + } + } + else { + throw new PsiInvalidElementAccessException(this); + } + + return null; + } + + @NotNull + public PsiElement getNavigationElement() { + PsiClass aClass = getSourceMirrorClass(); + return aClass != null && aClass != this ? aClass.getNavigationElement() : this; + } + + public ItemPresentation getPresentation() { + return ClassPresentationUtil.getPresentation(this); + } + + public Icon getElementIcon(final int flags) { + return PsiClassImplUtil.getClassIcon(flags, this); + } + + @Override + public boolean isEquivalentTo(final PsiElement another) { + return PsiClassImplUtil.isClassEquivalentTo(this, another); + } + + @NotNull + public SearchScope getUseScope() { + return PsiClassImplUtil.getClassUseScope(this); + } + + public PsiQualifiedNamedElement getContainer() { + final PsiFile file = getContainingFile(); + final PsiDirectory dir; + return file == null ? null : (dir = file.getContainingDirectory()) == null + ? null : JavaDirectoryService.getInstance().getPackage(dir); + } + + public void putInfo(Map info) { + PsiClassImpl.putInfo(this, info); + } + + @Override + protected boolean isVisibilitySupported() { + return true; + } + +} diff --git a/java/java-impl/src/com/intellij/psi/impl/search/AllClassesSearchExecutor.java b/java/java-impl/src/com/intellij/psi/impl/search/AllClassesSearchExecutor.java index 36401c928d0a..e2fcbcec8acd 100644 --- a/java/java-impl/src/com/intellij/psi/impl/search/AllClassesSearchExecutor.java +++ b/java/java-impl/src/com/intellij/psi/impl/search/AllClassesSearchExecutor.java @@ -1,131 +1,131 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.psi.impl.search; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.util.Computable; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.PsiShortNamesCache; -import com.intellij.psi.search.SearchScope; -import com.intellij.psi.search.searches.AllClassesSearch; -import com.intellij.util.Processor; -import com.intellij.util.QueryExecutor; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class AllClassesSearchExecutor implements QueryExecutor { - public boolean execute(@NotNull final AllClassesSearch.SearchParameters queryParameters, @NotNull final Processor consumer) { - SearchScope scope = queryParameters.getScope(); - - if (scope instanceof GlobalSearchScope) { - return processAllClassesInGlobalScope((GlobalSearchScope)scope, consumer, queryParameters); - } - - PsiElement[] scopeRoots = ((LocalSearchScope)scope).getScope(); - for (final PsiElement scopeRoot : scopeRoots) { - if (!processScopeRootForAllClasses(scopeRoot, consumer)) return false; - } - return true; - } - - private static boolean processAllClassesInGlobalScope(final GlobalSearchScope scope, final Processor processor, AllClassesSearch.SearchParameters parameters) { - final PsiManager manager = PsiManager.getInstance(parameters.getProject()); - - final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); - final PsiShortNamesCache cache = facade.getShortNamesCache(); - - final String[] names = ApplicationManager.getApplication().runReadAction(new Computable() { - public String[] compute() { - return cache.getAllClassNames(); - } - }); - - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.checkCanceled(); - } - - List sorted = new ArrayList(names.length); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - if (parameters.nameMatches(name)) { - sorted.add(name); - } - if (indicator != null && i % 512 == 0) { - indicator.checkCanceled(); - } - } - - if (indicator != null) { - indicator.checkCanceled(); - } - - Collections.sort(sorted, new Comparator() { - public int compare(final String o1, final String o2) { - return o1.compareToIgnoreCase(o2); - } - }); - - for (final String name : sorted) { - ProgressManager.checkCanceled(); - final PsiClass[] classes = ApplicationManager.getApplication().runReadAction(new Computable() { - public PsiClass[] compute() { - return cache.getClassesByName(name, scope); - } - }); - for (PsiClass psiClass : classes) { - ProgressManager.checkCanceled(); - if (!processor.process(psiClass)) { - return false; - } - } - } - return true; - } - - private static boolean processScopeRootForAllClasses(PsiElement scopeRoot, final Processor processor) { - if (scopeRoot == null) return true; - final boolean[] stopped = new boolean[]{false}; - - scopeRoot.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(PsiElement element) { - if (!stopped[0]) { - super.visitElement(element); - } - } - - @Override public void visitClass(PsiClass aClass) { - stopped[0] = !processor.process(aClass); - super.visitClass(aClass); - } - }); - - return !stopped[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. + */ + +/* + * @author max + */ +package com.intellij.psi.impl.search; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Computable; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.search.searches.AllClassesSearch; +import com.intellij.util.Processor; +import com.intellij.util.QueryExecutor; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class AllClassesSearchExecutor implements QueryExecutor { + public boolean execute(@NotNull final AllClassesSearch.SearchParameters queryParameters, @NotNull final Processor consumer) { + SearchScope scope = queryParameters.getScope(); + + if (scope instanceof GlobalSearchScope) { + return processAllClassesInGlobalScope((GlobalSearchScope)scope, consumer, queryParameters); + } + + PsiElement[] scopeRoots = ((LocalSearchScope)scope).getScope(); + for (final PsiElement scopeRoot : scopeRoots) { + if (!processScopeRootForAllClasses(scopeRoot, consumer)) return false; + } + return true; + } + + private static boolean processAllClassesInGlobalScope(final GlobalSearchScope scope, final Processor processor, AllClassesSearch.SearchParameters parameters) { + final PsiManager manager = PsiManager.getInstance(parameters.getProject()); + + final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); + final PsiShortNamesCache cache = facade.getShortNamesCache(); + + final String[] names = ApplicationManager.getApplication().runReadAction(new Computable() { + public String[] compute() { + return cache.getAllClassNames(); + } + }); + + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + } + + List sorted = new ArrayList(names.length); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + if (parameters.nameMatches(name)) { + sorted.add(name); + } + if (indicator != null && i % 512 == 0) { + indicator.checkCanceled(); + } + } + + if (indicator != null) { + indicator.checkCanceled(); + } + + Collections.sort(sorted, new Comparator() { + public int compare(final String o1, final String o2) { + return o1.compareToIgnoreCase(o2); + } + }); + + for (final String name : sorted) { + ProgressManager.checkCanceled(); + final PsiClass[] classes = ApplicationManager.getApplication().runReadAction(new Computable() { + public PsiClass[] compute() { + return cache.getClassesByName(name, scope); + } + }); + for (PsiClass psiClass : classes) { + ProgressManager.checkCanceled(); + if (!processor.process(psiClass)) { + return false; + } + } + } + return true; + } + + private static boolean processScopeRootForAllClasses(PsiElement scopeRoot, final Processor processor) { + if (scopeRoot == null) return true; + final boolean[] stopped = new boolean[]{false}; + + scopeRoot.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if (!stopped[0]) { + super.visitElement(element); + } + } + + @Override public void visitClass(PsiClass aClass) { + stopped[0] = !processor.process(aClass); + super.visitClass(aClass); + } + }); + + return !stopped[0]; + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java index 7d37cc17cbbb..276c6771dc4f 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/ImportHelper.java @@ -1,853 +1,853 @@ -/* - * 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.impl.source.codeStyle; - -import com.intellij.lang.ASTNode; -import com.intellij.lang.StdLanguages; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.PackageEntry; -import com.intellij.psi.codeStyle.PackageEntryTable; -import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl; -import com.intellij.psi.impl.source.SourceTreeToPsiMap; -import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement; -import com.intellij.psi.impl.source.resolve.ResolveClassUtil; -import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference; -import com.intellij.psi.impl.source.tree.ElementType; -import com.intellij.psi.jsp.JspFile; -import com.intellij.psi.jsp.JspSpiUtil; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.tree.IElementType; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashMap; -import gnu.trove.THashSet; -import gnu.trove.TObjectIntHashMap; -import gnu.trove.TObjectIntProcedure; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.util.*; - -public class ImportHelper{ - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.ImportHelper"); - - private final CodeStyleSettings mySettings; - @NonNls private static final String JAVA_LANG_PACKAGE = "java.lang"; - - public ImportHelper(@NotNull CodeStyleSettings settings){ - mySettings = settings; - } - - public PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file) { - // Java parser works in a way that comments may be included to the import list, e.g.: - // import a; - // /* comment */ - // import b; - // We want to preserve those comments then. - List nonImports = new ArrayList(); - // Note: this array may contain ".*" for unresolved imports! - List> names = new ArrayList>(collectNamesToImport(file, nonImports)); - Collections.sort(names, new Comparator>() { - public int compare(Pair o1, Pair o2) { - return o1.getFirst().compareTo(o2.getFirst()); - } - }); - - int[] entryForName = ArrayUtil.newIntArray(names.size()); - PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries(); - for(int i = 0; i < names.size(); i++){ - Pair pair = names.get(i); - String packageName = pair.getFirst(); - Boolean isStatic = pair.getSecond(); - entryForName[i] = findEntryIndex(packageName, isStatic, entries); - } - - List> resultList = new ArrayList>(names.size()); - for(int i = 0; i < entries.length; i++){ - for(int j = 0; j < names.size(); j++){ - if (entryForName[j] == i){ - resultList.add(names.get(j)); - names.set(j, null); - } - } - } - for (Pair name : names) { - if (name != null) resultList.add(name); - } - - TObjectIntHashMap packageToCountMap = new TObjectIntHashMap(); - TObjectIntHashMap classToCountMap = new TObjectIntHashMap(); - for (Pair pair : resultList) { - String name = pair.getFirst(); - Boolean isStatic = pair.getSecond(); - String packageOrClassName = getPackageOrClassName(name); - if (packageOrClassName.length() == 0) continue; - if (isStatic) { - int count = classToCountMap.get(packageOrClassName); - classToCountMap.put(packageOrClassName, count + 1); - } - else { - int count = packageToCountMap.get(packageOrClassName); - packageToCountMap.put(packageOrClassName, count + 1); - } - } - - final Set classesOrPackagesToImportOnDemand = new THashSet(); - class MyVisitorProcedure implements TObjectIntProcedure { - private final boolean myIsVisitingPackages; - - MyVisitorProcedure(boolean isVisitingPackages) { - myIsVisitingPackages = isVisitingPackages; - } - - public boolean execute(final String packageOrClassName, final int count) { - if (isToUseImportOnDemand(packageOrClassName, count, !myIsVisitingPackages)){ - classesOrPackagesToImportOnDemand.add(packageOrClassName); - } - return true; - } - } - classToCountMap.forEachEntry(new MyVisitorProcedure(false)); - packageToCountMap.forEachEntry(new MyVisitorProcedure(true)); - - Set classesToUseSingle = findSingleImports(file, resultList, classesOrPackagesToImportOnDemand); - Set toReimport = new THashSet(); - calcClassesConflictingViaOnDemandImports(file, classesOrPackagesToImportOnDemand, file.getResolveScope(), toReimport); - classesToUseSingle.addAll(toReimport); - - try { - StringBuilder text = buildImportListText(resultList, classesOrPackagesToImportOnDemand, classesToUseSingle); - for (PsiElement nonImport : nonImports) { - text.append("\n").append(nonImport.getText()); - } - String ext = StdFileTypes.JAVA.getDefaultExtension(); - PsiFileFactory factory = PsiFileFactory.getInstance(file.getProject()); - final PsiJavaFile dummyFile = (PsiJavaFile)factory.createFileFromText("_Dummy_." + ext, StdFileTypes.JAVA, text); - CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(file.getProject()); - codeStyleManager.reformat(dummyFile); - - PsiImportList newImportList = dummyFile.getImportList(); - PsiImportList result = (PsiImportList)newImportList.copy(); - PsiImportList oldList = file.getImportList(); - if (oldList.isReplaceEquivalent(result)) return null; - if (!nonImports.isEmpty()) { - PsiElement firstPrevious = newImportList.getPrevSibling(); - while (firstPrevious != null && firstPrevious.getPrevSibling() != null) { - firstPrevious = firstPrevious.getPrevSibling(); - } - for (PsiElement element = firstPrevious; element != null && element != newImportList; element = element.getNextSibling()) { - result.add(element.copy()); - } - for (PsiElement element = newImportList.getNextSibling(); element != null; element = element.getNextSibling()) { - result.add(element.copy()); - } - } - return result; - } - catch(IncorrectOperationException e) { - LOG.error(e); - return null; - } - } - - @NotNull - private static Set findSingleImports(@NotNull final PsiJavaFile file, - @NotNull List> names, - @NotNull final Set onDemandImports - ) { - final GlobalSearchScope resolveScope = file.getResolveScope(); - Set namesToUseSingle = new THashSet(); - final String thisPackageName = file.getPackageName(); - final Set implicitlyImportedPackages = new THashSet(Arrays.asList(file.getImplicitlyImportedPackages())); - final PsiManager manager = file.getManager(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); - List onDemandElements = new ArrayList(onDemandImports.size()); - List onDemandImportsList = new ArrayList(onDemandImports); - for (String onDemandName : onDemandImportsList) { - PsiElement aClass = facade.findClass(onDemandName, resolveScope); - onDemandElements.add(aClass); - } - for (Pair pair : names) { - String name = pair.getFirst(); - Boolean isStatic = pair.getSecond(); - String prefix = getPackageOrClassName(name); - if (prefix.length() == 0) continue; - final boolean isImplicitlyImported = implicitlyImportedPackages.contains(prefix); - if (!onDemandImports.contains(prefix) && !isImplicitlyImported) continue; - String shortName = PsiNameHelper.getShortClassName(name); - - String thisPackageClass = thisPackageName.length() > 0 ? thisPackageName + "." + shortName : shortName; - if (JavaPsiFacade.getInstance(manager.getProject()).findClass(thisPackageClass, resolveScope) != null) { - namesToUseSingle.add(name); - continue; - } - if (!isImplicitlyImported) { - String langPackageClass = JAVA_LANG_PACKAGE + "." + shortName; //TODO : JSP! - if (facade.findClass(langPackageClass, resolveScope) != null) { - namesToUseSingle.add(name); - continue; - } - } - for (int i = 0; i < onDemandImportsList.size(); i++) { - String onDemandName = onDemandImportsList.get(i); - if (prefix.equals(onDemandName)) continue; - if (isStatic) { - PsiElement element = onDemandElements.get(i); - PsiClass aClass = (PsiClass)element; - if (aClass != null) { - PsiField field = aClass.findFieldByName(shortName, true); - if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) { - namesToUseSingle.add(name); - } - else { - PsiClass inner = aClass.findInnerClassByName(shortName, true); - if (inner != null && inner.hasModifierProperty(PsiModifier.STATIC)) { - namesToUseSingle.add(name); - } - else { - PsiMethod[] methods = aClass.findMethodsByName(shortName, true); - for (PsiMethod method : methods) { - if (method.hasModifierProperty(PsiModifier.STATIC)) { - namesToUseSingle.add(name); - } - } - } - } - } - } - else { - PsiClass aClass = facade.findClass(onDemandName + "." + shortName, resolveScope); - if (aClass != null) { - namesToUseSingle.add(name); - } - } - } - } - - return namesToUseSingle; - } - - private static void calcClassesConflictingViaOnDemandImports(PsiJavaFile file, Collection onDemandImportsList, - GlobalSearchScope resolveScope, final Set namesToUseSingle) { - List onDemands = new ArrayList(Arrays.asList(file.getImplicitlyImportedPackages())); - onDemands.addAll(onDemandImportsList); - if (onDemands.size() < 2) return; - - Map> classNames = new THashMap>(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject()); - for (int i = onDemands.size()-1; i>=0; i--) { - String onDemand = onDemands.get(i); - PsiPackage aPackage = facade.findPackage(onDemand); - if (aPackage == null) { - onDemands.remove(i); - continue; - } - PsiClass[] psiClasses = aPackage.getClasses(resolveScope); - Set set = new THashSet(psiClasses.length); - for (PsiClass psiClass : psiClasses) { - set.add(psiClass.getName()); - } - classNames.put(onDemand, set); - } - - final Set conflicts = new THashSet(); - for (int i = 0; i < onDemands.size(); i++) { - String on1 = onDemands.get(i); - for (int j = i+1; j < onDemands.size(); j++) { - String on2 = onDemands.get(j); - Set inter = new THashSet(classNames.get(on1)); - inter.retainAll(classNames.get(on2)); - - conflicts.addAll(inter); - } - } - if (!conflicts.isEmpty()) { - file.accept(new JavaRecursiveElementVisitor() { - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - if (reference.getQualifier() != null) return; - PsiElement element = reference.resolve(); - if (element instanceof PsiClass && conflicts.contains(((PsiClass)element).getName())) { - String fqn = ((PsiClass)element).getQualifiedName(); - namesToUseSingle.add(fqn); - } - } - }); - } - } - - @NotNull - private static StringBuilder buildImportListText(@NotNull List> names, - @NotNull final Set packagesOrClassesToImportOnDemand, - @NotNull final Set namesToUseSingle) { - final Set importedPackagesOrClasses = new THashSet(); - @NonNls final StringBuilder buffer = new StringBuilder(); - for (Pair pair : names) { - String name = pair.getFirst(); - Boolean isStatic = pair.getSecond(); - String packageOrClassName = getPackageOrClassName(name); - final boolean implicitlyImported = JAVA_LANG_PACKAGE.equals(packageOrClassName); - boolean useOnDemand = implicitlyImported || packagesOrClassesToImportOnDemand.contains(packageOrClassName); - if (useOnDemand && namesToUseSingle.remove(name)) { - useOnDemand = false; - } - if (useOnDemand && (importedPackagesOrClasses.contains(packageOrClassName) || implicitlyImported)) continue; - buffer.append("import "); - if (isStatic) buffer.append("static "); - if (useOnDemand) { - importedPackagesOrClasses.add(packageOrClassName); - buffer.append(packageOrClassName); - buffer.append(".*"); - } - else { - buffer.append(name); - } - buffer.append(";\n"); - } - - for (String remainingSingle : namesToUseSingle) { - buffer.append("import "); - buffer.append(remainingSingle); - buffer.append(";\n"); - } - - return buffer; - } - - /** - * Adds import if it is needed. - * @return false when the FQ-name have to be used in code (e.g. when conflicting imports already exist) - */ - public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass){ - final JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject()); - PsiElementFactory factory = facade.getElementFactory(); - PsiResolveHelper helper = facade.getResolveHelper(); - - String className = refClass.getQualifiedName(); - if (className == null) return true; - String packageName = getPackageOrClassName(className); - String shortName = PsiNameHelper.getShortClassName(className); - - PsiClass conflictSingleRef = findSingleImportByShortName(file, shortName); - if (conflictSingleRef != null){ - return className.equals(conflictSingleRef.getQualifiedName()); - } - - PsiClass curRefClass = helper.resolveReferencedClass(shortName, file); - if (file.getManager().areElementsEquivalent(refClass, curRefClass)) { - return true; - } - - boolean useOnDemand = true; - if (packageName.length() == 0){ - useOnDemand = false; - } - - PsiElement conflictPackageRef = findImportOnDemand(file, packageName); - if (conflictPackageRef != null) { - useOnDemand = false; - } - - List classesToReimport = new ArrayList(); - - List importRefs = getImportsFromPackage(file, packageName); - if (useOnDemand){ - if (mySettings.USE_SINGLE_CLASS_IMPORTS && - importRefs.size() + 1 < mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND && - !mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.contains(packageName)) { - useOnDemand = false; - } - // name of class we try to import is the same as of the class defined in this file - if (curRefClass != null) { - useOnDemand = true; - } - // check conflicts - if (useOnDemand){ - PsiElement[] onDemandRefs = file.getOnDemandImports(false, true); - List refTexts = new ArrayList(onDemandRefs.length); - for (PsiElement ref : onDemandRefs) { - String refName = ref instanceof PsiClass ? ((PsiClass)ref).getQualifiedName() : ((PsiPackage)ref).getQualifiedName(); - refTexts.add(refName); - } - calcClassesToReimport(file, facade, helper, packageName, classesToReimport, refTexts); - } - } - - try { - PsiImportList importList = file.getImportList(); - PsiImportStatement statement = useOnDemand ? factory.createImportStatementOnDemand(packageName) : factory.createImportStatement(refClass); - importList.add(statement); - if (useOnDemand) { - for (PsiJavaCodeReferenceElement ref : importRefs) { - LOG.assertTrue(ref.getParent() instanceof PsiImportStatement); - if (!ref.isValid()) continue; // todo[dsl] Q? - classesToReimport.add((PsiClass)ref.resolve()); - PsiImportStatement importStatement = (PsiImportStatement) ref.getParent(); - importStatement.delete(); - } - } - - for (PsiClass aClass : classesToReimport) { - if (aClass != null) { - addImport(file, aClass); - } - } - } - catch(IncorrectOperationException e){ - LOG.error(e); - } - return true; - } - - private static void calcClassesToReimport(PsiJavaFile file, JavaPsiFacade facade, PsiResolveHelper helper, String packageName, List classesToReimport, - Collection onDemandRefs) { - if (onDemandRefs.isEmpty()) { - return; - } - PsiPackage aPackage = facade.findPackage(packageName); - if (aPackage != null) { - PsiDirectory[] dirs = aPackage.getDirectories(); - GlobalSearchScope resolveScope = file.getResolveScope(); - for (PsiDirectory dir : dirs) { - PsiFile[] files = dir.getFiles(); // do not iterate classes - too slow when not loaded - for (PsiFile aFile : files) { - if (!(aFile instanceof PsiJavaFile)) continue; - String name = aFile.getVirtualFile().getNameWithoutExtension(); - for (String refName : onDemandRefs) { - String conflictClassName = refName + "." + name; - PsiClass conflictClass = facade.findClass(conflictClassName, resolveScope); - if (conflictClass == null || !helper.isAccessible(conflictClass, file, null)) continue; - String conflictClassName2 = packageName + "." + name; - PsiClass conflictClass2 = facade.findClass(conflictClassName2, resolveScope); - if (conflictClass2 != null && - helper.isAccessible(conflictClass2, file, null) && - ReferencesSearch.search(conflictClass, new LocalSearchScope(file), false).findFirst() != null) { - classesToReimport.add(conflictClass); - } - } - } - } - } - } - - @NotNull - private static List getImportsFromPackage(@NotNull PsiJavaFile file, @NotNull String packageName){ - PsiClass[] refs = file.getSingleClassImports(true); - List array = new ArrayList(); - for (PsiClass ref1 : refs) { - String className = ref1.getQualifiedName(); - if (getPackageOrClassName(className).equals(packageName)) { - final PsiJavaCodeReferenceElement ref = file.findImportReferenceTo(ref1); - if (ref != null) { - array.add(ref); - } - } - } - return array; - } - - private static PsiClass findSingleImportByShortName(@NotNull final PsiJavaFile file, @NotNull String shortClassName){ - PsiClass[] refs = file.getSingleClassImports(true); - for (PsiClass ref : refs) { - String className = ref.getQualifiedName(); - if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) { - return ref; - } - } - for (PsiClass aClass : file.getClasses()) { - String className = aClass.getQualifiedName(); - if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) { - return aClass; - } - } - - // there maybe a class imported implicitly from current package - String packageName = file.getPackageName(); - if (!StringUtil.isEmptyOrSpaces(packageName)) { - String fqn = packageName + "." + shortClassName; - final PsiClass aClass = JavaPsiFacade.getInstance(file.getProject()).findClass(fqn, file.getResolveScope()); - if (aClass != null) { - final boolean[] foundRef = {false}; - // check if that short name referenced in the file - file.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(PsiElement element) { - if (foundRef[0]) return; - super.visitElement(element); - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - if (file.getManager().areElementsEquivalent(reference.resolve(), aClass)) { - foundRef[0] = true; - } - super.visitReferenceElement(reference); - } - }); - if (foundRef[0]) return aClass; - } - } - return null; - } - - private static PsiPackage findImportOnDemand(@NotNull PsiJavaFile file, @NotNull String packageName){ - PsiElement[] refs = file.getOnDemandImports(false, true); - for (PsiElement ref : refs) { - if (ref instanceof PsiPackage && ((PsiPackage)ref).getQualifiedName().equals(packageName)) { - return (PsiPackage)ref; - } - } - return null; - } - - public ASTNode getDefaultAnchor(@NotNull PsiImportList list, @NotNull PsiImportStatementBase statement){ - PsiJavaCodeReferenceElement ref = statement.getImportReference(); - if (ref == null) return null; - - int entryIndex = findEntryIndex(statement); - PsiImportStatementBase[] allStatements = list.getAllImportStatements(); - int[] entries = ArrayUtil.newIntArray(allStatements.length); - List statements = new ArrayList(); - for(int i = 0; i < allStatements.length; i++){ - PsiImportStatementBase statement1 = allStatements[i]; - int entryIndex1 = findEntryIndex(statement1); - entries[i] = entryIndex1; - if (entryIndex1 == entryIndex){ - statements.add(statement1); - } - } - - if (statements.isEmpty()){ - int index; - for(index = entries.length - 1; index >= 0; index--){ - if (entries[index] < entryIndex) break; - } - index++; - return index < entries.length ? SourceTreeToPsiMap.psiElementToTree(allStatements[index]) : null; - } - else { - String refText = ref.getCanonicalText(); - if (statement.isOnDemand()){ - refText += "."; - } - - PsiImportStatementBase insertBefore = null; - PsiImportStatementBase insertAfter = null; - for (PsiImportStatementBase statement1 : statements) { - PsiJavaCodeReferenceElement ref1 = statement1.getImportReference(); - if (ref1 == null) { - continue; - } - String refTextThis = ref1.getCanonicalText(); - if (statement1.isOnDemand()) { - refTextThis += "."; - } - - int comp = Comparing.compare(refText, refTextThis); - if (comp < 0 && insertBefore == null) { - insertBefore = statement1; - } - if (comp > 0) { - insertAfter = statement1; - } - } - - if (insertBefore != null) return insertBefore.getNode(); - if (insertAfter != null) return insertAfter.getNode().getTreeNext(); - return null; - } - } - - public int getEmptyLinesBetween(@NotNull PsiImportStatementBase statement1, @NotNull PsiImportStatementBase statement2){ - int index1 = findEntryIndex(statement1); - int index2 = findEntryIndex(statement2); - if (index1 == index2) return 0; - if (index1 > index2) { - int t = index1; - index1 = index2; - index2 = t; - } - PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries(); - int maxSpace = 0; - for(int i = index1 + 1; i < index2; i++){ - if (entries[i] == PackageEntry.BLANK_LINE_ENTRY){ - int space = 0; - do{ - space++; - } while(entries[++i] == PackageEntry.BLANK_LINE_ENTRY); - maxSpace = Math.max(maxSpace, space); - } - } - return maxSpace; - } - - private boolean isToUseImportOnDemand(@NotNull String packageName, int classCount, boolean isStaticImportNeeded){ - if (!mySettings.USE_SINGLE_CLASS_IMPORTS) return true; - int limitCount = isStaticImportNeeded ? mySettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND : - mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND; - if (classCount >= limitCount) return true; - if (packageName.length() == 0) return false; - PackageEntryTable table = mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND; - return table != null && table.contains(packageName); - } - - private static int findEntryIndex(@NotNull String packageName, boolean isStatic, @NotNull PackageEntry[] entries) { - PackageEntry bestEntry = null; - int bestEntryIndex = -1; - int allOtherStaticIndex = -1; - int allOtherIndex = -1; - for(int i = 0; i < entries.length; i++){ - PackageEntry entry = entries[i]; - if (entry == PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY) { - allOtherStaticIndex = i; - } - if (entry == PackageEntry.ALL_OTHER_IMPORTS_ENTRY) { - allOtherIndex = i; - } - if (entry.isBetterMatchForPackageThan(bestEntry, packageName, isStatic)) { - bestEntry = entry; - bestEntryIndex = i; - } - } - if (bestEntryIndex == -1 && isStatic && allOtherStaticIndex == -1 && allOtherIndex != -1) { - // if no layout for static imports specified, put them among all others - bestEntryIndex = allOtherIndex; - } - return bestEntryIndex; - } - - public int findEntryIndex(@NotNull PsiImportStatementBase statement){ - PsiJavaCodeReferenceElement ref = statement.getImportReference(); - if (ref == null) return -1; - String packageName; - if (statement.isOnDemand()){ - packageName = ref.getCanonicalText(); - } - else{ - String className = ref.getCanonicalText(); - packageName = getPackageOrClassName(className); - } - return findEntryIndex(packageName, statement instanceof PsiImportStaticStatement, mySettings.IMPORT_LAYOUT_TABLE.getEntries()); - } - - @NotNull - // returns list of (name, isImportStatic) pairs - private static Collection> collectNamesToImport(@NotNull PsiJavaFile file, List comments){ - Set> names = new THashSet>(); - - final JspFile jspFile = JspPsiUtil.getJspFile(file); - collectNamesToImport(names, comments, file, jspFile); - if (jspFile != null) { - PsiFile[] files = ArrayUtil.mergeArrays(JspSpiUtil.getIncludingFiles(jspFile), JspSpiUtil.getIncludedFiles(jspFile), PsiFile.class); - for (PsiFile includingFile : files) { - final PsiFile javaRoot = includingFile.getViewProvider().getPsi(StdLanguages.JAVA); - if (javaRoot instanceof PsiJavaFile && file != javaRoot) { - collectNamesToImport(names, comments, (PsiJavaFile)javaRoot, jspFile); - } - } - } - - addUnresolvedImportNames(names, file); - - return names; - } - - private static void collectNamesToImport(@NotNull final Set> names, - @NotNull List comments, - @NotNull final PsiJavaFile file, - PsiFile context) { - String packageName = file.getPackageName(); - - final PsiElement[] roots = file.getPsiRoots(); - for (PsiElement root : roots) { - addNamesToImport(names, comments, root, packageName, context); - } - } - - private static void addNamesToImport(@NotNull Set> names, - @NotNull List comments, - @NotNull PsiElement scope, - @NotNull String thisPackageName, - PsiFile context){ - if (scope instanceof PsiImportList) return; - - final LinkedList stack = new LinkedList(); - stack.add(scope); - while (!stack.isEmpty()) { - final PsiElement child = stack.removeFirst(); - if (child instanceof PsiImportList) { - for (PsiElement element : child.getChildren()) { - if (element == null) { - continue; - } - ASTNode node = element.getNode(); - if (node == null) { - continue; - } - IElementType elementType = node.getElementType(); - if (elementType != null &&!ElementType.IMPORT_STATEMENT_BASE_BIT_SET.contains(elementType) - && !ElementType.WHITE_SPACE_BIT_SET.contains(elementType)) - { - comments.add(element); - } - } - continue; - } - if (child instanceof PsiLiteralExpression) continue; - ContainerUtil.addAll(stack, child.getChildren()); - - for (final PsiReference reference : child.getReferences()) { - if (!(reference instanceof PsiJavaReference)) continue; - final PsiJavaReference javaReference = (PsiJavaReference)reference; - if (javaReference instanceof JavaClassReference && ((JavaClassReference)javaReference).getContextReference() != null) continue; - PsiJavaCodeReferenceElement referenceElement = null; - if (reference instanceof PsiJavaCodeReferenceElement) { - referenceElement = (PsiJavaCodeReferenceElement)child; - if (referenceElement.getQualifier() != null) { - continue; - } - if (reference instanceof PsiJavaCodeReferenceElementImpl - && ((PsiJavaCodeReferenceElementImpl)reference).getKind() == PsiJavaCodeReferenceElementImpl.CLASS_IN_QUALIFIED_NEW_KIND) { - continue; - } - } - - final JavaResolveResult resolveResult = javaReference.advancedResolve(true); - PsiElement refElement = resolveResult.getElement(); - if (refElement == null && referenceElement != null) { - refElement = ResolveClassUtil.resolveClass(referenceElement); // might be uncomplete code - } - if (refElement == null) continue; - - PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope(); - if (!(currentFileResolveScope instanceof PsiImportStatementBase)) continue; - if (context != null && - (!currentFileResolveScope.isValid() || - currentFileResolveScope instanceof JspxImportStatement && - context != ((JspxImportStatement)currentFileResolveScope).getDeclarationFile())) { - continue; - } - - if (referenceElement != null) { - if (currentFileResolveScope instanceof PsiImportStaticStatement) { - PsiImportStaticStatement importStaticStatement = (PsiImportStaticStatement)currentFileResolveScope; - String name = importStaticStatement.getImportReference().getCanonicalText(); - if (importStaticStatement.isOnDemand()) { - String refName = referenceElement.getReferenceName(); - if (refName != null) name = name + "." + refName; - } - names.add(Pair.create(name, Boolean.TRUE)); - continue; - } - } - - if (refElement instanceof PsiClass) { - String qName = ((PsiClass)refElement).getQualifiedName(); - if (hasPackage(qName, thisPackageName)) continue; - names.add(Pair.create(qName, Boolean.FALSE)); - } - } - } - } - - private static void addUnresolvedImportNames(@NotNull final Set> namesToImport, @NotNull PsiJavaFile file) { - PsiImportStatementBase[] imports = file.getImportList().getAllImportStatements(); - final Map> unresolvedNames = new THashMap>(); - @NotNull Set> unresolvedOnDemand = new THashSet>(); - for (PsiImportStatementBase anImport : imports) { - PsiJavaCodeReferenceElement ref = anImport.getImportReference(); - if (ref == null) continue; - JavaResolveResult[] results = ref.multiResolve(false); - if (results.length == 0) { - String text = ref.getCanonicalText(); - if (anImport.isOnDemand()) { - text += ".*"; - } - - Pair pair = Pair.create(text, anImport instanceof PsiImportStaticStatement); - if (anImport.isOnDemand()) { - unresolvedOnDemand.add(pair); - } - else { - unresolvedNames.put(ref.getReferenceName(), pair); - } - } - } - - // do not optimize unresolved imports for things like JSP (IDEA-41814) - if (file.getViewProvider().getLanguages().size() > 1 && file.getViewProvider().getBaseLanguage() != StdLanguages.JAVA) { - namesToImport.addAll(unresolvedOnDemand); - namesToImport.addAll(unresolvedNames.values()); - return; - } - - final boolean[] hasResolveProblem = {false}; - // do not visit imports - for (PsiClass aClass : file.getClasses()) { - aClass.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - String name = reference.getReferenceName(); - Pair pair = unresolvedNames.get(name); - if (reference.multiResolve(false).length == 0) { - hasResolveProblem[0] = true; - if (pair != null) { - namesToImport.add(pair); - unresolvedNames.remove(name); - } - } - super.visitReferenceElement(reference); - } - }); - } - if (hasResolveProblem[0]) { - namesToImport.addAll(unresolvedOnDemand); - } - // otherwise, optimize out all red on demand imports for green file - } - - public static boolean isImplicitlyImported(@NotNull String className, @NotNull PsiJavaFile file) { - String[] packageNames = file.getImplicitlyImportedPackages(); - for (String packageName : packageNames) { - if (hasPackage(className, packageName)) return true; - } - return false; - } - - public static boolean hasPackage(@NotNull String className, @NotNull String packageName){ - if (!className.startsWith(packageName)) return false; - if (className.length() == packageName.length()) return false; - if (packageName.length() > 0 && className.charAt(packageName.length()) != '.') return false; - return className.indexOf('.', packageName.length() + 1) < 0; - } - - @NotNull - private static String getPackageOrClassName(@NotNull String className){ - int dotIndex = className.lastIndexOf('.'); - return dotIndex < 0 ? "" : className.substring(0, dotIndex); - } - -} +/* + * 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.impl.source.codeStyle; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.StdLanguages; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.PackageEntry; +import com.intellij.psi.codeStyle.PackageEntryTable; +import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl; +import com.intellij.psi.impl.source.SourceTreeToPsiMap; +import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement; +import com.intellij.psi.impl.source.resolve.ResolveClassUtil; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference; +import com.intellij.psi.impl.source.tree.ElementType; +import com.intellij.psi.jsp.JspFile; +import com.intellij.psi.jsp.JspSpiUtil; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import gnu.trove.TObjectIntHashMap; +import gnu.trove.TObjectIntProcedure; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class ImportHelper{ + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.ImportHelper"); + + private final CodeStyleSettings mySettings; + @NonNls private static final String JAVA_LANG_PACKAGE = "java.lang"; + + public ImportHelper(@NotNull CodeStyleSettings settings){ + mySettings = settings; + } + + public PsiImportList prepareOptimizeImportsResult(@NotNull final PsiJavaFile file) { + // Java parser works in a way that comments may be included to the import list, e.g.: + // import a; + // /* comment */ + // import b; + // We want to preserve those comments then. + List nonImports = new ArrayList(); + // Note: this array may contain ".*" for unresolved imports! + List> names = new ArrayList>(collectNamesToImport(file, nonImports)); + Collections.sort(names, new Comparator>() { + public int compare(Pair o1, Pair o2) { + return o1.getFirst().compareTo(o2.getFirst()); + } + }); + + int[] entryForName = ArrayUtil.newIntArray(names.size()); + PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries(); + for(int i = 0; i < names.size(); i++){ + Pair pair = names.get(i); + String packageName = pair.getFirst(); + Boolean isStatic = pair.getSecond(); + entryForName[i] = findEntryIndex(packageName, isStatic, entries); + } + + List> resultList = new ArrayList>(names.size()); + for(int i = 0; i < entries.length; i++){ + for(int j = 0; j < names.size(); j++){ + if (entryForName[j] == i){ + resultList.add(names.get(j)); + names.set(j, null); + } + } + } + for (Pair name : names) { + if (name != null) resultList.add(name); + } + + TObjectIntHashMap packageToCountMap = new TObjectIntHashMap(); + TObjectIntHashMap classToCountMap = new TObjectIntHashMap(); + for (Pair pair : resultList) { + String name = pair.getFirst(); + Boolean isStatic = pair.getSecond(); + String packageOrClassName = getPackageOrClassName(name); + if (packageOrClassName.length() == 0) continue; + if (isStatic) { + int count = classToCountMap.get(packageOrClassName); + classToCountMap.put(packageOrClassName, count + 1); + } + else { + int count = packageToCountMap.get(packageOrClassName); + packageToCountMap.put(packageOrClassName, count + 1); + } + } + + final Set classesOrPackagesToImportOnDemand = new THashSet(); + class MyVisitorProcedure implements TObjectIntProcedure { + private final boolean myIsVisitingPackages; + + MyVisitorProcedure(boolean isVisitingPackages) { + myIsVisitingPackages = isVisitingPackages; + } + + public boolean execute(final String packageOrClassName, final int count) { + if (isToUseImportOnDemand(packageOrClassName, count, !myIsVisitingPackages)){ + classesOrPackagesToImportOnDemand.add(packageOrClassName); + } + return true; + } + } + classToCountMap.forEachEntry(new MyVisitorProcedure(false)); + packageToCountMap.forEachEntry(new MyVisitorProcedure(true)); + + Set classesToUseSingle = findSingleImports(file, resultList, classesOrPackagesToImportOnDemand); + Set toReimport = new THashSet(); + calcClassesConflictingViaOnDemandImports(file, classesOrPackagesToImportOnDemand, file.getResolveScope(), toReimport); + classesToUseSingle.addAll(toReimport); + + try { + StringBuilder text = buildImportListText(resultList, classesOrPackagesToImportOnDemand, classesToUseSingle); + for (PsiElement nonImport : nonImports) { + text.append("\n").append(nonImport.getText()); + } + String ext = StdFileTypes.JAVA.getDefaultExtension(); + PsiFileFactory factory = PsiFileFactory.getInstance(file.getProject()); + final PsiJavaFile dummyFile = (PsiJavaFile)factory.createFileFromText("_Dummy_." + ext, StdFileTypes.JAVA, text); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(file.getProject()); + codeStyleManager.reformat(dummyFile); + + PsiImportList newImportList = dummyFile.getImportList(); + PsiImportList result = (PsiImportList)newImportList.copy(); + PsiImportList oldList = file.getImportList(); + if (oldList.isReplaceEquivalent(result)) return null; + if (!nonImports.isEmpty()) { + PsiElement firstPrevious = newImportList.getPrevSibling(); + while (firstPrevious != null && firstPrevious.getPrevSibling() != null) { + firstPrevious = firstPrevious.getPrevSibling(); + } + for (PsiElement element = firstPrevious; element != null && element != newImportList; element = element.getNextSibling()) { + result.add(element.copy()); + } + for (PsiElement element = newImportList.getNextSibling(); element != null; element = element.getNextSibling()) { + result.add(element.copy()); + } + } + return result; + } + catch(IncorrectOperationException e) { + LOG.error(e); + return null; + } + } + + @NotNull + private static Set findSingleImports(@NotNull final PsiJavaFile file, + @NotNull List> names, + @NotNull final Set onDemandImports + ) { + final GlobalSearchScope resolveScope = file.getResolveScope(); + Set namesToUseSingle = new THashSet(); + final String thisPackageName = file.getPackageName(); + final Set implicitlyImportedPackages = new THashSet(Arrays.asList(file.getImplicitlyImportedPackages())); + final PsiManager manager = file.getManager(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); + List onDemandElements = new ArrayList(onDemandImports.size()); + List onDemandImportsList = new ArrayList(onDemandImports); + for (String onDemandName : onDemandImportsList) { + PsiElement aClass = facade.findClass(onDemandName, resolveScope); + onDemandElements.add(aClass); + } + for (Pair pair : names) { + String name = pair.getFirst(); + Boolean isStatic = pair.getSecond(); + String prefix = getPackageOrClassName(name); + if (prefix.length() == 0) continue; + final boolean isImplicitlyImported = implicitlyImportedPackages.contains(prefix); + if (!onDemandImports.contains(prefix) && !isImplicitlyImported) continue; + String shortName = PsiNameHelper.getShortClassName(name); + + String thisPackageClass = thisPackageName.length() > 0 ? thisPackageName + "." + shortName : shortName; + if (JavaPsiFacade.getInstance(manager.getProject()).findClass(thisPackageClass, resolveScope) != null) { + namesToUseSingle.add(name); + continue; + } + if (!isImplicitlyImported) { + String langPackageClass = JAVA_LANG_PACKAGE + "." + shortName; //TODO : JSP! + if (facade.findClass(langPackageClass, resolveScope) != null) { + namesToUseSingle.add(name); + continue; + } + } + for (int i = 0; i < onDemandImportsList.size(); i++) { + String onDemandName = onDemandImportsList.get(i); + if (prefix.equals(onDemandName)) continue; + if (isStatic) { + PsiElement element = onDemandElements.get(i); + PsiClass aClass = (PsiClass)element; + if (aClass != null) { + PsiField field = aClass.findFieldByName(shortName, true); + if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) { + namesToUseSingle.add(name); + } + else { + PsiClass inner = aClass.findInnerClassByName(shortName, true); + if (inner != null && inner.hasModifierProperty(PsiModifier.STATIC)) { + namesToUseSingle.add(name); + } + else { + PsiMethod[] methods = aClass.findMethodsByName(shortName, true); + for (PsiMethod method : methods) { + if (method.hasModifierProperty(PsiModifier.STATIC)) { + namesToUseSingle.add(name); + } + } + } + } + } + } + else { + PsiClass aClass = facade.findClass(onDemandName + "." + shortName, resolveScope); + if (aClass != null) { + namesToUseSingle.add(name); + } + } + } + } + + return namesToUseSingle; + } + + private static void calcClassesConflictingViaOnDemandImports(PsiJavaFile file, Collection onDemandImportsList, + GlobalSearchScope resolveScope, final Set namesToUseSingle) { + List onDemands = new ArrayList(Arrays.asList(file.getImplicitlyImportedPackages())); + onDemands.addAll(onDemandImportsList); + if (onDemands.size() < 2) return; + + Map> classNames = new THashMap>(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject()); + for (int i = onDemands.size()-1; i>=0; i--) { + String onDemand = onDemands.get(i); + PsiPackage aPackage = facade.findPackage(onDemand); + if (aPackage == null) { + onDemands.remove(i); + continue; + } + PsiClass[] psiClasses = aPackage.getClasses(resolveScope); + Set set = new THashSet(psiClasses.length); + for (PsiClass psiClass : psiClasses) { + set.add(psiClass.getName()); + } + classNames.put(onDemand, set); + } + + final Set conflicts = new THashSet(); + for (int i = 0; i < onDemands.size(); i++) { + String on1 = onDemands.get(i); + for (int j = i+1; j < onDemands.size(); j++) { + String on2 = onDemands.get(j); + Set inter = new THashSet(classNames.get(on1)); + inter.retainAll(classNames.get(on2)); + + conflicts.addAll(inter); + } + } + if (!conflicts.isEmpty()) { + file.accept(new JavaRecursiveElementVisitor() { + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + if (reference.getQualifier() != null) return; + PsiElement element = reference.resolve(); + if (element instanceof PsiClass && conflicts.contains(((PsiClass)element).getName())) { + String fqn = ((PsiClass)element).getQualifiedName(); + namesToUseSingle.add(fqn); + } + } + }); + } + } + + @NotNull + private static StringBuilder buildImportListText(@NotNull List> names, + @NotNull final Set packagesOrClassesToImportOnDemand, + @NotNull final Set namesToUseSingle) { + final Set importedPackagesOrClasses = new THashSet(); + @NonNls final StringBuilder buffer = new StringBuilder(); + for (Pair pair : names) { + String name = pair.getFirst(); + Boolean isStatic = pair.getSecond(); + String packageOrClassName = getPackageOrClassName(name); + final boolean implicitlyImported = JAVA_LANG_PACKAGE.equals(packageOrClassName); + boolean useOnDemand = implicitlyImported || packagesOrClassesToImportOnDemand.contains(packageOrClassName); + if (useOnDemand && namesToUseSingle.remove(name)) { + useOnDemand = false; + } + if (useOnDemand && (importedPackagesOrClasses.contains(packageOrClassName) || implicitlyImported)) continue; + buffer.append("import "); + if (isStatic) buffer.append("static "); + if (useOnDemand) { + importedPackagesOrClasses.add(packageOrClassName); + buffer.append(packageOrClassName); + buffer.append(".*"); + } + else { + buffer.append(name); + } + buffer.append(";\n"); + } + + for (String remainingSingle : namesToUseSingle) { + buffer.append("import "); + buffer.append(remainingSingle); + buffer.append(";\n"); + } + + return buffer; + } + + /** + * Adds import if it is needed. + * @return false when the FQ-name have to be used in code (e.g. when conflicting imports already exist) + */ + public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass){ + final JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject()); + PsiElementFactory factory = facade.getElementFactory(); + PsiResolveHelper helper = facade.getResolveHelper(); + + String className = refClass.getQualifiedName(); + if (className == null) return true; + String packageName = getPackageOrClassName(className); + String shortName = PsiNameHelper.getShortClassName(className); + + PsiClass conflictSingleRef = findSingleImportByShortName(file, shortName); + if (conflictSingleRef != null){ + return className.equals(conflictSingleRef.getQualifiedName()); + } + + PsiClass curRefClass = helper.resolveReferencedClass(shortName, file); + if (file.getManager().areElementsEquivalent(refClass, curRefClass)) { + return true; + } + + boolean useOnDemand = true; + if (packageName.length() == 0){ + useOnDemand = false; + } + + PsiElement conflictPackageRef = findImportOnDemand(file, packageName); + if (conflictPackageRef != null) { + useOnDemand = false; + } + + List classesToReimport = new ArrayList(); + + List importRefs = getImportsFromPackage(file, packageName); + if (useOnDemand){ + if (mySettings.USE_SINGLE_CLASS_IMPORTS && + importRefs.size() + 1 < mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND && + !mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND.contains(packageName)) { + useOnDemand = false; + } + // name of class we try to import is the same as of the class defined in this file + if (curRefClass != null) { + useOnDemand = true; + } + // check conflicts + if (useOnDemand){ + PsiElement[] onDemandRefs = file.getOnDemandImports(false, true); + List refTexts = new ArrayList(onDemandRefs.length); + for (PsiElement ref : onDemandRefs) { + String refName = ref instanceof PsiClass ? ((PsiClass)ref).getQualifiedName() : ((PsiPackage)ref).getQualifiedName(); + refTexts.add(refName); + } + calcClassesToReimport(file, facade, helper, packageName, classesToReimport, refTexts); + } + } + + try { + PsiImportList importList = file.getImportList(); + PsiImportStatement statement = useOnDemand ? factory.createImportStatementOnDemand(packageName) : factory.createImportStatement(refClass); + importList.add(statement); + if (useOnDemand) { + for (PsiJavaCodeReferenceElement ref : importRefs) { + LOG.assertTrue(ref.getParent() instanceof PsiImportStatement); + if (!ref.isValid()) continue; // todo[dsl] Q? + classesToReimport.add((PsiClass)ref.resolve()); + PsiImportStatement importStatement = (PsiImportStatement) ref.getParent(); + importStatement.delete(); + } + } + + for (PsiClass aClass : classesToReimport) { + if (aClass != null) { + addImport(file, aClass); + } + } + } + catch(IncorrectOperationException e){ + LOG.error(e); + } + return true; + } + + private static void calcClassesToReimport(PsiJavaFile file, JavaPsiFacade facade, PsiResolveHelper helper, String packageName, List classesToReimport, + Collection onDemandRefs) { + if (onDemandRefs.isEmpty()) { + return; + } + PsiPackage aPackage = facade.findPackage(packageName); + if (aPackage != null) { + PsiDirectory[] dirs = aPackage.getDirectories(); + GlobalSearchScope resolveScope = file.getResolveScope(); + for (PsiDirectory dir : dirs) { + PsiFile[] files = dir.getFiles(); // do not iterate classes - too slow when not loaded + for (PsiFile aFile : files) { + if (!(aFile instanceof PsiJavaFile)) continue; + String name = aFile.getVirtualFile().getNameWithoutExtension(); + for (String refName : onDemandRefs) { + String conflictClassName = refName + "." + name; + PsiClass conflictClass = facade.findClass(conflictClassName, resolveScope); + if (conflictClass == null || !helper.isAccessible(conflictClass, file, null)) continue; + String conflictClassName2 = packageName + "." + name; + PsiClass conflictClass2 = facade.findClass(conflictClassName2, resolveScope); + if (conflictClass2 != null && + helper.isAccessible(conflictClass2, file, null) && + ReferencesSearch.search(conflictClass, new LocalSearchScope(file), false).findFirst() != null) { + classesToReimport.add(conflictClass); + } + } + } + } + } + } + + @NotNull + private static List getImportsFromPackage(@NotNull PsiJavaFile file, @NotNull String packageName){ + PsiClass[] refs = file.getSingleClassImports(true); + List array = new ArrayList(); + for (PsiClass ref1 : refs) { + String className = ref1.getQualifiedName(); + if (getPackageOrClassName(className).equals(packageName)) { + final PsiJavaCodeReferenceElement ref = file.findImportReferenceTo(ref1); + if (ref != null) { + array.add(ref); + } + } + } + return array; + } + + private static PsiClass findSingleImportByShortName(@NotNull final PsiJavaFile file, @NotNull String shortClassName){ + PsiClass[] refs = file.getSingleClassImports(true); + for (PsiClass ref : refs) { + String className = ref.getQualifiedName(); + if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) { + return ref; + } + } + for (PsiClass aClass : file.getClasses()) { + String className = aClass.getQualifiedName(); + if (className != null && PsiNameHelper.getShortClassName(className).equals(shortClassName)) { + return aClass; + } + } + + // there maybe a class imported implicitly from current package + String packageName = file.getPackageName(); + if (!StringUtil.isEmptyOrSpaces(packageName)) { + String fqn = packageName + "." + shortClassName; + final PsiClass aClass = JavaPsiFacade.getInstance(file.getProject()).findClass(fqn, file.getResolveScope()); + if (aClass != null) { + final boolean[] foundRef = {false}; + // check if that short name referenced in the file + file.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if (foundRef[0]) return; + super.visitElement(element); + } + + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + if (file.getManager().areElementsEquivalent(reference.resolve(), aClass)) { + foundRef[0] = true; + } + super.visitReferenceElement(reference); + } + }); + if (foundRef[0]) return aClass; + } + } + return null; + } + + private static PsiPackage findImportOnDemand(@NotNull PsiJavaFile file, @NotNull String packageName){ + PsiElement[] refs = file.getOnDemandImports(false, true); + for (PsiElement ref : refs) { + if (ref instanceof PsiPackage && ((PsiPackage)ref).getQualifiedName().equals(packageName)) { + return (PsiPackage)ref; + } + } + return null; + } + + public ASTNode getDefaultAnchor(@NotNull PsiImportList list, @NotNull PsiImportStatementBase statement){ + PsiJavaCodeReferenceElement ref = statement.getImportReference(); + if (ref == null) return null; + + int entryIndex = findEntryIndex(statement); + PsiImportStatementBase[] allStatements = list.getAllImportStatements(); + int[] entries = ArrayUtil.newIntArray(allStatements.length); + List statements = new ArrayList(); + for(int i = 0; i < allStatements.length; i++){ + PsiImportStatementBase statement1 = allStatements[i]; + int entryIndex1 = findEntryIndex(statement1); + entries[i] = entryIndex1; + if (entryIndex1 == entryIndex){ + statements.add(statement1); + } + } + + if (statements.isEmpty()){ + int index; + for(index = entries.length - 1; index >= 0; index--){ + if (entries[index] < entryIndex) break; + } + index++; + return index < entries.length ? SourceTreeToPsiMap.psiElementToTree(allStatements[index]) : null; + } + else { + String refText = ref.getCanonicalText(); + if (statement.isOnDemand()){ + refText += "."; + } + + PsiImportStatementBase insertBefore = null; + PsiImportStatementBase insertAfter = null; + for (PsiImportStatementBase statement1 : statements) { + PsiJavaCodeReferenceElement ref1 = statement1.getImportReference(); + if (ref1 == null) { + continue; + } + String refTextThis = ref1.getCanonicalText(); + if (statement1.isOnDemand()) { + refTextThis += "."; + } + + int comp = Comparing.compare(refText, refTextThis); + if (comp < 0 && insertBefore == null) { + insertBefore = statement1; + } + if (comp > 0) { + insertAfter = statement1; + } + } + + if (insertBefore != null) return insertBefore.getNode(); + if (insertAfter != null) return insertAfter.getNode().getTreeNext(); + return null; + } + } + + public int getEmptyLinesBetween(@NotNull PsiImportStatementBase statement1, @NotNull PsiImportStatementBase statement2){ + int index1 = findEntryIndex(statement1); + int index2 = findEntryIndex(statement2); + if (index1 == index2) return 0; + if (index1 > index2) { + int t = index1; + index1 = index2; + index2 = t; + } + PackageEntry[] entries = mySettings.IMPORT_LAYOUT_TABLE.getEntries(); + int maxSpace = 0; + for(int i = index1 + 1; i < index2; i++){ + if (entries[i] == PackageEntry.BLANK_LINE_ENTRY){ + int space = 0; + do{ + space++; + } while(entries[++i] == PackageEntry.BLANK_LINE_ENTRY); + maxSpace = Math.max(maxSpace, space); + } + } + return maxSpace; + } + + private boolean isToUseImportOnDemand(@NotNull String packageName, int classCount, boolean isStaticImportNeeded){ + if (!mySettings.USE_SINGLE_CLASS_IMPORTS) return true; + int limitCount = isStaticImportNeeded ? mySettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND : + mySettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND; + if (classCount >= limitCount) return true; + if (packageName.length() == 0) return false; + PackageEntryTable table = mySettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND; + return table != null && table.contains(packageName); + } + + private static int findEntryIndex(@NotNull String packageName, boolean isStatic, @NotNull PackageEntry[] entries) { + PackageEntry bestEntry = null; + int bestEntryIndex = -1; + int allOtherStaticIndex = -1; + int allOtherIndex = -1; + for(int i = 0; i < entries.length; i++){ + PackageEntry entry = entries[i]; + if (entry == PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY) { + allOtherStaticIndex = i; + } + if (entry == PackageEntry.ALL_OTHER_IMPORTS_ENTRY) { + allOtherIndex = i; + } + if (entry.isBetterMatchForPackageThan(bestEntry, packageName, isStatic)) { + bestEntry = entry; + bestEntryIndex = i; + } + } + if (bestEntryIndex == -1 && isStatic && allOtherStaticIndex == -1 && allOtherIndex != -1) { + // if no layout for static imports specified, put them among all others + bestEntryIndex = allOtherIndex; + } + return bestEntryIndex; + } + + public int findEntryIndex(@NotNull PsiImportStatementBase statement){ + PsiJavaCodeReferenceElement ref = statement.getImportReference(); + if (ref == null) return -1; + String packageName; + if (statement.isOnDemand()){ + packageName = ref.getCanonicalText(); + } + else{ + String className = ref.getCanonicalText(); + packageName = getPackageOrClassName(className); + } + return findEntryIndex(packageName, statement instanceof PsiImportStaticStatement, mySettings.IMPORT_LAYOUT_TABLE.getEntries()); + } + + @NotNull + // returns list of (name, isImportStatic) pairs + private static Collection> collectNamesToImport(@NotNull PsiJavaFile file, List comments){ + Set> names = new THashSet>(); + + final JspFile jspFile = JspPsiUtil.getJspFile(file); + collectNamesToImport(names, comments, file, jspFile); + if (jspFile != null) { + PsiFile[] files = ArrayUtil.mergeArrays(JspSpiUtil.getIncludingFiles(jspFile), JspSpiUtil.getIncludedFiles(jspFile), PsiFile.class); + for (PsiFile includingFile : files) { + final PsiFile javaRoot = includingFile.getViewProvider().getPsi(StdLanguages.JAVA); + if (javaRoot instanceof PsiJavaFile && file != javaRoot) { + collectNamesToImport(names, comments, (PsiJavaFile)javaRoot, jspFile); + } + } + } + + addUnresolvedImportNames(names, file); + + return names; + } + + private static void collectNamesToImport(@NotNull final Set> names, + @NotNull List comments, + @NotNull final PsiJavaFile file, + PsiFile context) { + String packageName = file.getPackageName(); + + final PsiElement[] roots = file.getPsiRoots(); + for (PsiElement root : roots) { + addNamesToImport(names, comments, root, packageName, context); + } + } + + private static void addNamesToImport(@NotNull Set> names, + @NotNull List comments, + @NotNull PsiElement scope, + @NotNull String thisPackageName, + PsiFile context){ + if (scope instanceof PsiImportList) return; + + final LinkedList stack = new LinkedList(); + stack.add(scope); + while (!stack.isEmpty()) { + final PsiElement child = stack.removeFirst(); + if (child instanceof PsiImportList) { + for (PsiElement element : child.getChildren()) { + if (element == null) { + continue; + } + ASTNode node = element.getNode(); + if (node == null) { + continue; + } + IElementType elementType = node.getElementType(); + if (elementType != null &&!ElementType.IMPORT_STATEMENT_BASE_BIT_SET.contains(elementType) + && !ElementType.WHITE_SPACE_BIT_SET.contains(elementType)) + { + comments.add(element); + } + } + continue; + } + if (child instanceof PsiLiteralExpression) continue; + ContainerUtil.addAll(stack, child.getChildren()); + + for (final PsiReference reference : child.getReferences()) { + if (!(reference instanceof PsiJavaReference)) continue; + final PsiJavaReference javaReference = (PsiJavaReference)reference; + if (javaReference instanceof JavaClassReference && ((JavaClassReference)javaReference).getContextReference() != null) continue; + PsiJavaCodeReferenceElement referenceElement = null; + if (reference instanceof PsiJavaCodeReferenceElement) { + referenceElement = (PsiJavaCodeReferenceElement)child; + if (referenceElement.getQualifier() != null) { + continue; + } + if (reference instanceof PsiJavaCodeReferenceElementImpl + && ((PsiJavaCodeReferenceElementImpl)reference).getKind() == PsiJavaCodeReferenceElementImpl.CLASS_IN_QUALIFIED_NEW_KIND) { + continue; + } + } + + final JavaResolveResult resolveResult = javaReference.advancedResolve(true); + PsiElement refElement = resolveResult.getElement(); + if (refElement == null && referenceElement != null) { + refElement = ResolveClassUtil.resolveClass(referenceElement); // might be uncomplete code + } + if (refElement == null) continue; + + PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope(); + if (!(currentFileResolveScope instanceof PsiImportStatementBase)) continue; + if (context != null && + (!currentFileResolveScope.isValid() || + currentFileResolveScope instanceof JspxImportStatement && + context != ((JspxImportStatement)currentFileResolveScope).getDeclarationFile())) { + continue; + } + + if (referenceElement != null) { + if (currentFileResolveScope instanceof PsiImportStaticStatement) { + PsiImportStaticStatement importStaticStatement = (PsiImportStaticStatement)currentFileResolveScope; + String name = importStaticStatement.getImportReference().getCanonicalText(); + if (importStaticStatement.isOnDemand()) { + String refName = referenceElement.getReferenceName(); + if (refName != null) name = name + "." + refName; + } + names.add(Pair.create(name, Boolean.TRUE)); + continue; + } + } + + if (refElement instanceof PsiClass) { + String qName = ((PsiClass)refElement).getQualifiedName(); + if (hasPackage(qName, thisPackageName)) continue; + names.add(Pair.create(qName, Boolean.FALSE)); + } + } + } + } + + private static void addUnresolvedImportNames(@NotNull final Set> namesToImport, @NotNull PsiJavaFile file) { + PsiImportStatementBase[] imports = file.getImportList().getAllImportStatements(); + final Map> unresolvedNames = new THashMap>(); + @NotNull Set> unresolvedOnDemand = new THashSet>(); + for (PsiImportStatementBase anImport : imports) { + PsiJavaCodeReferenceElement ref = anImport.getImportReference(); + if (ref == null) continue; + JavaResolveResult[] results = ref.multiResolve(false); + if (results.length == 0) { + String text = ref.getCanonicalText(); + if (anImport.isOnDemand()) { + text += ".*"; + } + + Pair pair = Pair.create(text, anImport instanceof PsiImportStaticStatement); + if (anImport.isOnDemand()) { + unresolvedOnDemand.add(pair); + } + else { + unresolvedNames.put(ref.getReferenceName(), pair); + } + } + } + + // do not optimize unresolved imports for things like JSP (IDEA-41814) + if (file.getViewProvider().getLanguages().size() > 1 && file.getViewProvider().getBaseLanguage() != StdLanguages.JAVA) { + namesToImport.addAll(unresolvedOnDemand); + namesToImport.addAll(unresolvedNames.values()); + return; + } + + final boolean[] hasResolveProblem = {false}; + // do not visit imports + for (PsiClass aClass : file.getClasses()) { + aClass.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + String name = reference.getReferenceName(); + Pair pair = unresolvedNames.get(name); + if (reference.multiResolve(false).length == 0) { + hasResolveProblem[0] = true; + if (pair != null) { + namesToImport.add(pair); + unresolvedNames.remove(name); + } + } + super.visitReferenceElement(reference); + } + }); + } + if (hasResolveProblem[0]) { + namesToImport.addAll(unresolvedOnDemand); + } + // otherwise, optimize out all red on demand imports for green file + } + + public static boolean isImplicitlyImported(@NotNull String className, @NotNull PsiJavaFile file) { + String[] packageNames = file.getImplicitlyImportedPackages(); + for (String packageName : packageNames) { + if (hasPackage(className, packageName)) return true; + } + return false; + } + + public static boolean hasPackage(@NotNull String className, @NotNull String packageName){ + if (!className.startsWith(packageName)) return false; + if (className.length() == packageName.length()) return false; + if (packageName.length() > 0 && className.charAt(packageName.length()) != '.') return false; + return className.indexOf('.', packageName.length() + 1) < 0; + } + + @NotNull + private static String getPackageOrClassName(@NotNull String className){ + int dotIndex = className.lastIndexOf('.'); + return dotIndex < 0 ? "" : className.substring(0, dotIndex); + } + +} diff --git a/java/java-impl/src/com/intellij/refactoring/extractMethod/InputVariables.java b/java/java-impl/src/com/intellij/refactoring/extractMethod/InputVariables.java index d72fecab0039..896a3cea2b80 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractMethod/InputVariables.java +++ b/java/java-impl/src/com/intellij/refactoring/extractMethod/InputVariables.java @@ -1,313 +1,313 @@ -/* - * 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. - */ - -/* - * User: anna - * Date: 22-Jun-2009 - */ -package com.intellij.refactoring.extractMethod; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.codeStyle.VariableKind; -import com.intellij.psi.controlFlow.ControlFlow; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.refactoring.util.ParameterTablePanel; -import com.intellij.refactoring.util.duplicates.DuplicatesFinder; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class InputVariables { - private final List myInputVariables; - - private List myInitialParameters; - private final Project myProject; - private final LocalSearchScope myScope; - - private ParametersFolder myFolding; - private boolean myFoldingAvailable; - - public InputVariables(final List inputVariables, - Project project, - LocalSearchScope scope, - boolean foldingAvailable) { - myInitialParameters = inputVariables; - myProject = project; - myScope = scope; - myFoldingAvailable = foldingAvailable; - myFolding = new ParametersFolder(); - myInputVariables = wrapInputVariables(inputVariables); - } - - /** - * copy use only - */ - public InputVariables(List inputVariables, - Project project, - LocalSearchScope scope) { - myProject = project; - myScope = scope; - myInputVariables = new ArrayList(inputVariables); - } - - public boolean isFoldable() { - return myFolding.isFoldable(); - } - - public ArrayList wrapInputVariables(final List inputVariables) { - final ArrayList inputData = new ArrayList(inputVariables.size()); - for (PsiVariable var : inputVariables) { - String name = var.getName(); - if (!(var instanceof PsiParameter)) { - JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myProject); - VariableKind kind = codeStyleManager.getVariableKind(var); - name = codeStyleManager.variableNameToPropertyName(name, kind); - name = codeStyleManager.propertyNameToVariableName(name, VariableKind.PARAMETER); - } - PsiType type = var.getType(); - if (type instanceof PsiEllipsisType) { - type = ((PsiEllipsisType)type).toArrayType(); - } - final Map casts = new HashMap(); - for (PsiReference reference : ReferencesSearch.search(var, myScope)) { - final PsiElement element = reference.getElement(); - final PsiElement parent = element.getParent(); - final PsiCodeBlock block = PsiTreeUtil.getParentOfType(parent, PsiCodeBlock.class); - if (parent instanceof PsiTypeCastExpression) { - final PsiType currentType = casts.get(block); - final PsiType castType = ((PsiTypeCastExpression)parent).getType(); - casts.put(block, casts.containsKey(block) && currentType == null ? null : getBroaderType(currentType, castType)); - } else { - casts.put(block, null); - } - } - if (!casts.containsValue(null)) { - PsiType currentType = null; - for (PsiType psiType : casts.values()) { - currentType = getBroaderType(currentType, psiType); - if (currentType == null) { - break; - } - } - if (currentType != null) { - currentType = checkTopLevelInstanceOf(currentType); - if (currentType != null) { - type = currentType; - } - } - } - - ParameterTablePanel.VariableData data = new ParameterTablePanel.VariableData(var, type); - data.name = name; - data.passAsParameter = true; - inputData.add(data); - - if (myFoldingAvailable) myFolding.isParameterFoldable(data, myScope, inputVariables); - } - - - if (myFoldingAvailable) { - final Set toDelete = new HashSet(); - for (int i = inputData.size() - 1; i >=0; i--) { - final ParameterTablePanel.VariableData data = inputData.get(i); - if (myFolding.isParameterSafeToDelete(data, myScope)) { - toDelete.add(data); - } - } - inputData.removeAll(toDelete); - } - - - return inputData; - } - - @Nullable - private PsiType checkTopLevelInstanceOf(final PsiType currentType) { - final PsiElement[] scope = myScope.getScope(); - if (scope.length == 1 && scope[0] instanceof PsiIfStatement) { - final PsiExpression condition = ((PsiIfStatement)scope[0]).getCondition(); - if (condition != null) { - class CheckInstanceOf { - boolean check(PsiInstanceOfExpression expr) { - final PsiTypeElement checkType = expr.getCheckType(); - return checkType == null || !checkType.getType().equals(currentType); - } - } - CheckInstanceOf checker = new CheckInstanceOf(); - final PsiInstanceOfExpression[] expressions = PsiTreeUtil.getChildrenOfType(condition, PsiInstanceOfExpression.class); - if (expressions != null) { - for (PsiInstanceOfExpression instanceOfExpression : expressions) { - if (!checker.check(instanceOfExpression)) return null; - } - } else if (condition instanceof PsiInstanceOfExpression) { - if (!checker.check((PsiInstanceOfExpression)condition)) return null; - } - } - } - return currentType; - } - - @Nullable - private static PsiType getBroaderType(PsiType currentType, PsiType castType) { - if (currentType != null) { - if (castType != null) { - if (TypeConversionUtil.isAssignable(castType, currentType)) { - return castType; - } else if (!TypeConversionUtil.isAssignable(currentType, castType)) { - for (PsiType superType : castType.getSuperTypes()) { - if (TypeConversionUtil.isAssignable(superType, currentType)) { - return superType; - } - } - return null; - } - } - } - else { - return castType; - } - return currentType; - } - - public List getInputVariables() { - return myInputVariables; - } - - public PsiExpression replaceWrappedReferences(PsiElement[] elements, PsiExpression expression) { - if (!myFoldingAvailable) return expression; - - boolean update = elements[0] == expression; - for (ParameterTablePanel.VariableData inputVariable : myInputVariables) { - myFolding.foldParameterUsagesInBody(inputVariable, elements, myScope); - } - return update ? (PsiExpression)elements[0] : expression; - } - - public boolean toDeclareInsideBody(PsiVariable variable) { - final ArrayList knownVars = new ArrayList(myInputVariables); - for (ParameterTablePanel.VariableData data : knownVars) { - if (data.variable.equals(variable)) { - return false; - } - } - return !myFolding.wasExcluded(variable); - } - - public boolean contains(PsiVariable variable) { - for (ParameterTablePanel.VariableData data : myInputVariables) { - if (data.variable.equals(variable)) return true; - } - return false; - } - - public void removeParametersUsedInExitsOnly(PsiElement codeFragment, - Collection exitStatements, - ControlFlow controlFlow, - int startOffset, - int endOffset) { - final LocalSearchScope scope = new LocalSearchScope(codeFragment); - Variables: - for (Iterator iterator = myInputVariables.iterator(); iterator.hasNext();) { - final ParameterTablePanel.VariableData data = iterator.next(); - for (PsiReference ref : ReferencesSearch.search(data.variable, scope)) { - PsiElement element = ref.getElement(); - int elementOffset = controlFlow.getStartOffset(element); - if (elementOffset >= startOffset && elementOffset <= endOffset) { - if (!isInExitStatements(element, exitStatements)) continue Variables; - } - } - iterator.remove(); - } - } - - private static boolean isInExitStatements(PsiElement element, Collection exitStatements) { - for (PsiStatement exitStatement : exitStatements) { - if (PsiTreeUtil.isAncestor(exitStatement, element, false)) return true; - } - return false; - } - - - public InputVariables copy() { - final InputVariables inputVariables = new InputVariables(myInputVariables, myProject, myScope); - inputVariables.myFoldingAvailable = myFoldingAvailable; - inputVariables.myFolding = myFolding; - inputVariables.myInitialParameters = myInitialParameters; - return inputVariables; - } - - - public void appendCallArguments(ParameterTablePanel.VariableData data, StringBuilder buffer) { - if (myFoldingAvailable) { - buffer.append(myFolding.getGeneratedCallArgument(data)); - } else { - if (!TypeConversionUtil.isAssignable(data.type, data.variable.getType())) { - buffer.append("(").append(data.variable.getType().getCanonicalText()).append(")"); - } - buffer.append(data.variable.getName()); - } - } - - public void setFoldingAvailable(boolean foldingAvailable) { - myFoldingAvailable = foldingAvailable; - - myFolding.clear(); - myInputVariables.clear(); - myInputVariables.addAll(wrapInputVariables(myInitialParameters)); - } - - public void annotateWithParameter(PsiJavaCodeReferenceElement reference) { - for (ParameterTablePanel.VariableData data : myInputVariables) { - final PsiElement element = reference.resolve(); - if (data.variable.equals(element)) { - PsiType type = data.variable.getType(); - final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(reference, PsiMethodCallExpression.class); - if (methodCallExpression != null) { - int idx = ArrayUtil.find(methodCallExpression.getArgumentList().getExpressions(), reference); - if (idx > -1) { - final PsiMethod psiMethod = methodCallExpression.resolveMethod(); - if (psiMethod != null) { - final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); - if (idx >= parameters.length) { //vararg parameter - idx = parameters.length - 1; - if (idx >= 0) { //incomplete code - type = parameters[idx].getType(); - } - } - if (type instanceof PsiEllipsisType) { - type = ((PsiEllipsisType)type).getComponentType(); - } - } - } - } - if (!myFoldingAvailable || !myFolding.annotateWithParameter(data, reference)) { - reference.putUserData(DuplicatesFinder.PARAMETER, Pair.create(data.variable, type)); - } - } - } - } - - public boolean isFoldingSelectedByDefault() { - return myFolding.isFoldingSelectedByDefault(); - } +/* + * 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. + */ + +/* + * User: anna + * Date: 22-Jun-2009 + */ +package com.intellij.refactoring.extractMethod; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.controlFlow.ControlFlow; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.refactoring.util.ParameterTablePanel; +import com.intellij.refactoring.util.duplicates.DuplicatesFinder; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class InputVariables { + private final List myInputVariables; + + private List myInitialParameters; + private final Project myProject; + private final LocalSearchScope myScope; + + private ParametersFolder myFolding; + private boolean myFoldingAvailable; + + public InputVariables(final List inputVariables, + Project project, + LocalSearchScope scope, + boolean foldingAvailable) { + myInitialParameters = inputVariables; + myProject = project; + myScope = scope; + myFoldingAvailable = foldingAvailable; + myFolding = new ParametersFolder(); + myInputVariables = wrapInputVariables(inputVariables); + } + + /** + * copy use only + */ + public InputVariables(List inputVariables, + Project project, + LocalSearchScope scope) { + myProject = project; + myScope = scope; + myInputVariables = new ArrayList(inputVariables); + } + + public boolean isFoldable() { + return myFolding.isFoldable(); + } + + public ArrayList wrapInputVariables(final List inputVariables) { + final ArrayList inputData = new ArrayList(inputVariables.size()); + for (PsiVariable var : inputVariables) { + String name = var.getName(); + if (!(var instanceof PsiParameter)) { + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myProject); + VariableKind kind = codeStyleManager.getVariableKind(var); + name = codeStyleManager.variableNameToPropertyName(name, kind); + name = codeStyleManager.propertyNameToVariableName(name, VariableKind.PARAMETER); + } + PsiType type = var.getType(); + if (type instanceof PsiEllipsisType) { + type = ((PsiEllipsisType)type).toArrayType(); + } + final Map casts = new HashMap(); + for (PsiReference reference : ReferencesSearch.search(var, myScope)) { + final PsiElement element = reference.getElement(); + final PsiElement parent = element.getParent(); + final PsiCodeBlock block = PsiTreeUtil.getParentOfType(parent, PsiCodeBlock.class); + if (parent instanceof PsiTypeCastExpression) { + final PsiType currentType = casts.get(block); + final PsiType castType = ((PsiTypeCastExpression)parent).getType(); + casts.put(block, casts.containsKey(block) && currentType == null ? null : getBroaderType(currentType, castType)); + } else { + casts.put(block, null); + } + } + if (!casts.containsValue(null)) { + PsiType currentType = null; + for (PsiType psiType : casts.values()) { + currentType = getBroaderType(currentType, psiType); + if (currentType == null) { + break; + } + } + if (currentType != null) { + currentType = checkTopLevelInstanceOf(currentType); + if (currentType != null) { + type = currentType; + } + } + } + + ParameterTablePanel.VariableData data = new ParameterTablePanel.VariableData(var, type); + data.name = name; + data.passAsParameter = true; + inputData.add(data); + + if (myFoldingAvailable) myFolding.isParameterFoldable(data, myScope, inputVariables); + } + + + if (myFoldingAvailable) { + final Set toDelete = new HashSet(); + for (int i = inputData.size() - 1; i >=0; i--) { + final ParameterTablePanel.VariableData data = inputData.get(i); + if (myFolding.isParameterSafeToDelete(data, myScope)) { + toDelete.add(data); + } + } + inputData.removeAll(toDelete); + } + + + return inputData; + } + + @Nullable + private PsiType checkTopLevelInstanceOf(final PsiType currentType) { + final PsiElement[] scope = myScope.getScope(); + if (scope.length == 1 && scope[0] instanceof PsiIfStatement) { + final PsiExpression condition = ((PsiIfStatement)scope[0]).getCondition(); + if (condition != null) { + class CheckInstanceOf { + boolean check(PsiInstanceOfExpression expr) { + final PsiTypeElement checkType = expr.getCheckType(); + return checkType == null || !checkType.getType().equals(currentType); + } + } + CheckInstanceOf checker = new CheckInstanceOf(); + final PsiInstanceOfExpression[] expressions = PsiTreeUtil.getChildrenOfType(condition, PsiInstanceOfExpression.class); + if (expressions != null) { + for (PsiInstanceOfExpression instanceOfExpression : expressions) { + if (!checker.check(instanceOfExpression)) return null; + } + } else if (condition instanceof PsiInstanceOfExpression) { + if (!checker.check((PsiInstanceOfExpression)condition)) return null; + } + } + } + return currentType; + } + + @Nullable + private static PsiType getBroaderType(PsiType currentType, PsiType castType) { + if (currentType != null) { + if (castType != null) { + if (TypeConversionUtil.isAssignable(castType, currentType)) { + return castType; + } else if (!TypeConversionUtil.isAssignable(currentType, castType)) { + for (PsiType superType : castType.getSuperTypes()) { + if (TypeConversionUtil.isAssignable(superType, currentType)) { + return superType; + } + } + return null; + } + } + } + else { + return castType; + } + return currentType; + } + + public List getInputVariables() { + return myInputVariables; + } + + public PsiExpression replaceWrappedReferences(PsiElement[] elements, PsiExpression expression) { + if (!myFoldingAvailable) return expression; + + boolean update = elements[0] == expression; + for (ParameterTablePanel.VariableData inputVariable : myInputVariables) { + myFolding.foldParameterUsagesInBody(inputVariable, elements, myScope); + } + return update ? (PsiExpression)elements[0] : expression; + } + + public boolean toDeclareInsideBody(PsiVariable variable) { + final ArrayList knownVars = new ArrayList(myInputVariables); + for (ParameterTablePanel.VariableData data : knownVars) { + if (data.variable.equals(variable)) { + return false; + } + } + return !myFolding.wasExcluded(variable); + } + + public boolean contains(PsiVariable variable) { + for (ParameterTablePanel.VariableData data : myInputVariables) { + if (data.variable.equals(variable)) return true; + } + return false; + } + + public void removeParametersUsedInExitsOnly(PsiElement codeFragment, + Collection exitStatements, + ControlFlow controlFlow, + int startOffset, + int endOffset) { + final LocalSearchScope scope = new LocalSearchScope(codeFragment); + Variables: + for (Iterator iterator = myInputVariables.iterator(); iterator.hasNext();) { + final ParameterTablePanel.VariableData data = iterator.next(); + for (PsiReference ref : ReferencesSearch.search(data.variable, scope)) { + PsiElement element = ref.getElement(); + int elementOffset = controlFlow.getStartOffset(element); + if (elementOffset >= startOffset && elementOffset <= endOffset) { + if (!isInExitStatements(element, exitStatements)) continue Variables; + } + } + iterator.remove(); + } + } + + private static boolean isInExitStatements(PsiElement element, Collection exitStatements) { + for (PsiStatement exitStatement : exitStatements) { + if (PsiTreeUtil.isAncestor(exitStatement, element, false)) return true; + } + return false; + } + + + public InputVariables copy() { + final InputVariables inputVariables = new InputVariables(myInputVariables, myProject, myScope); + inputVariables.myFoldingAvailable = myFoldingAvailable; + inputVariables.myFolding = myFolding; + inputVariables.myInitialParameters = myInitialParameters; + return inputVariables; + } + + + public void appendCallArguments(ParameterTablePanel.VariableData data, StringBuilder buffer) { + if (myFoldingAvailable) { + buffer.append(myFolding.getGeneratedCallArgument(data)); + } else { + if (!TypeConversionUtil.isAssignable(data.type, data.variable.getType())) { + buffer.append("(").append(data.variable.getType().getCanonicalText()).append(")"); + } + buffer.append(data.variable.getName()); + } + } + + public void setFoldingAvailable(boolean foldingAvailable) { + myFoldingAvailable = foldingAvailable; + + myFolding.clear(); + myInputVariables.clear(); + myInputVariables.addAll(wrapInputVariables(myInitialParameters)); + } + + public void annotateWithParameter(PsiJavaCodeReferenceElement reference) { + for (ParameterTablePanel.VariableData data : myInputVariables) { + final PsiElement element = reference.resolve(); + if (data.variable.equals(element)) { + PsiType type = data.variable.getType(); + final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(reference, PsiMethodCallExpression.class); + if (methodCallExpression != null) { + int idx = ArrayUtil.find(methodCallExpression.getArgumentList().getExpressions(), reference); + if (idx > -1) { + final PsiMethod psiMethod = methodCallExpression.resolveMethod(); + if (psiMethod != null) { + final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); + if (idx >= parameters.length) { //vararg parameter + idx = parameters.length - 1; + if (idx >= 0) { //incomplete code + type = parameters[idx].getType(); + } + } + if (type instanceof PsiEllipsisType) { + type = ((PsiEllipsisType)type).getComponentType(); + } + } + } + } + if (!myFoldingAvailable || !myFolding.annotateWithParameter(data, reference)) { + reference.putUserData(DuplicatesFinder.PARAMETER, Pair.create(data.variable, type)); + } + } + } + } + + public boolean isFoldingSelectedByDefault() { + return myFolding.isFoldingSelectedByDefault(); + } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java index 54508365f00b..ede4be1340d3 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java @@ -1,868 +1,868 @@ -/* - * 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. - */ - -/** - * Created by IntelliJ IDEA. - * User: dsl - * Date: Nov 15, 2002 - * Time: 5:21:33 PM - * To change this template use Options | File Templates. - */ -package com.intellij.refactoring.introduceVariable; - -import com.intellij.codeInsight.CodeInsightUtil; -import com.intellij.codeInsight.completion.JavaCompletionUtil; -import com.intellij.codeInsight.highlighting.HighlightManager; -import com.intellij.codeInsight.intention.impl.TypeExpression; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.codeInsight.template.*; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.lang.LanguageRefactoringSupport; -import com.intellij.lang.refactoring.RefactoringSupportProvider; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.*; -import com.intellij.psi.impl.source.tree.java.ReplaceExpressionUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.refactoring.*; -import com.intellij.refactoring.introduceField.ElementToWorkOn; -import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; -import com.intellij.refactoring.ui.TypeSelectorManagerImpl; -import com.intellij.refactoring.util.CommonRefactoringUtil; -import com.intellij.refactoring.util.FieldConflictsResolver; -import com.intellij.refactoring.util.RefactoringUIUtil; -import com.intellij.refactoring.util.RefactoringUtil; -import com.intellij.refactoring.util.occurences.ExpressionOccurenceManager; -import com.intellij.refactoring.util.occurences.NotInSuperCallOccurenceFilter; -import com.intellij.util.Consumer; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.containers.*; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.List; - -public abstract class IntroduceVariableBase extends IntroduceHandlerBase implements RefactoringActionHandler { - private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase"); - @NonNls private static final String PREFER_STATEMENTS_OPTION = "introduce.variable.prefer.statements"; - - protected static String REFACTORING_NAME = RefactoringBundle.message("introduce.variable.title"); - - public static SuggestedNameInfo getSuggestedName(PsiType type, final PsiExpression expression) { - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); - final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, expression, type); - final String[] strings = JavaCompletionUtil - .completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, nameInfo); - final SuggestedNameInfo.Delegate delegate = new SuggestedNameInfo.Delegate(strings, nameInfo); - return codeStyleManager.suggestUniqueVariableName(delegate, expression, true); - } - - public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) { - final SelectionModel selectionModel = editor.getSelectionModel(); - if (!selectionModel.hasSelection()) { - final int offset = editor.getCaretModel().getOffset(); - final PsiElement[] statementsInRange = findStatementsAtOffset(editor, file, offset); - - //try line selection - if (statementsInRange.length == 1 && (PsiUtil.hasErrorElementChild(statementsInRange[0]) || !PsiUtil.isStatement(statementsInRange[0]) || isPreferStatements())) { - selectionModel.selectLineAtCaret(); - if (findExpressionInRange(project, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) == null) { - selectionModel.removeSelection(); - } - } - - if (!selectionModel.hasSelection()) { - final List expressions = collectExpressions(file, editor, offset, statementsInRange); - if (expressions.isEmpty()) { - selectionModel.selectLineAtCaret(); - } else if (expressions.size() == 1) { - final TextRange textRange = expressions.get(0).getTextRange(); - selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset()); - } - else { - IntroduceTargetChooser.showChooser(editor, expressions, - new Pass(){ - public void pass(final PsiExpression selectedValue) { - invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset()); - } - }, - new PsiExpressionTrimRenderer.RenderFunction()); - return; - } - } - } - if (invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) && - LookupManager.getActiveLookup(editor) == null) { - selectionModel.removeSelection(); - } - } - - public static boolean isPreferStatements() { - return Boolean.valueOf(PropertiesComponent.getInstance().getOrInit(PREFER_STATEMENTS_OPTION, "false")).booleanValue(); - } - - public static List collectExpressions(final PsiFile file, final Editor editor, final int offset, final PsiElement... statementsInRange) { - Document document = editor.getDocument(); - CharSequence text = document.getCharsSequence(); - int correctedOffset = offset; - int textLength = document.getTextLength(); - if (offset >= textLength) { - correctedOffset = textLength - 1; - } - else if (!Character.isJavaIdentifierPart(text.charAt(offset))) { - correctedOffset--; - } - if (correctedOffset < 0) { - correctedOffset = offset; - } - else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) { - if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line - correctedOffset--; - } - if (text.charAt(correctedOffset) != ')') { - correctedOffset = offset; - } - } - final PsiElement elementAtCaret = file.findElementAt(correctedOffset); - final List expressions = new ArrayList(); - /*for (PsiElement element : statementsInRange) { - if (element instanceof PsiExpressionStatement) { - final PsiExpression expression = ((PsiExpressionStatement)element).getExpression(); - if (expression.getType() != PsiType.VOID) { - expressions.add(expression); - } - } - }*/ - PsiExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class); - while (expression != null) { - if (!expressions.contains(expression) && !(expression instanceof PsiParenthesizedExpression) && !(expression instanceof PsiSuperExpression) && expression.getType() != PsiType.VOID) { - if (!(expression instanceof PsiReferenceExpression && (expression.getParent() instanceof PsiMethodCallExpression || - ((PsiReferenceExpression)expression).resolve() instanceof PsiClass)) - && !(expression instanceof PsiAssignmentExpression)) { - expressions.add(expression); - } - } - expression = PsiTreeUtil.getParentOfType(expression, PsiExpression.class); - } - return expressions; - } - - public static PsiElement[] findStatementsAtOffset(final Editor editor, final PsiFile file, final int offset) { - final Document document = editor.getDocument(); - final int lineNumber = document.getLineNumber(offset); - final int lineStart = document.getLineStartOffset(lineNumber); - final int lineEnd = document.getLineEndOffset(lineNumber); - - return CodeInsightUtil.findStatementsInRange(file, lineStart, lineEnd); - } - - private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable"); - PsiDocumentManager.getInstance(project).commitAllDocuments(); - - - return invokeImpl(project, findExpressionInRange(project, file, startOffset, endOffset), editor); - } - - private static PsiExpression findExpressionInRange(Project project, PsiFile file, int startOffset, int endOffset) { - PsiExpression tempExpr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); - if (tempExpr == null) { - PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); - if (statements.length == 1) { - if (statements[0] instanceof PsiExpressionStatement) { - tempExpr = ((PsiExpressionStatement) statements[0]).getExpression(); - } else if (statements[0] instanceof PsiReturnStatement) { - tempExpr = ((PsiReturnStatement)statements[0]).getReturnValue(); - } - } - } - - if (tempExpr == null) { - tempExpr = getSelectedExpression(project, file, startOffset, endOffset); - } - return tempExpr; - } - - public static PsiExpression getSelectedExpression(final Project project, final PsiFile file, int startOffset, int endOffset) { - - PsiElement elementAtStart = file.findElementAt(startOffset); - if (elementAtStart == null || elementAtStart instanceof PsiWhiteSpace || elementAtStart instanceof PsiComment) { - elementAtStart = PsiTreeUtil.skipSiblingsForward(elementAtStart, PsiWhiteSpace.class, PsiComment.class); - if (elementAtStart == null) return null; - startOffset = elementAtStart.getTextOffset(); - } - PsiElement elementAtEnd = file.findElementAt(endOffset - 1); - if (elementAtEnd == null || elementAtEnd instanceof PsiWhiteSpace || elementAtEnd instanceof PsiComment) { - elementAtEnd = PsiTreeUtil.skipSiblingsBackward(elementAtEnd, PsiWhiteSpace.class, PsiComment.class); - if (elementAtEnd == null) return null; - endOffset = elementAtEnd.getTextRange().getEndOffset(); - } - - if (endOffset <= startOffset) return null; - - PsiExpression tempExpr; - PsiElement elementAt = PsiTreeUtil.findCommonParent(elementAtStart, elementAtEnd); - if (PsiTreeUtil.getParentOfType(elementAt, PsiExpression.class, false) == null) { - elementAt = null; - } - final PsiLiteralExpression literalExpression = PsiTreeUtil.getParentOfType(elementAt, PsiLiteralExpression.class); - - final PsiLiteralExpression startLiteralExpression = PsiTreeUtil.getParentOfType(elementAtStart, PsiLiteralExpression.class); - final PsiLiteralExpression endLiteralExpression = PsiTreeUtil.getParentOfType(file.findElementAt(endOffset), PsiLiteralExpression.class); - - final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); - try { - String text = file.getText().subSequence(startOffset, endOffset).toString(); - String prefix = null; - String suffix = null; - String stripped = text; - if (startLiteralExpression != null) { - final int startExpressionOffset = startLiteralExpression.getTextOffset(); - if (startOffset == startExpressionOffset) { - if (StringUtil.startsWithChar(text, '\"') || StringUtil.startsWithChar(text, '\'')) { - stripped = text.substring(1); - } - } else if (startOffset == startExpressionOffset + 1) { - text = "\"" + text; - } else if (startOffset > startExpressionOffset + 1){ - prefix = "\" + "; - text = "\"" + text; - } - } - - if (endLiteralExpression != null) { - final int endExpressionOffset = endLiteralExpression.getTextOffset() + endLiteralExpression.getTextLength(); - if (endOffset == endExpressionOffset ) { - if (StringUtil.endsWithChar(stripped, '\"') || StringUtil.endsWithChar(stripped, '\'')) { - stripped = stripped.substring(0, stripped.length() - 1); - } - } else if (endOffset == endExpressionOffset - 1) { - text += "\""; - } else if (endOffset < endExpressionOffset - 1) { - suffix = " + \""; - text += "\""; - } - } - - boolean primitive = false; - if (stripped.equals("true") || stripped.equals("false")) { - primitive = true; - } - else { - try { - Integer.parseInt(stripped); - primitive = true; - } - catch (NumberFormatException e1) { - //then not primitive - } - } - - if (primitive) { - text = stripped; - } - - final PsiElement parent = literalExpression != null ? literalExpression : elementAt; - tempExpr = elementFactory.createExpressionFromText(text, parent); - - final boolean [] hasErrors = new boolean[1]; - final JavaRecursiveElementWalkingVisitor errorsVisitor = new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(final PsiElement element) { - if (hasErrors[0]) { - return; - } - super.visitElement(element); - } - - @Override - public void visitErrorElement(final PsiErrorElement element) { - hasErrors[0] = true; - } - }; - tempExpr.accept(errorsVisitor); - if (hasErrors[0]) return null; - - tempExpr.putUserData(ElementToWorkOn.PREFIX, prefix); - tempExpr.putUserData(ElementToWorkOn.SUFFIX, suffix); - - final RangeMarker rangeMarker = - FileDocumentManager.getInstance().getDocument(file.getVirtualFile()).createRangeMarker(startOffset, endOffset); - tempExpr.putUserData(ElementToWorkOn.TEXT_RANGE, rangeMarker); - - if (parent != null) { - tempExpr.putUserData(ElementToWorkOn.PARENT, parent); - } - else { - PsiErrorElement errorElement = elementAtStart instanceof PsiErrorElement - ? (PsiErrorElement)elementAtStart - : PsiTreeUtil.getNextSiblingOfType(elementAtStart, PsiErrorElement.class); - if (errorElement == null) { - errorElement = PsiTreeUtil.getParentOfType(elementAtStart, PsiErrorElement.class); - } - if (errorElement == null) return null; - if (!(errorElement.getParent() instanceof PsiClass)) return null; - tempExpr.putUserData(ElementToWorkOn.PARENT, errorElement); - tempExpr.putUserData(ElementToWorkOn.OUT_OF_CODE_BLOCK, Boolean.TRUE); - } - - final String fakeInitializer = "intellijidearulezzz"; - final int[] refIdx = new int[1]; - final PsiExpression toBeExpression = createReplacement(fakeInitializer, project, prefix, suffix, parent, rangeMarker, refIdx); - toBeExpression.accept(errorsVisitor); - if (hasErrors[0]) return null; - - final PsiReferenceExpression refExpr = PsiTreeUtil.getParentOfType(toBeExpression.findElementAt(refIdx[0]), PsiReferenceExpression.class); - assert refExpr != null; - if (ReplaceExpressionUtil.isNeedParenthesis(refExpr.getNode(), tempExpr.getNode())) { - return null; - } - } - catch (IncorrectOperationException e) { - return null; - } - - return tempExpr; - } - - protected boolean invokeImpl(final Project project, final PsiExpression expr, - final Editor editor) { - if (expr != null && expr.getParent() instanceof PsiExpressionStatement) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable.incompleteStatement"); - } - if (LOG.isDebugEnabled()) { - LOG.debug("expression:" + expr); - } - - if (expr == null) { - if (ReassignVariableUtil.reassign(editor)) return false; - String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.block.should.represent.an.expression")); - showErrorMessage(project, editor, message); - return false; - } - - - final PsiType originalType = RefactoringUtil.getTypeByExpressionWithExpectedType(expr); - if (originalType == null) { - String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("unknown.expression.type")); - showErrorMessage(project, editor, message); - return false; - } - - if (PsiType.VOID.equals(originalType)) { - String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type")); - showErrorMessage(project, editor, message); - return false; - } - - - final PsiElement physicalElement = expr.getUserData(ElementToWorkOn.PARENT); - - final PsiElement anchorStatement = RefactoringUtil.getParentStatement(physicalElement != null ? physicalElement : expr, false); - - if (anchorStatement == null) { - return parentStatementNotFound(project, editor); - } - if (anchorStatement instanceof PsiExpressionStatement) { - PsiExpression enclosingExpr = ((PsiExpressionStatement)anchorStatement).getExpression(); - if (enclosingExpr instanceof PsiMethodCallExpression) { - PsiMethod method = ((PsiMethodCallExpression)enclosingExpr).resolveMethod(); - if (method != null && method.isConstructor()) { - //This is either 'this' or 'super', both must be the first in the respective contructor - String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("invalid.expression.context")); - showErrorMessage(project, editor, message); - return false; - } - } - } - - final PsiElement tempContainer = anchorStatement.getParent(); - - if (!(tempContainer instanceof PsiCodeBlock) && !isLoopOrIf(tempContainer)) { - String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME); - showErrorMessage(project, editor, message); - return false; - } - - if(!NotInSuperCallOccurenceFilter.INSTANCE.isOK(expr)) { - String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("cannot.introduce.variable.in.super.constructor.call")); - showErrorMessage(project, editor, message); - return false; - } - - final PsiFile file = anchorStatement.getContainingFile(); - LOG.assertTrue(file != null, "expr.getContainingFile() == null"); - - if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return false; - - PsiElement containerParent = tempContainer; - PsiElement lastScope = tempContainer; - while (true) { - if (containerParent instanceof PsiFile) break; - if (containerParent instanceof PsiMethod) break; - containerParent = containerParent.getParent(); - if (containerParent instanceof PsiCodeBlock) { - lastScope = containerParent; - } - } - - final ExpressionOccurenceManager occurenceManager = new ExpressionOccurenceManager(expr, lastScope, - NotInSuperCallOccurenceFilter.INSTANCE); - final PsiExpression[] occurrences = occurenceManager.getOccurences(); - final PsiElement anchorStatementIfAll = occurenceManager.getAnchorStatementForAll(); - - final LinkedHashMap occurrencesMap = - new LinkedHashMap(); - - final boolean hasWriteAccess = OccurrencesChooser.fillChoices(expr, occurrences, occurrencesMap); - - final PsiElement nameSuggestionContext = editor != null ? file.findElementAt(editor.getCaretModel().getOffset()) : null; - final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(expr.getLanguage()); - final boolean isInplaceAvailableOnDataContext = - supportProvider != null && - editor.getSettings().isVariableInplaceRenameEnabled() && - supportProvider.isInplaceIntroduceAvailable(expr, nameSuggestionContext) && - !ApplicationManager.getApplication().isUnitTestMode(); - final boolean inFinalContext = occurenceManager.isInFinalContext(); - final InputValidator validator = new InputValidator(this, project, anchorStatementIfAll, anchorStatement, occurenceManager); - final TypeSelectorManagerImpl typeSelectorManager = new TypeSelectorManagerImpl(project, originalType, expr, occurrences); - - final Pass callback = new Pass() { - @Override - public void pass(OccurrencesChooser.ReplaceChoice choice) { - final Ref> variable = new Ref>(); - final IntroduceVariableSettings settings = - getSettings(project, editor, expr, occurrences, typeSelectorManager, inFinalContext, hasWriteAccess, validator, choice); - if (!settings.isOK()) return; - typeSelectorManager.setAllOccurences(choice != OccurrencesChooser.ReplaceChoice.NO); - final TypeExpression expression = new TypeExpression(project, typeSelectorManager.getTypesForAll()); - final RangeMarker exprMarker = editor.getDocument().createRangeMarker(expr.getTextRange()); - final SuggestedNameInfo suggestedName = getSuggestedName(settings.getSelectedType(), expr); - final List occurrenceMarkers = new ArrayList(); - for (PsiExpression occurrence : occurrences) { - occurrenceMarkers.add(editor.getDocument().createRangeMarker(occurrence.getTextRange())); - } - final Runnable runnable = - introduce(project, expr, editor, anchorStatement, tempContainer, occurrences, anchorStatementIfAll, settings, variable); - CommandProcessor.getInstance().executeCommand( - project, - new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(runnable); - if (isInplaceAvailableOnDataContext) { - final PsiVariable elementToRename = variable.get().getElement(); - if (elementToRename != null) { - editor.getCaretModel().moveToOffset(elementToRename.getTextOffset()); - final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(elementToRename, PsiDeclarationStatement.class); - editor.putUserData(ReassignVariableUtil.DECLARATION_KEY, declarationStatement); - editor.putUserData(ReassignVariableUtil.OCCURRENCES_KEY, - occurrenceMarkers.toArray(new RangeMarker[occurrenceMarkers.size()])); - final VariableInplaceRenamer renamer = new VariableInplaceRenamer(elementToRename, editor){ - @Override - protected void addAdditionalVariables(TemplateBuilderImpl builder) { - final PsiTypeElement typeElement = elementToRename.getTypeElement(); - builder.replaceElement(typeElement, "Variable_Type", - ReassignVariableUtil.createExpression(expression, typeElement.getText()), false, true); - } - }; - renamer.setAdvertisementText( - ReassignVariableUtil.getAdvertisementText(editor, declarationStatement, elementToRename.getType(), typeSelectorManager.getTypesForAll())); - renamer.performInplaceRename(false, new LinkedHashSet(Arrays.asList(suggestedName.names)), new Consumer() { - @Override - public void consume(Boolean apply) { - if (apply) { - int startOffset = exprMarker.getStartOffset(); - final PsiReference referenceAt = file.findReferenceAt(startOffset); - if (referenceAt != null && referenceAt.resolve() instanceof PsiLocalVariable) { - startOffset = referenceAt.getElement().getTextRange().getEndOffset(); - } else { - startOffset = editor.getDocument().getLineEndOffset(editor.getDocument().getLineNumber(startOffset)); - } - editor.getCaretModel().moveToOffset(startOffset); - } - editor.putUserData(ReassignVariableUtil.DECLARATION_KEY, null); - for (RangeMarker occurrenceMarker : occurrenceMarkers) { - occurrenceMarker.dispose(); - } - editor.putUserData(ReassignVariableUtil.OCCURRENCES_KEY, null); - typeSelectorManager.typeSelected(ReassignVariableUtil.getVariableType(declarationStatement)); - exprMarker.dispose(); - } - }); - } - } - } - }, REFACTORING_NAME, null); - } - }; - - if (!isInplaceAvailableOnDataContext) { - callback.pass(null); - } - else { - new OccurrencesChooser(editor).showChooser(callback, occurrencesMap); - } - return true; - } - - private static Runnable introduce(final Project project, - final PsiExpression expr, - final Editor editor, - PsiElement anchorStatement, - PsiElement tempContainer, - final PsiExpression[] occurrences, - PsiElement anchorStatementIfAll, - final IntroduceVariableSettings settings, - final Ref> variable) { - if (settings.isReplaceAllOccurrences()) { - anchorStatement = anchorStatementIfAll; - tempContainer = anchorStatement.getParent(); - } - - final PsiElement container = tempContainer; - - PsiElement child = anchorStatement; - if (!isLoopOrIf(container)) { - child = locateAnchor(child); - } - final PsiElement anchor = child == null ? anchorStatement : child; - - boolean tempDeleteSelf = false; - final boolean replaceSelf = settings.isReplaceLValues() || !RefactoringUtil.isAssignmentLHS(expr); - if (!isLoopOrIf(container)) { - if (expr.getParent() instanceof PsiExpressionStatement && anchor.equals(anchorStatement)) { - PsiStatement statement = (PsiStatement) expr.getParent(); - PsiElement parent = statement.getParent(); - if (parent instanceof PsiCodeBlock || - //fabrique - parent instanceof PsiCodeFragment) { - tempDeleteSelf = true; - } - } - tempDeleteSelf &= replaceSelf; - } - final boolean deleteSelf = tempDeleteSelf; - - - final int col = editor != null ? editor.getCaretModel().getLogicalPosition().column : 0; - final int line = editor != null ? editor.getCaretModel().getLogicalPosition().line : 0; - if (deleteSelf) { - if (editor != null) { - LogicalPosition pos = new LogicalPosition(line, col); - editor.getCaretModel().moveToLogicalPosition(pos); - } - } - - final PsiCodeBlock newDeclarationScope = PsiTreeUtil.getParentOfType(container, PsiCodeBlock.class, false); - final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(settings.getEnteredName(), newDeclarationScope); - final PsiElement finalAnchorStatement = anchorStatement; - return new Runnable() { - public void run() { - try { - PsiStatement statement = null; - final boolean isInsideLoop = isLoopOrIf(container); - if (!isInsideLoop && deleteSelf) { - statement = (PsiStatement) expr.getParent(); - } - - final PsiExpression expr1 = fieldConflictsResolver.fixInitializer(expr); - PsiExpression initializer = RefactoringUtil.unparenthesizeExpression(expr1); - if (expr1 instanceof PsiNewExpression) { - final PsiNewExpression newExpression = (PsiNewExpression)expr1; - if (newExpression.getArrayInitializer() != null) { - initializer = newExpression.getArrayInitializer(); - } - } - PsiDeclarationStatement declaration = JavaPsiFacade.getInstance(project).getElementFactory() - .createVariableDeclarationStatement(settings.getEnteredName(), settings.getSelectedType(), initializer); - if (!isInsideLoop) { - declaration = (PsiDeclarationStatement) container.addBefore(declaration, anchor); - LOG.assertTrue(expr1.isValid()); - if (deleteSelf) { // never true - final PsiElement lastChild = statement.getLastChild(); - if (lastChild instanceof PsiComment) { // keep trailing comment - declaration.addBefore(lastChild, null); - } - statement.delete(); - if (editor != null) { - LogicalPosition pos = new LogicalPosition(line, col); - editor.getCaretModel().moveToLogicalPosition(pos); - editor.getCaretModel().moveToOffset(declaration.getTextRange().getEndOffset()); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().removeSelection(); - } - } - } - - PsiExpression ref = JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(settings.getEnteredName(), null); - if (settings.isReplaceAllOccurrences()) { - ArrayList array = new ArrayList(); - for (PsiExpression occurrence : occurrences) { - if (deleteSelf && occurrence.equals(expr)) continue; - if (occurrence.equals(expr)) { - occurrence = expr1; - } - if (occurrence != null) { - occurrence = RefactoringUtil.outermostParenthesizedExpression(occurrence); - } - if (settings.isReplaceLValues() || !RefactoringUtil.isAssignmentLHS(occurrence)) { - array.add(replace(occurrence, ref, project)); - } - } - - if (editor != null) { - final PsiElement[] replacedOccurences = array.toArray(new PsiElement[array.size()]); - highlightReplacedOccurences(project, editor, replacedOccurences); - } - } else { - if (!deleteSelf && replaceSelf) { - replace(expr1, ref, project); - } - } - - declaration = (PsiDeclarationStatement) putStatementInLoopBody(declaration, container, finalAnchorStatement); - declaration = (PsiDeclarationStatement)JavaCodeStyleManager.getInstance(project).shortenClassReferences(declaration); - PsiVariable var = (PsiVariable) declaration.getDeclaredElements()[0]; - PsiUtil.setModifierProperty(var, PsiModifier.FINAL, settings.isDeclareFinal()); - variable.set(SmartPointerManager.getInstance(project).createLazyPointer(var)); - fieldConflictsResolver.fix(); - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } - }; - } - - public static PsiElement replace(final PsiExpression expr1, final PsiExpression ref, final Project project) - throws IncorrectOperationException { - final PsiExpression expr2 = RefactoringUtil.outermostParenthesizedExpression(expr1); - if (expr2.isPhysical()) { - return expr2.replace(ref); - } - else { - final String prefix = expr1.getUserData(ElementToWorkOn.PREFIX); - final String suffix = expr1.getUserData(ElementToWorkOn.SUFFIX); - final PsiElement parent = expr1.getUserData(ElementToWorkOn.PARENT); - final RangeMarker rangeMarker = expr1.getUserData(ElementToWorkOn.TEXT_RANGE); - - return parent.replace(createReplacement(ref.getText(), project, prefix, suffix, parent, rangeMarker, new int[1])); - } - } - - private static PsiExpression createReplacement(final String refText, final Project project, - final String prefix, - final String suffix, - final PsiElement parent, final RangeMarker rangeMarker, int[] refIdx) { - String text = refText; - if (parent != null) { - final String allText = parent.getContainingFile().getText(); - final TextRange parentRange = parent.getTextRange(); - - String beg = allText.substring(parentRange.getStartOffset(), rangeMarker.getStartOffset()); - if (StringUtil.stripQuotesAroundValue(beg).trim().length() == 0 && prefix == null) beg = ""; - - String end = allText.substring(rangeMarker.getEndOffset(), parentRange.getEndOffset()); - if (StringUtil.stripQuotesAroundValue(end).trim().length() == 0 && suffix == null) end = ""; - - final String start = beg + (prefix != null ? prefix : ""); - refIdx[0] = start.length(); - text = start + refText + (suffix != null ? suffix : "") + end; - } - return JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(text, parent); - } - - public static PsiStatement putStatementInLoopBody(PsiStatement declaration, PsiElement container, PsiElement finalAnchorStatement) - throws IncorrectOperationException { - if(isLoopOrIf(container)) { - PsiStatement loopBody = getLoopBody(container, finalAnchorStatement); - PsiStatement loopBodyCopy = loopBody != null ? (PsiStatement) loopBody.copy() : null; - PsiBlockStatement blockStatement = (PsiBlockStatement)JavaPsiFacade.getInstance(container.getProject()).getElementFactory() - .createStatementFromText("{}", null); - blockStatement = (PsiBlockStatement) CodeStyleManager.getInstance(container.getProject()).reformat(blockStatement); - final PsiElement prevSibling = loopBody.getPrevSibling(); - if(prevSibling instanceof PsiWhiteSpace) { - final PsiElement pprev = prevSibling.getPrevSibling(); - if (!(pprev instanceof PsiComment) || !((PsiComment)pprev).getTokenType().equals(JavaTokenType.END_OF_LINE_COMMENT)) { - prevSibling.delete(); - } - } - blockStatement = (PsiBlockStatement) loopBody.replace(blockStatement); - final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); - declaration = (PsiStatement) codeBlock.add(declaration); - JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration); - if (loopBodyCopy != null) codeBlock.add(loopBodyCopy); - } - return declaration; - } - - private boolean parentStatementNotFound(final Project project, Editor editor) { - String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME); - showErrorMessage(project, editor, message); - return false; - } - - protected boolean invokeImpl(Project project, PsiLocalVariable localVariable, Editor editor) { - throw new UnsupportedOperationException(); - } - - private static PsiElement locateAnchor(PsiElement child) { - while (child != null) { - PsiElement prev = child.getPrevSibling(); - if (prev instanceof PsiStatement) break; - if (prev instanceof PsiJavaToken && ((PsiJavaToken)prev).getTokenType() == JavaTokenType.LBRACE) break; - child = prev; - } - - while (child instanceof PsiWhiteSpace || child instanceof PsiComment) { - child = child.getNextSibling(); - } - return child; - } - - protected static void highlightReplacedOccurences(Project project, Editor editor, PsiElement[] replacedOccurences){ - if (editor == null) return; - if (ApplicationManager.getApplication().isUnitTestMode()) return; - HighlightManager highlightManager = HighlightManager.getInstance(project); - EditorColorsManager colorsManager = EditorColorsManager.getInstance(); - TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); - highlightManager.addOccurrenceHighlights(editor, replacedOccurences, attributes, true, null); - WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); - } - - protected abstract void showErrorMessage(Project project, Editor editor, String message); - - @Nullable - private static PsiStatement getLoopBody(PsiElement container, PsiElement anchorStatement) { - if(container instanceof PsiLoopStatement) { - return ((PsiLoopStatement) container).getBody(); - } - else if (container instanceof PsiIfStatement) { - final PsiStatement thenBranch = ((PsiIfStatement)container).getThenBranch(); - if (thenBranch != null && PsiTreeUtil.isAncestor(thenBranch, anchorStatement, false)) { - return thenBranch; - } - final PsiStatement elseBranch = ((PsiIfStatement)container).getElseBranch(); - if (elseBranch != null && PsiTreeUtil.isAncestor(elseBranch, anchorStatement, false)) { - return elseBranch; - } - LOG.assertTrue(false); - } - LOG.assertTrue(false); - return null; - } - - - public static boolean isLoopOrIf(PsiElement element) { - return element instanceof PsiLoopStatement || element instanceof PsiIfStatement; - } - - protected boolean reportConflicts(MultiMap conflicts, Project project, IntroduceVariableSettings settings){ - return false; - } - - public IntroduceVariableSettings getSettings(Project project, Editor editor, - PsiExpression expr, PsiExpression[] occurrences, - final TypeSelectorManagerImpl typeSelectorManager, - boolean declareFinalIfAll, - boolean anyAssignmentLHS, - final InputValidator validator, - final OccurrencesChooser.ReplaceChoice replaceChoice) { - final SuggestedNameInfo suggestedName = getSuggestedName(typeSelectorManager.getDefaultType(), expr); - final String variableName = suggestedName.names[0]; - final Boolean generateFinals = JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS; - final boolean replaceAll = - replaceChoice == OccurrencesChooser.ReplaceChoice.ALL || replaceChoice == OccurrencesChooser.ReplaceChoice.NO_WRITE; - final boolean declareFinal = - !anyAssignmentLHS && (replaceAll && - declareFinalIfAll || generateFinals == null ? - CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS : - generateFinals.booleanValue()); - final boolean replaceWrite = anyAssignmentLHS && replaceChoice == OccurrencesChooser.ReplaceChoice.ALL; - return new IntroduceVariableSettings() { - @Override - public String getEnteredName() { - return variableName; - } - - @Override - public boolean isReplaceAllOccurrences() { - return replaceAll; - } - - @Override - public boolean isDeclareFinal() { - return declareFinal; - } - - @Override - public boolean isReplaceLValues() { - return replaceWrite; - } - - @Override - public PsiType getSelectedType() { - final PsiType selectedType = typeSelectorManager.getTypeSelector().getSelectedType(); - return selectedType != null ? selectedType : typeSelectorManager.getDefaultType(); - } - - @Override - public boolean isOK() { - return true; - } - }; - } - - public interface Validator { - boolean isOK(IntroduceVariableSettings dialog); - } - - public static void checkInLoopCondition(PsiExpression occurence, MultiMap conflicts) { - final PsiElement loopForLoopCondition = RefactoringUtil.getLoopForLoopCondition(occurence); - if (loopForLoopCondition == null) return; - final List referencedVariables = RefactoringUtil.collectReferencedVariables(occurence); - final List modifiedInBody = new ArrayList(); - for (PsiVariable psiVariable : referencedVariables) { - if (RefactoringUtil.isModifiedInScope(psiVariable, loopForLoopCondition)) { - modifiedInBody.add(psiVariable); - } - } - - if (!modifiedInBody.isEmpty()) { - for (PsiVariable variable : modifiedInBody) { - final String message = RefactoringBundle.message("is.modified.in.loop.body", RefactoringUIUtil.getDescription(variable, false)); - conflicts.putValue(variable, CommonRefactoringUtil.capitalize(message)); - } - conflicts.putValue(occurence, RefactoringBundle.message("introducing.variable.may.break.code.logic")); - } - } -} +/* + * 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. + */ + +/** + * Created by IntelliJ IDEA. + * User: dsl + * Date: Nov 15, 2002 + * Time: 5:21:33 PM + * To change this template use Options | File Templates. + */ +package com.intellij.refactoring.introduceVariable; + +import com.intellij.codeInsight.CodeInsightUtil; +import com.intellij.codeInsight.completion.JavaCompletionUtil; +import com.intellij.codeInsight.highlighting.HighlightManager; +import com.intellij.codeInsight.intention.impl.TypeExpression; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.template.*; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.lang.LanguageRefactoringSupport; +import com.intellij.lang.refactoring.RefactoringSupportProvider; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.*; +import com.intellij.psi.impl.source.tree.java.ReplaceExpressionUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.*; +import com.intellij.refactoring.introduceField.ElementToWorkOn; +import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; +import com.intellij.refactoring.ui.TypeSelectorManagerImpl; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.refactoring.util.FieldConflictsResolver; +import com.intellij.refactoring.util.RefactoringUIUtil; +import com.intellij.refactoring.util.RefactoringUtil; +import com.intellij.refactoring.util.occurences.ExpressionOccurenceManager; +import com.intellij.refactoring.util.occurences.NotInSuperCallOccurenceFilter; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.*; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.List; + +public abstract class IntroduceVariableBase extends IntroduceHandlerBase implements RefactoringActionHandler { + private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase"); + @NonNls private static final String PREFER_STATEMENTS_OPTION = "introduce.variable.prefer.statements"; + + protected static String REFACTORING_NAME = RefactoringBundle.message("introduce.variable.title"); + + public static SuggestedNameInfo getSuggestedName(PsiType type, final PsiExpression expression) { + final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); + final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, expression, type); + final String[] strings = JavaCompletionUtil + .completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, nameInfo); + final SuggestedNameInfo.Delegate delegate = new SuggestedNameInfo.Delegate(strings, nameInfo); + return codeStyleManager.suggestUniqueVariableName(delegate, expression, true); + } + + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) { + final SelectionModel selectionModel = editor.getSelectionModel(); + if (!selectionModel.hasSelection()) { + final int offset = editor.getCaretModel().getOffset(); + final PsiElement[] statementsInRange = findStatementsAtOffset(editor, file, offset); + + //try line selection + if (statementsInRange.length == 1 && (PsiUtil.hasErrorElementChild(statementsInRange[0]) || !PsiUtil.isStatement(statementsInRange[0]) || isPreferStatements())) { + selectionModel.selectLineAtCaret(); + if (findExpressionInRange(project, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) == null) { + selectionModel.removeSelection(); + } + } + + if (!selectionModel.hasSelection()) { + final List expressions = collectExpressions(file, editor, offset, statementsInRange); + if (expressions.isEmpty()) { + selectionModel.selectLineAtCaret(); + } else if (expressions.size() == 1) { + final TextRange textRange = expressions.get(0).getTextRange(); + selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset()); + } + else { + IntroduceTargetChooser.showChooser(editor, expressions, + new Pass(){ + public void pass(final PsiExpression selectedValue) { + invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset()); + } + }, + new PsiExpressionTrimRenderer.RenderFunction()); + return; + } + } + } + if (invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) && + LookupManager.getActiveLookup(editor) == null) { + selectionModel.removeSelection(); + } + } + + public static boolean isPreferStatements() { + return Boolean.valueOf(PropertiesComponent.getInstance().getOrInit(PREFER_STATEMENTS_OPTION, "false")).booleanValue(); + } + + public static List collectExpressions(final PsiFile file, final Editor editor, final int offset, final PsiElement... statementsInRange) { + Document document = editor.getDocument(); + CharSequence text = document.getCharsSequence(); + int correctedOffset = offset; + int textLength = document.getTextLength(); + if (offset >= textLength) { + correctedOffset = textLength - 1; + } + else if (!Character.isJavaIdentifierPart(text.charAt(offset))) { + correctedOffset--; + } + if (correctedOffset < 0) { + correctedOffset = offset; + } + else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) { + if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line + correctedOffset--; + } + if (text.charAt(correctedOffset) != ')') { + correctedOffset = offset; + } + } + final PsiElement elementAtCaret = file.findElementAt(correctedOffset); + final List expressions = new ArrayList(); + /*for (PsiElement element : statementsInRange) { + if (element instanceof PsiExpressionStatement) { + final PsiExpression expression = ((PsiExpressionStatement)element).getExpression(); + if (expression.getType() != PsiType.VOID) { + expressions.add(expression); + } + } + }*/ + PsiExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class); + while (expression != null) { + if (!expressions.contains(expression) && !(expression instanceof PsiParenthesizedExpression) && !(expression instanceof PsiSuperExpression) && expression.getType() != PsiType.VOID) { + if (!(expression instanceof PsiReferenceExpression && (expression.getParent() instanceof PsiMethodCallExpression || + ((PsiReferenceExpression)expression).resolve() instanceof PsiClass)) + && !(expression instanceof PsiAssignmentExpression)) { + expressions.add(expression); + } + } + expression = PsiTreeUtil.getParentOfType(expression, PsiExpression.class); + } + return expressions; + } + + public static PsiElement[] findStatementsAtOffset(final Editor editor, final PsiFile file, final int offset) { + final Document document = editor.getDocument(); + final int lineNumber = document.getLineNumber(offset); + final int lineStart = document.getLineStartOffset(lineNumber); + final int lineEnd = document.getLineEndOffset(lineNumber); + + return CodeInsightUtil.findStatementsInRange(file, lineStart, lineEnd); + } + + private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable"); + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + + return invokeImpl(project, findExpressionInRange(project, file, startOffset, endOffset), editor); + } + + private static PsiExpression findExpressionInRange(Project project, PsiFile file, int startOffset, int endOffset) { + PsiExpression tempExpr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); + if (tempExpr == null) { + PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); + if (statements.length == 1) { + if (statements[0] instanceof PsiExpressionStatement) { + tempExpr = ((PsiExpressionStatement) statements[0]).getExpression(); + } else if (statements[0] instanceof PsiReturnStatement) { + tempExpr = ((PsiReturnStatement)statements[0]).getReturnValue(); + } + } + } + + if (tempExpr == null) { + tempExpr = getSelectedExpression(project, file, startOffset, endOffset); + } + return tempExpr; + } + + public static PsiExpression getSelectedExpression(final Project project, final PsiFile file, int startOffset, int endOffset) { + + PsiElement elementAtStart = file.findElementAt(startOffset); + if (elementAtStart == null || elementAtStart instanceof PsiWhiteSpace || elementAtStart instanceof PsiComment) { + elementAtStart = PsiTreeUtil.skipSiblingsForward(elementAtStart, PsiWhiteSpace.class, PsiComment.class); + if (elementAtStart == null) return null; + startOffset = elementAtStart.getTextOffset(); + } + PsiElement elementAtEnd = file.findElementAt(endOffset - 1); + if (elementAtEnd == null || elementAtEnd instanceof PsiWhiteSpace || elementAtEnd instanceof PsiComment) { + elementAtEnd = PsiTreeUtil.skipSiblingsBackward(elementAtEnd, PsiWhiteSpace.class, PsiComment.class); + if (elementAtEnd == null) return null; + endOffset = elementAtEnd.getTextRange().getEndOffset(); + } + + if (endOffset <= startOffset) return null; + + PsiExpression tempExpr; + PsiElement elementAt = PsiTreeUtil.findCommonParent(elementAtStart, elementAtEnd); + if (PsiTreeUtil.getParentOfType(elementAt, PsiExpression.class, false) == null) { + elementAt = null; + } + final PsiLiteralExpression literalExpression = PsiTreeUtil.getParentOfType(elementAt, PsiLiteralExpression.class); + + final PsiLiteralExpression startLiteralExpression = PsiTreeUtil.getParentOfType(elementAtStart, PsiLiteralExpression.class); + final PsiLiteralExpression endLiteralExpression = PsiTreeUtil.getParentOfType(file.findElementAt(endOffset), PsiLiteralExpression.class); + + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + try { + String text = file.getText().subSequence(startOffset, endOffset).toString(); + String prefix = null; + String suffix = null; + String stripped = text; + if (startLiteralExpression != null) { + final int startExpressionOffset = startLiteralExpression.getTextOffset(); + if (startOffset == startExpressionOffset) { + if (StringUtil.startsWithChar(text, '\"') || StringUtil.startsWithChar(text, '\'')) { + stripped = text.substring(1); + } + } else if (startOffset == startExpressionOffset + 1) { + text = "\"" + text; + } else if (startOffset > startExpressionOffset + 1){ + prefix = "\" + "; + text = "\"" + text; + } + } + + if (endLiteralExpression != null) { + final int endExpressionOffset = endLiteralExpression.getTextOffset() + endLiteralExpression.getTextLength(); + if (endOffset == endExpressionOffset ) { + if (StringUtil.endsWithChar(stripped, '\"') || StringUtil.endsWithChar(stripped, '\'')) { + stripped = stripped.substring(0, stripped.length() - 1); + } + } else if (endOffset == endExpressionOffset - 1) { + text += "\""; + } else if (endOffset < endExpressionOffset - 1) { + suffix = " + \""; + text += "\""; + } + } + + boolean primitive = false; + if (stripped.equals("true") || stripped.equals("false")) { + primitive = true; + } + else { + try { + Integer.parseInt(stripped); + primitive = true; + } + catch (NumberFormatException e1) { + //then not primitive + } + } + + if (primitive) { + text = stripped; + } + + final PsiElement parent = literalExpression != null ? literalExpression : elementAt; + tempExpr = elementFactory.createExpressionFromText(text, parent); + + final boolean [] hasErrors = new boolean[1]; + final JavaRecursiveElementWalkingVisitor errorsVisitor = new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitElement(final PsiElement element) { + if (hasErrors[0]) { + return; + } + super.visitElement(element); + } + + @Override + public void visitErrorElement(final PsiErrorElement element) { + hasErrors[0] = true; + } + }; + tempExpr.accept(errorsVisitor); + if (hasErrors[0]) return null; + + tempExpr.putUserData(ElementToWorkOn.PREFIX, prefix); + tempExpr.putUserData(ElementToWorkOn.SUFFIX, suffix); + + final RangeMarker rangeMarker = + FileDocumentManager.getInstance().getDocument(file.getVirtualFile()).createRangeMarker(startOffset, endOffset); + tempExpr.putUserData(ElementToWorkOn.TEXT_RANGE, rangeMarker); + + if (parent != null) { + tempExpr.putUserData(ElementToWorkOn.PARENT, parent); + } + else { + PsiErrorElement errorElement = elementAtStart instanceof PsiErrorElement + ? (PsiErrorElement)elementAtStart + : PsiTreeUtil.getNextSiblingOfType(elementAtStart, PsiErrorElement.class); + if (errorElement == null) { + errorElement = PsiTreeUtil.getParentOfType(elementAtStart, PsiErrorElement.class); + } + if (errorElement == null) return null; + if (!(errorElement.getParent() instanceof PsiClass)) return null; + tempExpr.putUserData(ElementToWorkOn.PARENT, errorElement); + tempExpr.putUserData(ElementToWorkOn.OUT_OF_CODE_BLOCK, Boolean.TRUE); + } + + final String fakeInitializer = "intellijidearulezzz"; + final int[] refIdx = new int[1]; + final PsiExpression toBeExpression = createReplacement(fakeInitializer, project, prefix, suffix, parent, rangeMarker, refIdx); + toBeExpression.accept(errorsVisitor); + if (hasErrors[0]) return null; + + final PsiReferenceExpression refExpr = PsiTreeUtil.getParentOfType(toBeExpression.findElementAt(refIdx[0]), PsiReferenceExpression.class); + assert refExpr != null; + if (ReplaceExpressionUtil.isNeedParenthesis(refExpr.getNode(), tempExpr.getNode())) { + return null; + } + } + catch (IncorrectOperationException e) { + return null; + } + + return tempExpr; + } + + protected boolean invokeImpl(final Project project, final PsiExpression expr, + final Editor editor) { + if (expr != null && expr.getParent() instanceof PsiExpressionStatement) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable.incompleteStatement"); + } + if (LOG.isDebugEnabled()) { + LOG.debug("expression:" + expr); + } + + if (expr == null) { + if (ReassignVariableUtil.reassign(editor)) return false; + String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.block.should.represent.an.expression")); + showErrorMessage(project, editor, message); + return false; + } + + + final PsiType originalType = RefactoringUtil.getTypeByExpressionWithExpectedType(expr); + if (originalType == null) { + String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("unknown.expression.type")); + showErrorMessage(project, editor, message); + return false; + } + + if (PsiType.VOID.equals(originalType)) { + String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type")); + showErrorMessage(project, editor, message); + return false; + } + + + final PsiElement physicalElement = expr.getUserData(ElementToWorkOn.PARENT); + + final PsiElement anchorStatement = RefactoringUtil.getParentStatement(physicalElement != null ? physicalElement : expr, false); + + if (anchorStatement == null) { + return parentStatementNotFound(project, editor); + } + if (anchorStatement instanceof PsiExpressionStatement) { + PsiExpression enclosingExpr = ((PsiExpressionStatement)anchorStatement).getExpression(); + if (enclosingExpr instanceof PsiMethodCallExpression) { + PsiMethod method = ((PsiMethodCallExpression)enclosingExpr).resolveMethod(); + if (method != null && method.isConstructor()) { + //This is either 'this' or 'super', both must be the first in the respective contructor + String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("invalid.expression.context")); + showErrorMessage(project, editor, message); + return false; + } + } + } + + final PsiElement tempContainer = anchorStatement.getParent(); + + if (!(tempContainer instanceof PsiCodeBlock) && !isLoopOrIf(tempContainer)) { + String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME); + showErrorMessage(project, editor, message); + return false; + } + + if(!NotInSuperCallOccurenceFilter.INSTANCE.isOK(expr)) { + String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("cannot.introduce.variable.in.super.constructor.call")); + showErrorMessage(project, editor, message); + return false; + } + + final PsiFile file = anchorStatement.getContainingFile(); + LOG.assertTrue(file != null, "expr.getContainingFile() == null"); + + if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return false; + + PsiElement containerParent = tempContainer; + PsiElement lastScope = tempContainer; + while (true) { + if (containerParent instanceof PsiFile) break; + if (containerParent instanceof PsiMethod) break; + containerParent = containerParent.getParent(); + if (containerParent instanceof PsiCodeBlock) { + lastScope = containerParent; + } + } + + final ExpressionOccurenceManager occurenceManager = new ExpressionOccurenceManager(expr, lastScope, + NotInSuperCallOccurenceFilter.INSTANCE); + final PsiExpression[] occurrences = occurenceManager.getOccurences(); + final PsiElement anchorStatementIfAll = occurenceManager.getAnchorStatementForAll(); + + final LinkedHashMap occurrencesMap = + new LinkedHashMap(); + + final boolean hasWriteAccess = OccurrencesChooser.fillChoices(expr, occurrences, occurrencesMap); + + final PsiElement nameSuggestionContext = editor != null ? file.findElementAt(editor.getCaretModel().getOffset()) : null; + final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(expr.getLanguage()); + final boolean isInplaceAvailableOnDataContext = + supportProvider != null && + editor.getSettings().isVariableInplaceRenameEnabled() && + supportProvider.isInplaceIntroduceAvailable(expr, nameSuggestionContext) && + !ApplicationManager.getApplication().isUnitTestMode(); + final boolean inFinalContext = occurenceManager.isInFinalContext(); + final InputValidator validator = new InputValidator(this, project, anchorStatementIfAll, anchorStatement, occurenceManager); + final TypeSelectorManagerImpl typeSelectorManager = new TypeSelectorManagerImpl(project, originalType, expr, occurrences); + + final Pass callback = new Pass() { + @Override + public void pass(OccurrencesChooser.ReplaceChoice choice) { + final Ref> variable = new Ref>(); + final IntroduceVariableSettings settings = + getSettings(project, editor, expr, occurrences, typeSelectorManager, inFinalContext, hasWriteAccess, validator, choice); + if (!settings.isOK()) return; + typeSelectorManager.setAllOccurences(choice != OccurrencesChooser.ReplaceChoice.NO); + final TypeExpression expression = new TypeExpression(project, typeSelectorManager.getTypesForAll()); + final RangeMarker exprMarker = editor.getDocument().createRangeMarker(expr.getTextRange()); + final SuggestedNameInfo suggestedName = getSuggestedName(settings.getSelectedType(), expr); + final List occurrenceMarkers = new ArrayList(); + for (PsiExpression occurrence : occurrences) { + occurrenceMarkers.add(editor.getDocument().createRangeMarker(occurrence.getTextRange())); + } + final Runnable runnable = + introduce(project, expr, editor, anchorStatement, tempContainer, occurrences, anchorStatementIfAll, settings, variable); + CommandProcessor.getInstance().executeCommand( + project, + new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(runnable); + if (isInplaceAvailableOnDataContext) { + final PsiVariable elementToRename = variable.get().getElement(); + if (elementToRename != null) { + editor.getCaretModel().moveToOffset(elementToRename.getTextOffset()); + final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(elementToRename, PsiDeclarationStatement.class); + editor.putUserData(ReassignVariableUtil.DECLARATION_KEY, declarationStatement); + editor.putUserData(ReassignVariableUtil.OCCURRENCES_KEY, + occurrenceMarkers.toArray(new RangeMarker[occurrenceMarkers.size()])); + final VariableInplaceRenamer renamer = new VariableInplaceRenamer(elementToRename, editor){ + @Override + protected void addAdditionalVariables(TemplateBuilderImpl builder) { + final PsiTypeElement typeElement = elementToRename.getTypeElement(); + builder.replaceElement(typeElement, "Variable_Type", + ReassignVariableUtil.createExpression(expression, typeElement.getText()), false, true); + } + }; + renamer.setAdvertisementText( + ReassignVariableUtil.getAdvertisementText(editor, declarationStatement, elementToRename.getType(), typeSelectorManager.getTypesForAll())); + renamer.performInplaceRename(false, new LinkedHashSet(Arrays.asList(suggestedName.names)), new Consumer() { + @Override + public void consume(Boolean apply) { + if (apply) { + int startOffset = exprMarker.getStartOffset(); + final PsiReference referenceAt = file.findReferenceAt(startOffset); + if (referenceAt != null && referenceAt.resolve() instanceof PsiLocalVariable) { + startOffset = referenceAt.getElement().getTextRange().getEndOffset(); + } else { + startOffset = editor.getDocument().getLineEndOffset(editor.getDocument().getLineNumber(startOffset)); + } + editor.getCaretModel().moveToOffset(startOffset); + } + editor.putUserData(ReassignVariableUtil.DECLARATION_KEY, null); + for (RangeMarker occurrenceMarker : occurrenceMarkers) { + occurrenceMarker.dispose(); + } + editor.putUserData(ReassignVariableUtil.OCCURRENCES_KEY, null); + typeSelectorManager.typeSelected(ReassignVariableUtil.getVariableType(declarationStatement)); + exprMarker.dispose(); + } + }); + } + } + } + }, REFACTORING_NAME, null); + } + }; + + if (!isInplaceAvailableOnDataContext) { + callback.pass(null); + } + else { + new OccurrencesChooser(editor).showChooser(callback, occurrencesMap); + } + return true; + } + + private static Runnable introduce(final Project project, + final PsiExpression expr, + final Editor editor, + PsiElement anchorStatement, + PsiElement tempContainer, + final PsiExpression[] occurrences, + PsiElement anchorStatementIfAll, + final IntroduceVariableSettings settings, + final Ref> variable) { + if (settings.isReplaceAllOccurrences()) { + anchorStatement = anchorStatementIfAll; + tempContainer = anchorStatement.getParent(); + } + + final PsiElement container = tempContainer; + + PsiElement child = anchorStatement; + if (!isLoopOrIf(container)) { + child = locateAnchor(child); + } + final PsiElement anchor = child == null ? anchorStatement : child; + + boolean tempDeleteSelf = false; + final boolean replaceSelf = settings.isReplaceLValues() || !RefactoringUtil.isAssignmentLHS(expr); + if (!isLoopOrIf(container)) { + if (expr.getParent() instanceof PsiExpressionStatement && anchor.equals(anchorStatement)) { + PsiStatement statement = (PsiStatement) expr.getParent(); + PsiElement parent = statement.getParent(); + if (parent instanceof PsiCodeBlock || + //fabrique + parent instanceof PsiCodeFragment) { + tempDeleteSelf = true; + } + } + tempDeleteSelf &= replaceSelf; + } + final boolean deleteSelf = tempDeleteSelf; + + + final int col = editor != null ? editor.getCaretModel().getLogicalPosition().column : 0; + final int line = editor != null ? editor.getCaretModel().getLogicalPosition().line : 0; + if (deleteSelf) { + if (editor != null) { + LogicalPosition pos = new LogicalPosition(line, col); + editor.getCaretModel().moveToLogicalPosition(pos); + } + } + + final PsiCodeBlock newDeclarationScope = PsiTreeUtil.getParentOfType(container, PsiCodeBlock.class, false); + final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(settings.getEnteredName(), newDeclarationScope); + final PsiElement finalAnchorStatement = anchorStatement; + return new Runnable() { + public void run() { + try { + PsiStatement statement = null; + final boolean isInsideLoop = isLoopOrIf(container); + if (!isInsideLoop && deleteSelf) { + statement = (PsiStatement) expr.getParent(); + } + + final PsiExpression expr1 = fieldConflictsResolver.fixInitializer(expr); + PsiExpression initializer = RefactoringUtil.unparenthesizeExpression(expr1); + if (expr1 instanceof PsiNewExpression) { + final PsiNewExpression newExpression = (PsiNewExpression)expr1; + if (newExpression.getArrayInitializer() != null) { + initializer = newExpression.getArrayInitializer(); + } + } + PsiDeclarationStatement declaration = JavaPsiFacade.getInstance(project).getElementFactory() + .createVariableDeclarationStatement(settings.getEnteredName(), settings.getSelectedType(), initializer); + if (!isInsideLoop) { + declaration = (PsiDeclarationStatement) container.addBefore(declaration, anchor); + LOG.assertTrue(expr1.isValid()); + if (deleteSelf) { // never true + final PsiElement lastChild = statement.getLastChild(); + if (lastChild instanceof PsiComment) { // keep trailing comment + declaration.addBefore(lastChild, null); + } + statement.delete(); + if (editor != null) { + LogicalPosition pos = new LogicalPosition(line, col); + editor.getCaretModel().moveToLogicalPosition(pos); + editor.getCaretModel().moveToOffset(declaration.getTextRange().getEndOffset()); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + } + } + } + + PsiExpression ref = JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(settings.getEnteredName(), null); + if (settings.isReplaceAllOccurrences()) { + ArrayList array = new ArrayList(); + for (PsiExpression occurrence : occurrences) { + if (deleteSelf && occurrence.equals(expr)) continue; + if (occurrence.equals(expr)) { + occurrence = expr1; + } + if (occurrence != null) { + occurrence = RefactoringUtil.outermostParenthesizedExpression(occurrence); + } + if (settings.isReplaceLValues() || !RefactoringUtil.isAssignmentLHS(occurrence)) { + array.add(replace(occurrence, ref, project)); + } + } + + if (editor != null) { + final PsiElement[] replacedOccurences = array.toArray(new PsiElement[array.size()]); + highlightReplacedOccurences(project, editor, replacedOccurences); + } + } else { + if (!deleteSelf && replaceSelf) { + replace(expr1, ref, project); + } + } + + declaration = (PsiDeclarationStatement) putStatementInLoopBody(declaration, container, finalAnchorStatement); + declaration = (PsiDeclarationStatement)JavaCodeStyleManager.getInstance(project).shortenClassReferences(declaration); + PsiVariable var = (PsiVariable) declaration.getDeclaredElements()[0]; + PsiUtil.setModifierProperty(var, PsiModifier.FINAL, settings.isDeclareFinal()); + variable.set(SmartPointerManager.getInstance(project).createLazyPointer(var)); + fieldConflictsResolver.fix(); + } catch (IncorrectOperationException e) { + LOG.error(e); + } + } + }; + } + + public static PsiElement replace(final PsiExpression expr1, final PsiExpression ref, final Project project) + throws IncorrectOperationException { + final PsiExpression expr2 = RefactoringUtil.outermostParenthesizedExpression(expr1); + if (expr2.isPhysical()) { + return expr2.replace(ref); + } + else { + final String prefix = expr1.getUserData(ElementToWorkOn.PREFIX); + final String suffix = expr1.getUserData(ElementToWorkOn.SUFFIX); + final PsiElement parent = expr1.getUserData(ElementToWorkOn.PARENT); + final RangeMarker rangeMarker = expr1.getUserData(ElementToWorkOn.TEXT_RANGE); + + return parent.replace(createReplacement(ref.getText(), project, prefix, suffix, parent, rangeMarker, new int[1])); + } + } + + private static PsiExpression createReplacement(final String refText, final Project project, + final String prefix, + final String suffix, + final PsiElement parent, final RangeMarker rangeMarker, int[] refIdx) { + String text = refText; + if (parent != null) { + final String allText = parent.getContainingFile().getText(); + final TextRange parentRange = parent.getTextRange(); + + String beg = allText.substring(parentRange.getStartOffset(), rangeMarker.getStartOffset()); + if (StringUtil.stripQuotesAroundValue(beg).trim().length() == 0 && prefix == null) beg = ""; + + String end = allText.substring(rangeMarker.getEndOffset(), parentRange.getEndOffset()); + if (StringUtil.stripQuotesAroundValue(end).trim().length() == 0 && suffix == null) end = ""; + + final String start = beg + (prefix != null ? prefix : ""); + refIdx[0] = start.length(); + text = start + refText + (suffix != null ? suffix : "") + end; + } + return JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(text, parent); + } + + public static PsiStatement putStatementInLoopBody(PsiStatement declaration, PsiElement container, PsiElement finalAnchorStatement) + throws IncorrectOperationException { + if(isLoopOrIf(container)) { + PsiStatement loopBody = getLoopBody(container, finalAnchorStatement); + PsiStatement loopBodyCopy = loopBody != null ? (PsiStatement) loopBody.copy() : null; + PsiBlockStatement blockStatement = (PsiBlockStatement)JavaPsiFacade.getInstance(container.getProject()).getElementFactory() + .createStatementFromText("{}", null); + blockStatement = (PsiBlockStatement) CodeStyleManager.getInstance(container.getProject()).reformat(blockStatement); + final PsiElement prevSibling = loopBody.getPrevSibling(); + if(prevSibling instanceof PsiWhiteSpace) { + final PsiElement pprev = prevSibling.getPrevSibling(); + if (!(pprev instanceof PsiComment) || !((PsiComment)pprev).getTokenType().equals(JavaTokenType.END_OF_LINE_COMMENT)) { + prevSibling.delete(); + } + } + blockStatement = (PsiBlockStatement) loopBody.replace(blockStatement); + final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); + declaration = (PsiStatement) codeBlock.add(declaration); + JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration); + if (loopBodyCopy != null) codeBlock.add(loopBodyCopy); + } + return declaration; + } + + private boolean parentStatementNotFound(final Project project, Editor editor) { + String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME); + showErrorMessage(project, editor, message); + return false; + } + + protected boolean invokeImpl(Project project, PsiLocalVariable localVariable, Editor editor) { + throw new UnsupportedOperationException(); + } + + private static PsiElement locateAnchor(PsiElement child) { + while (child != null) { + PsiElement prev = child.getPrevSibling(); + if (prev instanceof PsiStatement) break; + if (prev instanceof PsiJavaToken && ((PsiJavaToken)prev).getTokenType() == JavaTokenType.LBRACE) break; + child = prev; + } + + while (child instanceof PsiWhiteSpace || child instanceof PsiComment) { + child = child.getNextSibling(); + } + return child; + } + + protected static void highlightReplacedOccurences(Project project, Editor editor, PsiElement[] replacedOccurences){ + if (editor == null) return; + if (ApplicationManager.getApplication().isUnitTestMode()) return; + HighlightManager highlightManager = HighlightManager.getInstance(project); + EditorColorsManager colorsManager = EditorColorsManager.getInstance(); + TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); + highlightManager.addOccurrenceHighlights(editor, replacedOccurences, attributes, true, null); + WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); + } + + protected abstract void showErrorMessage(Project project, Editor editor, String message); + + @Nullable + private static PsiStatement getLoopBody(PsiElement container, PsiElement anchorStatement) { + if(container instanceof PsiLoopStatement) { + return ((PsiLoopStatement) container).getBody(); + } + else if (container instanceof PsiIfStatement) { + final PsiStatement thenBranch = ((PsiIfStatement)container).getThenBranch(); + if (thenBranch != null && PsiTreeUtil.isAncestor(thenBranch, anchorStatement, false)) { + return thenBranch; + } + final PsiStatement elseBranch = ((PsiIfStatement)container).getElseBranch(); + if (elseBranch != null && PsiTreeUtil.isAncestor(elseBranch, anchorStatement, false)) { + return elseBranch; + } + LOG.assertTrue(false); + } + LOG.assertTrue(false); + return null; + } + + + public static boolean isLoopOrIf(PsiElement element) { + return element instanceof PsiLoopStatement || element instanceof PsiIfStatement; + } + + protected boolean reportConflicts(MultiMap conflicts, Project project, IntroduceVariableSettings settings){ + return false; + } + + public IntroduceVariableSettings getSettings(Project project, Editor editor, + PsiExpression expr, PsiExpression[] occurrences, + final TypeSelectorManagerImpl typeSelectorManager, + boolean declareFinalIfAll, + boolean anyAssignmentLHS, + final InputValidator validator, + final OccurrencesChooser.ReplaceChoice replaceChoice) { + final SuggestedNameInfo suggestedName = getSuggestedName(typeSelectorManager.getDefaultType(), expr); + final String variableName = suggestedName.names[0]; + final Boolean generateFinals = JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS; + final boolean replaceAll = + replaceChoice == OccurrencesChooser.ReplaceChoice.ALL || replaceChoice == OccurrencesChooser.ReplaceChoice.NO_WRITE; + final boolean declareFinal = + !anyAssignmentLHS && (replaceAll && + declareFinalIfAll || generateFinals == null ? + CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS : + generateFinals.booleanValue()); + final boolean replaceWrite = anyAssignmentLHS && replaceChoice == OccurrencesChooser.ReplaceChoice.ALL; + return new IntroduceVariableSettings() { + @Override + public String getEnteredName() { + return variableName; + } + + @Override + public boolean isReplaceAllOccurrences() { + return replaceAll; + } + + @Override + public boolean isDeclareFinal() { + return declareFinal; + } + + @Override + public boolean isReplaceLValues() { + return replaceWrite; + } + + @Override + public PsiType getSelectedType() { + final PsiType selectedType = typeSelectorManager.getTypeSelector().getSelectedType(); + return selectedType != null ? selectedType : typeSelectorManager.getDefaultType(); + } + + @Override + public boolean isOK() { + return true; + } + }; + } + + public interface Validator { + boolean isOK(IntroduceVariableSettings dialog); + } + + public static void checkInLoopCondition(PsiExpression occurence, MultiMap conflicts) { + final PsiElement loopForLoopCondition = RefactoringUtil.getLoopForLoopCondition(occurence); + if (loopForLoopCondition == null) return; + final List referencedVariables = RefactoringUtil.collectReferencedVariables(occurence); + final List modifiedInBody = new ArrayList(); + for (PsiVariable psiVariable : referencedVariables) { + if (RefactoringUtil.isModifiedInScope(psiVariable, loopForLoopCondition)) { + modifiedInBody.add(psiVariable); + } + } + + if (!modifiedInBody.isEmpty()) { + for (PsiVariable variable : modifiedInBody) { + final String message = RefactoringBundle.message("is.modified.in.loop.body", RefactoringUIUtil.getDescription(variable, false)); + conflicts.putValue(variable, CommonRefactoringUtil.capitalize(message)); + } + conflicts.putValue(occurence, RefactoringBundle.message("introducing.variable.may.break.code.logic")); + } + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/introduceVariable/ReassignVariableUtil.java b/java/java-impl/src/com/intellij/refactoring/introduceVariable/ReassignVariableUtil.java index 4eca29d85f09..4417ef29a1fd 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceVariable/ReassignVariableUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceVariable/ReassignVariableUtil.java @@ -1,207 +1,207 @@ -/* - * 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.refactoring.introduceVariable; - -import com.intellij.codeInsight.intention.impl.TypeExpression; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.codeInsight.lookup.PsiTypeLookupItem; -import com.intellij.codeInsight.template.Expression; -import com.intellij.codeInsight.template.ExpressionContext; -import com.intellij.codeInsight.template.TextResult; -import com.intellij.codeInsight.template.impl.TemplateManagerImpl; -import com.intellij.codeInsight.template.impl.TemplateState; -import com.intellij.ide.ui.ListCellRendererWrapper; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.Shortcut; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.VisualPosition; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Key; -import com.intellij.psi.*; -import com.intellij.psi.scope.processor.VariablesProcessor; -import com.intellij.psi.scope.util.PsiScopesUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; -import com.intellij.refactoring.ui.TypeSelectorManager; -import com.intellij.refactoring.ui.TypeSelectorManagerImpl; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.components.JBList; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; - -/** - * User: anna - * Date: 11/8/10 - */ -public class ReassignVariableUtil { - static final Key DECLARATION_KEY = Key.create("var.type"); - static final Key OCCURRENCES_KEY = Key.create("occurrences"); - - private ReassignVariableUtil() { - } - - static boolean reassign(final Editor editor) { - final PsiDeclarationStatement declaration = editor.getUserData(DECLARATION_KEY); - final PsiType type = getVariableType(declaration); - if (type != null) { - VariablesProcessor proc = findVariablesOfType(editor, declaration, type); - if (proc.size() > 0) { - - if (proc.size() == 1) { - replaceWithAssignment(declaration, proc.getResult(0), editor); - return true; - } - - final DefaultListModel model = new DefaultListModel(); - for (int i = 0; i < proc.size(); i++) { - model.addElement(proc.getResult(i)); - } - final JList list = new JBList(model); - list.setCellRenderer(new ListCellRendererWrapper(new DefaultListCellRenderer()) { - @Override - public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) { - if (value instanceof PsiVariable) { - setText(((PsiVariable)value).getName()); - setIcon(((PsiVariable)value).getIcon(0)); - } - } - }); - - - final VisualPosition visualPosition = editor.getCaretModel().getVisualPosition(); - final Point point = editor.visualPositionToXY(new VisualPosition(visualPosition.line + 1, visualPosition.column)); - JBPopupFactory.getInstance().createListPopupBuilder(list) - .setTitle("Choose variable to reassign") - .setRequestFocus(true) - .setItemChoosenCallback(new Runnable() { - public void run() { - replaceWithAssignment(declaration, (PsiVariable)list.getSelectedValue(), editor); - } - }).createPopup().show(new RelativePoint(editor.getContentComponent(), point)); - } - - return true; - } - return false; - } - - @Nullable - static PsiType getVariableType(@Nullable PsiDeclarationStatement declaration) { - if (declaration != null) { - final PsiElement[] declaredElements = declaration.getDeclaredElements(); - if (declaredElements.length > 0 && declaredElements[0] instanceof PsiVariable) { - return ((PsiVariable)declaredElements[0]).getType(); - } - } - return null; - } - - private static VariablesProcessor findVariablesOfType(Editor editor, final PsiDeclarationStatement declaration, final PsiType type) { - VariablesProcessor proc = new VariablesProcessor(false) { - @Override - protected boolean check(PsiVariable var, ResolveState state) { - for (PsiElement element : declaration.getDeclaredElements()) { - if (element == var) return false; - } - return TypeConversionUtil.isAssignable(var.getType(), type); - } - }; - PsiElement scope = editor.getUserData(DECLARATION_KEY); - while (scope != null) { - if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer) break; - scope = scope.getParent(); - } - if (scope == null) return proc; - PsiScopesUtil.treeWalkUp(proc, declaration, scope); - return proc; - } - - static void replaceWithAssignment(final PsiDeclarationStatement declaration, final PsiVariable variable, final Editor editor) { - final PsiVariable var = (PsiVariable)declaration.getDeclaredElements()[0]; - final PsiExpression initializer = var.getInitializer(); - new WriteCommandAction(declaration.getProject()) { - @Override - protected void run(Result result) throws Throwable { - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(variable.getProject()); - final String chosenVariableName = variable.getName(); - //would generate red code for final variables - PsiElement newDeclaration = elementFactory.createStatementFromText(chosenVariableName + " = " + initializer.getText() + ";", - declaration); - newDeclaration = declaration.replace(newDeclaration); - final PsiFile containingFile = newDeclaration.getContainingFile(); - final RangeMarker[] occurrenceMarkers = editor.getUserData(OCCURRENCES_KEY); - if (occurrenceMarkers != null) { - for (RangeMarker marker : occurrenceMarkers) { - final PsiElement refVariableElement = containingFile.findElementAt(marker.getStartOffset()); - final PsiExpression expression = PsiTreeUtil.getParentOfType(refVariableElement, PsiReferenceExpression.class); - if (expression != null) { - expression.replace(elementFactory.createExpressionFromText(chosenVariableName, newDeclaration)); - } - } - } - } - }.execute(); - finishTemplate(editor); - } - - private static void finishTemplate(Editor editor) { - final TemplateState templateState = TemplateManagerImpl.getTemplateState(editor); - final VariableInplaceRenamer renamer = editor.getUserData(VariableInplaceRenamer.INPLACE_RENAMER); - if (templateState != null && renamer != null) { - templateState.gotoEnd(true); - editor.putUserData(VariableInplaceRenamer.INPLACE_RENAMER, null); - } - } - - @Nullable - static String getAdvertisementText(Editor editor, PsiDeclarationStatement declaration, PsiType type, PsiType[] typesForAll) { - final VariablesProcessor processor = findVariablesOfType(editor, declaration, type); - if (processor.size() > 0) { - final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); - final Shortcut[] shortcuts = keymap.getShortcuts("IntroduceVariable"); - if (shortcuts.length > 0) { - return "Press " + shortcuts[0] + " to reassign existing variable"; - } - } - return typesForAll.length > 1 ? "Press Shift Tab to change type" : null; - } - - public static Expression createExpression(final TypeExpression expression, final String defaultType) { - return new Expression() { - @Override - public com.intellij.codeInsight.template.Result calculateResult(ExpressionContext context) { - return new TextResult(defaultType); - } - - @Override - public com.intellij.codeInsight.template.Result calculateQuickResult(ExpressionContext context) { - return new TextResult(defaultType); - } - - @Override - public LookupElement[] calculateLookupItems(ExpressionContext context) { - return expression.calculateLookupItems(context); - } - }; - } -} +/* + * 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.refactoring.introduceVariable; + +import com.intellij.codeInsight.intention.impl.TypeExpression; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInsight.lookup.PsiTypeLookupItem; +import com.intellij.codeInsight.template.Expression; +import com.intellij.codeInsight.template.ExpressionContext; +import com.intellij.codeInsight.template.TextResult; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateState; +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.Shortcut; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Key; +import com.intellij.psi.*; +import com.intellij.psi.scope.processor.VariablesProcessor; +import com.intellij.psi.scope.util.PsiScopesUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; +import com.intellij.refactoring.ui.TypeSelectorManager; +import com.intellij.refactoring.ui.TypeSelectorManagerImpl; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBList; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +/** + * User: anna + * Date: 11/8/10 + */ +public class ReassignVariableUtil { + static final Key DECLARATION_KEY = Key.create("var.type"); + static final Key OCCURRENCES_KEY = Key.create("occurrences"); + + private ReassignVariableUtil() { + } + + static boolean reassign(final Editor editor) { + final PsiDeclarationStatement declaration = editor.getUserData(DECLARATION_KEY); + final PsiType type = getVariableType(declaration); + if (type != null) { + VariablesProcessor proc = findVariablesOfType(editor, declaration, type); + if (proc.size() > 0) { + + if (proc.size() == 1) { + replaceWithAssignment(declaration, proc.getResult(0), editor); + return true; + } + + final DefaultListModel model = new DefaultListModel(); + for (int i = 0; i < proc.size(); i++) { + model.addElement(proc.getResult(i)); + } + final JList list = new JBList(model); + list.setCellRenderer(new ListCellRendererWrapper(new DefaultListCellRenderer()) { + @Override + public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (value instanceof PsiVariable) { + setText(((PsiVariable)value).getName()); + setIcon(((PsiVariable)value).getIcon(0)); + } + } + }); + + + final VisualPosition visualPosition = editor.getCaretModel().getVisualPosition(); + final Point point = editor.visualPositionToXY(new VisualPosition(visualPosition.line + 1, visualPosition.column)); + JBPopupFactory.getInstance().createListPopupBuilder(list) + .setTitle("Choose variable to reassign") + .setRequestFocus(true) + .setItemChoosenCallback(new Runnable() { + public void run() { + replaceWithAssignment(declaration, (PsiVariable)list.getSelectedValue(), editor); + } + }).createPopup().show(new RelativePoint(editor.getContentComponent(), point)); + } + + return true; + } + return false; + } + + @Nullable + static PsiType getVariableType(@Nullable PsiDeclarationStatement declaration) { + if (declaration != null) { + final PsiElement[] declaredElements = declaration.getDeclaredElements(); + if (declaredElements.length > 0 && declaredElements[0] instanceof PsiVariable) { + return ((PsiVariable)declaredElements[0]).getType(); + } + } + return null; + } + + private static VariablesProcessor findVariablesOfType(Editor editor, final PsiDeclarationStatement declaration, final PsiType type) { + VariablesProcessor proc = new VariablesProcessor(false) { + @Override + protected boolean check(PsiVariable var, ResolveState state) { + for (PsiElement element : declaration.getDeclaredElements()) { + if (element == var) return false; + } + return TypeConversionUtil.isAssignable(var.getType(), type); + } + }; + PsiElement scope = editor.getUserData(DECLARATION_KEY); + while (scope != null) { + if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer) break; + scope = scope.getParent(); + } + if (scope == null) return proc; + PsiScopesUtil.treeWalkUp(proc, declaration, scope); + return proc; + } + + static void replaceWithAssignment(final PsiDeclarationStatement declaration, final PsiVariable variable, final Editor editor) { + final PsiVariable var = (PsiVariable)declaration.getDeclaredElements()[0]; + final PsiExpression initializer = var.getInitializer(); + new WriteCommandAction(declaration.getProject()) { + @Override + protected void run(Result result) throws Throwable { + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(variable.getProject()); + final String chosenVariableName = variable.getName(); + //would generate red code for final variables + PsiElement newDeclaration = elementFactory.createStatementFromText(chosenVariableName + " = " + initializer.getText() + ";", + declaration); + newDeclaration = declaration.replace(newDeclaration); + final PsiFile containingFile = newDeclaration.getContainingFile(); + final RangeMarker[] occurrenceMarkers = editor.getUserData(OCCURRENCES_KEY); + if (occurrenceMarkers != null) { + for (RangeMarker marker : occurrenceMarkers) { + final PsiElement refVariableElement = containingFile.findElementAt(marker.getStartOffset()); + final PsiExpression expression = PsiTreeUtil.getParentOfType(refVariableElement, PsiReferenceExpression.class); + if (expression != null) { + expression.replace(elementFactory.createExpressionFromText(chosenVariableName, newDeclaration)); + } + } + } + } + }.execute(); + finishTemplate(editor); + } + + private static void finishTemplate(Editor editor) { + final TemplateState templateState = TemplateManagerImpl.getTemplateState(editor); + final VariableInplaceRenamer renamer = editor.getUserData(VariableInplaceRenamer.INPLACE_RENAMER); + if (templateState != null && renamer != null) { + templateState.gotoEnd(true); + editor.putUserData(VariableInplaceRenamer.INPLACE_RENAMER, null); + } + } + + @Nullable + static String getAdvertisementText(Editor editor, PsiDeclarationStatement declaration, PsiType type, PsiType[] typesForAll) { + final VariablesProcessor processor = findVariablesOfType(editor, declaration, type); + if (processor.size() > 0) { + final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); + final Shortcut[] shortcuts = keymap.getShortcuts("IntroduceVariable"); + if (shortcuts.length > 0) { + return "Press " + shortcuts[0] + " to reassign existing variable"; + } + } + return typesForAll.length > 1 ? "Press Shift Tab to change type" : null; + } + + public static Expression createExpression(final TypeExpression expression, final String defaultType) { + return new Expression() { + @Override + public com.intellij.codeInsight.template.Result calculateResult(ExpressionContext context) { + return new TextResult(defaultType); + } + + @Override + public com.intellij.codeInsight.template.Result calculateQuickResult(ExpressionContext context) { + return new TextResult(defaultType); + } + + @Override + public LookupElement[] calculateLookupItems(ExpressionContext context) { + return expression.calculateLookupItems(context); + } + }; + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/memberPullUp/PullUpHelper.java b/java/java-impl/src/com/intellij/refactoring/memberPullUp/PullUpHelper.java index a6e6c889613c..ba0e17e0adea 100644 --- a/java/java-impl/src/com/intellij/refactoring/memberPullUp/PullUpHelper.java +++ b/java/java-impl/src/com/intellij/refactoring/memberPullUp/PullUpHelper.java @@ -1,868 +1,868 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: dsl - * Date: 14.06.2002 - * Time: 22:35:19 - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ -package com.intellij.refactoring.memberPullUp; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInsight.ChangeContextUtil; -import com.intellij.codeInsight.PsiEquivalenceUtil; -import com.intellij.codeInsight.intention.AddAnnotationFix; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Key; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.searches.OverridingMethodsSearch; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.*; -import com.intellij.refactoring.BaseRefactoringProcessor; -import com.intellij.refactoring.RefactoringBundle; -import com.intellij.refactoring.listeners.JavaRefactoringListenerManager; -import com.intellij.refactoring.listeners.impl.JavaRefactoringListenerManagerImpl; -import com.intellij.refactoring.util.DocCommentPolicy; -import com.intellij.refactoring.util.RefactoringHierarchyUtil; -import com.intellij.refactoring.util.RefactoringUIUtil; -import com.intellij.refactoring.util.RefactoringUtil; -import com.intellij.refactoring.util.classMembers.ClassMemberReferencesVisitor; -import com.intellij.refactoring.util.classMembers.MemberInfo; -import com.intellij.usageView.UsageInfo; -import com.intellij.usageView.UsageViewDescriptor; -import com.intellij.usageView.UsageViewUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.VisibilityUtil; -import com.intellij.util.containers.HashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class PullUpHelper extends BaseRefactoringProcessor{ - private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.memberPullUp.PullUpHelper"); - private static final Key PRESERVE_QUALIFIER = Key.create("PRESERVE_QUALIFIER"); - private final PsiClass mySourceClass; - private final PsiClass myTargetSuperClass; - private final boolean myIsTargetInterface; - private final MemberInfo[] myMembersToMove; - private final DocCommentPolicy myJavaDocPolicy; - private HashSet myMembersAfterMove = null; - private final PsiManager myManager; - - - public PullUpHelper(PsiClass sourceClass, PsiClass targetSuperClass, MemberInfo[] membersToMove, - DocCommentPolicy javaDocPolicy) { - super(sourceClass.getProject()); - mySourceClass = sourceClass; - myTargetSuperClass = targetSuperClass; - myMembersToMove = membersToMove; - myJavaDocPolicy = javaDocPolicy; - myIsTargetInterface = targetSuperClass.isInterface(); - myManager = mySourceClass.getManager(); - } - - protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { - return new PullUpUsageViewDescriptor(); - } - - @NotNull - protected UsageInfo[] findUsages() { - return new UsageInfo[0]; - } - - protected void refreshElements(PsiElement[] elements) { - } - - protected void performRefactoring(UsageInfo[] usages) { - moveMembersToBase(); - moveFieldInitializations(); - } - - protected String getCommandName() { - return RefactoringBundle.message("pullUp.command", UsageViewUtil.getDescriptiveName(mySourceClass)); - } - - public void moveMembersToBase() - throws IncorrectOperationException { - final HashSet movedMembers = new HashSet(); - myMembersAfterMove = new HashSet(); - - // build aux sets - for (MemberInfo info : myMembersToMove) { - movedMembers.add(info.getMember()); - } - - // correct private member visibility - for (MemberInfo info : myMembersToMove) { - if (info.getMember() instanceof PsiClass && info.getOverrides() != null) continue; - PsiModifierListOwner modifierListOwner = info.getMember(); - if (myIsTargetInterface) { - PsiUtil.setModifierProperty(modifierListOwner, PsiModifier.PUBLIC, true); - } - else if (modifierListOwner.hasModifierProperty(PsiModifier.PRIVATE)) { - if (info.isToAbstract() || willBeUsedInSubclass(modifierListOwner, movedMembers, myTargetSuperClass, mySourceClass)) { - PsiUtil.setModifierProperty(modifierListOwner, PsiModifier.PROTECTED, true); - } - if (modifierListOwner instanceof PsiClass) { - modifierListOwner.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitMethod(PsiMethod method) { - check(method); - } - - @Override - public void visitField(PsiField field) { - check(field); - } - - @Override - public void visitClass(PsiClass aClass) { - check(aClass); - super.visitClass(aClass); - } - - private void check(PsiMember member) { - if (member.hasModifierProperty(PsiModifier.PRIVATE)) { - if (willBeUsedInSubclass(member, movedMembers, myTargetSuperClass, mySourceClass)) { - PsiUtil.setModifierProperty(member, PsiModifier.PROTECTED, true); - } - } - } - }); - } - } - ChangeContextUtil.encodeContextInfo(info.getMember(), true); - } - - final PsiSubstitutor substitutor = upDownSuperClassSubstitutor(); - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject); - - // do actual move - for (MemberInfo info : myMembersToMove) { - if (info.getMember() instanceof PsiMethod) { - PsiMethod method = (PsiMethod)info.getMember(); - final boolean isOriginalMethodAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); - if (myIsTargetInterface || info.isToAbstract()) { - PsiMethod methodCopy = (PsiMethod)method.copy(); - ChangeContextUtil.clearContextInfo(method); - RefactoringUtil.abstractizeMethod(myTargetSuperClass, methodCopy); - RefactoringUtil.replaceMovedMemberTypeParameters(methodCopy, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); - if (method.findDeepestSuperMethods().length == 0 || (myTargetSuperClass.isInterface() && !PsiUtil.isLanguageLevel6OrHigher(mySourceClass))) { - deleteOverrideAnnotationIfFound(methodCopy); - } - - myJavaDocPolicy.processCopiedJavaDoc(methodCopy.getDocComment(), method.getDocComment(), isOriginalMethodAbstract); - - final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(methodCopy); - CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(method.getProject()); - if (styleSettings.INSERT_OVERRIDE_ANNOTATION) { - if (PsiUtil.isLanguageLevel5OrHigher(mySourceClass) && !myTargetSuperClass.isInterface() || PsiUtil.isLanguageLevel6OrHigher(mySourceClass)) { - new AddAnnotationFix(Override.class.getName(), method).invoke(method.getProject(), null, mySourceClass.getContainingFile()); - } - } - if (!PsiUtil.isLanguageLevel6OrHigher(mySourceClass) && myTargetSuperClass.isInterface()) { - if (isOriginalMethodAbstract) { - for (PsiMethod oMethod : OverridingMethodsSearch.search(method)) { - deleteOverrideAnnotationIfFound(oMethod); - } - } - deleteOverrideAnnotationIfFound(method); - } - myMembersAfterMove.add(movedElement); - if (isOriginalMethodAbstract) { - method.delete(); - } - } - else { - if (isOriginalMethodAbstract) { - PsiUtil.setModifierProperty(myTargetSuperClass, PsiModifier.ABSTRACT, true); - } - RefactoringUtil.replaceMovedMemberTypeParameters(method, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); - fixReferencesToStatic(method, movedMembers); - final PsiMethod superClassMethod = myTargetSuperClass.findMethodBySignature(method, false); - if (superClassMethod != null && superClassMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { - superClassMethod.replace(method); - } - else { - final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(method); - myMembersAfterMove.add(movedElement); - } - method.delete(); - } - } - else if (info.getMember() instanceof PsiField) { - PsiField field = (PsiField)info.getMember(); - field.normalizeDeclaration(); - RefactoringUtil.replaceMovedMemberTypeParameters(field, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); - fixReferencesToStatic(field, movedMembers); - if (myIsTargetInterface) { - PsiUtil.setModifierProperty(field, PsiModifier.PUBLIC, true); - } - final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(field); - myMembersAfterMove.add(movedElement); - field.delete(); - } - else if (info.getMember() instanceof PsiClass) { - PsiClass aClass = (PsiClass)info.getMember(); - if (Boolean.FALSE.equals(info.getOverrides())) { - final PsiReferenceList sourceReferenceList = info.getSourceReferenceList(); - LOG.assertTrue(sourceReferenceList != null); - PsiJavaCodeReferenceElement ref = mySourceClass.equals(sourceReferenceList.getParent()) ? - RefactoringUtil.removeFromReferenceList(sourceReferenceList, aClass) : - RefactoringUtil.findReferenceToClass(sourceReferenceList, aClass); - if (ref != null) { - RefactoringUtil.replaceMovedMemberTypeParameters(ref, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); - final PsiReferenceList referenceList = - myTargetSuperClass.isInterface() ? myTargetSuperClass.getExtendsList() : myTargetSuperClass.getImplementsList(); - assert referenceList != null; - referenceList.add(ref); - } - } - else { - RefactoringUtil.replaceMovedMemberTypeParameters(aClass, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); - fixReferencesToStatic(aClass, movedMembers); - final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(aClass); - myMembersAfterMove.add(movedElement); - aClass.delete(); - } - } - } - - ExplicitSuperDeleter explicitSuperDeleter = new ExplicitSuperDeleter(); - for (PsiMember member : myMembersAfterMove) { - member.accept(explicitSuperDeleter); - } - explicitSuperDeleter.fixSupers(); - - final QualifiedThisSuperAdjuster qualifiedThisSuperAdjuster = new QualifiedThisSuperAdjuster(); - for (PsiMember member : myMembersAfterMove) { - member.accept(qualifiedThisSuperAdjuster); - } - - ChangeContextUtil.decodeContextInfo(myTargetSuperClass, null, null); - - for (final PsiMember movedMember : myMembersAfterMove) { - movedMember.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - final PsiExpression qualifierExpression = expression.getQualifierExpression(); - if (qualifierExpression != null) { - final Boolean preserveQualifier = qualifierExpression.getCopyableUserData(PRESERVE_QUALIFIER); - if (preserveQualifier != null && !preserveQualifier) { - qualifierExpression.delete(); - return; - } - } - super.visitReferenceExpression(expression); - } - }); - final JavaRefactoringListenerManager listenerManager = JavaRefactoringListenerManager.getInstance(movedMember.getProject()); - ((JavaRefactoringListenerManagerImpl)listenerManager).fireMemberMoved(mySourceClass, movedMember); - } - } - - private PsiSubstitutor upDownSuperClassSubstitutor() { - PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; - for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(mySourceClass)) { - substitutor = substitutor.put(parameter, null); - } - final Map substitutionMap = - TypeConversionUtil.getSuperClassSubstitutor(myTargetSuperClass, mySourceClass, PsiSubstitutor.EMPTY).getSubstitutionMap(); - for (PsiTypeParameter parameter : substitutionMap.keySet()) { - final PsiType type = substitutionMap.get(parameter); - final PsiClass resolvedClass = PsiUtil.resolveClassInType(type); - if (resolvedClass instanceof PsiTypeParameter) { - substitutor = substitutor.put((PsiTypeParameter)resolvedClass, JavaPsiFacade.getElementFactory(myProject).createType(parameter)); - } - } - return substitutor; - } - - private static void deleteOverrideAnnotationIfFound(PsiMethod oMethod) { - final PsiAnnotation annotation = AnnotationUtil.findAnnotation(oMethod, Override.class.getName()); - if (annotation != null) { - annotation.delete(); - } - } - - public void moveFieldInitializations() throws IncorrectOperationException { - LOG.assertTrue(myMembersAfterMove != null); - - final LinkedHashSet movedFields = new LinkedHashSet(); - for (PsiMember member : myMembersAfterMove) { - if (member instanceof PsiField) { - movedFields.add((PsiField)member); - } - } - - if (movedFields.isEmpty()) return; - PsiMethod[] constructors = myTargetSuperClass.getConstructors(); - - if (constructors.length == 0) { - constructors = new PsiMethod[]{null}; - } - - HashMap> constructorsToSubConstructors = buildConstructorsToSubConstructorsMap(constructors); - for (PsiMethod constructor : constructors) { - HashSet subConstructors = constructorsToSubConstructors.get(constructor); - tryToMoveInitializers(constructor, subConstructors, movedFields); - } - } - - private static class Initializer { - public final PsiStatement initializer; - public final Set movedFieldsUsed; - public final Set usedParameters; - public final List statementsToRemove; - - private Initializer(PsiStatement initializer, Set movedFieldsUsed, Set usedParameters, List statementsToRemove) { - this.initializer = initializer; - this.movedFieldsUsed = movedFieldsUsed; - this.statementsToRemove = statementsToRemove; - this.usedParameters = usedParameters; - } - } - - private void tryToMoveInitializers(PsiMethod constructor, HashSet subConstructors, LinkedHashSet movedFields) throws IncorrectOperationException { - final LinkedHashMap fieldsToInitializers = new LinkedHashMap(); - boolean anyFound = false; - - for (PsiField field : movedFields) { - PsiStatement commonInitializer = null; - final ArrayList fieldInitializersToRemove = new ArrayList(); - for (PsiMethod subConstructor : subConstructors) { - commonInitializer = hasCommonInitializer(commonInitializer, subConstructor, field, fieldInitializersToRemove); - if (commonInitializer == null) break; - } - if (commonInitializer != null) { - final ParametersAndMovedFieldsUsedCollector visitor = new ParametersAndMovedFieldsUsedCollector(movedFields); - commonInitializer.accept(visitor); - fieldsToInitializers.put(field, new Initializer(commonInitializer, - visitor.getUsedFields(), visitor.getUsedParameters(), fieldInitializersToRemove)); - anyFound = true; - } - } - - if (!anyFound) return; - - - - { - final Set initializedFields = fieldsToInitializers.keySet(); - Set unmovable = RefactoringUtil.transitiveClosure( - new RefactoringUtil.Graph() { - public Set getVertices() { - return initializedFields; - } - - public Set getTargets(PsiField source) { - return fieldsToInitializers.get(source).movedFieldsUsed; - } - }, - new Condition() { - public boolean value(PsiField object) { - return !initializedFields.contains(object); - } - } - ); - - for (PsiField psiField : unmovable) { - fieldsToInitializers.remove(psiField); - } - } - - final PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); - - if (constructor == null) { - constructor = (PsiMethod) myTargetSuperClass.add(factory.createConstructor()); - final String visibilityModifier = VisibilityUtil.getVisibilityModifier(myTargetSuperClass.getModifierList()); - PsiUtil.setModifierProperty(constructor, visibilityModifier, true); - } - - - ArrayList initializedFields = new ArrayList(fieldsToInitializers.keySet()); - - Collections.sort(initializedFields, new Comparator() { - public int compare(PsiField field1, PsiField field2) { - Initializer i1 = fieldsToInitializers.get(field1); - Initializer i2 = fieldsToInitializers.get(field2); - if(i1.movedFieldsUsed.contains(field2)) return 1; - if(i2.movedFieldsUsed.contains(field1)) return -1; - return 0; - } - }); - - for (final PsiField initializedField : initializedFields) { - Initializer initializer = fieldsToInitializers.get(initializedField); - - //correct constructor parameters and subConstructors super calls - final PsiParameterList parameterList = constructor.getParameterList(); - for (final PsiParameter parameter : initializer.usedParameters) { - parameterList.add(parameter); - } - - for (final PsiMethod subConstructor : subConstructors) { - modifySuperCall(subConstructor, initializer.usedParameters); - } - - PsiStatement assignmentStatement = (PsiStatement)constructor.getBody().add(initializer.initializer); - - ChangeContextUtil.decodeContextInfo(assignmentStatement, - myTargetSuperClass, RefactoringUtil.createThisExpression(myManager, null)); - for (PsiElement psiElement : initializer.statementsToRemove) { - psiElement.delete(); - } - } - } - - private static void modifySuperCall(final PsiMethod subConstructor, final Set parametersToPassToSuper) { - final PsiCodeBlock body = subConstructor.getBody(); - if (body != null) { - PsiMethodCallExpression superCall = null; - final PsiStatement[] statements = body.getStatements(); - if (statements.length > 0) { - if (statements[0] instanceof PsiExpressionStatement) { - final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); - if (expression instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; - if ("super".equals(methodCall.getMethodExpression().getText())) { - superCall = methodCall; - } - } - } - } - - final PsiElementFactory factory = JavaPsiFacade.getInstance(subConstructor.getProject()).getElementFactory(); - try { - if (superCall == null) { - PsiExpressionStatement statement = - (PsiExpressionStatement)factory.createStatementFromText("super();", null); - statement = (PsiExpressionStatement)body.addAfter(statement, null); - superCall = (PsiMethodCallExpression)statement.getExpression(); - } - - final PsiExpressionList argList = superCall.getArgumentList(); - for (final PsiParameter parameter : parametersToPassToSuper) { - argList.add(factory.createExpressionFromText(parameter.getName(), null)); - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - } - - @Nullable - private PsiStatement hasCommonInitializer(PsiStatement commonInitializer, PsiMethod subConstructor, PsiField field, ArrayList statementsToRemove) { - final PsiCodeBlock body = subConstructor.getBody(); - if (body == null) return null; - final PsiStatement[] statements = body.getStatements(); - - // Algorithm: there should be only one write usage of field in a subConstructor, - // and in that usage field must be a target of top-level assignment, and RHS of assignment - // should be the same as commonInitializer if latter is non-null. - // - // There should be no usages before that initializer, and there should be - // no write usages afterwards. - PsiStatement commonInitializerCandidate = null; - for (PsiStatement statement : statements) { - final HashSet collectedStatements = new HashSet(); - collectPsiStatements(statement, collectedStatements); - boolean doLookup = true; - for (PsiStatement collectedStatement : collectedStatements) { - if (collectedStatement instanceof PsiExpressionStatement) { - final PsiExpression expression = ((PsiExpressionStatement)collectedStatement).getExpression(); - if (expression instanceof PsiAssignmentExpression) { - final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression; - final PsiExpression lExpression = assignmentExpression.getLExpression(); - if (lExpression instanceof PsiReferenceExpression) { - final PsiReferenceExpression lRef = (PsiReferenceExpression)lExpression; - if (lRef.getQualifierExpression() == null || lRef.getQualifierExpression() instanceof PsiThisExpression) { - final PsiElement resolved = lRef.resolve(); - if (resolved == field) { - doLookup = false; - if (commonInitializerCandidate == null) { - final PsiExpression initializer = assignmentExpression.getRExpression(); - if (initializer == null) return null; - if (commonInitializer == null) { - final IsMovableInitializerVisitor visitor = new IsMovableInitializerVisitor(); - statement.accept(visitor); - if (visitor.isMovable()) { - ChangeContextUtil.encodeContextInfo(statement, true); - PsiStatement statementCopy = (PsiStatement)statement.copy(); - ChangeContextUtil.clearContextInfo(statement); - statementsToRemove.add(statement); - commonInitializerCandidate = statementCopy; - } - else { - return null; - } - } - else { - if (PsiEquivalenceUtil.areElementsEquivalent(commonInitializer, statement)) { - statementsToRemove.add(statement); - commonInitializerCandidate = commonInitializer; - } - else { - return null; - } - } - } - else if (!PsiEquivalenceUtil.areElementsEquivalent(commonInitializerCandidate, statement)){ - return null; - } - } - } - } - } - } - } - if (doLookup) { - final PsiReference[] references = - ReferencesSearch.search(field, new LocalSearchScope(statement), false).toArray(new PsiReference[0]); - if (commonInitializerCandidate == null && references.length > 0) { - return null; - } - - for (PsiReference reference : references) { - if (RefactoringUtil.isAssignmentLHS(reference.getElement())) return null; - } - } - } - return commonInitializerCandidate; - } - - private static void collectPsiStatements(PsiElement root, Set collected) { - if (root instanceof PsiStatement){ - collected.add((PsiStatement)root); - } - - for (PsiElement element : root.getChildren()) { - collectPsiStatements(element, collected); - } - } - - private static class ParametersAndMovedFieldsUsedCollector extends JavaRecursiveElementWalkingVisitor { - private final Set myMovedFields; - private final Set myUsedFields; - - private final Set myUsedParameters = new LinkedHashSet(); - - private ParametersAndMovedFieldsUsedCollector(HashSet movedFields) { - myMovedFields = movedFields; - myUsedFields = new HashSet(); - } - - public Set getUsedParameters() { - return myUsedParameters; - } - - public Set getUsedFields() { - return myUsedFields; - } - - @Override public void visitReferenceExpression(PsiReferenceExpression expression) { - final PsiExpression qualifierExpression = expression.getQualifierExpression(); - if (qualifierExpression != null - && !(qualifierExpression instanceof PsiThisExpression)) { - return; - } - final PsiElement resolved = expression.resolve(); - if (resolved instanceof PsiParameter) { - myUsedParameters.add((PsiParameter)resolved); - } else if (myMovedFields.contains(resolved)) { - myUsedFields.add((PsiField)resolved); - } - } - } - - private class IsMovableInitializerVisitor extends JavaRecursiveElementWalkingVisitor { - private boolean myIsMovable = true; - - public boolean isMovable() { - return myIsMovable; - } - - @Override public void visitReferenceExpression(PsiReferenceExpression expression) { - visitReferenceElement(expression); - } - - @Override public void visitReferenceElement(PsiJavaCodeReferenceElement referenceElement) { - if (!myIsMovable) return; - final PsiExpression qualifier; - if (referenceElement instanceof PsiReferenceExpression) { - qualifier = ((PsiReferenceExpression) referenceElement).getQualifierExpression(); - } else { - qualifier = null; - } - if (qualifier == null || qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { - final PsiElement resolved = referenceElement.resolve(); - if (!(resolved instanceof PsiParameter)) { - if (resolved instanceof PsiClass && (((PsiClass) resolved).hasModifierProperty(PsiModifier.STATIC) || ((PsiClass)resolved).getContainingClass() == null)) { - return; - } - PsiClass containingClass = null; - if (resolved instanceof PsiMember && !((PsiMember)resolved).hasModifierProperty(PsiModifier.STATIC)) { - containingClass = ((PsiMember) resolved).getContainingClass(); - } - myIsMovable = containingClass != null && InheritanceUtil.isInheritorOrSelf(myTargetSuperClass, containingClass, true); - } - } else { - qualifier.accept(this); - } - } - - @Override public void visitElement(PsiElement element) { - if (myIsMovable) { - super.visitElement(element); - } - } - } - - private HashMap> buildConstructorsToSubConstructorsMap(final PsiMethod[] constructors) { - final HashMap> constructorsToSubConstructors = new HashMap>(); - for (PsiMethod constructor : constructors) { - final HashSet referencingSubConstructors = new HashSet(); - constructorsToSubConstructors.put(constructor, referencingSubConstructors); - if (constructor != null) { - // find references - for (PsiReference reference : ReferencesSearch.search(constructor, new LocalSearchScope(mySourceClass), false)) { - final PsiElement element = reference.getElement(); - if (element != null && "super".equals(element.getText())) { - PsiMethod parentMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); - if (parentMethod != null && parentMethod.isConstructor()) { - referencingSubConstructors.add(parentMethod); - } - } - } - } - - // check default constructor - if (constructor == null || constructor.getParameterList().getParametersCount() == 0) { - RefactoringUtil.visitImplicitSuperConstructorUsages(mySourceClass, new RefactoringUtil.ImplicitConstructorUsageVisitor() { - public void visitConstructor(PsiMethod constructor, PsiMethod baseConstructor) { - referencingSubConstructors.add(constructor); - } - - public void visitClassWithoutConstructors(PsiClass aClass) { - } - }, myTargetSuperClass); - - } - } - return constructorsToSubConstructors; - } - - private void fixReferencesToStatic(PsiElement classMember, Set movedMembers) throws IncorrectOperationException { - final StaticReferencesCollector collector = new StaticReferencesCollector(movedMembers); - classMember.accept(collector); - ArrayList refs = collector.getReferences(); - ArrayList members = collector.getReferees(); - ArrayList classes = collector.getRefereeClasses(); - PsiElementFactory factory = JavaPsiFacade.getInstance(classMember.getProject()).getElementFactory(); - - for (int i = 0; i < refs.size(); i++) { - PsiJavaCodeReferenceElement ref = refs.get(i); - PsiElement namedElement = members.get(i); - PsiClass aClass = classes.get(i); - - if (namedElement instanceof PsiNamedElement) { - PsiReferenceExpression newRef = - (PsiReferenceExpression) factory.createExpressionFromText - ("a." + ((PsiNamedElement) namedElement).getName(), - null); - PsiExpression qualifierExpression = newRef.getQualifierExpression(); - assert qualifierExpression != null; - qualifierExpression = (PsiExpression)qualifierExpression.replace(factory.createReferenceExpression(aClass)); - qualifierExpression.putCopyableUserData(PRESERVE_QUALIFIER, ref.isQualified()); - ref.replace(newRef); - } - } - } - - private class StaticReferencesCollector extends ClassMemberReferencesVisitor { - private ArrayList myReferences; - private ArrayList myReferees; - private ArrayList myRefereeClasses; - private final Set myMovedMembers; - - private StaticReferencesCollector(Set movedMembers) { - super(mySourceClass); - myMovedMembers = movedMembers; - myReferees = new ArrayList(); - myRefereeClasses = new ArrayList(); - myReferences = new ArrayList(); - } - - public ArrayList getReferees() { - return myReferees; - } - - public ArrayList getRefereeClasses() { - return myRefereeClasses; - } - - public ArrayList getReferences() { - return myReferences; - } - - protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) { - if (classMember.hasModifierProperty(PsiModifier.STATIC)) { - if (!myMovedMembers.contains(classMember) && - RefactoringHierarchyUtil.isMemberBetween(myTargetSuperClass, mySourceClass, classMember)) { - myReferences.add(classMemberReference); - myReferees.add(classMember); - myRefereeClasses.add(classMember.getContainingClass()); - } - else if (myMovedMembers.contains(classMember) || myMembersAfterMove.contains(classMember)) { - myReferences.add(classMemberReference); - myReferees.add(classMember); - myRefereeClasses.add(myTargetSuperClass); - } - } - } - } - - private class QualifiedThisSuperAdjuster extends JavaRecursiveElementVisitor { - @Override public void visitThisExpression(PsiThisExpression expression) { - super.visitThisExpression(expression); - final PsiJavaCodeReferenceElement qualifier = expression.getQualifier(); - if (qualifier != null && qualifier.isReferenceTo(mySourceClass)) { - try { - qualifier.bindToElement(myTargetSuperClass); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - } - - @Override public void visitSuperExpression(PsiSuperExpression expression) { - super.visitSuperExpression(expression); - final PsiJavaCodeReferenceElement qualifier = expression.getQualifier(); - if (qualifier != null && qualifier.isReferenceTo(mySourceClass)) { - try { - expression.replace(JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createExpressionFromText(myTargetSuperClass.getName() + ".this", null)); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - } - } - - private class ExplicitSuperDeleter extends JavaRecursiveElementWalkingVisitor { - private final ArrayList mySupersToDelete = new ArrayList(); - private final ArrayList mySupersToChangeToThis = new ArrayList(); - - @Override public void visitReferenceExpression(PsiReferenceExpression expression) { - if(expression.getQualifierExpression() instanceof PsiSuperExpression) { - PsiElement resolved = expression.resolve(); - if (resolved == null || resolved instanceof PsiMethod && shouldFixSuper((PsiMethod) resolved)) { - mySupersToDelete.add(expression.getQualifierExpression()); - } - } - } - - @Override public void visitSuperExpression(PsiSuperExpression expression) { - mySupersToChangeToThis.add(expression); - } - - @Override public void visitClass(PsiClass aClass) { - // do nothing - } - - private boolean shouldFixSuper(PsiMethod method) { - for (PsiMember element : myMembersAfterMove) { - if (element instanceof PsiMethod) { - PsiMethod member = (PsiMethod)element; - // if there is such member among moved members, super qualifier - // should not be removed - final PsiManager manager = method.getManager(); - if (manager.areElementsEquivalent(member.getContainingClass(), method.getContainingClass()) && - MethodSignatureUtil.areSignaturesEqual(member, method)) { - return false; - } - } - } - - final PsiMethod methodFromSuper = myTargetSuperClass.findMethodBySignature(method, false); - return methodFromSuper == null; - } - - public void fixSupers() throws IncorrectOperationException { - final PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); - PsiThisExpression thisExpression = (PsiThisExpression) factory.createExpressionFromText("this", null); - for (PsiExpression psiExpression : mySupersToDelete) { - psiExpression.delete(); - } - - for (PsiSuperExpression psiSuperExpression : mySupersToChangeToThis) { - psiSuperExpression.replace(thisExpression); - } - } - } - - private static boolean willBeUsedInSubclass(PsiElement member, Set movedMembers, PsiClass superclass, PsiClass subclass) { - for (PsiReference ref : ReferencesSearch.search(member, new LocalSearchScope(subclass), false)) { - PsiElement element = ref.getElement(); - if (!RefactoringHierarchyUtil.willBeInTargetClass(element, movedMembers, superclass, false)) { - return true; - } - } - return false; - } - - public static boolean checkedInterfacesContain(Collection memberInfos, PsiMethod psiMethod) { - for (MemberInfo memberInfo : memberInfos) { - if (memberInfo.isChecked() && - memberInfo.getMember() instanceof PsiClass && - Boolean.FALSE.equals(memberInfo.getOverrides())) { - if (((PsiClass)memberInfo.getMember()).findMethodBySignature(psiMethod, true) != null) { - return true; - } - } - } - return false; - } - - private class PullUpUsageViewDescriptor implements UsageViewDescriptor { - public String getProcessedElementsHeader() { - return "Pull up members from"; - } - - @NotNull - public PsiElement[] getElements() { - return new PsiElement[]{mySourceClass}; - } - - public String getCodeReferencesText(int usagesCount, int filesCount) { - return "Class to pull up members to \"" + RefactoringUIUtil.getDescription(myTargetSuperClass, true) + "\""; - } - - public String getCommentReferencesText(int usagesCount, int filesCount) { - return null; - } - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: dsl + * Date: 14.06.2002 + * Time: 22:35:19 + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.refactoring.memberPullUp; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.ChangeContextUtil; +import com.intellij.codeInsight.PsiEquivalenceUtil; +import com.intellij.codeInsight.intention.AddAnnotationFix; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Key; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.OverridingMethodsSearch; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.*; +import com.intellij.refactoring.BaseRefactoringProcessor; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.listeners.JavaRefactoringListenerManager; +import com.intellij.refactoring.listeners.impl.JavaRefactoringListenerManagerImpl; +import com.intellij.refactoring.util.DocCommentPolicy; +import com.intellij.refactoring.util.RefactoringHierarchyUtil; +import com.intellij.refactoring.util.RefactoringUIUtil; +import com.intellij.refactoring.util.RefactoringUtil; +import com.intellij.refactoring.util.classMembers.ClassMemberReferencesVisitor; +import com.intellij.refactoring.util.classMembers.MemberInfo; +import com.intellij.usageView.UsageInfo; +import com.intellij.usageView.UsageViewDescriptor; +import com.intellij.usageView.UsageViewUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.VisibilityUtil; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class PullUpHelper extends BaseRefactoringProcessor{ + private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.memberPullUp.PullUpHelper"); + private static final Key PRESERVE_QUALIFIER = Key.create("PRESERVE_QUALIFIER"); + private final PsiClass mySourceClass; + private final PsiClass myTargetSuperClass; + private final boolean myIsTargetInterface; + private final MemberInfo[] myMembersToMove; + private final DocCommentPolicy myJavaDocPolicy; + private HashSet myMembersAfterMove = null; + private final PsiManager myManager; + + + public PullUpHelper(PsiClass sourceClass, PsiClass targetSuperClass, MemberInfo[] membersToMove, + DocCommentPolicy javaDocPolicy) { + super(sourceClass.getProject()); + mySourceClass = sourceClass; + myTargetSuperClass = targetSuperClass; + myMembersToMove = membersToMove; + myJavaDocPolicy = javaDocPolicy; + myIsTargetInterface = targetSuperClass.isInterface(); + myManager = mySourceClass.getManager(); + } + + protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { + return new PullUpUsageViewDescriptor(); + } + + @NotNull + protected UsageInfo[] findUsages() { + return new UsageInfo[0]; + } + + protected void refreshElements(PsiElement[] elements) { + } + + protected void performRefactoring(UsageInfo[] usages) { + moveMembersToBase(); + moveFieldInitializations(); + } + + protected String getCommandName() { + return RefactoringBundle.message("pullUp.command", UsageViewUtil.getDescriptiveName(mySourceClass)); + } + + public void moveMembersToBase() + throws IncorrectOperationException { + final HashSet movedMembers = new HashSet(); + myMembersAfterMove = new HashSet(); + + // build aux sets + for (MemberInfo info : myMembersToMove) { + movedMembers.add(info.getMember()); + } + + // correct private member visibility + for (MemberInfo info : myMembersToMove) { + if (info.getMember() instanceof PsiClass && info.getOverrides() != null) continue; + PsiModifierListOwner modifierListOwner = info.getMember(); + if (myIsTargetInterface) { + PsiUtil.setModifierProperty(modifierListOwner, PsiModifier.PUBLIC, true); + } + else if (modifierListOwner.hasModifierProperty(PsiModifier.PRIVATE)) { + if (info.isToAbstract() || willBeUsedInSubclass(modifierListOwner, movedMembers, myTargetSuperClass, mySourceClass)) { + PsiUtil.setModifierProperty(modifierListOwner, PsiModifier.PROTECTED, true); + } + if (modifierListOwner instanceof PsiClass) { + modifierListOwner.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitMethod(PsiMethod method) { + check(method); + } + + @Override + public void visitField(PsiField field) { + check(field); + } + + @Override + public void visitClass(PsiClass aClass) { + check(aClass); + super.visitClass(aClass); + } + + private void check(PsiMember member) { + if (member.hasModifierProperty(PsiModifier.PRIVATE)) { + if (willBeUsedInSubclass(member, movedMembers, myTargetSuperClass, mySourceClass)) { + PsiUtil.setModifierProperty(member, PsiModifier.PROTECTED, true); + } + } + } + }); + } + } + ChangeContextUtil.encodeContextInfo(info.getMember(), true); + } + + final PsiSubstitutor substitutor = upDownSuperClassSubstitutor(); + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject); + + // do actual move + for (MemberInfo info : myMembersToMove) { + if (info.getMember() instanceof PsiMethod) { + PsiMethod method = (PsiMethod)info.getMember(); + final boolean isOriginalMethodAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); + if (myIsTargetInterface || info.isToAbstract()) { + PsiMethod methodCopy = (PsiMethod)method.copy(); + ChangeContextUtil.clearContextInfo(method); + RefactoringUtil.abstractizeMethod(myTargetSuperClass, methodCopy); + RefactoringUtil.replaceMovedMemberTypeParameters(methodCopy, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); + if (method.findDeepestSuperMethods().length == 0 || (myTargetSuperClass.isInterface() && !PsiUtil.isLanguageLevel6OrHigher(mySourceClass))) { + deleteOverrideAnnotationIfFound(methodCopy); + } + + myJavaDocPolicy.processCopiedJavaDoc(methodCopy.getDocComment(), method.getDocComment(), isOriginalMethodAbstract); + + final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(methodCopy); + CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(method.getProject()); + if (styleSettings.INSERT_OVERRIDE_ANNOTATION) { + if (PsiUtil.isLanguageLevel5OrHigher(mySourceClass) && !myTargetSuperClass.isInterface() || PsiUtil.isLanguageLevel6OrHigher(mySourceClass)) { + new AddAnnotationFix(Override.class.getName(), method).invoke(method.getProject(), null, mySourceClass.getContainingFile()); + } + } + if (!PsiUtil.isLanguageLevel6OrHigher(mySourceClass) && myTargetSuperClass.isInterface()) { + if (isOriginalMethodAbstract) { + for (PsiMethod oMethod : OverridingMethodsSearch.search(method)) { + deleteOverrideAnnotationIfFound(oMethod); + } + } + deleteOverrideAnnotationIfFound(method); + } + myMembersAfterMove.add(movedElement); + if (isOriginalMethodAbstract) { + method.delete(); + } + } + else { + if (isOriginalMethodAbstract) { + PsiUtil.setModifierProperty(myTargetSuperClass, PsiModifier.ABSTRACT, true); + } + RefactoringUtil.replaceMovedMemberTypeParameters(method, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); + fixReferencesToStatic(method, movedMembers); + final PsiMethod superClassMethod = myTargetSuperClass.findMethodBySignature(method, false); + if (superClassMethod != null && superClassMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { + superClassMethod.replace(method); + } + else { + final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(method); + myMembersAfterMove.add(movedElement); + } + method.delete(); + } + } + else if (info.getMember() instanceof PsiField) { + PsiField field = (PsiField)info.getMember(); + field.normalizeDeclaration(); + RefactoringUtil.replaceMovedMemberTypeParameters(field, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); + fixReferencesToStatic(field, movedMembers); + if (myIsTargetInterface) { + PsiUtil.setModifierProperty(field, PsiModifier.PUBLIC, true); + } + final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(field); + myMembersAfterMove.add(movedElement); + field.delete(); + } + else if (info.getMember() instanceof PsiClass) { + PsiClass aClass = (PsiClass)info.getMember(); + if (Boolean.FALSE.equals(info.getOverrides())) { + final PsiReferenceList sourceReferenceList = info.getSourceReferenceList(); + LOG.assertTrue(sourceReferenceList != null); + PsiJavaCodeReferenceElement ref = mySourceClass.equals(sourceReferenceList.getParent()) ? + RefactoringUtil.removeFromReferenceList(sourceReferenceList, aClass) : + RefactoringUtil.findReferenceToClass(sourceReferenceList, aClass); + if (ref != null) { + RefactoringUtil.replaceMovedMemberTypeParameters(ref, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); + final PsiReferenceList referenceList = + myTargetSuperClass.isInterface() ? myTargetSuperClass.getExtendsList() : myTargetSuperClass.getImplementsList(); + assert referenceList != null; + referenceList.add(ref); + } + } + else { + RefactoringUtil.replaceMovedMemberTypeParameters(aClass, PsiUtil.typeParametersIterable(mySourceClass), substitutor, elementFactory); + fixReferencesToStatic(aClass, movedMembers); + final PsiMember movedElement = (PsiMember)myTargetSuperClass.add(aClass); + myMembersAfterMove.add(movedElement); + aClass.delete(); + } + } + } + + ExplicitSuperDeleter explicitSuperDeleter = new ExplicitSuperDeleter(); + for (PsiMember member : myMembersAfterMove) { + member.accept(explicitSuperDeleter); + } + explicitSuperDeleter.fixSupers(); + + final QualifiedThisSuperAdjuster qualifiedThisSuperAdjuster = new QualifiedThisSuperAdjuster(); + for (PsiMember member : myMembersAfterMove) { + member.accept(qualifiedThisSuperAdjuster); + } + + ChangeContextUtil.decodeContextInfo(myTargetSuperClass, null, null); + + for (final PsiMember movedMember : myMembersAfterMove) { + movedMember.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + final PsiExpression qualifierExpression = expression.getQualifierExpression(); + if (qualifierExpression != null) { + final Boolean preserveQualifier = qualifierExpression.getCopyableUserData(PRESERVE_QUALIFIER); + if (preserveQualifier != null && !preserveQualifier) { + qualifierExpression.delete(); + return; + } + } + super.visitReferenceExpression(expression); + } + }); + final JavaRefactoringListenerManager listenerManager = JavaRefactoringListenerManager.getInstance(movedMember.getProject()); + ((JavaRefactoringListenerManagerImpl)listenerManager).fireMemberMoved(mySourceClass, movedMember); + } + } + + private PsiSubstitutor upDownSuperClassSubstitutor() { + PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(mySourceClass)) { + substitutor = substitutor.put(parameter, null); + } + final Map substitutionMap = + TypeConversionUtil.getSuperClassSubstitutor(myTargetSuperClass, mySourceClass, PsiSubstitutor.EMPTY).getSubstitutionMap(); + for (PsiTypeParameter parameter : substitutionMap.keySet()) { + final PsiType type = substitutionMap.get(parameter); + final PsiClass resolvedClass = PsiUtil.resolveClassInType(type); + if (resolvedClass instanceof PsiTypeParameter) { + substitutor = substitutor.put((PsiTypeParameter)resolvedClass, JavaPsiFacade.getElementFactory(myProject).createType(parameter)); + } + } + return substitutor; + } + + private static void deleteOverrideAnnotationIfFound(PsiMethod oMethod) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(oMethod, Override.class.getName()); + if (annotation != null) { + annotation.delete(); + } + } + + public void moveFieldInitializations() throws IncorrectOperationException { + LOG.assertTrue(myMembersAfterMove != null); + + final LinkedHashSet movedFields = new LinkedHashSet(); + for (PsiMember member : myMembersAfterMove) { + if (member instanceof PsiField) { + movedFields.add((PsiField)member); + } + } + + if (movedFields.isEmpty()) return; + PsiMethod[] constructors = myTargetSuperClass.getConstructors(); + + if (constructors.length == 0) { + constructors = new PsiMethod[]{null}; + } + + HashMap> constructorsToSubConstructors = buildConstructorsToSubConstructorsMap(constructors); + for (PsiMethod constructor : constructors) { + HashSet subConstructors = constructorsToSubConstructors.get(constructor); + tryToMoveInitializers(constructor, subConstructors, movedFields); + } + } + + private static class Initializer { + public final PsiStatement initializer; + public final Set movedFieldsUsed; + public final Set usedParameters; + public final List statementsToRemove; + + private Initializer(PsiStatement initializer, Set movedFieldsUsed, Set usedParameters, List statementsToRemove) { + this.initializer = initializer; + this.movedFieldsUsed = movedFieldsUsed; + this.statementsToRemove = statementsToRemove; + this.usedParameters = usedParameters; + } + } + + private void tryToMoveInitializers(PsiMethod constructor, HashSet subConstructors, LinkedHashSet movedFields) throws IncorrectOperationException { + final LinkedHashMap fieldsToInitializers = new LinkedHashMap(); + boolean anyFound = false; + + for (PsiField field : movedFields) { + PsiStatement commonInitializer = null; + final ArrayList fieldInitializersToRemove = new ArrayList(); + for (PsiMethod subConstructor : subConstructors) { + commonInitializer = hasCommonInitializer(commonInitializer, subConstructor, field, fieldInitializersToRemove); + if (commonInitializer == null) break; + } + if (commonInitializer != null) { + final ParametersAndMovedFieldsUsedCollector visitor = new ParametersAndMovedFieldsUsedCollector(movedFields); + commonInitializer.accept(visitor); + fieldsToInitializers.put(field, new Initializer(commonInitializer, + visitor.getUsedFields(), visitor.getUsedParameters(), fieldInitializersToRemove)); + anyFound = true; + } + } + + if (!anyFound) return; + + + + { + final Set initializedFields = fieldsToInitializers.keySet(); + Set unmovable = RefactoringUtil.transitiveClosure( + new RefactoringUtil.Graph() { + public Set getVertices() { + return initializedFields; + } + + public Set getTargets(PsiField source) { + return fieldsToInitializers.get(source).movedFieldsUsed; + } + }, + new Condition() { + public boolean value(PsiField object) { + return !initializedFields.contains(object); + } + } + ); + + for (PsiField psiField : unmovable) { + fieldsToInitializers.remove(psiField); + } + } + + final PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); + + if (constructor == null) { + constructor = (PsiMethod) myTargetSuperClass.add(factory.createConstructor()); + final String visibilityModifier = VisibilityUtil.getVisibilityModifier(myTargetSuperClass.getModifierList()); + PsiUtil.setModifierProperty(constructor, visibilityModifier, true); + } + + + ArrayList initializedFields = new ArrayList(fieldsToInitializers.keySet()); + + Collections.sort(initializedFields, new Comparator() { + public int compare(PsiField field1, PsiField field2) { + Initializer i1 = fieldsToInitializers.get(field1); + Initializer i2 = fieldsToInitializers.get(field2); + if(i1.movedFieldsUsed.contains(field2)) return 1; + if(i2.movedFieldsUsed.contains(field1)) return -1; + return 0; + } + }); + + for (final PsiField initializedField : initializedFields) { + Initializer initializer = fieldsToInitializers.get(initializedField); + + //correct constructor parameters and subConstructors super calls + final PsiParameterList parameterList = constructor.getParameterList(); + for (final PsiParameter parameter : initializer.usedParameters) { + parameterList.add(parameter); + } + + for (final PsiMethod subConstructor : subConstructors) { + modifySuperCall(subConstructor, initializer.usedParameters); + } + + PsiStatement assignmentStatement = (PsiStatement)constructor.getBody().add(initializer.initializer); + + ChangeContextUtil.decodeContextInfo(assignmentStatement, + myTargetSuperClass, RefactoringUtil.createThisExpression(myManager, null)); + for (PsiElement psiElement : initializer.statementsToRemove) { + psiElement.delete(); + } + } + } + + private static void modifySuperCall(final PsiMethod subConstructor, final Set parametersToPassToSuper) { + final PsiCodeBlock body = subConstructor.getBody(); + if (body != null) { + PsiMethodCallExpression superCall = null; + final PsiStatement[] statements = body.getStatements(); + if (statements.length > 0) { + if (statements[0] instanceof PsiExpressionStatement) { + final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); + if (expression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; + if ("super".equals(methodCall.getMethodExpression().getText())) { + superCall = methodCall; + } + } + } + } + + final PsiElementFactory factory = JavaPsiFacade.getInstance(subConstructor.getProject()).getElementFactory(); + try { + if (superCall == null) { + PsiExpressionStatement statement = + (PsiExpressionStatement)factory.createStatementFromText("super();", null); + statement = (PsiExpressionStatement)body.addAfter(statement, null); + superCall = (PsiMethodCallExpression)statement.getExpression(); + } + + final PsiExpressionList argList = superCall.getArgumentList(); + for (final PsiParameter parameter : parametersToPassToSuper) { + argList.add(factory.createExpressionFromText(parameter.getName(), null)); + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + } + + @Nullable + private PsiStatement hasCommonInitializer(PsiStatement commonInitializer, PsiMethod subConstructor, PsiField field, ArrayList statementsToRemove) { + final PsiCodeBlock body = subConstructor.getBody(); + if (body == null) return null; + final PsiStatement[] statements = body.getStatements(); + + // Algorithm: there should be only one write usage of field in a subConstructor, + // and in that usage field must be a target of top-level assignment, and RHS of assignment + // should be the same as commonInitializer if latter is non-null. + // + // There should be no usages before that initializer, and there should be + // no write usages afterwards. + PsiStatement commonInitializerCandidate = null; + for (PsiStatement statement : statements) { + final HashSet collectedStatements = new HashSet(); + collectPsiStatements(statement, collectedStatements); + boolean doLookup = true; + for (PsiStatement collectedStatement : collectedStatements) { + if (collectedStatement instanceof PsiExpressionStatement) { + final PsiExpression expression = ((PsiExpressionStatement)collectedStatement).getExpression(); + if (expression instanceof PsiAssignmentExpression) { + final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression; + final PsiExpression lExpression = assignmentExpression.getLExpression(); + if (lExpression instanceof PsiReferenceExpression) { + final PsiReferenceExpression lRef = (PsiReferenceExpression)lExpression; + if (lRef.getQualifierExpression() == null || lRef.getQualifierExpression() instanceof PsiThisExpression) { + final PsiElement resolved = lRef.resolve(); + if (resolved == field) { + doLookup = false; + if (commonInitializerCandidate == null) { + final PsiExpression initializer = assignmentExpression.getRExpression(); + if (initializer == null) return null; + if (commonInitializer == null) { + final IsMovableInitializerVisitor visitor = new IsMovableInitializerVisitor(); + statement.accept(visitor); + if (visitor.isMovable()) { + ChangeContextUtil.encodeContextInfo(statement, true); + PsiStatement statementCopy = (PsiStatement)statement.copy(); + ChangeContextUtil.clearContextInfo(statement); + statementsToRemove.add(statement); + commonInitializerCandidate = statementCopy; + } + else { + return null; + } + } + else { + if (PsiEquivalenceUtil.areElementsEquivalent(commonInitializer, statement)) { + statementsToRemove.add(statement); + commonInitializerCandidate = commonInitializer; + } + else { + return null; + } + } + } + else if (!PsiEquivalenceUtil.areElementsEquivalent(commonInitializerCandidate, statement)){ + return null; + } + } + } + } + } + } + } + if (doLookup) { + final PsiReference[] references = + ReferencesSearch.search(field, new LocalSearchScope(statement), false).toArray(new PsiReference[0]); + if (commonInitializerCandidate == null && references.length > 0) { + return null; + } + + for (PsiReference reference : references) { + if (RefactoringUtil.isAssignmentLHS(reference.getElement())) return null; + } + } + } + return commonInitializerCandidate; + } + + private static void collectPsiStatements(PsiElement root, Set collected) { + if (root instanceof PsiStatement){ + collected.add((PsiStatement)root); + } + + for (PsiElement element : root.getChildren()) { + collectPsiStatements(element, collected); + } + } + + private static class ParametersAndMovedFieldsUsedCollector extends JavaRecursiveElementWalkingVisitor { + private final Set myMovedFields; + private final Set myUsedFields; + + private final Set myUsedParameters = new LinkedHashSet(); + + private ParametersAndMovedFieldsUsedCollector(HashSet movedFields) { + myMovedFields = movedFields; + myUsedFields = new HashSet(); + } + + public Set getUsedParameters() { + return myUsedParameters; + } + + public Set getUsedFields() { + return myUsedFields; + } + + @Override public void visitReferenceExpression(PsiReferenceExpression expression) { + final PsiExpression qualifierExpression = expression.getQualifierExpression(); + if (qualifierExpression != null + && !(qualifierExpression instanceof PsiThisExpression)) { + return; + } + final PsiElement resolved = expression.resolve(); + if (resolved instanceof PsiParameter) { + myUsedParameters.add((PsiParameter)resolved); + } else if (myMovedFields.contains(resolved)) { + myUsedFields.add((PsiField)resolved); + } + } + } + + private class IsMovableInitializerVisitor extends JavaRecursiveElementWalkingVisitor { + private boolean myIsMovable = true; + + public boolean isMovable() { + return myIsMovable; + } + + @Override public void visitReferenceExpression(PsiReferenceExpression expression) { + visitReferenceElement(expression); + } + + @Override public void visitReferenceElement(PsiJavaCodeReferenceElement referenceElement) { + if (!myIsMovable) return; + final PsiExpression qualifier; + if (referenceElement instanceof PsiReferenceExpression) { + qualifier = ((PsiReferenceExpression) referenceElement).getQualifierExpression(); + } else { + qualifier = null; + } + if (qualifier == null || qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { + final PsiElement resolved = referenceElement.resolve(); + if (!(resolved instanceof PsiParameter)) { + if (resolved instanceof PsiClass && (((PsiClass) resolved).hasModifierProperty(PsiModifier.STATIC) || ((PsiClass)resolved).getContainingClass() == null)) { + return; + } + PsiClass containingClass = null; + if (resolved instanceof PsiMember && !((PsiMember)resolved).hasModifierProperty(PsiModifier.STATIC)) { + containingClass = ((PsiMember) resolved).getContainingClass(); + } + myIsMovable = containingClass != null && InheritanceUtil.isInheritorOrSelf(myTargetSuperClass, containingClass, true); + } + } else { + qualifier.accept(this); + } + } + + @Override public void visitElement(PsiElement element) { + if (myIsMovable) { + super.visitElement(element); + } + } + } + + private HashMap> buildConstructorsToSubConstructorsMap(final PsiMethod[] constructors) { + final HashMap> constructorsToSubConstructors = new HashMap>(); + for (PsiMethod constructor : constructors) { + final HashSet referencingSubConstructors = new HashSet(); + constructorsToSubConstructors.put(constructor, referencingSubConstructors); + if (constructor != null) { + // find references + for (PsiReference reference : ReferencesSearch.search(constructor, new LocalSearchScope(mySourceClass), false)) { + final PsiElement element = reference.getElement(); + if (element != null && "super".equals(element.getText())) { + PsiMethod parentMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); + if (parentMethod != null && parentMethod.isConstructor()) { + referencingSubConstructors.add(parentMethod); + } + } + } + } + + // check default constructor + if (constructor == null || constructor.getParameterList().getParametersCount() == 0) { + RefactoringUtil.visitImplicitSuperConstructorUsages(mySourceClass, new RefactoringUtil.ImplicitConstructorUsageVisitor() { + public void visitConstructor(PsiMethod constructor, PsiMethod baseConstructor) { + referencingSubConstructors.add(constructor); + } + + public void visitClassWithoutConstructors(PsiClass aClass) { + } + }, myTargetSuperClass); + + } + } + return constructorsToSubConstructors; + } + + private void fixReferencesToStatic(PsiElement classMember, Set movedMembers) throws IncorrectOperationException { + final StaticReferencesCollector collector = new StaticReferencesCollector(movedMembers); + classMember.accept(collector); + ArrayList refs = collector.getReferences(); + ArrayList members = collector.getReferees(); + ArrayList classes = collector.getRefereeClasses(); + PsiElementFactory factory = JavaPsiFacade.getInstance(classMember.getProject()).getElementFactory(); + + for (int i = 0; i < refs.size(); i++) { + PsiJavaCodeReferenceElement ref = refs.get(i); + PsiElement namedElement = members.get(i); + PsiClass aClass = classes.get(i); + + if (namedElement instanceof PsiNamedElement) { + PsiReferenceExpression newRef = + (PsiReferenceExpression) factory.createExpressionFromText + ("a." + ((PsiNamedElement) namedElement).getName(), + null); + PsiExpression qualifierExpression = newRef.getQualifierExpression(); + assert qualifierExpression != null; + qualifierExpression = (PsiExpression)qualifierExpression.replace(factory.createReferenceExpression(aClass)); + qualifierExpression.putCopyableUserData(PRESERVE_QUALIFIER, ref.isQualified()); + ref.replace(newRef); + } + } + } + + private class StaticReferencesCollector extends ClassMemberReferencesVisitor { + private ArrayList myReferences; + private ArrayList myReferees; + private ArrayList myRefereeClasses; + private final Set myMovedMembers; + + private StaticReferencesCollector(Set movedMembers) { + super(mySourceClass); + myMovedMembers = movedMembers; + myReferees = new ArrayList(); + myRefereeClasses = new ArrayList(); + myReferences = new ArrayList(); + } + + public ArrayList getReferees() { + return myReferees; + } + + public ArrayList getRefereeClasses() { + return myRefereeClasses; + } + + public ArrayList getReferences() { + return myReferences; + } + + protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) { + if (classMember.hasModifierProperty(PsiModifier.STATIC)) { + if (!myMovedMembers.contains(classMember) && + RefactoringHierarchyUtil.isMemberBetween(myTargetSuperClass, mySourceClass, classMember)) { + myReferences.add(classMemberReference); + myReferees.add(classMember); + myRefereeClasses.add(classMember.getContainingClass()); + } + else if (myMovedMembers.contains(classMember) || myMembersAfterMove.contains(classMember)) { + myReferences.add(classMemberReference); + myReferees.add(classMember); + myRefereeClasses.add(myTargetSuperClass); + } + } + } + } + + private class QualifiedThisSuperAdjuster extends JavaRecursiveElementVisitor { + @Override public void visitThisExpression(PsiThisExpression expression) { + super.visitThisExpression(expression); + final PsiJavaCodeReferenceElement qualifier = expression.getQualifier(); + if (qualifier != null && qualifier.isReferenceTo(mySourceClass)) { + try { + qualifier.bindToElement(myTargetSuperClass); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + } + + @Override public void visitSuperExpression(PsiSuperExpression expression) { + super.visitSuperExpression(expression); + final PsiJavaCodeReferenceElement qualifier = expression.getQualifier(); + if (qualifier != null && qualifier.isReferenceTo(mySourceClass)) { + try { + expression.replace(JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createExpressionFromText(myTargetSuperClass.getName() + ".this", null)); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + } + } + + private class ExplicitSuperDeleter extends JavaRecursiveElementWalkingVisitor { + private final ArrayList mySupersToDelete = new ArrayList(); + private final ArrayList mySupersToChangeToThis = new ArrayList(); + + @Override public void visitReferenceExpression(PsiReferenceExpression expression) { + if(expression.getQualifierExpression() instanceof PsiSuperExpression) { + PsiElement resolved = expression.resolve(); + if (resolved == null || resolved instanceof PsiMethod && shouldFixSuper((PsiMethod) resolved)) { + mySupersToDelete.add(expression.getQualifierExpression()); + } + } + } + + @Override public void visitSuperExpression(PsiSuperExpression expression) { + mySupersToChangeToThis.add(expression); + } + + @Override public void visitClass(PsiClass aClass) { + // do nothing + } + + private boolean shouldFixSuper(PsiMethod method) { + for (PsiMember element : myMembersAfterMove) { + if (element instanceof PsiMethod) { + PsiMethod member = (PsiMethod)element; + // if there is such member among moved members, super qualifier + // should not be removed + final PsiManager manager = method.getManager(); + if (manager.areElementsEquivalent(member.getContainingClass(), method.getContainingClass()) && + MethodSignatureUtil.areSignaturesEqual(member, method)) { + return false; + } + } + } + + final PsiMethod methodFromSuper = myTargetSuperClass.findMethodBySignature(method, false); + return methodFromSuper == null; + } + + public void fixSupers() throws IncorrectOperationException { + final PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); + PsiThisExpression thisExpression = (PsiThisExpression) factory.createExpressionFromText("this", null); + for (PsiExpression psiExpression : mySupersToDelete) { + psiExpression.delete(); + } + + for (PsiSuperExpression psiSuperExpression : mySupersToChangeToThis) { + psiSuperExpression.replace(thisExpression); + } + } + } + + private static boolean willBeUsedInSubclass(PsiElement member, Set movedMembers, PsiClass superclass, PsiClass subclass) { + for (PsiReference ref : ReferencesSearch.search(member, new LocalSearchScope(subclass), false)) { + PsiElement element = ref.getElement(); + if (!RefactoringHierarchyUtil.willBeInTargetClass(element, movedMembers, superclass, false)) { + return true; + } + } + return false; + } + + public static boolean checkedInterfacesContain(Collection memberInfos, PsiMethod psiMethod) { + for (MemberInfo memberInfo : memberInfos) { + if (memberInfo.isChecked() && + memberInfo.getMember() instanceof PsiClass && + Boolean.FALSE.equals(memberInfo.getOverrides())) { + if (((PsiClass)memberInfo.getMember()).findMethodBySignature(psiMethod, true) != null) { + return true; + } + } + } + return false; + } + + private class PullUpUsageViewDescriptor implements UsageViewDescriptor { + public String getProcessedElementsHeader() { + return "Pull up members from"; + } + + @NotNull + public PsiElement[] getElements() { + return new PsiElement[]{mySourceClass}; + } + + public String getCodeReferencesText(int usagesCount, int filesCount) { + return "Class to pull up members to \"" + RefactoringUIUtil.getDescription(myTargetSuperClass, true) + "\""; + } + + public String getCommentReferencesText(int usagesCount, int filesCount) { + return null; + } + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/ui/InfoDialog.java b/java/java-impl/src/com/intellij/refactoring/ui/InfoDialog.java index ce37621bad05..c20ade157515 100644 --- a/java/java-impl/src/com/intellij/refactoring/ui/InfoDialog.java +++ b/java/java-impl/src/com/intellij/refactoring/ui/InfoDialog.java @@ -1,87 +1,87 @@ - -/* - * 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.refactoring.ui; - -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import javax.swing.*; - -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.project.Project; -import com.intellij.util.ui.UIUtil; -import com.intellij.refactoring.RefactoringBundle; - -public class InfoDialog extends DialogWrapper{ - private JCheckBox myShowInFutureCheckBox; - private JTextArea myTextArea; - private final String myText; - private boolean isToShowInFuture; - - public InfoDialog(String text, Project project) { - super(project, false); - myText = text; - setButtonsAlignment(SwingUtilities.CENTER); - setTitle(RefactoringBundle.message("information.title")); - setButtonsMargin(null); - init(); - setOKButtonText(RefactoringBundle.message("ok.button")); - } - - protected Action[] createActions(){ - return new Action[]{getOKAction()}; - } - - protected JComponent createCenterPanel() { - JPanel panel = new JPanel(); - panel.setBorder(BorderFactory.createEtchedBorder()); - panel.setLayout(new BorderLayout()); - - JPanel cbPanel = new JPanel(new BorderLayout()); - cbPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 10)); - myShowInFutureCheckBox = new JCheckBox(); - myShowInFutureCheckBox.setText(RefactoringBundle.message("do.not.show.this.message.in.the.future")); - panel.add(cbPanel, BorderLayout.SOUTH); - cbPanel.add(myShowInFutureCheckBox, BorderLayout.WEST); - - JPanel textPanel = new JPanel(new BorderLayout()); - textPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); - panel.add(textPanel, BorderLayout.CENTER); - - myTextArea = new JTextArea(myText); - textPanel.add(myTextArea, BorderLayout.CENTER); - myTextArea.setEditable(false); - myTextArea.setBackground(UIUtil.getPanelBackground()); - Font font = myShowInFutureCheckBox.getFont(); - font = new Font(font.getName(), font.getStyle(), font.getSize() + 1); - myTextArea.setFont(font); - myShowInFutureCheckBox.setFont(font); - isToShowInFuture = true; - myShowInFutureCheckBox.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent event) { - isToShowInFuture = !myShowInFutureCheckBox.isSelected(); - } - } - ); - return panel; - } - - public boolean isToShowInFuture() { - return isToShowInFuture; - } -} + +/* + * 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.refactoring.ui; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; + +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.project.Project; +import com.intellij.util.ui.UIUtil; +import com.intellij.refactoring.RefactoringBundle; + +public class InfoDialog extends DialogWrapper{ + private JCheckBox myShowInFutureCheckBox; + private JTextArea myTextArea; + private final String myText; + private boolean isToShowInFuture; + + public InfoDialog(String text, Project project) { + super(project, false); + myText = text; + setButtonsAlignment(SwingUtilities.CENTER); + setTitle(RefactoringBundle.message("information.title")); + setButtonsMargin(null); + init(); + setOKButtonText(RefactoringBundle.message("ok.button")); + } + + protected Action[] createActions(){ + return new Action[]{getOKAction()}; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createEtchedBorder()); + panel.setLayout(new BorderLayout()); + + JPanel cbPanel = new JPanel(new BorderLayout()); + cbPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 10)); + myShowInFutureCheckBox = new JCheckBox(); + myShowInFutureCheckBox.setText(RefactoringBundle.message("do.not.show.this.message.in.the.future")); + panel.add(cbPanel, BorderLayout.SOUTH); + cbPanel.add(myShowInFutureCheckBox, BorderLayout.WEST); + + JPanel textPanel = new JPanel(new BorderLayout()); + textPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); + panel.add(textPanel, BorderLayout.CENTER); + + myTextArea = new JTextArea(myText); + textPanel.add(myTextArea, BorderLayout.CENTER); + myTextArea.setEditable(false); + myTextArea.setBackground(UIUtil.getPanelBackground()); + Font font = myShowInFutureCheckBox.getFont(); + font = new Font(font.getName(), font.getStyle(), font.getSize() + 1); + myTextArea.setFont(font); + myShowInFutureCheckBox.setFont(font); + isToShowInFuture = true; + myShowInFutureCheckBox.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent event) { + isToShowInFuture = !myShowInFutureCheckBox.isSelected(); + } + } + ); + return panel; + } + + public boolean isToShowInFuture() { + return isToShowInFuture; + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java b/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java index be11c7c7e977..8137af5677ad 100644 --- a/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java +++ b/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java @@ -1,319 +1,319 @@ -/* - * 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.refactoring.ui; - -import com.intellij.codeInsight.ExpectedTypeInfo; -import com.intellij.codeInsight.ExpectedTypeUtil; -import com.intellij.codeInsight.ExpectedTypesProvider; -import com.intellij.codeInsight.TailType; -import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.statistics.StatisticsInfo; -import com.intellij.psi.statistics.StatisticsManager; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.refactoring.util.RefactoringHierarchyUtil; -import com.intellij.util.ArrayUtil; -import gnu.trove.THashMap; -import org.jetbrains.annotations.NotNull; - -import java.util.*; - -/** - * @author dsl - */ -public class TypeSelectorManagerImpl implements TypeSelectorManager { - private SmartTypePointer myPointer; - private PsiType myDefaultType; - private final PsiExpression myMainOccurence; - private final PsiExpression[] myOccurrences; - private final PsiType[] myTypesForMain; - private final PsiType[] myTypesForAll; - private final boolean myIsOneSuggestion; - private TypeSelector myTypeSelector; - private final PsiElementFactory myFactory; - private final SmartTypePointerManager mySmartTypePointerManager; - private ExpectedTypesProvider.ExpectedClassProvider myOccurrenceClassProvider; - private ExpectedTypesProvider myExpectedTypesProvider; - - public TypeSelectorManagerImpl(Project project, PsiType type, PsiExpression mainOccurence, PsiExpression[] occurrences) { - this(project, type, null, mainOccurence, occurrences); - } - - public TypeSelectorManagerImpl(Project project, PsiType type, PsiExpression[] occurrences) { - this(project, type, occurrences, true); - } - - public TypeSelectorManagerImpl(Project project, PsiType type, PsiExpression[] occurrences, boolean areTypesDirected) { - myFactory = JavaPsiFacade.getInstance(project).getElementFactory(); - mySmartTypePointerManager = SmartTypePointerManager.getInstance(project); - setDefaultType(type); - myMainOccurence = null; - myOccurrences = occurrences; - myExpectedTypesProvider = ExpectedTypesProvider.getInstance(project); - myOccurrenceClassProvider = createOccurrenceClassProvider(); - myTypesForAll = getTypesForAll(areTypesDirected); - myTypesForMain = PsiType.EMPTY_ARRAY; - myIsOneSuggestion = myTypesForAll.length == 1; - - if (myIsOneSuggestion) { - myTypeSelector = new TypeSelector(myTypesForAll[0]); - } - else { - myTypeSelector = new TypeSelector(); - setTypesAndPreselect(myTypesForAll); - } - } - - public TypeSelectorManagerImpl(Project project, - PsiType type, - PsiMethod containingMethod, - PsiExpression mainOccurence, - PsiExpression[] occurrences) { - myFactory = JavaPsiFacade.getInstance(project).getElementFactory(); - mySmartTypePointerManager = SmartTypePointerManager.getInstance(project); - setDefaultType(type); - myMainOccurence = mainOccurence; - myOccurrences = occurrences; - myExpectedTypesProvider = ExpectedTypesProvider.getInstance(project); - - myOccurrenceClassProvider = createOccurrenceClassProvider(); - myTypesForMain = getTypesForMain(); - myTypesForAll = getTypesForAll(true); - - if (containingMethod != null) { - if (PsiUtil.resolveClassInType(type) != null) { - setDefaultType(checkIfTypeAccessible(type, project, containingMethod)); - } - } - - myIsOneSuggestion = - myTypesForMain.length == 1 && myTypesForAll.length == 1 && - myTypesForAll[0].equals(myTypesForMain[0]); - if (myIsOneSuggestion) { - myTypeSelector = new TypeSelector(myTypesForAll[0]); - } - else { - myTypeSelector = new TypeSelector(); - } - } - - private PsiType checkIfTypeAccessible(PsiType type, Project project, PsiMethod containingMethod) { - PsiClass parentClass = containingMethod.getContainingClass(); - final PsiClass typeClass = PsiUtil.resolveClassInType(type); - if (typeClass != null) { - if (typeClass instanceof PsiTypeParameter) { - if (ArrayUtil.find(parentClass.getTypeParameters(), typeClass) == -1) { //unknown type parameter - return PsiType.getJavaLangObject(PsiManager.getInstance(project), GlobalSearchScope.allScope(project)); - } - } else if (PsiTreeUtil.isAncestor(containingMethod, typeClass, true)) { //local class type - final int nextTypeIdx = ArrayUtil.find(myTypesForAll, type) + 1; - if (nextTypeIdx < myTypesForAll.length) { - return checkIfTypeAccessible(myTypesForAll[nextTypeIdx], project, containingMethod); - } - } - } - return type; - } - - public PsiType[] getTypesForAll() { - return myTypesForAll; - } - - public PsiType getDefaultType() { - if (myDefaultType.isValid()) { - return myDefaultType; - } - return myPointer.getType(); - } - - public void setDefaultType(PsiType defaultType) { - myDefaultType = defaultType; - myPointer = mySmartTypePointerManager.createSmartTypePointer(defaultType); - } - - private ExpectedTypesProvider.ExpectedClassProvider createOccurrenceClassProvider() { - final Set occurrenceClasses = new HashSet(); - for (final PsiExpression occurence : myOccurrences) { - final PsiType occurrenceType = occurence.getType(); - final PsiClass aClass = PsiUtil.resolveClassInType(occurrenceType); - if (aClass != null) { - occurrenceClasses.add(aClass); - } - } - return new ExpectedTypeUtil.ExpectedClassesFromSetProvider(occurrenceClasses); - } - - private PsiType[] getTypesForMain() { - final ExpectedTypeInfo[] expectedTypes = ExpectedTypesProvider.getExpectedTypes(myMainOccurence, false, myOccurrenceClassProvider, - false); - final ArrayList allowedTypes = new ArrayList(); - RefactoringHierarchyUtil.processSuperTypes(getDefaultType(), new RefactoringHierarchyUtil.SuperTypeVisitor() { - public void visitType(PsiType aType) { - checkIfAllowed(aType); - } - - public void visitClass(PsiClass aClass) { - checkIfAllowed(myFactory.createType(aClass)); - } - - private void checkIfAllowed(PsiType type) { - if (expectedTypes != null && expectedTypes.length > 0) { - final ExpectedTypeInfo - typeInfo = ExpectedTypesProvider.createInfo(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.NONE); - for (ExpectedTypeInfo expectedType : expectedTypes) { - if (expectedType.intersect(typeInfo).length != 0) { - allowedTypes.add(type); - break; - } - } - } - else { - allowedTypes.add(type); - } - } - }); - - ArrayList result = normalizeTypeList(allowedTypes); - return result.toArray(new PsiType[result.size()]); - } - - private PsiType[] getTypesForAll(final boolean areTypesDirected) { - final ArrayList expectedTypesFromAll = new ArrayList(); - for (PsiExpression occurrence : myOccurrences) { - - final ExpectedTypeInfo[] expectedTypes = ExpectedTypesProvider.getExpectedTypes(occurrence, false, myOccurrenceClassProvider, - isUsedAfter()); - if (expectedTypes.length > 0) { - expectedTypesFromAll.add(expectedTypes); - } - } - - final ArrayList allowedTypes = new ArrayList(); - RefactoringHierarchyUtil.processSuperTypes(getDefaultType(), new RefactoringHierarchyUtil.SuperTypeVisitor() { - public void visitType(PsiType aType) { - checkIfAllowed(aType); - } - - public void visitClass(PsiClass aClass) { - checkIfAllowed(myFactory.createType(aClass)); - } - - private void checkIfAllowed(PsiType type) { - NextInfo: - for (ExpectedTypeInfo[] expectedTypes : expectedTypesFromAll) { - for (final ExpectedTypeInfo info : expectedTypes) { - if (ExpectedTypeUtil.matches(type, info)) continue NextInfo; - } - return; - } - allowedTypes.add(type); - } - }); - - final ArrayList result = normalizeTypeList(allowedTypes); - if (!areTypesDirected) { - Collections.reverse(result); - } - return result.toArray(new PsiType[result.size()]); - } - - protected boolean isUsedAfter() { - return false; - } - - private ArrayList normalizeTypeList(final ArrayList typeList) { - ArrayList result = new ArrayList(); - TypeListCreatingVisitor visitor = new TypeListCreatingVisitor(result, myFactory); - for (PsiType psiType : typeList) { - visitor.visitType(psiType); - } - - for (int index = 0; index < result.size(); index++) { - PsiType psiType = result.get(index); - if (psiType.equals(getDefaultType())) { - result.remove(index); - break; - } - } - - final PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(getDefaultType()); - if (unboxedType != null) { - result.remove(unboxedType); - result.add(0, unboxedType); - } - result.add(0, getDefaultType()); - return result; - } - - public void setAllOccurences(boolean allOccurences) { - if (myIsOneSuggestion) return; - setTypesAndPreselect(allOccurences ? myTypesForAll : myTypesForMain); - } - - private void setTypesAndPreselect(PsiType[] types) { - myTypeSelector.setTypes(types); - - Map map = new THashMap(); - for (final PsiType type : types) { - map.put(serialize(type), type); - } - - for (StatisticsInfo info : StatisticsManager.getInstance().getAllValues(getStatsKey())) { - final PsiType candidate = map.get(info.getValue()); - if (candidate != null && StatisticsManager.getInstance().getUseCount(info) > 0) { - myTypeSelector.selectType(candidate); - return; - } - } - } - - public boolean isSuggestedType(final String fqName) { - for(PsiType type: myTypesForAll) { - if (type.getCanonicalText().equals(fqName)) { - return true; - } - } - - for(PsiType type: myTypesForMain) { - if (type.getCanonicalText().equals(fqName)) { - return true; - } - } - - return false; - } - - public void typeSelected(@NotNull PsiType type) { - StatisticsManager.getInstance().incUseCount(new StatisticsInfo(getStatsKey(), serialize(type))); - } - - private String getStatsKey() { - return "IntroduceVariable##" + serialize(getDefaultType()); - } - - private String serialize(PsiType type) { - if (PsiUtil.resolveClassInType(type) instanceof PsiTypeParameter) return type.getCanonicalText(); - return TypeConversionUtil.erasure(type).getCanonicalText(); - } - - public TypeSelector getTypeSelector() { - return myTypeSelector; - } - -} +/* + * 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.refactoring.ui; + +import com.intellij.codeInsight.ExpectedTypeInfo; +import com.intellij.codeInsight.ExpectedTypeUtil; +import com.intellij.codeInsight.ExpectedTypesProvider; +import com.intellij.codeInsight.TailType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.statistics.StatisticsInfo; +import com.intellij.psi.statistics.StatisticsManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.refactoring.util.RefactoringHierarchyUtil; +import com.intellij.util.ArrayUtil; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author dsl + */ +public class TypeSelectorManagerImpl implements TypeSelectorManager { + private SmartTypePointer myPointer; + private PsiType myDefaultType; + private final PsiExpression myMainOccurence; + private final PsiExpression[] myOccurrences; + private final PsiType[] myTypesForMain; + private final PsiType[] myTypesForAll; + private final boolean myIsOneSuggestion; + private TypeSelector myTypeSelector; + private final PsiElementFactory myFactory; + private final SmartTypePointerManager mySmartTypePointerManager; + private ExpectedTypesProvider.ExpectedClassProvider myOccurrenceClassProvider; + private ExpectedTypesProvider myExpectedTypesProvider; + + public TypeSelectorManagerImpl(Project project, PsiType type, PsiExpression mainOccurence, PsiExpression[] occurrences) { + this(project, type, null, mainOccurence, occurrences); + } + + public TypeSelectorManagerImpl(Project project, PsiType type, PsiExpression[] occurrences) { + this(project, type, occurrences, true); + } + + public TypeSelectorManagerImpl(Project project, PsiType type, PsiExpression[] occurrences, boolean areTypesDirected) { + myFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + mySmartTypePointerManager = SmartTypePointerManager.getInstance(project); + setDefaultType(type); + myMainOccurence = null; + myOccurrences = occurrences; + myExpectedTypesProvider = ExpectedTypesProvider.getInstance(project); + myOccurrenceClassProvider = createOccurrenceClassProvider(); + myTypesForAll = getTypesForAll(areTypesDirected); + myTypesForMain = PsiType.EMPTY_ARRAY; + myIsOneSuggestion = myTypesForAll.length == 1; + + if (myIsOneSuggestion) { + myTypeSelector = new TypeSelector(myTypesForAll[0]); + } + else { + myTypeSelector = new TypeSelector(); + setTypesAndPreselect(myTypesForAll); + } + } + + public TypeSelectorManagerImpl(Project project, + PsiType type, + PsiMethod containingMethod, + PsiExpression mainOccurence, + PsiExpression[] occurrences) { + myFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + mySmartTypePointerManager = SmartTypePointerManager.getInstance(project); + setDefaultType(type); + myMainOccurence = mainOccurence; + myOccurrences = occurrences; + myExpectedTypesProvider = ExpectedTypesProvider.getInstance(project); + + myOccurrenceClassProvider = createOccurrenceClassProvider(); + myTypesForMain = getTypesForMain(); + myTypesForAll = getTypesForAll(true); + + if (containingMethod != null) { + if (PsiUtil.resolveClassInType(type) != null) { + setDefaultType(checkIfTypeAccessible(type, project, containingMethod)); + } + } + + myIsOneSuggestion = + myTypesForMain.length == 1 && myTypesForAll.length == 1 && + myTypesForAll[0].equals(myTypesForMain[0]); + if (myIsOneSuggestion) { + myTypeSelector = new TypeSelector(myTypesForAll[0]); + } + else { + myTypeSelector = new TypeSelector(); + } + } + + private PsiType checkIfTypeAccessible(PsiType type, Project project, PsiMethod containingMethod) { + PsiClass parentClass = containingMethod.getContainingClass(); + final PsiClass typeClass = PsiUtil.resolveClassInType(type); + if (typeClass != null) { + if (typeClass instanceof PsiTypeParameter) { + if (ArrayUtil.find(parentClass.getTypeParameters(), typeClass) == -1) { //unknown type parameter + return PsiType.getJavaLangObject(PsiManager.getInstance(project), GlobalSearchScope.allScope(project)); + } + } else if (PsiTreeUtil.isAncestor(containingMethod, typeClass, true)) { //local class type + final int nextTypeIdx = ArrayUtil.find(myTypesForAll, type) + 1; + if (nextTypeIdx < myTypesForAll.length) { + return checkIfTypeAccessible(myTypesForAll[nextTypeIdx], project, containingMethod); + } + } + } + return type; + } + + public PsiType[] getTypesForAll() { + return myTypesForAll; + } + + public PsiType getDefaultType() { + if (myDefaultType.isValid()) { + return myDefaultType; + } + return myPointer.getType(); + } + + public void setDefaultType(PsiType defaultType) { + myDefaultType = defaultType; + myPointer = mySmartTypePointerManager.createSmartTypePointer(defaultType); + } + + private ExpectedTypesProvider.ExpectedClassProvider createOccurrenceClassProvider() { + final Set occurrenceClasses = new HashSet(); + for (final PsiExpression occurence : myOccurrences) { + final PsiType occurrenceType = occurence.getType(); + final PsiClass aClass = PsiUtil.resolveClassInType(occurrenceType); + if (aClass != null) { + occurrenceClasses.add(aClass); + } + } + return new ExpectedTypeUtil.ExpectedClassesFromSetProvider(occurrenceClasses); + } + + private PsiType[] getTypesForMain() { + final ExpectedTypeInfo[] expectedTypes = ExpectedTypesProvider.getExpectedTypes(myMainOccurence, false, myOccurrenceClassProvider, + false); + final ArrayList allowedTypes = new ArrayList(); + RefactoringHierarchyUtil.processSuperTypes(getDefaultType(), new RefactoringHierarchyUtil.SuperTypeVisitor() { + public void visitType(PsiType aType) { + checkIfAllowed(aType); + } + + public void visitClass(PsiClass aClass) { + checkIfAllowed(myFactory.createType(aClass)); + } + + private void checkIfAllowed(PsiType type) { + if (expectedTypes != null && expectedTypes.length > 0) { + final ExpectedTypeInfo + typeInfo = ExpectedTypesProvider.createInfo(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.NONE); + for (ExpectedTypeInfo expectedType : expectedTypes) { + if (expectedType.intersect(typeInfo).length != 0) { + allowedTypes.add(type); + break; + } + } + } + else { + allowedTypes.add(type); + } + } + }); + + ArrayList result = normalizeTypeList(allowedTypes); + return result.toArray(new PsiType[result.size()]); + } + + private PsiType[] getTypesForAll(final boolean areTypesDirected) { + final ArrayList expectedTypesFromAll = new ArrayList(); + for (PsiExpression occurrence : myOccurrences) { + + final ExpectedTypeInfo[] expectedTypes = ExpectedTypesProvider.getExpectedTypes(occurrence, false, myOccurrenceClassProvider, + isUsedAfter()); + if (expectedTypes.length > 0) { + expectedTypesFromAll.add(expectedTypes); + } + } + + final ArrayList allowedTypes = new ArrayList(); + RefactoringHierarchyUtil.processSuperTypes(getDefaultType(), new RefactoringHierarchyUtil.SuperTypeVisitor() { + public void visitType(PsiType aType) { + checkIfAllowed(aType); + } + + public void visitClass(PsiClass aClass) { + checkIfAllowed(myFactory.createType(aClass)); + } + + private void checkIfAllowed(PsiType type) { + NextInfo: + for (ExpectedTypeInfo[] expectedTypes : expectedTypesFromAll) { + for (final ExpectedTypeInfo info : expectedTypes) { + if (ExpectedTypeUtil.matches(type, info)) continue NextInfo; + } + return; + } + allowedTypes.add(type); + } + }); + + final ArrayList result = normalizeTypeList(allowedTypes); + if (!areTypesDirected) { + Collections.reverse(result); + } + return result.toArray(new PsiType[result.size()]); + } + + protected boolean isUsedAfter() { + return false; + } + + private ArrayList normalizeTypeList(final ArrayList typeList) { + ArrayList result = new ArrayList(); + TypeListCreatingVisitor visitor = new TypeListCreatingVisitor(result, myFactory); + for (PsiType psiType : typeList) { + visitor.visitType(psiType); + } + + for (int index = 0; index < result.size(); index++) { + PsiType psiType = result.get(index); + if (psiType.equals(getDefaultType())) { + result.remove(index); + break; + } + } + + final PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(getDefaultType()); + if (unboxedType != null) { + result.remove(unboxedType); + result.add(0, unboxedType); + } + result.add(0, getDefaultType()); + return result; + } + + public void setAllOccurences(boolean allOccurences) { + if (myIsOneSuggestion) return; + setTypesAndPreselect(allOccurences ? myTypesForAll : myTypesForMain); + } + + private void setTypesAndPreselect(PsiType[] types) { + myTypeSelector.setTypes(types); + + Map map = new THashMap(); + for (final PsiType type : types) { + map.put(serialize(type), type); + } + + for (StatisticsInfo info : StatisticsManager.getInstance().getAllValues(getStatsKey())) { + final PsiType candidate = map.get(info.getValue()); + if (candidate != null && StatisticsManager.getInstance().getUseCount(info) > 0) { + myTypeSelector.selectType(candidate); + return; + } + } + } + + public boolean isSuggestedType(final String fqName) { + for(PsiType type: myTypesForAll) { + if (type.getCanonicalText().equals(fqName)) { + return true; + } + } + + for(PsiType type: myTypesForMain) { + if (type.getCanonicalText().equals(fqName)) { + return true; + } + } + + return false; + } + + public void typeSelected(@NotNull PsiType type) { + StatisticsManager.getInstance().incUseCount(new StatisticsInfo(getStatsKey(), serialize(type))); + } + + private String getStatsKey() { + return "IntroduceVariable##" + serialize(getDefaultType()); + } + + private String serialize(PsiType type) { + if (PsiUtil.resolveClassInType(type) instanceof PsiTypeParameter) return type.getCanonicalText(); + return TypeConversionUtil.erasure(type).getCanonicalText(); + } + + public TypeSelector getTypeSelector() { + return myTypeSelector; + } + +} diff --git a/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop.java b/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop.java index 260eef5c9387..14705a9705a8 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop.java +++ b/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop.java @@ -1,7 +1,7 @@ -public class Foooo { - - { - for (finx) - } - -} +public class Foooo { + + { + for (finx) + } + +} diff --git a/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace.java b/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace.java index f707575ecb78..f1918d7ca687 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace.java +++ b/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace.java @@ -1,8 +1,8 @@ -public class Foooo { - - { - int intaaaa; - for (in) - } - -} +public class Foooo { + + { + int intaaaa; + for (in) + } + +} diff --git a/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace_after.java b/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace_after.java index 47d481f96f68..b5b04b7c477a 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace_after.java +++ b/java/java-tests/testData/codeInsight/completion/normal/PrimitiveTypesInForLoopSpace_after.java @@ -1,8 +1,8 @@ -public class Foooo { - - { - int intaaaa; - for (int ) - } - -} +public class Foooo { + + { + int intaaaa; + for (int ) + } + +} diff --git a/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn.java b/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn.java index 2a49e103ed87..de9477c87cf4 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn.java +++ b/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn.java @@ -1,8 +1,8 @@ -public abstract class Zzza { - - int foo() { - retx - } - - +public abstract class Zzza { + + int foo() { + retx + } + + } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn_after.java b/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn_after.java index a8462a4de2bc..3b12fd48f3b0 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn_after.java +++ b/java/java-tests/testData/codeInsight/completion/normal/SpaceAfterReturn_after.java @@ -1,8 +1,8 @@ -public abstract class Zzza { - - int foo() { - return x - } - - +public abstract class Zzza { + + int foo() { + return x + } + + } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/Text.txt b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/Text.txt index 6a9e922fde98..8f7b7c24f052 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/Text.txt +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lossyEncoding/Text.txt @@ -1 +1 @@ -abcd +abcd diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/after7.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/after7.java index b36afa45fc78..82c28405a968 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/after7.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/after7.java @@ -1,9 +1,9 @@ -// "Change 'implements b' to 'extends b'" "true" -class a extends b { -} - -class b {} - -class C { - static class D {} -} +// "Change 'implements b' to 'extends b'" "true" +class a extends b { +} + +class b {} + +class C { + static class D {} +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/before7.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/before7.java index 799e039033bd..7c7a75fc8ef8 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/before7.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeExtendsToImplements/before7.java @@ -1,9 +1,9 @@ -// "Change 'implements b' to 'extends b'" "true" -class a implements b { -} - -class b {} - -class C { - static class D {} -} +// "Change 'implements b' to 'extends b'" "true" +class a implements b { +} + +class b {} + +class C { + static class D {} +} diff --git a/java/java-tests/testData/codeInsight/nullityinferrer/afterAssertParamNotNull.java b/java/java-tests/testData/codeInsight/nullityinferrer/afterAssertParamNotNull.java index 3dbe44d3e7fe..b46a0beb43d9 100644 --- a/java/java-tests/testData/codeInsight/nullityinferrer/afterAssertParamNotNull.java +++ b/java/java-tests/testData/codeInsight/nullityinferrer/afterAssertParamNotNull.java @@ -1,14 +1,14 @@ -import org.jetbrains.annotations.NotNull; - -class Test { - @NotNull - public String noNull(@NotNull String text) { - assert text != null; - return ""; - } - - private void foo() { - @NotNull String str = ""; - assert str != null; - } +import org.jetbrains.annotations.NotNull; + +class Test { + @NotNull + public String noNull(@NotNull String text) { + assert text != null; + return ""; + } + + private void foo() { + @NotNull String str = ""; + assert str != null; + } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/nullityinferrer/afterConditionalReturnNotNull.java b/java/java-tests/testData/codeInsight/nullityinferrer/afterConditionalReturnNotNull.java index 5ee387e8cc74..5a89e47550ad 100644 --- a/java/java-tests/testData/codeInsight/nullityinferrer/afterConditionalReturnNotNull.java +++ b/java/java-tests/testData/codeInsight/nullityinferrer/afterConditionalReturnNotNull.java @@ -1,9 +1,9 @@ -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -class Test { - @NotNull - public String noNull( @Nullable String text) { - return text == null ? "" : text; - } +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class Test { + @NotNull + public String noNull( @Nullable String text) { + return text == null ? "" : text; + } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/nullityinferrer/afterParameterCheckedForNull.java b/java/java-tests/testData/codeInsight/nullityinferrer/afterParameterCheckedForNull.java index 908d9ab9282a..4ca28d85db02 100644 --- a/java/java-tests/testData/codeInsight/nullityinferrer/afterParameterCheckedForNull.java +++ b/java/java-tests/testData/codeInsight/nullityinferrer/afterParameterCheckedForNull.java @@ -1,37 +1,37 @@ -import org.jetbrains.annotations.*; - -class Test { - void bar(@Nullable String str) { - if (str == null) { - foo(str); - } - } - - String foo(String str) { - return str; - } - - @Nullable - String foo1(@Nullable String str) { - if (str == null); - return (str); - } - - @NotNull - String foo2(@Nullable String str) { - if (str == null); - return ((String)str); - } - - @NotNull - String fram(@Nullable String str, boolean b) { - if (str != null) { - return b ? str : "not null strimg"; - } - return "str was null"; - } - - - - +import org.jetbrains.annotations.*; + +class Test { + void bar(@Nullable String str) { + if (str == null) { + foo(str); + } + } + + String foo(String str) { + return str; + } + + @Nullable + String foo1(@Nullable String str) { + if (str == null); + return (str); + } + + @NotNull + String foo2(@Nullable String str) { + if (str == null); + return ((String)str); + } + + @NotNull + String fram(@Nullable String str, boolean b) { + if (str != null) { + return b ? str : "not null strimg"; + } + return "str was null"; + } + + + + } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/nullityinferrer/beforeAssertParamNotNull.java b/java/java-tests/testData/codeInsight/nullityinferrer/beforeAssertParamNotNull.java index e20568b6ffbd..ef4a89c56066 100644 --- a/java/java-tests/testData/codeInsight/nullityinferrer/beforeAssertParamNotNull.java +++ b/java/java-tests/testData/codeInsight/nullityinferrer/beforeAssertParamNotNull.java @@ -1,11 +1,11 @@ -class Test { - public String noNull(String text) { - assert text != null; - return ""; - } - - private void foo() { - String str = ""; - assert str != null; - } +class Test { + public String noNull(String text) { + assert text != null; + return ""; + } + + private void foo() { + String str = ""; + assert str != null; + } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/nullityinferrer/beforeConditionalReturnNotNull.java b/java/java-tests/testData/codeInsight/nullityinferrer/beforeConditionalReturnNotNull.java index cd441d4aa847..ae5f1a8524a3 100644 --- a/java/java-tests/testData/codeInsight/nullityinferrer/beforeConditionalReturnNotNull.java +++ b/java/java-tests/testData/codeInsight/nullityinferrer/beforeConditionalReturnNotNull.java @@ -1,5 +1,5 @@ -class Test { - public String noNull( String text) { - return text == null ? "" : text; - } +class Test { + public String noNull( String text) { + return text == null ? "" : text; + } } \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-partial/annotations/ExtraCommaInList.txt b/java/java-tests/testData/psi/parser-partial/annotations/ExtraCommaInList.txt index d9b50bfddc90..c58fc1971df5 100644 --- a/java/java-tests/testData/psi/parser-partial/annotations/ExtraCommaInList.txt +++ b/java/java-tests/testData/psi/parser-partial/annotations/ExtraCommaInList.txt @@ -1,21 +1,21 @@ -PsiJavaFile:ExtraCommaInList.java - PsiAnnotation - PsiJavaToken:AT('@') - PsiJavaCodeReferenceElement:Anno - PsiIdentifier:Anno('Anno') - PsiReferenceParameterList - - PsiAnnotationParameterList - PsiJavaToken:LPARENTH('(') - PsiNameValuePair - PsiArrayInitializerMemberValue:{0, 1,} - PsiJavaToken:LBRACE('{') - PsiLiteralExpression:0 - PsiJavaToken:INTEGER_LITERAL('0') - PsiJavaToken:COMMA(',') - PsiWhiteSpace(' ') - PsiLiteralExpression:1 - PsiJavaToken:INTEGER_LITERAL('1') - PsiJavaToken:COMMA(',') - PsiJavaToken:RBRACE('}') - PsiJavaToken:RPARENTH(')') +PsiJavaFile:ExtraCommaInList.java + PsiAnnotation + PsiJavaToken:AT('@') + PsiJavaCodeReferenceElement:Anno + PsiIdentifier:Anno('Anno') + PsiReferenceParameterList + + PsiAnnotationParameterList + PsiJavaToken:LPARENTH('(') + PsiNameValuePair + PsiArrayInitializerMemberValue:{0, 1,} + PsiJavaToken:LBRACE('{') + PsiLiteralExpression:0 + PsiJavaToken:INTEGER_LITERAL('0') + PsiJavaToken:COMMA(',') + PsiWhiteSpace(' ') + PsiLiteralExpression:1 + PsiJavaToken:INTEGER_LITERAL('1') + PsiJavaToken:COMMA(',') + PsiJavaToken:RBRACE('}') + PsiJavaToken:RPARENTH(')') diff --git a/java/java-tests/testData/psi/parser-partial/declarations/VarArgBrackets.txt b/java/java-tests/testData/psi/parser-partial/declarations/VarArgBrackets.txt index 624e11d00a3a..da117679ea18 100644 --- a/java/java-tests/testData/psi/parser-partial/declarations/VarArgBrackets.txt +++ b/java/java-tests/testData/psi/parser-partial/declarations/VarArgBrackets.txt @@ -1,32 +1,32 @@ -PsiJavaFile:VarArgBrackets.java - PsiJavaToken:LBRACE('{') - PsiWhiteSpace(' ') - PsiMethod:foo - PsiModifierList: - - PsiTypeParameterList - - PsiTypeElement:void - PsiKeyword:void('void') - PsiWhiteSpace(' ') - PsiIdentifier:foo('foo') - PsiParameterList:(int... x[]) - PsiJavaToken:LPARENTH('(') - PsiParameter:x - PsiModifierList: - - PsiTypeElement:int... - PsiTypeElement:int - PsiKeyword:int('int') - PsiJavaToken:ELLIPSIS('...') - PsiWhiteSpace(' ') - PsiIdentifier:x('x') - PsiErrorElement:')' expected - PsiJavaToken:LBRACKET('[') - PsiJavaToken:RBRACKET(']') - PsiJavaToken:RPARENTH(')') - PsiReferenceList - - PsiJavaToken:SEMICOLON(';') - PsiWhiteSpace(' ') +PsiJavaFile:VarArgBrackets.java + PsiJavaToken:LBRACE('{') + PsiWhiteSpace(' ') + PsiMethod:foo + PsiModifierList: + + PsiTypeParameterList + + PsiTypeElement:void + PsiKeyword:void('void') + PsiWhiteSpace(' ') + PsiIdentifier:foo('foo') + PsiParameterList:(int... x[]) + PsiJavaToken:LPARENTH('(') + PsiParameter:x + PsiModifierList: + + PsiTypeElement:int... + PsiTypeElement:int + PsiKeyword:int('int') + PsiJavaToken:ELLIPSIS('...') + PsiWhiteSpace(' ') + PsiIdentifier:x('x') + PsiErrorElement:')' expected + PsiJavaToken:LBRACKET('[') + PsiJavaToken:RBRACKET(']') + PsiJavaToken:RPARENTH(')') + PsiReferenceList + + PsiJavaToken:SEMICOLON(';') + PsiWhiteSpace(' ') PsiJavaToken:RBRACE('}') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-partial/expressions/PinesInReferenceExpression2.txt b/java/java-tests/testData/psi/parser-partial/expressions/PinesInReferenceExpression2.txt index 825cdb487fdb..52b37d3ab61b 100644 --- a/java/java-tests/testData/psi/parser-partial/expressions/PinesInReferenceExpression2.txt +++ b/java/java-tests/testData/psi/parser-partial/expressions/PinesInReferenceExpression2.txt @@ -1,17 +1,17 @@ -PsiJavaFile:PinesInReferenceExpression2.java - PsiMethodCallExpression:super(null) - PsiReferenceExpression:super - PsiReferenceParameterList - PsiJavaToken:LT('<') - PsiTypeElement:String - PsiJavaCodeReferenceElement:String - PsiIdentifier:String('String') - PsiReferenceParameterList - - PsiJavaToken:GT('>') - PsiKeyword:super('super') - PsiExpressionList - PsiJavaToken:LPARENTH('(') - PsiLiteralExpression:null - PsiJavaToken:NULL_KEYWORD('null') +PsiJavaFile:PinesInReferenceExpression2.java + PsiMethodCallExpression:super(null) + PsiReferenceExpression:super + PsiReferenceParameterList + PsiJavaToken:LT('<') + PsiTypeElement:String + PsiJavaCodeReferenceElement:String + PsiIdentifier:String('String') + PsiReferenceParameterList + + PsiJavaToken:GT('>') + PsiKeyword:super('super') + PsiExpressionList + PsiJavaToken:LPARENTH('(') + PsiLiteralExpression:null + PsiJavaToken:NULL_KEYWORD('null') PsiJavaToken:RPARENTH(')') \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration.java b/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration.java index 27203b7fcefd..a36c65d88461 100644 --- a/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration.java +++ b/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration.java @@ -1,11 +1,11 @@ -public class Test { - void foo(Object o) { - if (o instanceof A) { - ((A)o).bar(); - } - } -} - -class A { - void bar(){} +public class Test { + void foo(Object o) { + if (o instanceof A) { + ((A)o).bar(); + } + } +} + +class A { + void bar(){} } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration_after.java b/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration_after.java index beea16add93a..cf2ed5088494 100644 --- a/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/Cast4ParamGeneration_after.java @@ -1,15 +1,15 @@ -public class Test { - void foo(Object o) { - if (o instanceof A) { - newMethod((Object) o); - } - } - - private void newMethod(A o) { - ((A)o).bar(); - } -} - -class A { - void bar(){} +public class Test { + void foo(Object o) { + if (o instanceof A) { + newMethod((Object) o); + } + } + + private void newMethod(A o) { + ((A)o).bar(); + } +} + +class A { + void bar(){} } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor.java b/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor.java index 67be6c41b795..40e3e394af48 100644 --- a/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor.java +++ b/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor.java @@ -1,15 +1,15 @@ -public class B extends A { - private void f(){ - new C(); - } - - private static class C{ - private C(){ - - } - } -} - -//A.java -class A { +public class B extends A { + private void f(){ + new C(); + } + + private static class C{ + private C(){ + + } + } +} + +//A.java +class A { } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor_after.java b/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor_after.java index c98c1fe338aa..0c9baaf55407 100644 --- a/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor_after.java +++ b/java/java-tests/testData/refactoring/pullUp/PullUpPrivateInnerClassWithPrivateConstructor_after.java @@ -1,15 +1,15 @@ -public class B extends A { - private void f(){ - new C(); - } - -} - -//A.java -class A { - protected static class C{ - protected C(){ - - } - } +public class B extends A { + private void f(){ + new C(); + } + +} + +//A.java +class A { + protected static class C{ + protected C(){ + + } + } } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/suggestedTypes/NoCastWhenWrapped.java b/java/java-tests/testData/refactoring/suggestedTypes/NoCastWhenWrapped.java index ec8a205073e5..4bd81856679c 100644 --- a/java/java-tests/testData/refactoring/suggestedTypes/NoCastWhenWrapped.java +++ b/java/java-tests/testData/refactoring/suggestedTypes/NoCastWhenWrapped.java @@ -1,11 +1,11 @@ -class Test { - void foo(Object o) { - - if (o instanceof A1) { - ((A1)o).doSmth(); - } - - } -} - +class Test { + void foo(Object o) { + + if (o instanceof A1) { + ((A1)o).doSmth(); + } + + } +} + class A1 {} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/NullityInferrerTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/NullityInferrerTest.java index 18be2c7909ea..ccb879aea866 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/NullityInferrerTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/NullityInferrerTest.java @@ -1,108 +1,108 @@ -/* - * 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.codeInsight; - -import com.intellij.JavaTestUtil; -import com.intellij.codeInspection.inferNullity.NullityInferrer; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; - -/** - * User: anna - * Date: Sep 2, 2010 - */ -public class NullityInferrerTest extends CodeInsightTestCase { - @Override - protected String getTestDataPath() { - return JavaTestUtil.getJavaTestDataPath(); - } - - //-----------------------params and return values--------------------------------- - public void testParameterPassed2NotNull() throws Exception { - doTest(false); - } - - public void testParameterCheckedForNull() throws Exception { - doTest(false); - } - - public void testParameterDereferenced() throws Exception { - doTest(false); - } - - public void testParameterUsedInForeachIteratedValue() throws Exception { - doTest(false); - } - - public void testForEachParameter() throws Exception { - doTest(true); - } - - public void testConditionalReturnNotNull() throws Exception { - doTest(false); - } - - public void testAssertParamNotNull() throws Exception { - doTest(true); - } - - //-----------------------fields--------------------------------------------------- - public void testFieldsAssignment() throws Exception { - doTest(false); - } - - //-----------------------methods--------------------------------------------------- - public void testMethodReturnValue() throws Exception { - doTest(false); - } - - - private void doTest(boolean annotateLocalVariables) throws Exception { - final String nullityPath = "/codeInsight/nullityinferrer"; - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - final VirtualFile aLib = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + nullityPath + "/lib/annotations.jar"); - if (aLib != null) { - final VirtualFile file = JarFileSystem.getInstance().getJarRootForLocalFile(aLib); - if (file != null) { - final ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); - final LibraryTable libraryTable = model.getModuleLibraryTable(); - final Library library = libraryTable.createLibrary("test"); - - final Library.ModifiableModel libraryModel = library.getModifiableModel(); - libraryModel.addRoot(file.getUrl(), OrderRootType.CLASSES); - libraryModel.commit(); - model.commit(); - } - } - } - }); - - configureByFile(nullityPath + "/before" + getTestName(false) + ".java"); - final NullityInferrer nullityInferrer = new NullityInferrer(annotateLocalVariables, getProject()); - nullityInferrer.collect(getFile()); - nullityInferrer.apply(getProject()); - checkResultByFile(nullityPath + "/after" + getTestName(false)+ ".java"); - } -} +/* + * 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.codeInsight; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInspection.inferNullity.NullityInferrer; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; + +/** + * User: anna + * Date: Sep 2, 2010 + */ +public class NullityInferrerTest extends CodeInsightTestCase { + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath(); + } + + //-----------------------params and return values--------------------------------- + public void testParameterPassed2NotNull() throws Exception { + doTest(false); + } + + public void testParameterCheckedForNull() throws Exception { + doTest(false); + } + + public void testParameterDereferenced() throws Exception { + doTest(false); + } + + public void testParameterUsedInForeachIteratedValue() throws Exception { + doTest(false); + } + + public void testForEachParameter() throws Exception { + doTest(true); + } + + public void testConditionalReturnNotNull() throws Exception { + doTest(false); + } + + public void testAssertParamNotNull() throws Exception { + doTest(true); + } + + //-----------------------fields--------------------------------------------------- + public void testFieldsAssignment() throws Exception { + doTest(false); + } + + //-----------------------methods--------------------------------------------------- + public void testMethodReturnValue() throws Exception { + doTest(false); + } + + + private void doTest(boolean annotateLocalVariables) throws Exception { + final String nullityPath = "/codeInsight/nullityinferrer"; + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final VirtualFile aLib = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + nullityPath + "/lib/annotations.jar"); + if (aLib != null) { + final VirtualFile file = JarFileSystem.getInstance().getJarRootForLocalFile(aLib); + if (file != null) { + final ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); + final LibraryTable libraryTable = model.getModuleLibraryTable(); + final Library library = libraryTable.createLibrary("test"); + + final Library.ModifiableModel libraryModel = library.getModifiableModel(); + libraryModel.addRoot(file.getUrl(), OrderRootType.CLASSES); + libraryModel.commit(); + model.commit(); + } + } + } + }); + + configureByFile(nullityPath + "/before" + getTestName(false) + ".java"); + final NullityInferrer nullityInferrer = new NullityInferrer(annotateLocalVariables, getProject()); + nullityInferrer.collect(getFile()); + nullityInferrer.apply(getProject()); + checkResultByFile(nullityPath + "/after" + getTestName(false)+ ".java"); + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index b40a150a7a61..2dd319a56d86 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -1,866 +1,866 @@ -package com.intellij.codeInsight.completion; - - -import com.intellij.JavaTestUtil -import com.intellij.codeInsight.CodeInsightSettings -import com.intellij.codeInsight.lookup.Lookup -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupManager -import com.intellij.openapi.command.WriteCommandAction -import com.intellij.psi.CommonClassNames -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiMethod -import com.intellij.psi.codeStyle.CodeStyleSettings -import com.intellij.psi.codeStyle.CodeStyleSettingsManager - -public class NormalCompletionTest extends LightFixtureCompletionTestCase { - @Override - protected String getBasePath() { - return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/normal/"; - } - - public void testSimple() throws Exception { - configureByFile("Simple.java"); - assertStringItems("_local1", "_local2", "_field", "_method", "_baseField", "_baseMethod"); - } - - public void testDontCompleteFieldsAndMethodsInReferenceCodeFragment() throws Throwable { - final String text = CommonClassNames.JAVA_LANG_OBJECT + "."; - PsiFile file = getJavaFacade().getElementFactory().createReferenceCodeFragment(text, null, true, true); - myFixture.configureFromExistingVirtualFile(file.getVirtualFile()); - complete(); - myFixture.checkResult(text); - assertEmpty(myItems); - } - - public void testNoPackagesInExpressionCodeFragment() throws Throwable { - final String text = "jav"; - PsiFile file = getJavaFacade().getElementFactory().createExpressionCodeFragment(text, null, null, true); - myFixture.configureFromExistingVirtualFile(file.getVirtualFile()); - complete(); - myFixture.checkResult(text); - assertEmpty(myItems); - } - - public void testSubPackagesInExpressionCodeFragment() throws Throwable { - PsiFile file = getJavaFacade().getElementFactory().createExpressionCodeFragment("java.la", null, null, true); - myFixture.configureFromExistingVirtualFile(file.getVirtualFile()); - complete(); - myFixture.checkResult("java.lang."); - assertNull(myItems); - } - - private JavaPsiFacade getJavaFacade() { - return JavaPsiFacade.getInstance(getProject()); - } - - public void testCastToPrimitive1() throws Exception { - configureByFile("CastToPrimitive1.java"); - - for (final LookupElement item : myItems) { - if (item.getLookupString().equals("int")) return; - } - assertTrue(false); - } - - public void testCastToPrimitive2() throws Exception { - configureByFile("CastToPrimitive2.java"); - - for (final LookupElement item : myItems) { - if (item.getLookupString().equals("int")) return; - } - assertTrue(false); - } - - public void testCastToPrimitive3() throws Exception { - configureByFile("CastToPrimitive3.java"); - - for (final LookupElement item : myItems) { - if (item.getLookupString().equals("int")) return; - } - assertTrue(false); - } - - public void testWriteInInvokeLater() throws Exception { - configureByFile("WriteInInvokeLater.java"); - } - - public void testQualifiedNew1() throws Exception { - configureByFile("QualifiedNew1.java"); - - assertEquals(2, myItems.length); - assertEquals("IInner", myItems[0].getLookupString()); - assertEquals("Inner", myItems[1].getLookupString()); - } - - public void testQualifiedNew2() throws Exception { - configureByFile("QualifiedNew2.java"); - - assertEquals(2, myItems.length); - assertEquals("AnInner", myItems[0].getLookupString()); - assertEquals("Inner", myItems[1].getLookupString()); - } - - public void testKeywordsInName() throws Exception { - myFixture.configureByFile(getTestName(false) + ".java"); - myFixture.completeBasic() - myFixture.type 'a\n' - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testSimpleVariable() throws Exception { doTest() } - - public void testPreferLongerNamesOption() throws Exception { - configureByFile("PreferLongerNamesOption.java"); - - assertEquals(3, myItems.length); - assertEquals("abcdEfghIjk", myItems[0].getLookupString()); - assertEquals("efghIjk", myItems[1].getLookupString()); - assertEquals("ijk", myItems[2].getLookupString()); - - LookupManager.getInstance(getProject()).hideActiveLookup(); - - CodeStyleSettingsManager.getSettings(getProject()).PREFER_LONGER_NAMES = false; - try{ - configureByFile("PreferLongerNamesOption.java"); - - assertEquals(3, myItems.length); - assertEquals("ijk", myItems[0].getLookupString()); - assertEquals("efghIjk", myItems[1].getLookupString()); - assertEquals("abcdEfghIjk", myItems[2].getLookupString()); - } - finally{ - CodeStyleSettingsManager.getSettings(getProject()).PREFER_LONGER_NAMES = true; - } - } - - public void testSCR7208() throws Exception { - configureByFile("SCR7208.java"); - } - - public void testProtectedFromSuper() throws Exception { - configureByFile("ProtectedFromSuper.java"); - Arrays.sort(myItems); - assertTrue("Exception not found", Arrays.binarySearch(myItems, "xxx") > 0); - } - - public void testBeforeInitialization() throws Exception { - configureByFile("BeforeInitialization.java"); - assertNotNull(myItems); - assertTrue(myItems.length > 0); - } - - public void testProtectedFromSuper2() throws Exception { - - configureByFile("ProtectedFromSuper.java"); - Arrays.sort(myItems); - assertTrue("Exception not found", Arrays.binarySearch(myItems, "xxx") > 0); - } - - public void testReferenceParameters() throws Exception { - configureByFile("ReferenceParameters.java"); - assertNotNull(myItems); - assertEquals(myItems.length, 2); - assertEquals(myItems[0].getLookupString(), "AAAA"); - assertEquals(myItems[1].getLookupString(), "AAAB"); - } - - public void testConstructorName1() throws Exception{ - final CodeInsightSettings settings = CodeInsightSettings.getInstance(); - final boolean autocomplete_on_code_completion = settings.AUTOCOMPLETE_ON_CODE_COMPLETION; - settings.AUTOCOMPLETE_ON_CODE_COMPLETION = false; - configureByFile("ConstructorName1.java"); - assertNotNull(myItems); - boolean failed = true; - for (final LookupElement item : myItems) { - if (item.getLookupString().equals("ABCDE")) { - failed = false; - } - } - assertFalse(failed); - settings.AUTOCOMPLETE_ON_CODE_COMPLETION = autocomplete_on_code_completion; - } - - public void testConstructorName2() throws Exception{ - final CodeInsightSettings settings = CodeInsightSettings.getInstance(); - final boolean autocomplete_on_code_completion = settings.AUTOCOMPLETE_ON_CODE_COMPLETION; - settings.AUTOCOMPLETE_ON_CODE_COMPLETION = false; - configureByFile("ConstructorName2.java"); - assertNotNull(myItems); - boolean failed = true; - for (final LookupElement item : myItems) { - if (item.getLookupString().equals("ABCDE")) { - failed = false; - } - } - assertFalse(failed); - settings.AUTOCOMPLETE_ON_CODE_COMPLETION = autocomplete_on_code_completion; - } - - public void testObjectsInThrowsBlock() throws Exception { - configureByFile("InThrowsCompletion.java"); - - Arrays.sort(myItems); - assertTrue("Exception not found", Arrays.binarySearch(myItems, "C") > 0); - assertFalse("Found not an Exception", Arrays.binarySearch(myItems, "B") > 0); - } - - public void testAfterInstanceof() throws Exception { - configureByFile("AfterInstanceof.java"); - - assertNotNull(myItems); - Arrays.sort(myItems); - assertTrue("Classes not found after instanceof", Arrays.binarySearch(myItems, "A") >= 0); - } - - public void testAfterCast1() throws Exception { - configureByFile("AfterCast1.java"); - - assertNotNull(myItems); - assertEquals(2, myItems.length); - } - - public void testAfterCast2() throws Exception { - configureByFile("AfterCast2.java"); - checkResultByFile("AfterCast2-result.java"); - } - - public void testMethodCallForTwoLevelSelection() throws Exception { - configureByFile("MethodLookup.java"); - assertEquals(2, myItems.length); - } - - public void testMethodCallBeforeAnotherStatementWithParen() throws Exception { - configureByFile("MethodLookup2.java"); - checkResultByFile("MethodLookup2_After.java"); - } - - public void testMethodCallBeforeAnotherStatementWithParen2() throws Exception { - CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(getProject()).getCurrentSettings(); - boolean oldvalue = settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE; - settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE = true; - configureByFile("MethodLookup2.java"); - checkResultByFile("MethodLookup2_After2.java"); - settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE = oldvalue; - } - - public void testSwitchEnumLabel() throws Exception { - configureByFile("SwitchEnumLabel.java"); - assertEquals(3, myItems.length); - } - - public void testMethodInAnnotation() throws Exception { - configureByFile("Annotation.java"); - checkResultByFile("Annotation_after.java"); - } - - public void testMethodInAnnotation2() throws Exception { - configureByFile("Annotation2.java"); - checkResultByFile("Annotation2_after.java"); - } - - public void testMethodInAnnotation3() throws Exception { - - configureByFile("Annotation3.java"); - checkResultByFile("Annotation3_after.java"); - } - - public void testMethodInAnnotation5() throws Exception { - - configureByFile("Annotation5.java"); - checkResultByFile("Annotation5_after.java"); - } - - public void testMethodInAnnotation7() throws Exception { - - configureByFile("Annotation7.java"); - selectItem(myItems[0]); - checkResultByFile("Annotation7_after.java"); - } - - public void testEnumInAnnotation() throws Exception { - configureByFile("Annotation4.java"); - checkResultByFile("Annotation4_after.java"); - } - - public void testSecondAttribute() throws Exception { - configureByFile("Annotation6.java"); - checkResultByFile("Annotation6_after.java"); - } - - public void testIDEADEV6408() throws Exception { - configureByFile("IDEADEV6408.java"); - assertEquals(2, myItems.length); - } - - public void testMethodWithLeftParTailType() throws Exception { - configureByFile("MethodWithLeftParTailType.java"); - type('('); - checkResultByFile("MethodWithLeftParTailType_after.java"); - - configureByFile("MethodWithLeftParTailType2.java"); - type('('); - checkResultByFile("MethodWithLeftParTailType2_after.java"); - } - - public void testMethodWithLeftParTailTypeNoPairBrace() throws Exception { - final boolean old = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET; - CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = false; - - try { - configureByFile(getTestName(false) + ".java"); - type('('); - checkResultByFile(getTestName(false) + "_after.java"); - } - finally { - CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = old; - } - } - - public void testMethodWithLeftParTailTypeNoPairBrace2() throws Exception { - final boolean old = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET; - CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = false; - - try { - //no tail type should work the normal way - configureByFile("MethodWithLeftParTailTypeNoPairBrace.java"); - selectItem(myItems[0]); - checkResultByFile("MethodWithLeftParTailTypeNoPairBrace_after2.java"); - } - finally { - CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = old; - } - } - - public void testExcessSpaceInTypeCast() throws Throwable { - configureByFile(getTestName(false) + ".java"); - selectItem(myItems[0]); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testPackageInAnnoParam() throws Throwable { - doTest(); - } - - public void testClassLiteralInAnnoParam() throws Throwable { - doTest(); - } - - public void testAtUnderClass() throws Throwable { - doTest(); - } - - public void testAtUnderClassNoModifiers() throws Throwable { - doTest(); - } - - public void testLastExpressionInFor() throws Throwable { doTest(); } - - public void testUndoCommonPrefixOnHide() throws Throwable {//actually don't undo - configureByFile(getTestName(false) + ".java"); - checkResultByFile(getTestName(false) + "_after.java"); - LookupManager.getInstance(getProject()).hideActiveLookup(); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testOnlyKeywordsInsideSwitch() throws Throwable { - configureByFile(getTestName(false) + ".java"); - assertStringItems("case", "default"); - } - - public void testBooleanLiterals() throws Throwable { - doTest(); - } - - public void testNotOnlyKeywordsInsideSwitch() throws Throwable { - doTest(); - } - - public void testChainedCallOnNextLine() throws Throwable { - configureByFile(getTestName(false) + ".java"); - selectItem(myItems[0]); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testFinishWithDot() throws Throwable { - configureByFile(getTestName(false) + ".java"); - type('.'); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testEnclosingThis() throws Throwable { doTest(); } - - public void testSeamlessConstant() throws Throwable { - configureByFile(getTestName(false) + ".java"); - selectItem(myItems[0]); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testDefaultAnnoParam() throws Throwable { doTest(); } - - public void testSpaceAfterLookupString() throws Throwable { - configureByFile(getTestName(false) + ".java"); - type(' '); - assertNull(getLookup()); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testNoSpaceInParensWithoutParams() throws Throwable { - CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = true; - try { - doTest(); - } - finally { - CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = false; - } - } - - public void testTwoSpacesInParensWithParams() throws Throwable { - CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = true; - try { - doTest(); - } - finally { - CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = false; - } - } - - public void testFillCommonPrefixOnSecondCompletion() throws Throwable { - configureByFile(getTestName(false) + ".java"); - type('g'); - complete(); - checkResultByFile(getTestName(false) + "_after.java"); - assertStringItems("getBar", "getFoo", "getClass"); - } - - public void testQualifierAsPackage() throws Throwable { - configureByFile(getTestName(false) + ".java"); - selectItem(myItems[0]); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testQualifierAsPackage2() throws Throwable { - doTest(); - } - - public void testQualifierAsPackage3() throws Throwable { - doTest(); - } - - public void testPackageNamedVariableBeforeAssignment() throws Throwable { - doTest(); - } - - public void testMethodReturnType() throws Throwable { - doTest(); - } - - public void testMethodReturnTypeNoSpace() throws Throwable { - configureByFile(getTestName(false) + ".java"); - selectItem(myItems[0]); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testEnumWithoutConstants() throws Throwable { - doTest(); - } - - public void testDoWhileMethodCall() throws Throwable { - doTest(); - } - - public void testSecondTypeParameterExtends() throws Throwable { - doTest(); - } - - public void testGetterWithExistingNonEmptyParameterList() throws Throwable { - doTest(); - } - - public void testNothingAfterNumericLiteral() throws Throwable { doAntiTest(); } - - public void testNoAllClassesOnQualifiedReference() throws Throwable { - configureByFile(getTestName(false) + ".java"); - assertEmpty(myItems); - checkResultByFile(getTestName(false) + ".java"); - } - - public void testFinishClassNameWithDot() throws Throwable { - configureByFile(getTestName(false) + ".java"); - type('.'); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testFinishClassNameWithLParen() throws Throwable { - configureByFile(getTestName(false) + ".java"); - type('('); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testSelectNoParameterSignature() throws Throwable { - configureByFile(getTestName(false) + ".java"); - final int parametersCount = ((PsiMethod)getLookup().getCurrentItem().getObject()).getParameterList().getParametersCount(); - assertEquals(0, parametersCount); - new WriteCommandAction.Simple(getProject(), new PsiFile[0]) { - @Override - protected void run() throws Throwable { - getLookup().finishLookup(Lookup.NORMAL_SELECT_CHAR); - } - }.execute().throwException(); - - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testCompletionInsideClassLiteral() throws Throwable { - configureByFile(getTestName(false) + ".java"); - new WriteCommandAction.Simple(getProject(), new PsiFile[0]) { - @Override - protected void run() throws Throwable { - getLookup().finishLookup(Lookup.NORMAL_SELECT_CHAR); - } - }.execute().throwException(); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testSuperInConstructor() throws Throwable { - doTest(); - } - - public void testSuperInConstructorWithParams() throws Throwable { - doTest(); - } - - public void testSuperInMethod() throws Throwable { - doTest(); - } - - public void testSecondMethodParameterName() throws Throwable { - doTest(); - } - - public void testAnnotationAsUsualObject() throws Throwable { - doTest(); - } - - public void testAnnotationAsUsualObjectFromJavadoc() throws Throwable { - doTest(); - } - - public void testAnnotationAsUsualObjectInsideClass() throws Throwable { - doTest(); - } - - public void testAnnotationOnNothingParens() throws Throwable { - doTest(); - } - - public void testMultiResolveQualifier() throws Throwable { - doTest(); - } - - public void testSecondMethodParameter() throws Throwable { doTest(); } - - public void testAnnotationWithoutValueMethod() throws Throwable { - configureByFile(getTestName(false) + ".java"); - assertStringItems("bar", "foo"); - } - - public void testUnnecessaryMethodMerging() throws Throwable { - configureByFile(getTestName(false) + ".java"); - assertStringItems("fofoo", "fofoo"); - } - - public void testDontCancelPrefixOnTyping() throws Throwable { - configureByFile(getTestName(false) + ".java"); - type('~'); - assertNull(getLookup()); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testAnnotationQualifiedName() throws Throwable { - doTest(); - } - - public void testDoubleFalse() throws Throwable { - configureByFile(getTestName(false) + ".java"); - assertStringItems("false", "finalize"); - } - - public void testSameNamedVariableInNestedClasses() throws Throwable { - doTest(); - assertNull(getLookup()); - } - - public void testHonorUnderscoreInPrefix() throws Throwable { - doTest(); - } - - public void testNoSemicolonAfterExistingParenthesesEspeciallyIfItsACast() throws Throwable { doTest(); } - public void testReturningTypeVariable() throws Throwable { doTest(); } - public void testReturningTypeVariable2() throws Throwable { doTest(); } - public void testReturningTypeVariable3() throws Throwable { doTest(); } - - public void testCaseTailType() throws Throwable { doTest(); } - - def doPrimitiveTypeTest() { - configure() - checkResultByFile(getTestName(false) + ".java"); - assertTrue 'boolean' in myFixture.lookupElementStrings - } - - private def configure() { - configureByFile(getTestName(false) + ".java") - } - - public void testFinalInForLoop() throws Throwable { - configure() - checkResultByFile(getTestName(false) + ".java") - assertOrderedEquals myFixture.lookupElementStrings, 'final' - } - - public void testPrimitiveTypesInForLoop() throws Throwable { doPrimitiveTypeTest() } - public void testPrimitiveTypesInForLoop2() throws Throwable { doPrimitiveTypeTest() } - public void testPrimitiveTypesInForLoop3() throws Throwable { doPrimitiveTypeTest() } - public void testPrimitiveTypesInForLoop4() throws Throwable { doPrimitiveTypeTest() } - public void testPrimitiveTypesInForLoop5() throws Throwable { doPrimitiveTypeTest() } - public void testPrimitiveTypesInForLoop6() throws Throwable { doPrimitiveTypeTest() } - - public void testPrimitiveTypesInForLoopSpace() throws Throwable { - configure() - myFixture.type ' ' - checkResultByFile(getTestName(false) + "_after.java") - } - - public void testSecondInvocationToFillCommonPrefix() throws Throwable { - configure() - type('a'); - complete(); - assertStringItems("fai1", "fai2", "fai3"); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testSuggestInaccessibleOnSecondInvocation() throws Throwable { - configure() - assertStringItems("_bar", "_goo"); - complete(); - assertStringItems("_bar", "_goo", "_foo"); - getLookup().setCurrentItem(getLookup().getItems().get(2)); - new WriteCommandAction.Simple(getProject(), new PsiFile[0]) { - @Override - protected void run() throws Throwable { - getLookup().finishLookup(Lookup.NORMAL_SELECT_CHAR); - } - }.execute().throwException(); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testNoCommonPrefixInsideIdentifier() throws Throwable { - final String path = getTestName(false) + ".java"; - configureByFile(path); - checkResultByFile(path); - assertStringItems("fai1", "fai2"); - } - - public void testProtectedInaccessibleOnSecondInvocation() throws Throwable { - myFixture.configureByFile(getTestName(false) + ".java"); - myFixture.complete(CompletionType.BASIC, 2); - checkResultByFile(getTestName(false) + "_after.java"); - } - - public void testPropertyReferencePrefix() throws Throwable { - myFixture.addFileToProject("test.properties", "foo.bar=Foo! Bar!").getVirtualFile(); - - configure() - checkResultByFile(getTestName(false) + ".java"); - assertNull(getLookup()); - } - - private void doTest() throws Exception { - configure() - checkResultByFile(getTestName(false) + "_after.java"); - } - - private void doAntiTest() throws Exception { - configure() - checkResultByFile(getTestName(false) + ".java"); - assertEmpty(myItems); - assertNull(getLookup()); - } - - public void testSecondAnonymousClassParameter() throws Throwable { doTest(); } - - public void testSpaceAfterReturn() throws Throwable { - configure() - type '\n' - checkResultByFile(getTestName(false) + "_after.java") - } - - public void testCastInstanceofedQualifier() throws Throwable { doTest(); } - public void testCastComplexInstanceofedQualifier() throws Throwable { doTest(); } - - public void testCastTooComplexInstanceofedQualifier() throws Throwable { doAntiTest(); } - public void testDontCastInstanceofedQualifier() throws Throwable { doTest(); } - - public void testWildcardsInLookup() throws Exception { - configure() - assertNotNull(getLookup()); - type('*fz'); - final List list = getLookup().getItems(); - assertEquals("azzzfzzz", list.get(0).getLookupString()); - assertEquals("fzazzz", list.get(1).getLookupString()); - } - - public void testMethodParameterAnnotationClass() throws Throwable { doTest(); } - - public void testEnumConstantFromEnumMember() throws Throwable { doTest(); } - - public void testPrimitiveMethodParameter() throws Throwable { doTest(); } - - public void testRightShift() throws Throwable { - configure() - assertStringItems("myField1", "myField2"); - } - - public void testSuggestMembersOfStaticallyImportedClasses() throws Exception { - myFixture.addClass("""package foo; - public class Foo { - public static void foo() {} - public static void bar() {} - } - """) - myFixture.configureByText("a.java", """ - import static foo.Foo.foo; - - class Bar {{ - foo(); - ba - }} - """) - complete() - myFixture.checkResult """ - import static foo.Foo.bar; - import static foo.Foo.foo; - - class Bar {{ - foo(); - bar(); - }} - """ - } - - public void testSuggestMembersOfStaticallyImportedClassesUnqualifiedOnly() throws Exception { - def old = CodeInsightSettings.instance.SHOW_STATIC_AFTER_INSTANCE - CodeInsightSettings.instance.SHOW_STATIC_AFTER_INSTANCE = true - - try { - myFixture.addClass("""package foo; - public class Foo { - public static void foo() {} - public static void bar() {} - } - """) - myFixture.configureByText("a.java", """ - import foo.Foo; - import static foo.Foo.foo; - - class Bar {{ - foo(); - new Foo().baz - }} - """) - complete() - assertOneElement(myFixture.getLookupElements()) - myFixture.type '\t' - myFixture.checkResult """ - import foo.Foo; - import static foo.Foo.foo; - - class Bar {{ - foo(); - new Foo().bar(); - }} - """ - } - finally { - CodeInsightSettings.instance.SHOW_STATIC_AFTER_INSTANCE = old - } - } - - public void testInstanceMagicMethod() throws Exception { - myFixture.configureByText("a.java", """ - public class JavaClass { - T magic() {} - - void foo() { - mag - } - } - """) - myFixture.completeBasic() - myFixture.checkResult """ - public class JavaClass { - T magic() {} - - void foo() { - magic() - } - } - """ - } - - public void testNoMethodsInParameterType() { - myFixture.configureByText("a.java", """ - class Foo { - static void foo(fint a) {} - } - """) - myFixture.completeBasic() - assertOrderedEquals myFixture.lookupElementStrings, "final", "float" - } - - public void testStaticallyImportedFieldsTwice() { - myFixture.addClass(""" - class Foo { - public static final int aZOO; - } - """) - myFixture.configureByText("a.java", """ - import static Foo.* - class Bar {{ - aZa - }} - """) - assertOneElement myFixture.completeBasic() - } - - public void testStatementKeywords() { - myFixture.configureByText("a.java", """ - class Bar {{ - xxx - }} - """) - myFixture.completeBasic() - final def strings = myFixture.lookupElementStrings - assertTrue 'if' in strings - assertTrue 'while' in strings - assertTrue 'do' in strings - assertTrue 'new' in strings - assertTrue 'try' in strings - - strings.remove 'new' - assertFalse 'new' in strings - } - - public void testExpressionKeywords() { - myFixture.configureByText("a.java", """ - class Bar {{ - foo(xxx) - }} - """) - myFixture.completeBasic() - final def strings = myFixture.lookupElementStrings - assertTrue 'new' in strings - } - -} +package com.intellij.codeInsight.completion; + + +import com.intellij.JavaTestUtil +import com.intellij.codeInsight.CodeInsightSettings +import com.intellij.codeInsight.lookup.Lookup +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupManager +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.psi.CommonClassNames +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMethod +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.codeStyle.CodeStyleSettingsManager + +public class NormalCompletionTest extends LightFixtureCompletionTestCase { + @Override + protected String getBasePath() { + return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/normal/"; + } + + public void testSimple() throws Exception { + configureByFile("Simple.java"); + assertStringItems("_local1", "_local2", "_field", "_method", "_baseField", "_baseMethod"); + } + + public void testDontCompleteFieldsAndMethodsInReferenceCodeFragment() throws Throwable { + final String text = CommonClassNames.JAVA_LANG_OBJECT + "."; + PsiFile file = getJavaFacade().getElementFactory().createReferenceCodeFragment(text, null, true, true); + myFixture.configureFromExistingVirtualFile(file.getVirtualFile()); + complete(); + myFixture.checkResult(text); + assertEmpty(myItems); + } + + public void testNoPackagesInExpressionCodeFragment() throws Throwable { + final String text = "jav"; + PsiFile file = getJavaFacade().getElementFactory().createExpressionCodeFragment(text, null, null, true); + myFixture.configureFromExistingVirtualFile(file.getVirtualFile()); + complete(); + myFixture.checkResult(text); + assertEmpty(myItems); + } + + public void testSubPackagesInExpressionCodeFragment() throws Throwable { + PsiFile file = getJavaFacade().getElementFactory().createExpressionCodeFragment("java.la", null, null, true); + myFixture.configureFromExistingVirtualFile(file.getVirtualFile()); + complete(); + myFixture.checkResult("java.lang."); + assertNull(myItems); + } + + private JavaPsiFacade getJavaFacade() { + return JavaPsiFacade.getInstance(getProject()); + } + + public void testCastToPrimitive1() throws Exception { + configureByFile("CastToPrimitive1.java"); + + for (final LookupElement item : myItems) { + if (item.getLookupString().equals("int")) return; + } + assertTrue(false); + } + + public void testCastToPrimitive2() throws Exception { + configureByFile("CastToPrimitive2.java"); + + for (final LookupElement item : myItems) { + if (item.getLookupString().equals("int")) return; + } + assertTrue(false); + } + + public void testCastToPrimitive3() throws Exception { + configureByFile("CastToPrimitive3.java"); + + for (final LookupElement item : myItems) { + if (item.getLookupString().equals("int")) return; + } + assertTrue(false); + } + + public void testWriteInInvokeLater() throws Exception { + configureByFile("WriteInInvokeLater.java"); + } + + public void testQualifiedNew1() throws Exception { + configureByFile("QualifiedNew1.java"); + + assertEquals(2, myItems.length); + assertEquals("IInner", myItems[0].getLookupString()); + assertEquals("Inner", myItems[1].getLookupString()); + } + + public void testQualifiedNew2() throws Exception { + configureByFile("QualifiedNew2.java"); + + assertEquals(2, myItems.length); + assertEquals("AnInner", myItems[0].getLookupString()); + assertEquals("Inner", myItems[1].getLookupString()); + } + + public void testKeywordsInName() throws Exception { + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.completeBasic() + myFixture.type 'a\n' + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testSimpleVariable() throws Exception { doTest() } + + public void testPreferLongerNamesOption() throws Exception { + configureByFile("PreferLongerNamesOption.java"); + + assertEquals(3, myItems.length); + assertEquals("abcdEfghIjk", myItems[0].getLookupString()); + assertEquals("efghIjk", myItems[1].getLookupString()); + assertEquals("ijk", myItems[2].getLookupString()); + + LookupManager.getInstance(getProject()).hideActiveLookup(); + + CodeStyleSettingsManager.getSettings(getProject()).PREFER_LONGER_NAMES = false; + try{ + configureByFile("PreferLongerNamesOption.java"); + + assertEquals(3, myItems.length); + assertEquals("ijk", myItems[0].getLookupString()); + assertEquals("efghIjk", myItems[1].getLookupString()); + assertEquals("abcdEfghIjk", myItems[2].getLookupString()); + } + finally{ + CodeStyleSettingsManager.getSettings(getProject()).PREFER_LONGER_NAMES = true; + } + } + + public void testSCR7208() throws Exception { + configureByFile("SCR7208.java"); + } + + public void testProtectedFromSuper() throws Exception { + configureByFile("ProtectedFromSuper.java"); + Arrays.sort(myItems); + assertTrue("Exception not found", Arrays.binarySearch(myItems, "xxx") > 0); + } + + public void testBeforeInitialization() throws Exception { + configureByFile("BeforeInitialization.java"); + assertNotNull(myItems); + assertTrue(myItems.length > 0); + } + + public void testProtectedFromSuper2() throws Exception { + + configureByFile("ProtectedFromSuper.java"); + Arrays.sort(myItems); + assertTrue("Exception not found", Arrays.binarySearch(myItems, "xxx") > 0); + } + + public void testReferenceParameters() throws Exception { + configureByFile("ReferenceParameters.java"); + assertNotNull(myItems); + assertEquals(myItems.length, 2); + assertEquals(myItems[0].getLookupString(), "AAAA"); + assertEquals(myItems[1].getLookupString(), "AAAB"); + } + + public void testConstructorName1() throws Exception{ + final CodeInsightSettings settings = CodeInsightSettings.getInstance(); + final boolean autocomplete_on_code_completion = settings.AUTOCOMPLETE_ON_CODE_COMPLETION; + settings.AUTOCOMPLETE_ON_CODE_COMPLETION = false; + configureByFile("ConstructorName1.java"); + assertNotNull(myItems); + boolean failed = true; + for (final LookupElement item : myItems) { + if (item.getLookupString().equals("ABCDE")) { + failed = false; + } + } + assertFalse(failed); + settings.AUTOCOMPLETE_ON_CODE_COMPLETION = autocomplete_on_code_completion; + } + + public void testConstructorName2() throws Exception{ + final CodeInsightSettings settings = CodeInsightSettings.getInstance(); + final boolean autocomplete_on_code_completion = settings.AUTOCOMPLETE_ON_CODE_COMPLETION; + settings.AUTOCOMPLETE_ON_CODE_COMPLETION = false; + configureByFile("ConstructorName2.java"); + assertNotNull(myItems); + boolean failed = true; + for (final LookupElement item : myItems) { + if (item.getLookupString().equals("ABCDE")) { + failed = false; + } + } + assertFalse(failed); + settings.AUTOCOMPLETE_ON_CODE_COMPLETION = autocomplete_on_code_completion; + } + + public void testObjectsInThrowsBlock() throws Exception { + configureByFile("InThrowsCompletion.java"); + + Arrays.sort(myItems); + assertTrue("Exception not found", Arrays.binarySearch(myItems, "C") > 0); + assertFalse("Found not an Exception", Arrays.binarySearch(myItems, "B") > 0); + } + + public void testAfterInstanceof() throws Exception { + configureByFile("AfterInstanceof.java"); + + assertNotNull(myItems); + Arrays.sort(myItems); + assertTrue("Classes not found after instanceof", Arrays.binarySearch(myItems, "A") >= 0); + } + + public void testAfterCast1() throws Exception { + configureByFile("AfterCast1.java"); + + assertNotNull(myItems); + assertEquals(2, myItems.length); + } + + public void testAfterCast2() throws Exception { + configureByFile("AfterCast2.java"); + checkResultByFile("AfterCast2-result.java"); + } + + public void testMethodCallForTwoLevelSelection() throws Exception { + configureByFile("MethodLookup.java"); + assertEquals(2, myItems.length); + } + + public void testMethodCallBeforeAnotherStatementWithParen() throws Exception { + configureByFile("MethodLookup2.java"); + checkResultByFile("MethodLookup2_After.java"); + } + + public void testMethodCallBeforeAnotherStatementWithParen2() throws Exception { + CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(getProject()).getCurrentSettings(); + boolean oldvalue = settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE; + settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE = true; + configureByFile("MethodLookup2.java"); + checkResultByFile("MethodLookup2_After2.java"); + settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE = oldvalue; + } + + public void testSwitchEnumLabel() throws Exception { + configureByFile("SwitchEnumLabel.java"); + assertEquals(3, myItems.length); + } + + public void testMethodInAnnotation() throws Exception { + configureByFile("Annotation.java"); + checkResultByFile("Annotation_after.java"); + } + + public void testMethodInAnnotation2() throws Exception { + configureByFile("Annotation2.java"); + checkResultByFile("Annotation2_after.java"); + } + + public void testMethodInAnnotation3() throws Exception { + + configureByFile("Annotation3.java"); + checkResultByFile("Annotation3_after.java"); + } + + public void testMethodInAnnotation5() throws Exception { + + configureByFile("Annotation5.java"); + checkResultByFile("Annotation5_after.java"); + } + + public void testMethodInAnnotation7() throws Exception { + + configureByFile("Annotation7.java"); + selectItem(myItems[0]); + checkResultByFile("Annotation7_after.java"); + } + + public void testEnumInAnnotation() throws Exception { + configureByFile("Annotation4.java"); + checkResultByFile("Annotation4_after.java"); + } + + public void testSecondAttribute() throws Exception { + configureByFile("Annotation6.java"); + checkResultByFile("Annotation6_after.java"); + } + + public void testIDEADEV6408() throws Exception { + configureByFile("IDEADEV6408.java"); + assertEquals(2, myItems.length); + } + + public void testMethodWithLeftParTailType() throws Exception { + configureByFile("MethodWithLeftParTailType.java"); + type('('); + checkResultByFile("MethodWithLeftParTailType_after.java"); + + configureByFile("MethodWithLeftParTailType2.java"); + type('('); + checkResultByFile("MethodWithLeftParTailType2_after.java"); + } + + public void testMethodWithLeftParTailTypeNoPairBrace() throws Exception { + final boolean old = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET; + CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = false; + + try { + configureByFile(getTestName(false) + ".java"); + type('('); + checkResultByFile(getTestName(false) + "_after.java"); + } + finally { + CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = old; + } + } + + public void testMethodWithLeftParTailTypeNoPairBrace2() throws Exception { + final boolean old = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET; + CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = false; + + try { + //no tail type should work the normal way + configureByFile("MethodWithLeftParTailTypeNoPairBrace.java"); + selectItem(myItems[0]); + checkResultByFile("MethodWithLeftParTailTypeNoPairBrace_after2.java"); + } + finally { + CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET = old; + } + } + + public void testExcessSpaceInTypeCast() throws Throwable { + configureByFile(getTestName(false) + ".java"); + selectItem(myItems[0]); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testPackageInAnnoParam() throws Throwable { + doTest(); + } + + public void testClassLiteralInAnnoParam() throws Throwable { + doTest(); + } + + public void testAtUnderClass() throws Throwable { + doTest(); + } + + public void testAtUnderClassNoModifiers() throws Throwable { + doTest(); + } + + public void testLastExpressionInFor() throws Throwable { doTest(); } + + public void testUndoCommonPrefixOnHide() throws Throwable {//actually don't undo + configureByFile(getTestName(false) + ".java"); + checkResultByFile(getTestName(false) + "_after.java"); + LookupManager.getInstance(getProject()).hideActiveLookup(); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testOnlyKeywordsInsideSwitch() throws Throwable { + configureByFile(getTestName(false) + ".java"); + assertStringItems("case", "default"); + } + + public void testBooleanLiterals() throws Throwable { + doTest(); + } + + public void testNotOnlyKeywordsInsideSwitch() throws Throwable { + doTest(); + } + + public void testChainedCallOnNextLine() throws Throwable { + configureByFile(getTestName(false) + ".java"); + selectItem(myItems[0]); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testFinishWithDot() throws Throwable { + configureByFile(getTestName(false) + ".java"); + type('.'); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testEnclosingThis() throws Throwable { doTest(); } + + public void testSeamlessConstant() throws Throwable { + configureByFile(getTestName(false) + ".java"); + selectItem(myItems[0]); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testDefaultAnnoParam() throws Throwable { doTest(); } + + public void testSpaceAfterLookupString() throws Throwable { + configureByFile(getTestName(false) + ".java"); + type(' '); + assertNull(getLookup()); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testNoSpaceInParensWithoutParams() throws Throwable { + CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = true; + try { + doTest(); + } + finally { + CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = false; + } + } + + public void testTwoSpacesInParensWithParams() throws Throwable { + CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = true; + try { + doTest(); + } + finally { + CodeStyleSettingsManager.getSettings(getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES = false; + } + } + + public void testFillCommonPrefixOnSecondCompletion() throws Throwable { + configureByFile(getTestName(false) + ".java"); + type('g'); + complete(); + checkResultByFile(getTestName(false) + "_after.java"); + assertStringItems("getBar", "getFoo", "getClass"); + } + + public void testQualifierAsPackage() throws Throwable { + configureByFile(getTestName(false) + ".java"); + selectItem(myItems[0]); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testQualifierAsPackage2() throws Throwable { + doTest(); + } + + public void testQualifierAsPackage3() throws Throwable { + doTest(); + } + + public void testPackageNamedVariableBeforeAssignment() throws Throwable { + doTest(); + } + + public void testMethodReturnType() throws Throwable { + doTest(); + } + + public void testMethodReturnTypeNoSpace() throws Throwable { + configureByFile(getTestName(false) + ".java"); + selectItem(myItems[0]); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testEnumWithoutConstants() throws Throwable { + doTest(); + } + + public void testDoWhileMethodCall() throws Throwable { + doTest(); + } + + public void testSecondTypeParameterExtends() throws Throwable { + doTest(); + } + + public void testGetterWithExistingNonEmptyParameterList() throws Throwable { + doTest(); + } + + public void testNothingAfterNumericLiteral() throws Throwable { doAntiTest(); } + + public void testNoAllClassesOnQualifiedReference() throws Throwable { + configureByFile(getTestName(false) + ".java"); + assertEmpty(myItems); + checkResultByFile(getTestName(false) + ".java"); + } + + public void testFinishClassNameWithDot() throws Throwable { + configureByFile(getTestName(false) + ".java"); + type('.'); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testFinishClassNameWithLParen() throws Throwable { + configureByFile(getTestName(false) + ".java"); + type('('); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testSelectNoParameterSignature() throws Throwable { + configureByFile(getTestName(false) + ".java"); + final int parametersCount = ((PsiMethod)getLookup().getCurrentItem().getObject()).getParameterList().getParametersCount(); + assertEquals(0, parametersCount); + new WriteCommandAction.Simple(getProject(), new PsiFile[0]) { + @Override + protected void run() throws Throwable { + getLookup().finishLookup(Lookup.NORMAL_SELECT_CHAR); + } + }.execute().throwException(); + + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testCompletionInsideClassLiteral() throws Throwable { + configureByFile(getTestName(false) + ".java"); + new WriteCommandAction.Simple(getProject(), new PsiFile[0]) { + @Override + protected void run() throws Throwable { + getLookup().finishLookup(Lookup.NORMAL_SELECT_CHAR); + } + }.execute().throwException(); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testSuperInConstructor() throws Throwable { + doTest(); + } + + public void testSuperInConstructorWithParams() throws Throwable { + doTest(); + } + + public void testSuperInMethod() throws Throwable { + doTest(); + } + + public void testSecondMethodParameterName() throws Throwable { + doTest(); + } + + public void testAnnotationAsUsualObject() throws Throwable { + doTest(); + } + + public void testAnnotationAsUsualObjectFromJavadoc() throws Throwable { + doTest(); + } + + public void testAnnotationAsUsualObjectInsideClass() throws Throwable { + doTest(); + } + + public void testAnnotationOnNothingParens() throws Throwable { + doTest(); + } + + public void testMultiResolveQualifier() throws Throwable { + doTest(); + } + + public void testSecondMethodParameter() throws Throwable { doTest(); } + + public void testAnnotationWithoutValueMethod() throws Throwable { + configureByFile(getTestName(false) + ".java"); + assertStringItems("bar", "foo"); + } + + public void testUnnecessaryMethodMerging() throws Throwable { + configureByFile(getTestName(false) + ".java"); + assertStringItems("fofoo", "fofoo"); + } + + public void testDontCancelPrefixOnTyping() throws Throwable { + configureByFile(getTestName(false) + ".java"); + type('~'); + assertNull(getLookup()); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testAnnotationQualifiedName() throws Throwable { + doTest(); + } + + public void testDoubleFalse() throws Throwable { + configureByFile(getTestName(false) + ".java"); + assertStringItems("false", "finalize"); + } + + public void testSameNamedVariableInNestedClasses() throws Throwable { + doTest(); + assertNull(getLookup()); + } + + public void testHonorUnderscoreInPrefix() throws Throwable { + doTest(); + } + + public void testNoSemicolonAfterExistingParenthesesEspeciallyIfItsACast() throws Throwable { doTest(); } + public void testReturningTypeVariable() throws Throwable { doTest(); } + public void testReturningTypeVariable2() throws Throwable { doTest(); } + public void testReturningTypeVariable3() throws Throwable { doTest(); } + + public void testCaseTailType() throws Throwable { doTest(); } + + def doPrimitiveTypeTest() { + configure() + checkResultByFile(getTestName(false) + ".java"); + assertTrue 'boolean' in myFixture.lookupElementStrings + } + + private def configure() { + configureByFile(getTestName(false) + ".java") + } + + public void testFinalInForLoop() throws Throwable { + configure() + checkResultByFile(getTestName(false) + ".java") + assertOrderedEquals myFixture.lookupElementStrings, 'final' + } + + public void testPrimitiveTypesInForLoop() throws Throwable { doPrimitiveTypeTest() } + public void testPrimitiveTypesInForLoop2() throws Throwable { doPrimitiveTypeTest() } + public void testPrimitiveTypesInForLoop3() throws Throwable { doPrimitiveTypeTest() } + public void testPrimitiveTypesInForLoop4() throws Throwable { doPrimitiveTypeTest() } + public void testPrimitiveTypesInForLoop5() throws Throwable { doPrimitiveTypeTest() } + public void testPrimitiveTypesInForLoop6() throws Throwable { doPrimitiveTypeTest() } + + public void testPrimitiveTypesInForLoopSpace() throws Throwable { + configure() + myFixture.type ' ' + checkResultByFile(getTestName(false) + "_after.java") + } + + public void testSecondInvocationToFillCommonPrefix() throws Throwable { + configure() + type('a'); + complete(); + assertStringItems("fai1", "fai2", "fai3"); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testSuggestInaccessibleOnSecondInvocation() throws Throwable { + configure() + assertStringItems("_bar", "_goo"); + complete(); + assertStringItems("_bar", "_goo", "_foo"); + getLookup().setCurrentItem(getLookup().getItems().get(2)); + new WriteCommandAction.Simple(getProject(), new PsiFile[0]) { + @Override + protected void run() throws Throwable { + getLookup().finishLookup(Lookup.NORMAL_SELECT_CHAR); + } + }.execute().throwException(); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testNoCommonPrefixInsideIdentifier() throws Throwable { + final String path = getTestName(false) + ".java"; + configureByFile(path); + checkResultByFile(path); + assertStringItems("fai1", "fai2"); + } + + public void testProtectedInaccessibleOnSecondInvocation() throws Throwable { + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.complete(CompletionType.BASIC, 2); + checkResultByFile(getTestName(false) + "_after.java"); + } + + public void testPropertyReferencePrefix() throws Throwable { + myFixture.addFileToProject("test.properties", "foo.bar=Foo! Bar!").getVirtualFile(); + + configure() + checkResultByFile(getTestName(false) + ".java"); + assertNull(getLookup()); + } + + private void doTest() throws Exception { + configure() + checkResultByFile(getTestName(false) + "_after.java"); + } + + private void doAntiTest() throws Exception { + configure() + checkResultByFile(getTestName(false) + ".java"); + assertEmpty(myItems); + assertNull(getLookup()); + } + + public void testSecondAnonymousClassParameter() throws Throwable { doTest(); } + + public void testSpaceAfterReturn() throws Throwable { + configure() + type '\n' + checkResultByFile(getTestName(false) + "_after.java") + } + + public void testCastInstanceofedQualifier() throws Throwable { doTest(); } + public void testCastComplexInstanceofedQualifier() throws Throwable { doTest(); } + + public void testCastTooComplexInstanceofedQualifier() throws Throwable { doAntiTest(); } + public void testDontCastInstanceofedQualifier() throws Throwable { doTest(); } + + public void testWildcardsInLookup() throws Exception { + configure() + assertNotNull(getLookup()); + type('*fz'); + final List list = getLookup().getItems(); + assertEquals("azzzfzzz", list.get(0).getLookupString()); + assertEquals("fzazzz", list.get(1).getLookupString()); + } + + public void testMethodParameterAnnotationClass() throws Throwable { doTest(); } + + public void testEnumConstantFromEnumMember() throws Throwable { doTest(); } + + public void testPrimitiveMethodParameter() throws Throwable { doTest(); } + + public void testRightShift() throws Throwable { + configure() + assertStringItems("myField1", "myField2"); + } + + public void testSuggestMembersOfStaticallyImportedClasses() throws Exception { + myFixture.addClass("""package foo; + public class Foo { + public static void foo() {} + public static void bar() {} + } + """) + myFixture.configureByText("a.java", """ + import static foo.Foo.foo; + + class Bar {{ + foo(); + ba + }} + """) + complete() + myFixture.checkResult """ + import static foo.Foo.bar; + import static foo.Foo.foo; + + class Bar {{ + foo(); + bar(); + }} + """ + } + + public void testSuggestMembersOfStaticallyImportedClassesUnqualifiedOnly() throws Exception { + def old = CodeInsightSettings.instance.SHOW_STATIC_AFTER_INSTANCE + CodeInsightSettings.instance.SHOW_STATIC_AFTER_INSTANCE = true + + try { + myFixture.addClass("""package foo; + public class Foo { + public static void foo() {} + public static void bar() {} + } + """) + myFixture.configureByText("a.java", """ + import foo.Foo; + import static foo.Foo.foo; + + class Bar {{ + foo(); + new Foo().baz + }} + """) + complete() + assertOneElement(myFixture.getLookupElements()) + myFixture.type '\t' + myFixture.checkResult """ + import foo.Foo; + import static foo.Foo.foo; + + class Bar {{ + foo(); + new Foo().bar(); + }} + """ + } + finally { + CodeInsightSettings.instance.SHOW_STATIC_AFTER_INSTANCE = old + } + } + + public void testInstanceMagicMethod() throws Exception { + myFixture.configureByText("a.java", """ + public class JavaClass { + T magic() {} + + void foo() { + mag + } + } + """) + myFixture.completeBasic() + myFixture.checkResult """ + public class JavaClass { + T magic() {} + + void foo() { + magic() + } + } + """ + } + + public void testNoMethodsInParameterType() { + myFixture.configureByText("a.java", """ + class Foo { + static void foo(fint a) {} + } + """) + myFixture.completeBasic() + assertOrderedEquals myFixture.lookupElementStrings, "final", "float" + } + + public void testStaticallyImportedFieldsTwice() { + myFixture.addClass(""" + class Foo { + public static final int aZOO; + } + """) + myFixture.configureByText("a.java", """ + import static Foo.* + class Bar {{ + aZa + }} + """) + assertOneElement myFixture.completeBasic() + } + + public void testStatementKeywords() { + myFixture.configureByText("a.java", """ + class Bar {{ + xxx + }} + """) + myFixture.completeBasic() + final def strings = myFixture.lookupElementStrings + assertTrue 'if' in strings + assertTrue 'while' in strings + assertTrue 'do' in strings + assertTrue 'new' in strings + assertTrue 'try' in strings + + strings.remove 'new' + assertFalse 'new' in strings + } + + public void testExpressionKeywords() { + myFixture.configureByText("a.java", """ + class Bar {{ + foo(xxx) + }} + """) + myFixture.completeBasic() + final def strings = myFixture.lookupElementStrings + assertTrue 'new' in strings + } + +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java index 5cbf9aacdeb5..9570f743409f 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java @@ -1,380 +1,380 @@ -package com.intellij.codeInsight.daemon; - -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; -import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix; -import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFixBase; -import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.command.impl.UndoManagerImpl; -import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.fileEditor.impl.LoadTextUtil; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.*; -import com.intellij.psi.impl.source.codeStyle.ImportHelper; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import java.io.File; -import java.util.List; - -@DaemonAnalyzerTestCase.CanChangeDocumentDuringHighlighting -public class ImportHelperTest extends DaemonAnalyzerTestCase { - @Override - protected LocalInspectionTool[] configureLocalInspectionTools() { - return new LocalInspectionTool[]{new UnusedImportLocalInspection()}; - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); - settings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 100; - CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(settings); - DaemonCodeAnalyzer.getInstance(getProject()).setUpdateByTimerEnabled(false); - } - - @Override - protected void tearDown() throws Exception { - CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); - super.tearDown(); - } - - public void testImportsInsertedAlphabetically() throws Throwable { - @NonNls String text = "class I {}"; - final PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, text); - assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); - CommandProcessor.getInstance().executeCommand( - getProject(), new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - checkAddImport(file, "java.util.List", "java.util.List"); - checkAddImport(file, "java.util.ArrayList", "java.util.ArrayList","java.util.List"); - checkAddImport(file, "java.util.HashMap", "java.util.ArrayList","java.util.HashMap","java.util.List"); - checkAddImport(file, "java.util.SortedMap", "java.util.ArrayList","java.util.HashMap","java.util.List","java.util.SortedMap"); - checkAddImport(file, "java.util.Map", "java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap"); - checkAddImport(file, "java.util.AbstractList", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap"); - checkAddImport(file, "java.util.AbstractList", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap"); - checkAddImport(file, "java.util.TreeMap", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap", "java.util.TreeMap"); - checkAddImport(file, "java.util.concurrent.atomic.AtomicBoolean", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap", "java.util.TreeMap", "java.util.concurrent.atomic.AtomicBoolean"); - checkAddImport(file, "java.io.File", "java.io.File","java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap", "java.util.TreeMap", "java.util.concurrent.atomic.AtomicBoolean"); - } - catch (Throwable e) { - LOG.error(e); - } - } - }); - } - }, "", ""); - } - public void testStaticImportsGrouping() throws Throwable { - @NonNls String text = "import static java.lang.Math.max;\n" + - "import java.util.Map;\n" + - "\n" + - "import static java.lang.Math.min;\n" + - "\n" + - "import java.awt.Component;\n" + - "\n" + - "\n" + - "\n" + - "import static javax.swing.SwingConstants.CENTER;\n" + - "class I {{ max(0, 0); Map.class.hashCode(); min(0,0); Component.class.hashCode(); int i = CENTER; }}"; - - final PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, text); - assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); - CommandProcessor.getInstance().executeCommand( - getProject(), new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); - settings.LAYOUT_STATIC_IMPORTS_SEPARATELY = true; - PackageEntryTable table = new PackageEntryTable(); - table.addEntry(PackageEntry.ALL_OTHER_IMPORTS_ENTRY); - table.addEntry(PackageEntry.BLANK_LINE_ENTRY); - table.addEntry(new PackageEntry(false, "javax", true)); - table.addEntry(new PackageEntry(false, "java", true)); - table.addEntry(PackageEntry.BLANK_LINE_ENTRY); - table.addEntry(new PackageEntry(true, "java", true)); - table.addEntry(PackageEntry.BLANK_LINE_ENTRY); - table.addEntry(PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY); - - settings.IMPORT_LAYOUT_TABLE.copyFrom(table); - CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(settings); - try { - JavaCodeStyleManager.getInstance(getProject()).optimizeImports(file); - } - finally { - CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); - } - - assertOrder(file, "java.awt.*", "java.util.Map", "static java.lang.Math.max", "static java.lang.Math.min", "static javax.swing.SwingConstants.CENTER"); - - } - catch (Throwable e) { - LOG.error(e); - } - } - }); - } - }, "", ""); - } - - @Override - protected Sdk getTestProjectJdk() { - return JavaSdkImpl.getMockJdk17(); - } - - private void checkAddImport(PsiJavaFile file, String fqn, String... expectedOrder) { - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); - ImportHelper importHelper = new ImportHelper(settings); - - PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(fqn, GlobalSearchScope.allScope(getProject())); - boolean b = importHelper.addImport(file, psiClass); - assertTrue(b); - - assertOrder(file, expectedOrder); - } - - private static void assertOrder(PsiJavaFile file, @NonNls String... expectedOrder) { - PsiImportStatementBase[] statements = file.getImportList().getAllImportStatements(); - - assertEquals(expectedOrder.length, statements.length); - for (int i = 0; i < statements.length; i++) { - PsiImportStatementBase statement = statements[i]; - String text = StringUtil.trimEnd(StringUtil.trimStart(statement.getText(), "import "), ";"); - assertEquals(expectedOrder[i], text); - } - } - - @NonNls private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/advHighlighting/reimportConflictingClasses"; - public void testReimportConflictingClasses() throws Exception { - configureByFile(BASE_PATH+"/x/Usage.java", BASE_PATH); - assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); - - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); - settings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 2; - CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(settings); - try { - new WriteCommandAction.Simple(getProject()) { - @Override - protected void run() throws Throwable { - JavaCodeStyleManager.getInstance(getProject()).optimizeImports(getFile()); - } - }.execute().throwException(); - } - finally { - CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); - } - - - @NonNls String fullPath = getTestDataPath() + BASE_PATH + "/x/Usage_afterOptimize.txt"; - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); - String text = LoadTextUtil.loadText(vFile).toString(); - assertEquals(text, getFile().getText()); - } - public void testConflictingClassesFromCurrentPackage() throws Throwable { - final PsiFile file = configureByText(StdFileTypes.JAVA, "package java.util; class X{ Date d;}"); - assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); - - new WriteCommandAction.Simple(getProject()) { - @Override - protected void run() throws Throwable { - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); - ImportHelper importHelper = new ImportHelper(settings); - - PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass("java.sql.Date", GlobalSearchScope.allScope(getProject())); - boolean b = importHelper.addImport((PsiJavaFile)file, psiClass); - assertFalse(b); // must fail - } - }.execute().throwException(); - } - - @DoNotWrapInCommand - public void testAutoImportCaretLocation() throws Throwable { - boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; - try { - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; - configureByText(StdFileTypes.JAVA, "class X { ArrayList c; }"); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); - type(" "); - backspace(); - - assertOneElement(filter(doHighlighting(), HighlightSeverity.ERROR)); - - int offset = myEditor.getCaretModel().getOffset(); - PsiReference ref = myFile.findReferenceAt(offset - 1); - assertTrue(ref instanceof PsiJavaCodeReferenceElement); - - ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); - assertEquals(ImportClassFixBase.Result.POPUP_NOT_SHOWN, result); - UIUtil.dispatchAllInvocationEvents(); - - myEditor.getCaretModel().moveToOffset(offset - 1); - result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); - assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); - UIUtil.dispatchAllInvocationEvents(); - - assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); - } - finally { - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; - } - } - - @DoNotWrapInCommand - public void testAutoImportCaretLocation2() throws Throwable { - boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; - try { - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; - configureByText(StdFileTypes.JAVA, "class X { ArrayList c = new ArrayList(); }"); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); - type(" "); - backspace(); - - assertEquals(2, filter(doHighlighting(), HighlightSeverity.ERROR).size()); - UIUtil.dispatchAllInvocationEvents(); - - int offset = myEditor.getCaretModel().getOffset(); - PsiReference ref = myFile.findReferenceAt(offset); - assertTrue(ref instanceof PsiJavaCodeReferenceElement); - - ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); - assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); - UIUtil.dispatchAllInvocationEvents(); - - assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); - } - finally { - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; - } - } - - @DoNotWrapInCommand - public void testAutoImportWorksWhenITypeSpaceAfterClassName() throws Throwable { - @NonNls String text = "class S { ArrayList }"; - configureByText(StdFileTypes.JAVA, text); - - boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; - DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); - - try { - doHighlighting(); - //caret is too close - assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); - - type(" "); - - PsiJavaCodeReferenceElement element = - (PsiJavaCodeReferenceElement)getFile().findReferenceAt(getEditor().getCaretModel().getOffset() - 2); - ImportClassFix fix = new ImportClassFix(element); - ImportClassFixBase.Result result = fix.doFix(getEditor(), false, false); - assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); - - assertNotSame(0, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); - } - finally { - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; - } - } - - @DoNotWrapInCommand - public void testAutoImportWorks() throws Throwable { - @NonNls final String text = "class S { JFrame x; }"; - configureByText(StdFileTypes.JAVA, text); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); - assertFalse(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile())); - - - doHighlighting(); - assertFalse(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile())); - - type(" "); - assertTrue(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile())); - - undo(); - - assertFalse(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile()));//CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; - } - - @DoNotWrapInCommand - public void testAutoImportOfGenericReference() throws Throwable { - @NonNls final String text = "class S {{ new ArrayList<> }}"; - configureByText(StdFileTypes.JAVA, text); - boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; - DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); - - ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); - ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); - type(" "); - backspace(); - - try { - doHighlighting(); - //caret is too close - assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); - - caretRight(); - - doHighlighting(); - - assertNotSame(0, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); - } - finally { - CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; - } - } - - @DoNotWrapInCommand - public void testAutoOptimizeUnresolvedImports() throws Throwable { - @NonNls String text = "import xxx.yyy; class S { } "; - configureByText(StdFileTypes.JAVA, text); - - boolean old = CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY; - CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY = true; - DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); - - try { - List errs = filter(doHighlighting(), HighlightSeverity.ERROR); - - assertEquals(1, errs.size()); - - assertEquals(1, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); - - type("/* */"); - doHighlighting(); - UIUtil.dispatchAllInvocationEvents(); - - assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); - } - finally { - CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY = old; - } - } - -} +package com.intellij.codeInsight.daemon; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix; +import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFixBase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.command.impl.UndoManagerImpl; +import com.intellij.openapi.command.undo.UndoManager; +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.*; +import com.intellij.psi.impl.source.codeStyle.ImportHelper; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import java.io.File; +import java.util.List; + +@DaemonAnalyzerTestCase.CanChangeDocumentDuringHighlighting +public class ImportHelperTest extends DaemonAnalyzerTestCase { + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new UnusedImportLocalInspection()}; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); + settings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 100; + CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(settings); + DaemonCodeAnalyzer.getInstance(getProject()).setUpdateByTimerEnabled(false); + } + + @Override + protected void tearDown() throws Exception { + CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); + super.tearDown(); + } + + public void testImportsInsertedAlphabetically() throws Throwable { + @NonNls String text = "class I {}"; + final PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, text); + assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); + CommandProcessor.getInstance().executeCommand( + getProject(), new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + checkAddImport(file, "java.util.List", "java.util.List"); + checkAddImport(file, "java.util.ArrayList", "java.util.ArrayList","java.util.List"); + checkAddImport(file, "java.util.HashMap", "java.util.ArrayList","java.util.HashMap","java.util.List"); + checkAddImport(file, "java.util.SortedMap", "java.util.ArrayList","java.util.HashMap","java.util.List","java.util.SortedMap"); + checkAddImport(file, "java.util.Map", "java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap"); + checkAddImport(file, "java.util.AbstractList", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap"); + checkAddImport(file, "java.util.AbstractList", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap"); + checkAddImport(file, "java.util.TreeMap", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap", "java.util.TreeMap"); + checkAddImport(file, "java.util.concurrent.atomic.AtomicBoolean", "java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap", "java.util.TreeMap", "java.util.concurrent.atomic.AtomicBoolean"); + checkAddImport(file, "java.io.File", "java.io.File","java.util.AbstractList","java.util.ArrayList","java.util.HashMap","java.util.List","java.util.Map","java.util.SortedMap", "java.util.TreeMap", "java.util.concurrent.atomic.AtomicBoolean"); + } + catch (Throwable e) { + LOG.error(e); + } + } + }); + } + }, "", ""); + } + public void testStaticImportsGrouping() throws Throwable { + @NonNls String text = "import static java.lang.Math.max;\n" + + "import java.util.Map;\n" + + "\n" + + "import static java.lang.Math.min;\n" + + "\n" + + "import java.awt.Component;\n" + + "\n" + + "\n" + + "\n" + + "import static javax.swing.SwingConstants.CENTER;\n" + + "class I {{ max(0, 0); Map.class.hashCode(); min(0,0); Component.class.hashCode(); int i = CENTER; }}"; + + final PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, text); + assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); + CommandProcessor.getInstance().executeCommand( + getProject(), new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); + settings.LAYOUT_STATIC_IMPORTS_SEPARATELY = true; + PackageEntryTable table = new PackageEntryTable(); + table.addEntry(PackageEntry.ALL_OTHER_IMPORTS_ENTRY); + table.addEntry(PackageEntry.BLANK_LINE_ENTRY); + table.addEntry(new PackageEntry(false, "javax", true)); + table.addEntry(new PackageEntry(false, "java", true)); + table.addEntry(PackageEntry.BLANK_LINE_ENTRY); + table.addEntry(new PackageEntry(true, "java", true)); + table.addEntry(PackageEntry.BLANK_LINE_ENTRY); + table.addEntry(PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY); + + settings.IMPORT_LAYOUT_TABLE.copyFrom(table); + CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(settings); + try { + JavaCodeStyleManager.getInstance(getProject()).optimizeImports(file); + } + finally { + CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); + } + + assertOrder(file, "java.awt.*", "java.util.Map", "static java.lang.Math.max", "static java.lang.Math.min", "static javax.swing.SwingConstants.CENTER"); + + } + catch (Throwable e) { + LOG.error(e); + } + } + }); + } + }, "", ""); + } + + @Override + protected Sdk getTestProjectJdk() { + return JavaSdkImpl.getMockJdk17(); + } + + private void checkAddImport(PsiJavaFile file, String fqn, String... expectedOrder) { + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); + ImportHelper importHelper = new ImportHelper(settings); + + PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(fqn, GlobalSearchScope.allScope(getProject())); + boolean b = importHelper.addImport(file, psiClass); + assertTrue(b); + + assertOrder(file, expectedOrder); + } + + private static void assertOrder(PsiJavaFile file, @NonNls String... expectedOrder) { + PsiImportStatementBase[] statements = file.getImportList().getAllImportStatements(); + + assertEquals(expectedOrder.length, statements.length); + for (int i = 0; i < statements.length; i++) { + PsiImportStatementBase statement = statements[i]; + String text = StringUtil.trimEnd(StringUtil.trimStart(statement.getText(), "import "), ";"); + assertEquals(expectedOrder[i], text); + } + } + + @NonNls private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/advHighlighting/reimportConflictingClasses"; + public void testReimportConflictingClasses() throws Exception { + configureByFile(BASE_PATH+"/x/Usage.java", BASE_PATH); + assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); + + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); + settings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 2; + CodeStyleSettingsManager.getInstance(getProject()).setTemporarySettings(settings); + try { + new WriteCommandAction.Simple(getProject()) { + @Override + protected void run() throws Throwable { + JavaCodeStyleManager.getInstance(getProject()).optimizeImports(getFile()); + } + }.execute().throwException(); + } + finally { + CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); + } + + + @NonNls String fullPath = getTestDataPath() + BASE_PATH + "/x/Usage_afterOptimize.txt"; + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); + String text = LoadTextUtil.loadText(vFile).toString(); + assertEquals(text, getFile().getText()); + } + public void testConflictingClassesFromCurrentPackage() throws Throwable { + final PsiFile file = configureByText(StdFileTypes.JAVA, "package java.util; class X{ Date d;}"); + assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); + + new WriteCommandAction.Simple(getProject()) { + @Override + protected void run() throws Throwable { + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); + ImportHelper importHelper = new ImportHelper(settings); + + PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass("java.sql.Date", GlobalSearchScope.allScope(getProject())); + boolean b = importHelper.addImport((PsiJavaFile)file, psiClass); + assertFalse(b); // must fail + } + }.execute().throwException(); + } + + @DoNotWrapInCommand + public void testAutoImportCaretLocation() throws Throwable { + boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + try { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; + configureByText(StdFileTypes.JAVA, "class X { ArrayList c; }"); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); + type(" "); + backspace(); + + assertOneElement(filter(doHighlighting(), HighlightSeverity.ERROR)); + + int offset = myEditor.getCaretModel().getOffset(); + PsiReference ref = myFile.findReferenceAt(offset - 1); + assertTrue(ref instanceof PsiJavaCodeReferenceElement); + + ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); + assertEquals(ImportClassFixBase.Result.POPUP_NOT_SHOWN, result); + UIUtil.dispatchAllInvocationEvents(); + + myEditor.getCaretModel().moveToOffset(offset - 1); + result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); + assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); + UIUtil.dispatchAllInvocationEvents(); + + assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); + } + finally { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; + } + } + + @DoNotWrapInCommand + public void testAutoImportCaretLocation2() throws Throwable { + boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + try { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; + configureByText(StdFileTypes.JAVA, "class X { ArrayList c = new ArrayList(); }"); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); + type(" "); + backspace(); + + assertEquals(2, filter(doHighlighting(), HighlightSeverity.ERROR).size()); + UIUtil.dispatchAllInvocationEvents(); + + int offset = myEditor.getCaretModel().getOffset(); + PsiReference ref = myFile.findReferenceAt(offset); + assertTrue(ref instanceof PsiJavaCodeReferenceElement); + + ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); + assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); + UIUtil.dispatchAllInvocationEvents(); + + assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); + } + finally { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; + } + } + + @DoNotWrapInCommand + public void testAutoImportWorksWhenITypeSpaceAfterClassName() throws Throwable { + @NonNls String text = "class S { ArrayList }"; + configureByText(StdFileTypes.JAVA, text); + + boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; + DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); + + try { + doHighlighting(); + //caret is too close + assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); + + type(" "); + + PsiJavaCodeReferenceElement element = + (PsiJavaCodeReferenceElement)getFile().findReferenceAt(getEditor().getCaretModel().getOffset() - 2); + ImportClassFix fix = new ImportClassFix(element); + ImportClassFixBase.Result result = fix.doFix(getEditor(), false, false); + assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); + + assertNotSame(0, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); + } + finally { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; + } + } + + @DoNotWrapInCommand + public void testAutoImportWorks() throws Throwable { + @NonNls final String text = "class S { JFrame x; }"; + configureByText(StdFileTypes.JAVA, text); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); + assertFalse(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile())); + + + doHighlighting(); + assertFalse(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile())); + + type(" "); + assertTrue(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile())); + + undo(); + + assertFalse(((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).canChangeFileSilently(getFile()));//CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; + } + + @DoNotWrapInCommand + public void testAutoImportOfGenericReference() throws Throwable { + @NonNls final String text = "class S {{ new ArrayList<> }}"; + configureByText(StdFileTypes.JAVA, text); + boolean old = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true; + DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); + + ((UndoManagerImpl)UndoManager.getInstance(getProject())).flushCurrentCommandMerger(); + ((UndoManagerImpl)UndoManager.getInstance(getProject())).clearUndoRedoQueueInTests(getFile().getVirtualFile()); + type(" "); + backspace(); + + try { + doHighlighting(); + //caret is too close + assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); + + caretRight(); + + doHighlighting(); + + assertNotSame(0, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); + } + finally { + CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = old; + } + } + + @DoNotWrapInCommand + public void testAutoOptimizeUnresolvedImports() throws Throwable { + @NonNls String text = "import xxx.yyy; class S { } "; + configureByText(StdFileTypes.JAVA, text); + + boolean old = CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY; + CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY = true; + DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); + + try { + List errs = filter(doHighlighting(), HighlightSeverity.ERROR); + + assertEquals(1, errs.size()); + + assertEquals(1, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); + + type("/* */"); + doHighlighting(); + UIUtil.dispatchAllInvocationEvents(); + + assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); + } + finally { + CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY = old; + } + } + +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java index 9ce4f2b1a592..6f7f21d5cc36 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LossyEncodingTest.java @@ -1,94 +1,94 @@ -/* - * Created by IntelliJ IDEA. - * User: max - * Date: Apr 11, 2002 - * Time: 7:51:16 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ -package com.intellij.codeInsight.daemon; - -import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.codeInspection.LossyEncodingInspection; -import com.intellij.openapi.vfs.encoding.EncodingManager; -import com.intellij.openapi.vfs.encoding.EncodingProjectManager; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import java.nio.charset.Charset; -import java.util.Collection; - -public class LossyEncodingTest extends LightDaemonAnalyzerTestCase { - @NonNls private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lossyEncoding"; - - @Override - protected LocalInspectionTool[] configureLocalInspectionTools() { - return new LocalInspectionTool[]{new LossyEncodingInspection()}; - } - - public void testText() throws Exception { - doTest("Text.txt"); - EncodingManager.getInstance().setEncoding(myVFile, Charset.forName("US-ASCII")); - int start = myEditor.getCaretModel().getOffset(); - type((char)0x445); - type((char)0x438); - int end = myEditor.getCaretModel().getOffset(); - - Collection infos = doHighlighting(); - HighlightInfo info = assertOneElement(infos); - assertEquals("Unsupported characters for the charset 'US-ASCII'", info.description); - assertEquals(start, info.startOffset); - assertEquals(end, info.endOffset); - - backspace(); - backspace(); - doTestConfiguredFile(true, false); - } - public void testNativeConversion() throws Exception { - configureFromFileText("x.properties","a=v"); - EncodingProjectManager.getInstance(getProject()).setNative2AsciiForPropertiesFiles(null, true); - UIUtil.dispatchAllInvocationEvents(); //reload files - - type('\\'); - type('\\'); - - Collection infos = doHighlighting(); - assertEquals(0, infos.size()); - } - - public void testTyping() throws Exception { - doTest("Simple.xml"); - type("US-ASCII"); - - Collection infos = doHighlighting(); - assertEquals(1, infos.size()); - boolean found = false; - - for(HighlightInfo info:infos) { - if (info.description.equals("Unsupported characters for the charset 'US-ASCII'")) { - found = true; - break; - } - } - assertTrue(found); - } - - public void testMultipleRanges() throws Exception { - configureByFile(BASE_PATH + "/" + "MultipleRanges.xml"); - type("US-ASCII"); - - doTestConfiguredFile(true, false); - } - - public void testNativeEncoding() throws Exception { - EncodingManager.getInstance().setNative2AsciiForPropertiesFiles(null, true); - configureByFile(BASE_PATH + "/" + "NativeEncoding.properties"); - - doTestConfiguredFile(true, false); - } - - private void doTest(@NonNls String filePath) throws Exception { - doTest(BASE_PATH + "/" + filePath, true, false); - } -} +/* + * Created by IntelliJ IDEA. + * User: max + * Date: Apr 11, 2002 + * Time: 7:51:16 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.codeInsight.daemon; + +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.LossyEncodingInspection; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.openapi.vfs.encoding.EncodingProjectManager; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import java.nio.charset.Charset; +import java.util.Collection; + +public class LossyEncodingTest extends LightDaemonAnalyzerTestCase { + @NonNls private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lossyEncoding"; + + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new LossyEncodingInspection()}; + } + + public void testText() throws Exception { + doTest("Text.txt"); + EncodingManager.getInstance().setEncoding(myVFile, Charset.forName("US-ASCII")); + int start = myEditor.getCaretModel().getOffset(); + type((char)0x445); + type((char)0x438); + int end = myEditor.getCaretModel().getOffset(); + + Collection infos = doHighlighting(); + HighlightInfo info = assertOneElement(infos); + assertEquals("Unsupported characters for the charset 'US-ASCII'", info.description); + assertEquals(start, info.startOffset); + assertEquals(end, info.endOffset); + + backspace(); + backspace(); + doTestConfiguredFile(true, false); + } + public void testNativeConversion() throws Exception { + configureFromFileText("x.properties","a=v"); + EncodingProjectManager.getInstance(getProject()).setNative2AsciiForPropertiesFiles(null, true); + UIUtil.dispatchAllInvocationEvents(); //reload files + + type('\\'); + type('\\'); + + Collection infos = doHighlighting(); + assertEquals(0, infos.size()); + } + + public void testTyping() throws Exception { + doTest("Simple.xml"); + type("US-ASCII"); + + Collection infos = doHighlighting(); + assertEquals(1, infos.size()); + boolean found = false; + + for(HighlightInfo info:infos) { + if (info.description.equals("Unsupported characters for the charset 'US-ASCII'")) { + found = true; + break; + } + } + assertTrue(found); + } + + public void testMultipleRanges() throws Exception { + configureByFile(BASE_PATH + "/" + "MultipleRanges.xml"); + type("US-ASCII"); + + doTestConfiguredFile(true, false); + } + + public void testNativeEncoding() throws Exception { + EncodingManager.getInstance().setNative2AsciiForPropertiesFiles(null, true); + configureByFile(BASE_PATH + "/" + "NativeEncoding.properties"); + + doTestConfiguredFile(true, false); + } + + private void doTest(@NonNls String filePath) throws Exception { + doTest(BASE_PATH + "/" + filePath, true, false); + } +} diff --git a/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/AnnotationParserTest.java b/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/AnnotationParserTest.java index 8a1b762d1f15..e0a38e2a58dd 100644 --- a/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/AnnotationParserTest.java +++ b/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/AnnotationParserTest.java @@ -1,49 +1,49 @@ -/* - * 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.lang.java.parser.partial; - -import com.intellij.lang.PsiBuilder; -import com.intellij.lang.java.parser.DeclarationParser; -import com.intellij.lang.java.parser.JavaParsingTestCase; - - -public class AnnotationParserTest extends JavaParsingTestCase { - public AnnotationParserTest() { - super("parser-partial/annotations"); - } - - public void testMarker() { doParserTest("@Preliminary"); } - public void testSimple0() { doParserTest("@Copyright(\"bla-bla-bla\")"); } - public void testSimple1() { doParserTest("@Copyright(treatedAsValue)"); } - public void testComplex() { doParserTest("@Author(first=\"Eugene\", second=\"Wampirchik\")"); } - public void testMultiple() { doParserTest("@Preliminary @Other(name=value)"); } - public void testArray() { doParserTest("@Endorsers({\"Children\", \"Unscrupulous dentists\"})"); } - public void testNested() { doParserTest("@Author(@Name(first=\"Eugene\", second=\"Wampirchik\"))"); } - public void testQualifiedAnnotation() { doParserTest("@org.jetbrains.annotations.Nullable"); } - public void testExtraCommaInList() { doParserTest("@Anno({0, 1,})"); } - - public void testParameterizedAnnotation () { doParserTest("@Nullable"); } - public void testFirstNameMissed() { doParserTest("@Anno(value1, param2=value2)"); } - - private void doParserTest(final String text) { - doParserTest(text, new TestParser() { - @Override - public void parse(final PsiBuilder builder) { - DeclarationParser.parseAnnotations(builder); - } - }); - } -} +/* + * 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.lang.java.parser.partial; + +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.java.parser.DeclarationParser; +import com.intellij.lang.java.parser.JavaParsingTestCase; + + +public class AnnotationParserTest extends JavaParsingTestCase { + public AnnotationParserTest() { + super("parser-partial/annotations"); + } + + public void testMarker() { doParserTest("@Preliminary"); } + public void testSimple0() { doParserTest("@Copyright(\"bla-bla-bla\")"); } + public void testSimple1() { doParserTest("@Copyright(treatedAsValue)"); } + public void testComplex() { doParserTest("@Author(first=\"Eugene\", second=\"Wampirchik\")"); } + public void testMultiple() { doParserTest("@Preliminary @Other(name=value)"); } + public void testArray() { doParserTest("@Endorsers({\"Children\", \"Unscrupulous dentists\"})"); } + public void testNested() { doParserTest("@Author(@Name(first=\"Eugene\", second=\"Wampirchik\"))"); } + public void testQualifiedAnnotation() { doParserTest("@org.jetbrains.annotations.Nullable"); } + public void testExtraCommaInList() { doParserTest("@Anno({0, 1,})"); } + + public void testParameterizedAnnotation () { doParserTest("@Nullable"); } + public void testFirstNameMissed() { doParserTest("@Anno(value1, param2=value2)"); } + + private void doParserTest(final String text) { + doParserTest(text, new TestParser() { + @Override + public void parse(final PsiBuilder builder) { + DeclarationParser.parseAnnotations(builder); + } + }); + } +} diff --git a/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java b/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java index c37d931169f9..be3eb7d94aa5 100644 --- a/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java +++ b/java/java-tests/testSrc/com/intellij/lang/java/parser/partial/DeclarationParserTest.java @@ -1,115 +1,115 @@ -/* - * 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.lang.java.parser.partial; - -import com.intellij.lang.PsiBuilder; -import com.intellij.lang.java.parser.DeclarationParser; -import com.intellij.lang.java.parser.JavaParsingTestCase; -import com.intellij.pom.java.LanguageLevel; - - -public class DeclarationParserTest extends JavaParsingTestCase { - public DeclarationParserTest() { - super("parser-partial/declarations"); - } - - public void testEmptyBody0() { doParserTest("{ }", false, false); } - public void testEmptyBody1() { doParserTest("{ ", false, false); } - public void testEmptyBody2() { doParserTest("{ @Null }", false, false); } - - public void testNoType() { doParserTest("{ new X(); }", false, false); } - public void testExtraSemicolon() { doParserTest("{ class C { }; }", false, false); } - public void testParameterizedClass() { doParserTest("{ public class A { } }", false, false); } - public void testPines() { doParserTest("{ class A> extends List> { } }", false, false); } - public void testIncompleteAnnotation() { doParserTest("{ public class Foo { public void testSomething(); @Null } }", false, false); } - public void testClassInit() { doParserTest("{ { /*comment*/ } }", false, false); } - public void testAnnoDeclaration() { doParserTest("{ public @interface Annotation {} }", false, false); } - public void testEnumSmartTypeCompletion() { doParserTest("{ @Preliminary(A.B\n#) public class TimeTravel {}\n" + - " @Preliminary(a=A.B\n#) public class TimeTravel {}\n" + - " @Preliminary(a=A.B\n#, b=c) public class TimeTravel {} }", false, false); } - public void testTypeAnno() { - withLevel(LanguageLevel.JDK_1_7, - new Runnable() { @Override - public void run() { - doParserTest("{ class C<@D T extends @F Object> extends @F Object {\n" + - " @F int @F[] method() @F throws @F Exception {\n" + - " a = this instanceof @F C;\n" + - " C<@F @G C> c = new @Q C<@F C>();\n" + - " c = (@F Object)c;\n" + - " Class c = @TA String.class;\n" + - " @F C.field++;\n" + - " }\n} }", false, false); - } }); - } - - public void testEnumBody0() { doParserTest("{ ; }", false, true); } - public void testEnumBody1() { doParserTest("{ RED, GREEN, BLUE; }", false, true); } - public void testEnumBody2() { doParserTest("{ RED, GREEN, BLUE }", false, true); } - public void testEnumBody3() { doParserTest("{ RED, GREEN, BLUE, }", false, true); } - public void testEnumBody4() { doParserTest("{ RED(0), GREEN(1), BLUE(2); }", false, true); } - public void testEnumBody5() { doParserTest("{ @ANNOTATION A(10) }", false, true); } - public void testEnumBody6() { doParserTest("{ RED, GREEN, BLUE\n OurEnum() {} }", false, true); } - public void testEnumWithInitializedConstants() { doParserTest("{ A(10) { },\n B { void method() {} } }", false, true); } - public void testEnumWithoutConstants() { doParserTest("{ private A }", false, true); } - - public void testAnnoSimple() { doParserTest("{ int foo (); }", true, false); } - public void testAnnoDefault() { doParserTest("{ Class foo () default String.class; }", true, false); } - public void testAnnoNested() { doParserTest("{ @interface Inner { String bar () default \"\"; } }", true, false); } - public void testAnnoInner() { doParserTest("{ @interface Inner { double bar () default 0.0; } }", false, false); } - public void testAnnoOtherMembers() { doParserTest("{ int field;\n void m() {}\n class C {}\n interface I {} }", true, false); } - - public void testFieldSimple() { doParserTest("{ int field = 0; }", false, false); } - public void testFieldMulti() { doParserTest("{ int field1 = 0, field2; }", false, false); } - public void testUnclosedBracket() { doParserTest("{ int field[ }", false, false); } - public void testMissingInitializer() { doParserTest("{ int field = }", false, false); } - public void testUnclosedComma() { doParserTest("{ int field, }", false, false); } - public void testUnclosedSemicolon() { doParserTest("{ int field }", false, false); } - public void testMissingInitializerExpression() { doParserTest("{ int field=; }", false, false); } - public void testMultiLineUnclosed() { doParserTest("{ int \n Object o; }", false, false); } - - public void testMethodNormal0() { doParserTest("{ void f() {} }", false, false); } - public void testMethodNormal1() { doParserTest("{ void f(); }", false, false); } - public void testSemicolons() { doParserTest("{ void f() {}; void g() {}; }", false, false); } - public void testUnclosed0() { doParserTest("{ void f() }", false, false); } - public void testUnclosed1() { doParserTest("{ void f( }", false, false); } - public void testUnclosed2() { doParserTest("{ void f()\n void g(); }", false, false); } - public void testUnclosed3() { doParserTest("{ void f(int a }", false, false); } - public void testUnclosed4() { doParserTest("{ void f(int a,, }", false, false); } - public void testUnclosed5() { doParserTest("{ void f(int a,); }", false, false); } - public void testConstructorBrackets() { doParserTest("{ A() [] { } }", false, false); } - public void testVarArgBrackets() { doParserTest("{ void foo(int... x[]); }", false, false); } - - public void testGenericMethod() { doParserTest("{ public static test();\n" + - " void test1();\n" + - " String test2(); }", false, false); } - public void testGenericMethodErrors() { doParserTest("{ test (); }", false, false); } - public void testErrors() { doParserTest("{ public static protected int f1 = 0; }", false, false); } - public void testCompletionHack0() { doParserTest("{ \n String s = \"\"; }", false, false); } - public void testCompletionHack1() { doParserTest("{ x(Collection x); }", false, false); } - public void testParameterAnnotation() { doParserTest("{ void foo (@Annotation(value=77) int param) {} }", false, false); } - public void testParameterizedMethod() { doParserTest("{ @Nullable T bar() {} }", false, false); } - - private void doParserTest(final String text, final boolean isAnnotation, final boolean isEnum) { - doParserTest(text, new TestParser() { - @Override - public void parse(final PsiBuilder builder) { - DeclarationParser.parseClassBodyWithBraces(builder, isAnnotation, isEnum); - } - }); - } -} +/* + * 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.lang.java.parser.partial; + +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.java.parser.DeclarationParser; +import com.intellij.lang.java.parser.JavaParsingTestCase; +import com.intellij.pom.java.LanguageLevel; + + +public class DeclarationParserTest extends JavaParsingTestCase { + public DeclarationParserTest() { + super("parser-partial/declarations"); + } + + public void testEmptyBody0() { doParserTest("{ }", false, false); } + public void testEmptyBody1() { doParserTest("{ ", false, false); } + public void testEmptyBody2() { doParserTest("{ @Null }", false, false); } + + public void testNoType() { doParserTest("{ new X(); }", false, false); } + public void testExtraSemicolon() { doParserTest("{ class C { }; }", false, false); } + public void testParameterizedClass() { doParserTest("{ public class A { } }", false, false); } + public void testPines() { doParserTest("{ class A> extends List> { } }", false, false); } + public void testIncompleteAnnotation() { doParserTest("{ public class Foo { public void testSomething(); @Null } }", false, false); } + public void testClassInit() { doParserTest("{ { /*comment*/ } }", false, false); } + public void testAnnoDeclaration() { doParserTest("{ public @interface Annotation {} }", false, false); } + public void testEnumSmartTypeCompletion() { doParserTest("{ @Preliminary(A.B\n#) public class TimeTravel {}\n" + + " @Preliminary(a=A.B\n#) public class TimeTravel {}\n" + + " @Preliminary(a=A.B\n#, b=c) public class TimeTravel {} }", false, false); } + public void testTypeAnno() { + withLevel(LanguageLevel.JDK_1_7, + new Runnable() { @Override + public void run() { + doParserTest("{ class C<@D T extends @F Object> extends @F Object {\n" + + " @F int @F[] method() @F throws @F Exception {\n" + + " a = this instanceof @F C;\n" + + " C<@F @G C> c = new @Q C<@F C>();\n" + + " c = (@F Object)c;\n" + + " Class c = @TA String.class;\n" + + " @F C.field++;\n" + + " }\n} }", false, false); + } }); + } + + public void testEnumBody0() { doParserTest("{ ; }", false, true); } + public void testEnumBody1() { doParserTest("{ RED, GREEN, BLUE; }", false, true); } + public void testEnumBody2() { doParserTest("{ RED, GREEN, BLUE }", false, true); } + public void testEnumBody3() { doParserTest("{ RED, GREEN, BLUE, }", false, true); } + public void testEnumBody4() { doParserTest("{ RED(0), GREEN(1), BLUE(2); }", false, true); } + public void testEnumBody5() { doParserTest("{ @ANNOTATION A(10) }", false, true); } + public void testEnumBody6() { doParserTest("{ RED, GREEN, BLUE\n OurEnum() {} }", false, true); } + public void testEnumWithInitializedConstants() { doParserTest("{ A(10) { },\n B { void method() {} } }", false, true); } + public void testEnumWithoutConstants() { doParserTest("{ private A }", false, true); } + + public void testAnnoSimple() { doParserTest("{ int foo (); }", true, false); } + public void testAnnoDefault() { doParserTest("{ Class foo () default String.class; }", true, false); } + public void testAnnoNested() { doParserTest("{ @interface Inner { String bar () default \"\"; } }", true, false); } + public void testAnnoInner() { doParserTest("{ @interface Inner { double bar () default 0.0; } }", false, false); } + public void testAnnoOtherMembers() { doParserTest("{ int field;\n void m() {}\n class C {}\n interface I {} }", true, false); } + + public void testFieldSimple() { doParserTest("{ int field = 0; }", false, false); } + public void testFieldMulti() { doParserTest("{ int field1 = 0, field2; }", false, false); } + public void testUnclosedBracket() { doParserTest("{ int field[ }", false, false); } + public void testMissingInitializer() { doParserTest("{ int field = }", false, false); } + public void testUnclosedComma() { doParserTest("{ int field, }", false, false); } + public void testUnclosedSemicolon() { doParserTest("{ int field }", false, false); } + public void testMissingInitializerExpression() { doParserTest("{ int field=; }", false, false); } + public void testMultiLineUnclosed() { doParserTest("{ int \n Object o; }", false, false); } + + public void testMethodNormal0() { doParserTest("{ void f() {} }", false, false); } + public void testMethodNormal1() { doParserTest("{ void f(); }", false, false); } + public void testSemicolons() { doParserTest("{ void f() {}; void g() {}; }", false, false); } + public void testUnclosed0() { doParserTest("{ void f() }", false, false); } + public void testUnclosed1() { doParserTest("{ void f( }", false, false); } + public void testUnclosed2() { doParserTest("{ void f()\n void g(); }", false, false); } + public void testUnclosed3() { doParserTest("{ void f(int a }", false, false); } + public void testUnclosed4() { doParserTest("{ void f(int a,, }", false, false); } + public void testUnclosed5() { doParserTest("{ void f(int a,); }", false, false); } + public void testConstructorBrackets() { doParserTest("{ A() [] { } }", false, false); } + public void testVarArgBrackets() { doParserTest("{ void foo(int... x[]); }", false, false); } + + public void testGenericMethod() { doParserTest("{ public static test();\n" + + " void test1();\n" + + " String test2(); }", false, false); } + public void testGenericMethodErrors() { doParserTest("{ test (); }", false, false); } + public void testErrors() { doParserTest("{ public static protected int f1 = 0; }", false, false); } + public void testCompletionHack0() { doParserTest("{ \n String s = \"\"; }", false, false); } + public void testCompletionHack1() { doParserTest("{ x(Collection x); }", false, false); } + public void testParameterAnnotation() { doParserTest("{ void foo (@Annotation(value=77) int param) {} }", false, false); } + public void testParameterizedMethod() { doParserTest("{ @Nullable T bar() {} }", false, false); } + + private void doParserTest(final String text, final boolean isAnnotation, final boolean isEnum) { + doParserTest(text, new TestParser() { + @Override + public void parse(final PsiBuilder builder) { + DeclarationParser.parseClassBodyWithBraces(builder, isAnnotation, isEnum); + } + }); + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/SCR17650Test.java b/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/SCR17650Test.java index 46e464489f67..634f37e0bb2e 100644 --- a/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/SCR17650Test.java +++ b/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/SCR17650Test.java @@ -1,74 +1,74 @@ -package com.intellij.psi.impl.cache.impl; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.PathManagerEx; -import com.intellij.openapi.roots.ContentEntry; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.PsiTestCase; - -import java.io.File; -import java.io.IOException; - -/** - * @author max - */ -public class SCR17650Test extends PsiTestCase { - private static final String TEST_ROOT = PathManagerEx.getTestDataPath() + "/psi/repositoryUse/cls"; - - private VirtualFile myDir; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - final File root = FileUtil.createTempFile(getName(), ""); - root.delete(); - root.mkdir(); - myFilesToDelete.add(root); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - VirtualFile rootVFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(root.getAbsolutePath().replace(File.separatorChar, '/')); - - myDir = rootVFile.createChildDirectory(null, "contentAndLibrary"); - - VirtualFile file1 = myDir.createChildData(null, "A.java"); - VfsUtil.saveText(file1, "package p; public class A{ public void foo(); }"); - VfsUtil.copyFile(null, getClassFile(), myDir); - - final ModifiableRootModel rootModel = ModuleRootManager.getInstance(myModule).getModifiableModel(); - final ContentEntry contentEntry1 = rootModel.addContentEntry(myDir); - contentEntry1.addSourceFolder(myDir, false); - final Library jarLibrary = rootModel.getModuleLibraryTable().createLibrary(); - final Library.ModifiableModel libraryModel = jarLibrary.getModifiableModel(); - libraryModel.addRoot(myDir, OrderRootType.CLASSES); - libraryModel.commit(); - rootModel.commit(); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - } - - private static VirtualFile getClassFile() { - VirtualFile vDir = LocalFileSystem.getInstance().findFileByPath(TEST_ROOT.replace(File.separatorChar, '/')); - VirtualFile child = vDir.findChild("pack").findChild("MyClass.class"); - return child; - } - - public void test17650() throws Exception { - assertEquals("p.A", myJavaFacade.findClass("p.A").getQualifiedName()); - assertEquals("pack.MyClass", myJavaFacade.findClass("pack.MyClass").getQualifiedName()); - } -} +package com.intellij.psi.impl.cache.impl; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.roots.ContentEntry; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PsiTestCase; + +import java.io.File; +import java.io.IOException; + +/** + * @author max + */ +public class SCR17650Test extends PsiTestCase { + private static final String TEST_ROOT = PathManagerEx.getTestDataPath() + "/psi/repositoryUse/cls"; + + private VirtualFile myDir; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + final File root = FileUtil.createTempFile(getName(), ""); + root.delete(); + root.mkdir(); + myFilesToDelete.add(root); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + VirtualFile rootVFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(root.getAbsolutePath().replace(File.separatorChar, '/')); + + myDir = rootVFile.createChildDirectory(null, "contentAndLibrary"); + + VirtualFile file1 = myDir.createChildData(null, "A.java"); + VfsUtil.saveText(file1, "package p; public class A{ public void foo(); }"); + VfsUtil.copyFile(null, getClassFile(), myDir); + + final ModifiableRootModel rootModel = ModuleRootManager.getInstance(myModule).getModifiableModel(); + final ContentEntry contentEntry1 = rootModel.addContentEntry(myDir); + contentEntry1.addSourceFolder(myDir, false); + final Library jarLibrary = rootModel.getModuleLibraryTable().createLibrary(); + final Library.ModifiableModel libraryModel = jarLibrary.getModifiableModel(); + libraryModel.addRoot(myDir, OrderRootType.CLASSES); + libraryModel.commit(); + rootModel.commit(); + } + catch (IOException e) { + LOG.error(e); + } + } + }); + } + + private static VirtualFile getClassFile() { + VirtualFile vDir = LocalFileSystem.getInstance().findFileByPath(TEST_ROOT.replace(File.separatorChar, '/')); + VirtualFile child = vDir.findChild("pack").findChild("MyClass.class"); + return child; + } + + public void test17650() throws Exception { + assertEquals("p.A", myJavaFacade.findClass("p.A").getQualifiedName()); + assertEquals("pack.MyClass", myJavaFacade.findClass("pack.MyClass").getQualifiedName()); + } +} diff --git a/java/java-tests/testSrc/com/intellij/refactoring/ExtractMethodTest.java b/java/java-tests/testSrc/com/intellij/refactoring/ExtractMethodTest.java index 056dd56b152b..378202b316f3 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/ExtractMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/ExtractMethodTest.java @@ -1,556 +1,556 @@ -package com.intellij.refactoring; - -import com.intellij.JavaTestUtil; -import com.intellij.codeInsight.CodeInsightUtil; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.refactoring.extractMethod.ExtractMethodProcessor; -import com.intellij.refactoring.extractMethod.PrepareFailedException; -import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; -import com.intellij.refactoring.util.duplicates.Match; -import com.intellij.testFramework.LightCodeInsightTestCase; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.NonNls; - -import java.util.List; - -public class ExtractMethodTest extends LightCodeInsightTestCase { - @NonNls private static final String BASE_PATH = "/refactoring/extractMethod/"; - private boolean myCatchOnNewLine = true; - - @Override - protected String getTestDataPath() { - return JavaTestUtil.getJavaTestDataPath(); - } - - @Override - protected Sdk getProjectJDK() { - return JavaSdkImpl.getMockJdk17("java 1.5"); - } - - public void testExitPoints1() throws Exception { - doExitPointsTest(true); - } - - public void testExitPoints2() throws Exception { - doTest(); - } - - public void testExitPoints3() throws Exception { - doExitPointsTest(true); - } - - public void testExitPoints4() throws Exception { - doExitPointsTest(false); - } - - public void testExitPointsInsideLoop() throws Exception { - doExitPointsTest(true); - } - - public void testExitPoints5() throws Exception { - doTest(); - } - - public void testExitPoints6() throws Exception { - doExitPointsTest(false); - } - - public void testExitPoints7() throws Exception { - doExitPointsTest(false); - } - - public void testExitPoints8() throws Exception { - doExitPointsTest(false); - } - - public void testExitPoints9() throws Exception { - doExitPointsTest(false); - } - - public void testContinueInside() throws Exception { - doTest(); - } - - public void testBooleanExpression() throws Exception { - doTest(); - } - - public void testScr6241() throws Exception { - doTest(); - } - - public void testScr7091() throws Exception { - doTest(); - } - - public void testScr10464() throws Exception { - doTest(); - } - - public void testScr9852() throws Exception { - doTest(); - } - - public void testUseVarAfterTry() throws Exception { - doTest(); - } - - public void testOneBranchAssignment() throws Exception { - doTest(); - } - - public void testExtractFromCodeBlock() throws Exception { - doTest(); - } - - public void testUnusedInitializedVar() throws Exception { - doTest(); - } - - public void testTryFinally() throws Exception { - doTest(); - } - - public void testFinally() throws Exception { - doTest(); - } - - public void testExtractFromAnonymous() throws Exception { - doTest(); - } - - public void testSCR12245() throws Exception { - doTest(); - } - - public void testSCR15815() throws Exception { - doTest(); - } - - public void testSCR27887() throws Exception { - doTest(); - } - - public void testSCR28427() throws Exception { - doTest(); - } - - public void testTryFinallyInsideFor() throws Exception { - doTest(); - } - - public void testExtractFromTryFinally() throws Exception { - doTest(); - } - - public void _testExtractFromTryFinally2() throws Exception { // IDEADEV-11844 - doTest(); - } - - public void testLesyaBug() throws Exception { - myCatchOnNewLine = false; - doTest(); - } - - public void testForEach() throws Exception { - doTest(); - } - - public void testAnonInner() throws Exception { - doTest(); - } - - - public void testConflictingAnonymous() throws Exception { - doTest(); - } - - - public void testFinalParamUsedInsideAnon() throws Exception { - CodeStyleSettingsManager.getSettings(getProject()).GENERATE_FINAL_PARAMETERS = false; - doTest(); - } - - public void testNonFinalWritableParam() throws Exception { - CodeStyleSettingsManager.getSettings(getProject()).GENERATE_FINAL_PARAMETERS = true; - doTest(); - } - - public void testCodeDuplicatesWithContinue() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithContinueNoReturn() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithStaticInitializer() throws Exception { - doDuplicatesTest(); - } - - public void testExpressionDuplicates() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicates() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicates2() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicates3() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicates4() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicates5() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithOutputValue() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithOutputValue1() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithMultExitPoints() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithReturn() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithReturn2() throws Exception { - doDuplicatesTest(); - } - - public void testCodeDuplicatesWithComments() throws Exception { - doDuplicatesTest(); - } - - public void testSCR32924() throws Exception { - doDuplicatesTest(); - } - - public void testFinalOutputVar() throws Exception { - doDuplicatesTest(); - } - - public void testIdeaDev2291() throws Exception { - doTest(); - } - - public void testOxfordBug() throws Exception { - doTest(); - } - - public void testIDEADEV33368() throws Exception { - doTest(); - } - - public void testInlineCreated2ReturnLocalVariablesOnly() throws Exception { - doTest(); - } - - public void testGuardMethodDuplicates() throws Exception { - doDuplicatesTest(); - } - - public void testGuardMethodDuplicates1() throws Exception { - doDuplicatesTest(); - } - - public void testInstanceMethodDuplicatesInStaticContext() throws Exception { - doDuplicatesTest(); - } - - - public void testLValueNotDuplicate() throws Exception { - doDuplicatesTest(); - } - - protected void doDuplicatesTest() throws Exception { - doTest(true); - } - - public void testExtractFromFinally() throws Exception { - doTest(); - } - - public void testNoShortCircuit() throws Exception { - doTest(); - } - - public void testStopFolding() throws Exception { - doTest(); - } - - public void testIDEADEV11748() throws Exception { - doTest(); - } - - public void testIDEADEV11848() throws Exception { - doTest(); - } - - public void testIDEADEV11036() throws Exception { - doTest(); - } - - public void testLocalClass() throws Exception { - doPrepareErrorTest("Cannot extract method because the selected code fragment uses local classes defined outside of the fragment"); - } - - public void testLocalClassUsage() throws Exception { - doPrepareErrorTest("Cannot extract method because the selected code fragment defines local classes used outside of the fragment"); - } - - public void testStaticImport() throws Exception { - doTest(); - } - - public void testThisCall() throws Exception { - doTest(); - } - - public void testChainedConstructor() throws Exception { - doChainedConstructorTest(false); - } - - public void testChainedConstructorDuplicates() throws Exception { - doChainedConstructorTest(true); - } - - public void testChainedConstructorInvalidDuplicates() throws Exception { - doChainedConstructorTest(true); - } - - public void testReturnFromTry() throws Exception { - doTest(); - } - - public void testLocalClassDefinedInMethodWhichIsUsedLater() throws Exception { - doPrepareErrorTest("Cannot extract method because the selected code fragment defines variable of local class type used outside of the fragment"); - } - - public void testForceBraces() throws Exception { - final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); - int old = settings.IF_BRACE_FORCE; - settings.IF_BRACE_FORCE = CodeStyleSettings.FORCE_BRACES_ALWAYS; - try { - doTest(); - } - finally { - settings.IF_BRACE_FORCE = old; - } - } - - public void testConstantConditionsAffectingControlFlow() throws Exception { - doTest(); - } - public void testNotInitializedInsideFinally() throws Exception { - doTest(); - } - - public void testGenericsParameters() throws Exception { - doTest(); - } - - public void testUnusedGenerics() throws Exception { - doTest(); - } - - public void testParamsUsedInLocalClass() throws Exception { - doTest(); - } - - private void doChainedConstructorTest(final boolean replaceAllDuplicates) throws Exception { - configureByFile(BASE_PATH + getTestName(false) + ".java"); - boolean success = performExtractMethod(true, replaceAllDuplicates, getEditor(), getFile(), getProject(), true); - assertTrue(success); - checkResultByFile(BASE_PATH + getTestName(false) + "_after.java"); - } - - public void testReassignedVarAfterCall() throws Exception { - final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); - boolean oldGenerateFinalLocals = settings.GENERATE_FINAL_LOCALS; - try { - settings.GENERATE_FINAL_LOCALS = true; - doTest(); - } - finally { - settings.GENERATE_FINAL_LOCALS = oldGenerateFinalLocals; - } - } - - public void testNullableCheck() throws Exception { - doTest(); - } - - public void testNullableCheck1() throws Exception { - doTest(); - } - - public void testNullableCheckVoid() throws Exception { - doTest(); - } - - public void testNullableCheckDontMissFinal() throws Exception { - doTest(); - } - - public void testSimpleArrayAccess() throws Exception { - doTest(); - } - - public void testArrayAccess() throws Exception { - doTest(); - } - - public void testArrayAccess1() throws Exception { - doTest(); - } - - public void testArrayAccessWithLocalIndex() throws Exception { - doTest(); - } - - public void testArrayAccessWithDuplicates() throws Exception { - doDuplicatesTest(); - } - - public void testVerboseArrayAccess() throws Exception { - doTest(); - } - - public void testReturnStatementFolding() throws Exception { - doTest(); - } - - public void testWriteArrayAccess() throws Exception { - doTest(); - } - - public void testShortCircuit() throws Exception { - doTest(); - } - - public void testRecursiveCallToExtracted() throws Exception { - doTest(); - } - - public void testCodeDuplicatesVarargsShouldNotChangeReturnType() throws Exception { - doDuplicatesTest(); - } - - public void testParametersFromAnonymous() throws Exception { - doTest(); - } - - public void testCast4ParamGeneration() throws Exception { - doTest(); - } - - private void doPrepareErrorTest(final String expectedMessage) throws Exception { - String expectedError = null; - try { - doExitPointsTest(false); - } - catch(PrepareFailedException ex) { - expectedError = ex.getMessage(); - } - assertEquals(expectedMessage, expectedError); - } - - private void doExitPointsTest(boolean shouldSucceed) throws Exception { - String fileName = getTestName(false) + ".java"; - configureByFile(BASE_PATH + fileName); - boolean success = performAction(false, false); - assertEquals(shouldSucceed, success); - } - - void doTest() throws Exception { - final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); - settings.ELSE_ON_NEW_LINE = true; - settings.CATCH_ON_NEW_LINE = myCatchOnNewLine; - doTest(true); - } - - private void doTest(boolean duplicates) throws Exception { - configureByFile(BASE_PATH + getTestName(false) + ".java"); - boolean success = performAction(true, duplicates); - assertTrue(success); - checkResultByFile(BASE_PATH + getTestName(false) + "_after.java"); - } - - private static boolean performAction(boolean doRefactor, boolean replaceAllDuplicates) throws Exception { - return performExtractMethod(doRefactor, replaceAllDuplicates, getEditor(), getFile(), getProject()); - } - - public static boolean performExtractMethod(boolean doRefactor, boolean replaceAllDuplicates, Editor editor, PsiFile file, Project project) - throws PrepareFailedException, IncorrectOperationException { - return performExtractMethod(doRefactor, replaceAllDuplicates, editor, file, project, false); - } - - public static boolean performExtractMethod(boolean doRefactor, boolean replaceAllDuplicates, Editor editor, PsiFile file, Project project, - final boolean extractChainedConstructor) - throws PrepareFailedException, IncorrectOperationException { - int startOffset = editor.getSelectionModel().getSelectionStart(); - int endOffset = editor.getSelectionModel().getSelectionEnd(); - - PsiElement[] elements; - PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); - if (expr != null) { - elements = new PsiElement[]{expr}; - } - else { - elements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); - } - if (elements.length == 0) { - final PsiExpression expression = IntroduceVariableBase.getSelectedExpression(project, file, startOffset, endOffset); - if (expression != null) { - elements = new PsiElement[]{expression}; - } - } - assertTrue(elements.length > 0); - - final ExtractMethodProcessor processor = - new ExtractMethodProcessor(project, editor, elements, null, "Extract Method", "newMethod", null); - processor.setShowErrorDialogs(false); - processor.setChainedConstructor(extractChainedConstructor); - - if (!processor.prepare()) { - return false; - } - - if (doRefactor) { - processor.testRun(); - } - - if (replaceAllDuplicates) { - final List duplicates = processor.getDuplicates(); - for (final Match match : duplicates) { - if (!match.getMatchStart().isValid() || !match.getMatchEnd().isValid()) continue; - processor.processMatch(match); - } - } - - return true; - } -} +package com.intellij.refactoring; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.CodeInsightUtil; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.refactoring.extractMethod.ExtractMethodProcessor; +import com.intellij.refactoring.extractMethod.PrepareFailedException; +import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; +import com.intellij.refactoring.util.duplicates.Match; +import com.intellij.testFramework.LightCodeInsightTestCase; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NonNls; + +import java.util.List; + +public class ExtractMethodTest extends LightCodeInsightTestCase { + @NonNls private static final String BASE_PATH = "/refactoring/extractMethod/"; + private boolean myCatchOnNewLine = true; + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath(); + } + + @Override + protected Sdk getProjectJDK() { + return JavaSdkImpl.getMockJdk17("java 1.5"); + } + + public void testExitPoints1() throws Exception { + doExitPointsTest(true); + } + + public void testExitPoints2() throws Exception { + doTest(); + } + + public void testExitPoints3() throws Exception { + doExitPointsTest(true); + } + + public void testExitPoints4() throws Exception { + doExitPointsTest(false); + } + + public void testExitPointsInsideLoop() throws Exception { + doExitPointsTest(true); + } + + public void testExitPoints5() throws Exception { + doTest(); + } + + public void testExitPoints6() throws Exception { + doExitPointsTest(false); + } + + public void testExitPoints7() throws Exception { + doExitPointsTest(false); + } + + public void testExitPoints8() throws Exception { + doExitPointsTest(false); + } + + public void testExitPoints9() throws Exception { + doExitPointsTest(false); + } + + public void testContinueInside() throws Exception { + doTest(); + } + + public void testBooleanExpression() throws Exception { + doTest(); + } + + public void testScr6241() throws Exception { + doTest(); + } + + public void testScr7091() throws Exception { + doTest(); + } + + public void testScr10464() throws Exception { + doTest(); + } + + public void testScr9852() throws Exception { + doTest(); + } + + public void testUseVarAfterTry() throws Exception { + doTest(); + } + + public void testOneBranchAssignment() throws Exception { + doTest(); + } + + public void testExtractFromCodeBlock() throws Exception { + doTest(); + } + + public void testUnusedInitializedVar() throws Exception { + doTest(); + } + + public void testTryFinally() throws Exception { + doTest(); + } + + public void testFinally() throws Exception { + doTest(); + } + + public void testExtractFromAnonymous() throws Exception { + doTest(); + } + + public void testSCR12245() throws Exception { + doTest(); + } + + public void testSCR15815() throws Exception { + doTest(); + } + + public void testSCR27887() throws Exception { + doTest(); + } + + public void testSCR28427() throws Exception { + doTest(); + } + + public void testTryFinallyInsideFor() throws Exception { + doTest(); + } + + public void testExtractFromTryFinally() throws Exception { + doTest(); + } + + public void _testExtractFromTryFinally2() throws Exception { // IDEADEV-11844 + doTest(); + } + + public void testLesyaBug() throws Exception { + myCatchOnNewLine = false; + doTest(); + } + + public void testForEach() throws Exception { + doTest(); + } + + public void testAnonInner() throws Exception { + doTest(); + } + + + public void testConflictingAnonymous() throws Exception { + doTest(); + } + + + public void testFinalParamUsedInsideAnon() throws Exception { + CodeStyleSettingsManager.getSettings(getProject()).GENERATE_FINAL_PARAMETERS = false; + doTest(); + } + + public void testNonFinalWritableParam() throws Exception { + CodeStyleSettingsManager.getSettings(getProject()).GENERATE_FINAL_PARAMETERS = true; + doTest(); + } + + public void testCodeDuplicatesWithContinue() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithContinueNoReturn() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithStaticInitializer() throws Exception { + doDuplicatesTest(); + } + + public void testExpressionDuplicates() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicates() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicates2() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicates3() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicates4() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicates5() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithOutputValue() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithOutputValue1() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithMultExitPoints() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithReturn() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithReturn2() throws Exception { + doDuplicatesTest(); + } + + public void testCodeDuplicatesWithComments() throws Exception { + doDuplicatesTest(); + } + + public void testSCR32924() throws Exception { + doDuplicatesTest(); + } + + public void testFinalOutputVar() throws Exception { + doDuplicatesTest(); + } + + public void testIdeaDev2291() throws Exception { + doTest(); + } + + public void testOxfordBug() throws Exception { + doTest(); + } + + public void testIDEADEV33368() throws Exception { + doTest(); + } + + public void testInlineCreated2ReturnLocalVariablesOnly() throws Exception { + doTest(); + } + + public void testGuardMethodDuplicates() throws Exception { + doDuplicatesTest(); + } + + public void testGuardMethodDuplicates1() throws Exception { + doDuplicatesTest(); + } + + public void testInstanceMethodDuplicatesInStaticContext() throws Exception { + doDuplicatesTest(); + } + + + public void testLValueNotDuplicate() throws Exception { + doDuplicatesTest(); + } + + protected void doDuplicatesTest() throws Exception { + doTest(true); + } + + public void testExtractFromFinally() throws Exception { + doTest(); + } + + public void testNoShortCircuit() throws Exception { + doTest(); + } + + public void testStopFolding() throws Exception { + doTest(); + } + + public void testIDEADEV11748() throws Exception { + doTest(); + } + + public void testIDEADEV11848() throws Exception { + doTest(); + } + + public void testIDEADEV11036() throws Exception { + doTest(); + } + + public void testLocalClass() throws Exception { + doPrepareErrorTest("Cannot extract method because the selected code fragment uses local classes defined outside of the fragment"); + } + + public void testLocalClassUsage() throws Exception { + doPrepareErrorTest("Cannot extract method because the selected code fragment defines local classes used outside of the fragment"); + } + + public void testStaticImport() throws Exception { + doTest(); + } + + public void testThisCall() throws Exception { + doTest(); + } + + public void testChainedConstructor() throws Exception { + doChainedConstructorTest(false); + } + + public void testChainedConstructorDuplicates() throws Exception { + doChainedConstructorTest(true); + } + + public void testChainedConstructorInvalidDuplicates() throws Exception { + doChainedConstructorTest(true); + } + + public void testReturnFromTry() throws Exception { + doTest(); + } + + public void testLocalClassDefinedInMethodWhichIsUsedLater() throws Exception { + doPrepareErrorTest("Cannot extract method because the selected code fragment defines variable of local class type used outside of the fragment"); + } + + public void testForceBraces() throws Exception { + final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); + int old = settings.IF_BRACE_FORCE; + settings.IF_BRACE_FORCE = CodeStyleSettings.FORCE_BRACES_ALWAYS; + try { + doTest(); + } + finally { + settings.IF_BRACE_FORCE = old; + } + } + + public void testConstantConditionsAffectingControlFlow() throws Exception { + doTest(); + } + public void testNotInitializedInsideFinally() throws Exception { + doTest(); + } + + public void testGenericsParameters() throws Exception { + doTest(); + } + + public void testUnusedGenerics() throws Exception { + doTest(); + } + + public void testParamsUsedInLocalClass() throws Exception { + doTest(); + } + + private void doChainedConstructorTest(final boolean replaceAllDuplicates) throws Exception { + configureByFile(BASE_PATH + getTestName(false) + ".java"); + boolean success = performExtractMethod(true, replaceAllDuplicates, getEditor(), getFile(), getProject(), true); + assertTrue(success); + checkResultByFile(BASE_PATH + getTestName(false) + "_after.java"); + } + + public void testReassignedVarAfterCall() throws Exception { + final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); + boolean oldGenerateFinalLocals = settings.GENERATE_FINAL_LOCALS; + try { + settings.GENERATE_FINAL_LOCALS = true; + doTest(); + } + finally { + settings.GENERATE_FINAL_LOCALS = oldGenerateFinalLocals; + } + } + + public void testNullableCheck() throws Exception { + doTest(); + } + + public void testNullableCheck1() throws Exception { + doTest(); + } + + public void testNullableCheckVoid() throws Exception { + doTest(); + } + + public void testNullableCheckDontMissFinal() throws Exception { + doTest(); + } + + public void testSimpleArrayAccess() throws Exception { + doTest(); + } + + public void testArrayAccess() throws Exception { + doTest(); + } + + public void testArrayAccess1() throws Exception { + doTest(); + } + + public void testArrayAccessWithLocalIndex() throws Exception { + doTest(); + } + + public void testArrayAccessWithDuplicates() throws Exception { + doDuplicatesTest(); + } + + public void testVerboseArrayAccess() throws Exception { + doTest(); + } + + public void testReturnStatementFolding() throws Exception { + doTest(); + } + + public void testWriteArrayAccess() throws Exception { + doTest(); + } + + public void testShortCircuit() throws Exception { + doTest(); + } + + public void testRecursiveCallToExtracted() throws Exception { + doTest(); + } + + public void testCodeDuplicatesVarargsShouldNotChangeReturnType() throws Exception { + doDuplicatesTest(); + } + + public void testParametersFromAnonymous() throws Exception { + doTest(); + } + + public void testCast4ParamGeneration() throws Exception { + doTest(); + } + + private void doPrepareErrorTest(final String expectedMessage) throws Exception { + String expectedError = null; + try { + doExitPointsTest(false); + } + catch(PrepareFailedException ex) { + expectedError = ex.getMessage(); + } + assertEquals(expectedMessage, expectedError); + } + + private void doExitPointsTest(boolean shouldSucceed) throws Exception { + String fileName = getTestName(false) + ".java"; + configureByFile(BASE_PATH + fileName); + boolean success = performAction(false, false); + assertEquals(shouldSucceed, success); + } + + void doTest() throws Exception { + final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()); + settings.ELSE_ON_NEW_LINE = true; + settings.CATCH_ON_NEW_LINE = myCatchOnNewLine; + doTest(true); + } + + private void doTest(boolean duplicates) throws Exception { + configureByFile(BASE_PATH + getTestName(false) + ".java"); + boolean success = performAction(true, duplicates); + assertTrue(success); + checkResultByFile(BASE_PATH + getTestName(false) + "_after.java"); + } + + private static boolean performAction(boolean doRefactor, boolean replaceAllDuplicates) throws Exception { + return performExtractMethod(doRefactor, replaceAllDuplicates, getEditor(), getFile(), getProject()); + } + + public static boolean performExtractMethod(boolean doRefactor, boolean replaceAllDuplicates, Editor editor, PsiFile file, Project project) + throws PrepareFailedException, IncorrectOperationException { + return performExtractMethod(doRefactor, replaceAllDuplicates, editor, file, project, false); + } + + public static boolean performExtractMethod(boolean doRefactor, boolean replaceAllDuplicates, Editor editor, PsiFile file, Project project, + final boolean extractChainedConstructor) + throws PrepareFailedException, IncorrectOperationException { + int startOffset = editor.getSelectionModel().getSelectionStart(); + int endOffset = editor.getSelectionModel().getSelectionEnd(); + + PsiElement[] elements; + PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); + if (expr != null) { + elements = new PsiElement[]{expr}; + } + else { + elements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); + } + if (elements.length == 0) { + final PsiExpression expression = IntroduceVariableBase.getSelectedExpression(project, file, startOffset, endOffset); + if (expression != null) { + elements = new PsiElement[]{expression}; + } + } + assertTrue(elements.length > 0); + + final ExtractMethodProcessor processor = + new ExtractMethodProcessor(project, editor, elements, null, "Extract Method", "newMethod", null); + processor.setShowErrorDialogs(false); + processor.setChainedConstructor(extractChainedConstructor); + + if (!processor.prepare()) { + return false; + } + + if (doRefactor) { + processor.testRun(); + } + + if (replaceAllDuplicates) { + final List duplicates = processor.getDuplicates(); + for (final Match match : duplicates) { + if (!match.getMatchStart().isValid() || !match.getMatchEnd().isValid()) continue; + processor.processMatch(match); + } + } + + return true; + } +} diff --git a/java/java-tests/testSrc/com/intellij/refactoring/PullUpTest.java b/java/java-tests/testSrc/com/intellij/refactoring/PullUpTest.java index 2e12521144d3..53ef64abd01f 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/PullUpTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/PullUpTest.java @@ -1,148 +1,148 @@ -/* - * Copyright (c) 2005 JetBrains s.r.o. All Rights Reserved. - */ -package com.intellij.refactoring; - -import com.intellij.JavaTestUtil; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.refactoring.listeners.JavaRefactoringListenerManager; -import com.intellij.refactoring.listeners.MoveMemberListener; -import com.intellij.refactoring.memberPullUp.PullUpHelper; -import com.intellij.refactoring.util.DocCommentPolicy; -import com.intellij.refactoring.util.classMembers.MemberInfo; -import com.intellij.testFramework.LightCodeInsightTestCase; - -/** - * @author ven - */ -public class PullUpTest extends LightCodeInsightTestCase { - private static final String BASE_PATH = "/refactoring/pullUp/"; - - - public void testQualifiedThis() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("Inner", PsiClass.class)); - } - - public void testQualifiedSuper() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("Inner", PsiClass.class)); - } - - public void testQualifiedReference() throws Exception { // IDEADEV-25008 - doTest(new RefactoringTestUtil.MemberDescriptor("x", PsiField.class), - new RefactoringTestUtil.MemberDescriptor("getX", PsiMethod.class), - new RefactoringTestUtil.MemberDescriptor("setX", PsiMethod.class)); - - } - - public void testPullUpInheritedStaticClasses() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("C", PsiClass.class), - new RefactoringTestUtil.MemberDescriptor("D", PsiClass.class)); - } - - public void testPullUpPrivateInnerClassWithPrivateConstructor() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("C", PsiClass.class)); - } - - public void testPullUpAndAbstractize() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("a", PsiMethod.class), - new RefactoringTestUtil.MemberDescriptor("b", PsiMethod.class, true)); - } - - public void testTryCatchFieldInitializer() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("field", PsiField.class)); - } - - public void testIfFieldInitializationWithNonMovedField() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - public void testIfFieldMovedInitialization() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - public void testMultipleConstructorsFieldInitialization() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - public void testMultipleConstructorsFieldInitializationNoGood() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - - public void testRemoveOverride() throws Exception { - setLanguageLevel(LanguageLevel.JDK_1_5); - doTest(new RefactoringTestUtil.MemberDescriptor("get", PsiMethod.class)); - } - - public void testTypeParamErasure() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - public void testTypeParamSubst() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - public void testTypeArgument() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); - } - - public void testGenericsInAbstractMethod() throws Exception { - doTest(new RefactoringTestUtil.MemberDescriptor("method", PsiMethod.class, true)); - } - - public void testGenericsInImplements() throws Exception { - doTest(false, new RefactoringTestUtil.MemberDescriptor("I", PsiClass.class)); - } - - private void doTest(RefactoringTestUtil.MemberDescriptor... membersToFind) throws Exception { - doTest(true, membersToFind); - } - - private void doTest(final boolean checkMemebersMovedCount, RefactoringTestUtil.MemberDescriptor... membersToFind) throws Exception { - configureByFile(BASE_PATH + getTestName(false) + ".java"); - PsiElement elementAt = getFile().findElementAt(getEditor().getCaretModel().getOffset()); - final PsiClass sourceClass = PsiTreeUtil.getParentOfType(elementAt, PsiClass.class); - assertNotNull(sourceClass); - - PsiClass targetClass = sourceClass.getSuperClass(); - if (!targetClass.isWritable()) { - final PsiClass[] interfaces = sourceClass.getInterfaces(); - assertTrue(interfaces.length == 1); - assertTrue(interfaces[0].isWritable()); - targetClass = interfaces[0]; - } - MemberInfo[] infos = RefactoringTestUtil.findMembers(sourceClass, membersToFind); - - final int[] countMoved = new int[] {0}; - final MoveMemberListener listener = new MoveMemberListener() { - @Override - public void memberMoved(PsiClass aClass, PsiMember member) { - assertEquals(sourceClass, aClass); - countMoved[0]++; - } - }; - JavaRefactoringListenerManager.getInstance(getProject()).addMoveMembersListener(listener); - final PullUpHelper helper = new PullUpHelper(sourceClass, targetClass, infos, new DocCommentPolicy(DocCommentPolicy.ASIS)); - helper.run(); - JavaRefactoringListenerManager.getInstance(getProject()).removeMoveMembersListener(listener); - if (checkMemebersMovedCount) { - assertEquals(countMoved[0], membersToFind.length); - } - checkResultByFile(BASE_PATH + getTestName(false) + "_after.java"); - } - - @Override - protected Sdk getProjectJDK() { - return JavaSdkImpl.getMockJdk17("50"); - } - - @Override - protected String getTestDataPath() { - return JavaTestUtil.getJavaTestDataPath(); - } - -} +/* + * Copyright (c) 2005 JetBrains s.r.o. All Rights Reserved. + */ +package com.intellij.refactoring; + +import com.intellij.JavaTestUtil; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.listeners.JavaRefactoringListenerManager; +import com.intellij.refactoring.listeners.MoveMemberListener; +import com.intellij.refactoring.memberPullUp.PullUpHelper; +import com.intellij.refactoring.util.DocCommentPolicy; +import com.intellij.refactoring.util.classMembers.MemberInfo; +import com.intellij.testFramework.LightCodeInsightTestCase; + +/** + * @author ven + */ +public class PullUpTest extends LightCodeInsightTestCase { + private static final String BASE_PATH = "/refactoring/pullUp/"; + + + public void testQualifiedThis() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("Inner", PsiClass.class)); + } + + public void testQualifiedSuper() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("Inner", PsiClass.class)); + } + + public void testQualifiedReference() throws Exception { // IDEADEV-25008 + doTest(new RefactoringTestUtil.MemberDescriptor("x", PsiField.class), + new RefactoringTestUtil.MemberDescriptor("getX", PsiMethod.class), + new RefactoringTestUtil.MemberDescriptor("setX", PsiMethod.class)); + + } + + public void testPullUpInheritedStaticClasses() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("C", PsiClass.class), + new RefactoringTestUtil.MemberDescriptor("D", PsiClass.class)); + } + + public void testPullUpPrivateInnerClassWithPrivateConstructor() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("C", PsiClass.class)); + } + + public void testPullUpAndAbstractize() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("a", PsiMethod.class), + new RefactoringTestUtil.MemberDescriptor("b", PsiMethod.class, true)); + } + + public void testTryCatchFieldInitializer() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("field", PsiField.class)); + } + + public void testIfFieldInitializationWithNonMovedField() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + public void testIfFieldMovedInitialization() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + public void testMultipleConstructorsFieldInitialization() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + public void testMultipleConstructorsFieldInitializationNoGood() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + + public void testRemoveOverride() throws Exception { + setLanguageLevel(LanguageLevel.JDK_1_5); + doTest(new RefactoringTestUtil.MemberDescriptor("get", PsiMethod.class)); + } + + public void testTypeParamErasure() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + public void testTypeParamSubst() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + public void testTypeArgument() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("f", PsiField.class)); + } + + public void testGenericsInAbstractMethod() throws Exception { + doTest(new RefactoringTestUtil.MemberDescriptor("method", PsiMethod.class, true)); + } + + public void testGenericsInImplements() throws Exception { + doTest(false, new RefactoringTestUtil.MemberDescriptor("I", PsiClass.class)); + } + + private void doTest(RefactoringTestUtil.MemberDescriptor... membersToFind) throws Exception { + doTest(true, membersToFind); + } + + private void doTest(final boolean checkMemebersMovedCount, RefactoringTestUtil.MemberDescriptor... membersToFind) throws Exception { + configureByFile(BASE_PATH + getTestName(false) + ".java"); + PsiElement elementAt = getFile().findElementAt(getEditor().getCaretModel().getOffset()); + final PsiClass sourceClass = PsiTreeUtil.getParentOfType(elementAt, PsiClass.class); + assertNotNull(sourceClass); + + PsiClass targetClass = sourceClass.getSuperClass(); + if (!targetClass.isWritable()) { + final PsiClass[] interfaces = sourceClass.getInterfaces(); + assertTrue(interfaces.length == 1); + assertTrue(interfaces[0].isWritable()); + targetClass = interfaces[0]; + } + MemberInfo[] infos = RefactoringTestUtil.findMembers(sourceClass, membersToFind); + + final int[] countMoved = new int[] {0}; + final MoveMemberListener listener = new MoveMemberListener() { + @Override + public void memberMoved(PsiClass aClass, PsiMember member) { + assertEquals(sourceClass, aClass); + countMoved[0]++; + } + }; + JavaRefactoringListenerManager.getInstance(getProject()).addMoveMembersListener(listener); + final PullUpHelper helper = new PullUpHelper(sourceClass, targetClass, infos, new DocCommentPolicy(DocCommentPolicy.ASIS)); + helper.run(); + JavaRefactoringListenerManager.getInstance(getProject()).removeMoveMembersListener(listener); + if (checkMemebersMovedCount) { + assertEquals(countMoved[0], membersToFind.length); + } + checkResultByFile(BASE_PATH + getTestName(false) + "_after.java"); + } + + @Override + protected Sdk getProjectJDK() { + return JavaSdkImpl.getMockJdk17("50"); + } + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath(); + } + +} diff --git a/java/java-tests/testSrc/com/intellij/refactoring/ReplaceMethodDuplicatesTestSuite.java b/java/java-tests/testSrc/com/intellij/refactoring/ReplaceMethodDuplicatesTestSuite.java index b8c1e3339dae..587184dd50a4 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/ReplaceMethodDuplicatesTestSuite.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/ReplaceMethodDuplicatesTestSuite.java @@ -1,25 +1,25 @@ -/* - * User: anna - * Date: 11-Jun-2009 - */ -package com.intellij.refactoring; - -import junit.framework.Test; -import junit.framework.TestSuite; - -public class ReplaceMethodDuplicatesTestSuite { - private ReplaceMethodDuplicatesTestSuite() { - } - - public static Test suite() { - final TestSuite testSuite = new TestSuite("Replace Duplicates Suite"); - testSuite.addTestSuite(ExtractMethod15Test.class); - testSuite.addTestSuite(ExtractMethodTest.class); - testSuite.addTestSuite(ExtractMethodObjectWithMultipleExitPointsTest.class); - testSuite.addTestSuite(ExtractMethodObjectTest.class); - testSuite.addTestSuite(FindMethodDuplicatesMiscTest.class); - testSuite.addTestSuite(FindMethodDuplicatesTest.class); - testSuite.addTestSuite(SuggestedParamTypesTest.class); - return testSuite; - } +/* + * User: anna + * Date: 11-Jun-2009 + */ +package com.intellij.refactoring; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ReplaceMethodDuplicatesTestSuite { + private ReplaceMethodDuplicatesTestSuite() { + } + + public static Test suite() { + final TestSuite testSuite = new TestSuite("Replace Duplicates Suite"); + testSuite.addTestSuite(ExtractMethod15Test.class); + testSuite.addTestSuite(ExtractMethodTest.class); + testSuite.addTestSuite(ExtractMethodObjectWithMultipleExitPointsTest.class); + testSuite.addTestSuite(ExtractMethodObjectTest.class); + testSuite.addTestSuite(FindMethodDuplicatesMiscTest.class); + testSuite.addTestSuite(FindMethodDuplicatesTest.class); + testSuite.addTestSuite(SuggestedParamTypesTest.class); + return testSuite; + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/SuggestedParamTypesTest.java b/java/java-tests/testSrc/com/intellij/refactoring/SuggestedParamTypesTest.java index 333b4d2cc11d..b9f55f2a3942 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/SuggestedParamTypesTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/SuggestedParamTypesTest.java @@ -1,144 +1,144 @@ -/* - * 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. - */ - -/* - * User: anna - * Date: 25-May-2010 - */ -package com.intellij.refactoring; - -import com.intellij.JavaTestUtil; -import com.intellij.codeInsight.CodeInsightUtil; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiFile; -import com.intellij.refactoring.extractMethod.ExtractMethodProcessor; -import com.intellij.refactoring.ui.TypeSelectorManager; -import com.intellij.refactoring.ui.TypeSelectorManagerImpl; -import com.intellij.refactoring.util.ParameterTablePanel; -import com.intellij.testFramework.LightCodeInsightTestCase; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; - -public class SuggestedParamTypesTest extends LightCodeInsightTestCase { - @NonNls private static final String BASE_PATH = "/refactoring/suggestedTypes/"; - - @Override - protected String getTestDataPath() { - return JavaTestUtil.getJavaTestDataPath(); - } - - @Override - protected Sdk getProjectJDK() { - return JavaSdkImpl.getMockJdk17("java 1.5"); - } - - public void testPostfixExprUsedAsOutput() throws Exception { - doTest("byte"); - } - - public void testPostfixExprUnusedAfter() throws Exception { - doTest("byte", "short", "int", "long", "float", "double"); - } - - public void testLtLtByte() throws Exception { - doTest("byte", "short", "int", "long"); - } - - public void testLtLtInt() throws Exception { - doTest("int", "long"); - } - - public void testAssignmentWithConcatenation() throws Exception { - doTest("String"); - } - - public void testAssignmentWithoutConcatenation() throws Exception { - doTest("String", "Object", "Serializable", "Comparable", "CharSequence"); - } - - public void testCastInside() throws Exception { - doTest("A", "Object"); - } - - public void testMultipleCasts() throws Exception { - doTest("A", "Object"); - } - - public void testCastNoCast() throws Exception { - doTest("Object"); - } - - public void testNoCastWhenWrapped() throws Exception { - doTest("Object"); - } - - private void doTest(String... types) throws Exception { - configureByFile(BASE_PATH + getTestName(false) + ".java"); - - final Editor editor = getEditor(); - final PsiFile file = getFile(); - final Project project = getProject(); - - int startOffset = editor.getSelectionModel().getSelectionStart(); - int endOffset = editor.getSelectionModel().getSelectionEnd(); - PsiElement[] elements; - PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); - if (expr != null) { - elements = new PsiElement[]{expr}; - } - else { - elements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); - } - assertTrue(elements.length > 0); - - final ExtractMethodProcessor processor = - new ExtractMethodProcessor(project, editor, elements, null, "Extract Method", "newMethod", null); - - processor.prepare(); - - for (final ParameterTablePanel.VariableData data : processor.getInputVariables().getInputVariables()) { - final PsiExpression[] occurrences = ParameterTablePanel.findVariableOccurrences(elements, data.variable); - final TypeSelectorManager manager = new TypeSelectorManagerImpl(project, data.type, occurrences, true) { - @Override - protected boolean isUsedAfter() { - return processor.isOutputVariable(data.variable); - } - }; - final JComponent component = manager.getTypeSelector().getComponent(); - if (types.length > 1) { - assertTrue("One type suggested", component instanceof JComboBox); - final DefaultComboBoxModel model = (DefaultComboBoxModel)((JComboBox)component).getModel(); - assertEquals(types.length, model.getSize()); - for (int i = 0, typesLength = types.length; i < typesLength; i++) { - String type = types[i]; - assertEquals(type, model.getElementAt(i).toString()); - } - } - else if (types.length == 1) { - assertTrue("Multiple types suggested", component instanceof JLabel); - assertEquals(types[0], ((JLabel)component).getText()); - } - } - } - - +/* + * 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. + */ + +/* + * User: anna + * Date: 25-May-2010 + */ +package com.intellij.refactoring; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.CodeInsightUtil; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiFile; +import com.intellij.refactoring.extractMethod.ExtractMethodProcessor; +import com.intellij.refactoring.ui.TypeSelectorManager; +import com.intellij.refactoring.ui.TypeSelectorManagerImpl; +import com.intellij.refactoring.util.ParameterTablePanel; +import com.intellij.testFramework.LightCodeInsightTestCase; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; + +public class SuggestedParamTypesTest extends LightCodeInsightTestCase { + @NonNls private static final String BASE_PATH = "/refactoring/suggestedTypes/"; + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath(); + } + + @Override + protected Sdk getProjectJDK() { + return JavaSdkImpl.getMockJdk17("java 1.5"); + } + + public void testPostfixExprUsedAsOutput() throws Exception { + doTest("byte"); + } + + public void testPostfixExprUnusedAfter() throws Exception { + doTest("byte", "short", "int", "long", "float", "double"); + } + + public void testLtLtByte() throws Exception { + doTest("byte", "short", "int", "long"); + } + + public void testLtLtInt() throws Exception { + doTest("int", "long"); + } + + public void testAssignmentWithConcatenation() throws Exception { + doTest("String"); + } + + public void testAssignmentWithoutConcatenation() throws Exception { + doTest("String", "Object", "Serializable", "Comparable", "CharSequence"); + } + + public void testCastInside() throws Exception { + doTest("A", "Object"); + } + + public void testMultipleCasts() throws Exception { + doTest("A", "Object"); + } + + public void testCastNoCast() throws Exception { + doTest("Object"); + } + + public void testNoCastWhenWrapped() throws Exception { + doTest("Object"); + } + + private void doTest(String... types) throws Exception { + configureByFile(BASE_PATH + getTestName(false) + ".java"); + + final Editor editor = getEditor(); + final PsiFile file = getFile(); + final Project project = getProject(); + + int startOffset = editor.getSelectionModel().getSelectionStart(); + int endOffset = editor.getSelectionModel().getSelectionEnd(); + PsiElement[] elements; + PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); + if (expr != null) { + elements = new PsiElement[]{expr}; + } + else { + elements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); + } + assertTrue(elements.length > 0); + + final ExtractMethodProcessor processor = + new ExtractMethodProcessor(project, editor, elements, null, "Extract Method", "newMethod", null); + + processor.prepare(); + + for (final ParameterTablePanel.VariableData data : processor.getInputVariables().getInputVariables()) { + final PsiExpression[] occurrences = ParameterTablePanel.findVariableOccurrences(elements, data.variable); + final TypeSelectorManager manager = new TypeSelectorManagerImpl(project, data.type, occurrences, true) { + @Override + protected boolean isUsedAfter() { + return processor.isOutputVariable(data.variable); + } + }; + final JComponent component = manager.getTypeSelector().getComponent(); + if (types.length > 1) { + assertTrue("One type suggested", component instanceof JComboBox); + final DefaultComboBoxModel model = (DefaultComboBoxModel)((JComboBox)component).getModel(); + assertEquals(types.length, model.getSize()); + for (int i = 0, typesLength = types.length; i < typesLength; i++) { + String type = types[i]; + assertEquals(type, model.getElementAt(i).toString()); + } + } + else if (types.length == 1) { + assertTrue("Multiple types suggested", component instanceof JLabel); + assertEquals(types[0], ((JLabel)component).getText()); + } + } + } + + } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java b/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java index 5fe8a8f7e736..52b7973ad1f8 100644 --- a/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java +++ b/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java @@ -1,279 +1,279 @@ -package com.intellij.roots; - -import com.intellij.application.options.ReplacePathToMacroMap; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.PathManagerEx; -import com.intellij.openapi.components.ExpandMacroToPathMap; -import com.intellij.openapi.components.PathMacroManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.ModuleTestCase; -import com.intellij.ide.highlighter.ModuleFileType; -import org.jdom.Element; -import org.jdom.output.XMLOutputter; - -import java.io.File; -import java.io.IOException; -import java.util.Iterator; - -/** - * @author dsl - */ -public class ModuleRootsExternalizationTest extends ModuleTestCase { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.ExternalizationTest"); - - public void testEmptyModuleWrite() throws Exception { - try { - ModuleRootManagerImpl moduleRootManager = createTempModuleRootManager(); - Element root = new Element("root"); - moduleRootManager.getState().writeExternal(root); - assertEquals(root.getText(), ""); - } - catch (IOException e) { - LOG.error(e); - } - } - - private ModuleRootManagerImpl createTempModuleRootManager() throws IOException { - File tmpModule = FileUtil.createTempFile("tst", ModuleFileType.DOT_DEFAULT_EXTENSION); - myFilesToDelete.add(tmpModule); - final Module module = createModule(tmpModule); - final ModuleRootManagerImpl moduleRootManager = - (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); - return moduleRootManager; - } - - public void testContentWrite() throws Exception { - File content = getTestRoot(); - File source = new File(content, "source"); - File testSource = new File(content, "testSource"); - File exclude = new File(content, "exclude"); - File classes = new File(content, "classes"); - File testClasses = new File(content, "testClasses"); - final VirtualFile contentFile = LocalFileSystem.getInstance().findFileByIoFile(content); - assertNotNull(contentFile); - final VirtualFile sourceFile = LocalFileSystem.getInstance().findFileByIoFile(source); - assertNotNull(sourceFile); - final VirtualFile testSourceFile = LocalFileSystem.getInstance().findFileByIoFile(testSource); - assertNotNull(testSourceFile); - final VirtualFile excludeFile = LocalFileSystem.getInstance().findFileByIoFile(exclude); - - assertNotNull(excludeFile); - final VirtualFile classesFile = LocalFileSystem.getInstance().findFileByIoFile(classes); - - assertNotNull(classesFile); - final VirtualFile testClassesFile = LocalFileSystem.getInstance().findFileByIoFile(testClasses); - - assertNotNull(testClassesFile); - - final File moduleFile = new File(content, "test.iml"); - final Module module = createModule(moduleFile); - final ModuleRootManagerImpl moduleRootManager = - (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - final ModifiableRootModel rootModel = moduleRootManager.getModifiableModel(); - final ContentEntry contentEntry = rootModel.addContentEntry(contentFile); - final CompilerModuleExtension compilerModuleExtension = rootModel.getModuleExtension(CompilerModuleExtension.class); - compilerModuleExtension.setCompilerOutputPath(classesFile); - compilerModuleExtension.setCompilerOutputPathForTests(testClassesFile); - compilerModuleExtension.inheritCompilerOutputPath(false); - rootModel.setSdk(JavaSdkImpl.getMockJdk17()); - contentEntry.addSourceFolder(sourceFile, false); - contentEntry.addSourceFolder(testSourceFile, true); - contentEntry.addExcludeFolder(excludeFile); - rootModel.commit(); - } - }); - - - final Element element = new Element("root"); - moduleRootManager.getState().writeExternal(element); - assertElementEquals(element, - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "", - module); - } - - public void testModuleLibraries() throws Exception { - File moduleFile = new File(getTestRoot(), "test.iml"); - Module module = createModule(moduleFile); - final ModuleRootManagerImpl moduleRootManager = - (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); - final ModifiableRootModel rootModel = moduleRootManager.getModifiableModel(); - final LibraryTable moduleLibraryTable = rootModel.getModuleLibraryTable(); - - final Library unnamedLibrary = moduleLibraryTable.createLibrary(); - final File unnamedLibClasses = new File(getTestRoot(), "unnamedLibClasses"); - final VirtualFile unnamedLibClassesRoot = LocalFileSystem.getInstance().findFileByIoFile(unnamedLibClasses); - final Library.ModifiableModel libraryModifyableModel = unnamedLibrary.getModifiableModel(); - libraryModifyableModel.addRoot(unnamedLibClassesRoot.getUrl(), OrderRootType.CLASSES); - - final Library namedLibrary = moduleLibraryTable.createLibrary("namedLibrary"); - final File namedLibClasses = new File(getTestRoot(), "namedLibClasses"); - final VirtualFile namedLibClassesRoot = LocalFileSystem.getInstance().findFileByIoFile(namedLibClasses); - final Library.ModifiableModel namedLibraryModel = namedLibrary.getModifiableModel(); - namedLibraryModel.addRoot(namedLibClassesRoot.getUrl(), OrderRootType.CLASSES); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - libraryModifyableModel.commit(); - namedLibraryModel.commit(); - } - }); - - final Iterator libraryIterator = moduleLibraryTable.getLibraryIterator(); - assertEquals(libraryIterator.next(), unnamedLibrary); - assertEquals(libraryIterator.next(), namedLibrary); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - rootModel.commit(); - } - }); - final Element element = new Element("root"); - moduleRootManager.getState().writeExternal(element); - assertElementEquals(element, - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "", module); - } - - public void testCompilerOutputInheritance() throws Exception { - File moduleFile = new File(getTestRoot(), "test.iml"); - Module module = createModule(moduleFile); - final ModuleRootManagerImpl moduleRootManager = - (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); - final ModifiableRootModel rootModel = moduleRootManager.getModifiableModel(); - rootModel.getModuleExtension(CompilerModuleExtension.class).inheritCompilerOutputPath(true); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - rootModel.commit(); - } - }); - Element element = new Element("root"); - moduleRootManager.getState().writeExternal(element); - assertElementEquals(element, - "" + - "" + - "" + - "", module); - } - - public void testMacroSubstituteWin() { - final ReplacePathToMacroMap map = new ReplacePathToMacroMap(); - final String path = "jar://C:/idea/lib/forms_rt.jar!/"; - map.put("jar://C:/", "jar://$MODULE_DIR$/../../"); - - final String substituted = map.substitute(path, false); - assertEquals("jar://$MODULE_DIR$/../../idea/lib/forms_rt.jar!/", substituted); - } - - public void testExpandMacro1() { - final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); - map.addMacroExpand("MACRO", "C:/idea"); - - final String expanded = map.substitute("jar://$MACRO$/lib/forms_rt.jar!/", false); - assertEquals("jar://C:/idea/lib/forms_rt.jar!/", expanded); - } - - public void testExpandMacro2() { - final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); - map.addMacroExpand("MACRO", "forms_rt.jar!/"); - - final String expanded = map.substitute("jar://C:/idea/lib/$MACRO$", false); - assertEquals("jar://C:/idea/lib/forms_rt.jar!/", expanded); - } - - public void testExpandMacro3() { - final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); - map.addMacroExpand("MACRO1", "C:/idea"); - map.addMacroExpand("MACRO2", "forms_rt.jar!/"); - - final String expanded = map.substitute("jar://$MACRO1$/lib/$MACRO2$", false); - assertEquals("jar://C:/idea/lib/forms_rt.jar!/", expanded); - } - - public void testExpandMacroNoExpand() { - final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); - map.addMacroExpand("MACRO", "C:/idea"); - - final String expanded = map.substitute("jar://C:/idea$/lib/forms_rt.jar!/", false); - assertEquals("jar://C:/idea$/lib/forms_rt.jar!/", expanded); - } - - public void testExpandMacroNoExpand2() { - final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); - map.addMacroExpand("MACRO", "C:/idea"); - - final String expanded = map.substitute("jar://C:/idea/lib/forms_rt.jar!/$", false); - assertEquals("jar://C:/idea/lib/forms_rt.jar!/$", expanded); - } - - public void testExpandMacroNoExpand3() { - final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); - map.addMacroExpand("MACRO", "C:/idea"); - - final String expanded = map.substitute("jar://$UNKNOWN$/lib/forms_rt.jar!/", false); - assertEquals("jar://$UNKNOWN$/lib/forms_rt.jar!/", expanded); - } - - public void testMacroSubstitute2() { - final ReplacePathToMacroMap map = new ReplacePathToMacroMap(); - final String path = "jar://C:/idea/lib/forms_rt.jar!/"; - map.put("jar://C:/id", "*SUBST*"); - - final String substituted = map.substitute(path, false); - assertEquals(path, substituted); - } - - public static void assertElementEquals(final Element element, String value, Module module) { - PathMacroManager.getInstance(module).collapsePaths(element); - assertEquals(value, new XMLOutputter().outputString(element)); - } - - private File getTestRoot() { - File testRoot = new File(PathManagerEx.getTestDataPath()); - File moduleRootManagerRoot = new File(testRoot, "moduleRootManager"); - File thisTestRoot = new File(moduleRootManagerRoot, getTestName(true)); - return thisTestRoot; - } -} +package com.intellij.roots; + +import com.intellij.application.options.ReplacePathToMacroMap; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.components.ExpandMacroToPathMap; +import com.intellij.openapi.components.PathMacroManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.ModuleTestCase; +import com.intellij.ide.highlighter.ModuleFileType; +import org.jdom.Element; +import org.jdom.output.XMLOutputter; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +/** + * @author dsl + */ +public class ModuleRootsExternalizationTest extends ModuleTestCase { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.ExternalizationTest"); + + public void testEmptyModuleWrite() throws Exception { + try { + ModuleRootManagerImpl moduleRootManager = createTempModuleRootManager(); + Element root = new Element("root"); + moduleRootManager.getState().writeExternal(root); + assertEquals(root.getText(), ""); + } + catch (IOException e) { + LOG.error(e); + } + } + + private ModuleRootManagerImpl createTempModuleRootManager() throws IOException { + File tmpModule = FileUtil.createTempFile("tst", ModuleFileType.DOT_DEFAULT_EXTENSION); + myFilesToDelete.add(tmpModule); + final Module module = createModule(tmpModule); + final ModuleRootManagerImpl moduleRootManager = + (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); + return moduleRootManager; + } + + public void testContentWrite() throws Exception { + File content = getTestRoot(); + File source = new File(content, "source"); + File testSource = new File(content, "testSource"); + File exclude = new File(content, "exclude"); + File classes = new File(content, "classes"); + File testClasses = new File(content, "testClasses"); + final VirtualFile contentFile = LocalFileSystem.getInstance().findFileByIoFile(content); + assertNotNull(contentFile); + final VirtualFile sourceFile = LocalFileSystem.getInstance().findFileByIoFile(source); + assertNotNull(sourceFile); + final VirtualFile testSourceFile = LocalFileSystem.getInstance().findFileByIoFile(testSource); + assertNotNull(testSourceFile); + final VirtualFile excludeFile = LocalFileSystem.getInstance().findFileByIoFile(exclude); + + assertNotNull(excludeFile); + final VirtualFile classesFile = LocalFileSystem.getInstance().findFileByIoFile(classes); + + assertNotNull(classesFile); + final VirtualFile testClassesFile = LocalFileSystem.getInstance().findFileByIoFile(testClasses); + + assertNotNull(testClassesFile); + + final File moduleFile = new File(content, "test.iml"); + final Module module = createModule(moduleFile); + final ModuleRootManagerImpl moduleRootManager = + (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final ModifiableRootModel rootModel = moduleRootManager.getModifiableModel(); + final ContentEntry contentEntry = rootModel.addContentEntry(contentFile); + final CompilerModuleExtension compilerModuleExtension = rootModel.getModuleExtension(CompilerModuleExtension.class); + compilerModuleExtension.setCompilerOutputPath(classesFile); + compilerModuleExtension.setCompilerOutputPathForTests(testClassesFile); + compilerModuleExtension.inheritCompilerOutputPath(false); + rootModel.setSdk(JavaSdkImpl.getMockJdk17()); + contentEntry.addSourceFolder(sourceFile, false); + contentEntry.addSourceFolder(testSourceFile, true); + contentEntry.addExcludeFolder(excludeFile); + rootModel.commit(); + } + }); + + + final Element element = new Element("root"); + moduleRootManager.getState().writeExternal(element); + assertElementEquals(element, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "", + module); + } + + public void testModuleLibraries() throws Exception { + File moduleFile = new File(getTestRoot(), "test.iml"); + Module module = createModule(moduleFile); + final ModuleRootManagerImpl moduleRootManager = + (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); + final ModifiableRootModel rootModel = moduleRootManager.getModifiableModel(); + final LibraryTable moduleLibraryTable = rootModel.getModuleLibraryTable(); + + final Library unnamedLibrary = moduleLibraryTable.createLibrary(); + final File unnamedLibClasses = new File(getTestRoot(), "unnamedLibClasses"); + final VirtualFile unnamedLibClassesRoot = LocalFileSystem.getInstance().findFileByIoFile(unnamedLibClasses); + final Library.ModifiableModel libraryModifyableModel = unnamedLibrary.getModifiableModel(); + libraryModifyableModel.addRoot(unnamedLibClassesRoot.getUrl(), OrderRootType.CLASSES); + + final Library namedLibrary = moduleLibraryTable.createLibrary("namedLibrary"); + final File namedLibClasses = new File(getTestRoot(), "namedLibClasses"); + final VirtualFile namedLibClassesRoot = LocalFileSystem.getInstance().findFileByIoFile(namedLibClasses); + final Library.ModifiableModel namedLibraryModel = namedLibrary.getModifiableModel(); + namedLibraryModel.addRoot(namedLibClassesRoot.getUrl(), OrderRootType.CLASSES); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + libraryModifyableModel.commit(); + namedLibraryModel.commit(); + } + }); + + final Iterator libraryIterator = moduleLibraryTable.getLibraryIterator(); + assertEquals(libraryIterator.next(), unnamedLibrary); + assertEquals(libraryIterator.next(), namedLibrary); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + rootModel.commit(); + } + }); + final Element element = new Element("root"); + moduleRootManager.getState().writeExternal(element); + assertElementEquals(element, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "", module); + } + + public void testCompilerOutputInheritance() throws Exception { + File moduleFile = new File(getTestRoot(), "test.iml"); + Module module = createModule(moduleFile); + final ModuleRootManagerImpl moduleRootManager = + (ModuleRootManagerImpl)ModuleRootManager.getInstance(module); + final ModifiableRootModel rootModel = moduleRootManager.getModifiableModel(); + rootModel.getModuleExtension(CompilerModuleExtension.class).inheritCompilerOutputPath(true); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + rootModel.commit(); + } + }); + Element element = new Element("root"); + moduleRootManager.getState().writeExternal(element); + assertElementEquals(element, + "" + + "" + + "" + + "", module); + } + + public void testMacroSubstituteWin() { + final ReplacePathToMacroMap map = new ReplacePathToMacroMap(); + final String path = "jar://C:/idea/lib/forms_rt.jar!/"; + map.put("jar://C:/", "jar://$MODULE_DIR$/../../"); + + final String substituted = map.substitute(path, false); + assertEquals("jar://$MODULE_DIR$/../../idea/lib/forms_rt.jar!/", substituted); + } + + public void testExpandMacro1() { + final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); + map.addMacroExpand("MACRO", "C:/idea"); + + final String expanded = map.substitute("jar://$MACRO$/lib/forms_rt.jar!/", false); + assertEquals("jar://C:/idea/lib/forms_rt.jar!/", expanded); + } + + public void testExpandMacro2() { + final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); + map.addMacroExpand("MACRO", "forms_rt.jar!/"); + + final String expanded = map.substitute("jar://C:/idea/lib/$MACRO$", false); + assertEquals("jar://C:/idea/lib/forms_rt.jar!/", expanded); + } + + public void testExpandMacro3() { + final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); + map.addMacroExpand("MACRO1", "C:/idea"); + map.addMacroExpand("MACRO2", "forms_rt.jar!/"); + + final String expanded = map.substitute("jar://$MACRO1$/lib/$MACRO2$", false); + assertEquals("jar://C:/idea/lib/forms_rt.jar!/", expanded); + } + + public void testExpandMacroNoExpand() { + final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); + map.addMacroExpand("MACRO", "C:/idea"); + + final String expanded = map.substitute("jar://C:/idea$/lib/forms_rt.jar!/", false); + assertEquals("jar://C:/idea$/lib/forms_rt.jar!/", expanded); + } + + public void testExpandMacroNoExpand2() { + final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); + map.addMacroExpand("MACRO", "C:/idea"); + + final String expanded = map.substitute("jar://C:/idea/lib/forms_rt.jar!/$", false); + assertEquals("jar://C:/idea/lib/forms_rt.jar!/$", expanded); + } + + public void testExpandMacroNoExpand3() { + final ExpandMacroToPathMap map = new ExpandMacroToPathMap(); + map.addMacroExpand("MACRO", "C:/idea"); + + final String expanded = map.substitute("jar://$UNKNOWN$/lib/forms_rt.jar!/", false); + assertEquals("jar://$UNKNOWN$/lib/forms_rt.jar!/", expanded); + } + + public void testMacroSubstitute2() { + final ReplacePathToMacroMap map = new ReplacePathToMacroMap(); + final String path = "jar://C:/idea/lib/forms_rt.jar!/"; + map.put("jar://C:/id", "*SUBST*"); + + final String substituted = map.substitute(path, false); + assertEquals(path, substituted); + } + + public static void assertElementEquals(final Element element, String value, Module module) { + PathMacroManager.getInstance(module).collapsePaths(element); + assertEquals(value, new XMLOutputter().outputString(element)); + } + + private File getTestRoot() { + File testRoot = new File(PathManagerEx.getTestDataPath()); + File moduleRootManagerRoot = new File(testRoot, "moduleRootManager"); + File thisTestRoot = new File(moduleRootManagerRoot, getTestName(true)); + return thisTestRoot; + } +} diff --git a/java/openapi/src/com/intellij/psi/PsiClassType.java b/java/openapi/src/com/intellij/psi/PsiClassType.java index 8ea0bf773375..e5a590be3e94 100644 --- a/java/openapi/src/com/intellij/psi/PsiClassType.java +++ b/java/openapi/src/com/intellij/psi/PsiClassType.java @@ -1,243 +1,243 @@ -/* - * 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; - -import com.intellij.openapi.util.Comparing; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Represents a class type. - * - * @author max - */ -public abstract class PsiClassType extends PsiType { - /** - * The empty array of PSI class types which can be reused to avoid unnecessary allocations. - */ - public static final PsiClassType[] EMPTY_ARRAY = new PsiClassType[0]; - protected final LanguageLevel myLanguageLevel; - - protected PsiClassType(LanguageLevel languageLevel) { - this(languageLevel, PsiAnnotation.EMPTY_ARRAY); - } - protected PsiClassType(LanguageLevel languageLevel, PsiAnnotation[] annotations) { - super(annotations); - myLanguageLevel = languageLevel; - } - - /** - * Resolves the class reference and returns the resulting class. - * - * @return the class instance, or null if the reference resolve failed. - */ - @Nullable - public abstract PsiClass resolve(); - - /** - * Returns the non-qualified name of the class referenced by the type. - * - * @return the name of the class. - */ - public abstract String getClassName(); - - /** - * Returns the list of type arguments for the type. - * - * @return the array of type arguments, or an empty array if the type does not point to a generic class or interface. - */ - @NotNull public abstract PsiType[] getParameters(); - - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof PsiClassType)) return false; - PsiClassType otherClassType = (PsiClassType) obj; - //if (!isValid() || !otherClassType.isValid()) return false; - - String className = getClassName(); - String otherClassName = otherClassType.getClassName(); - if (!Comparing.equal(className, otherClassName)) return false; - final PsiType[] parameters = getParameters(); - final PsiType[] otherParameters = otherClassType.getParameters(); - if (parameters.length != otherParameters.length) return false; - final ClassResolveResult result = resolveGenerics(); - final ClassResolveResult otherResult = otherClassType.resolveGenerics(); - if (result == otherResult) return true; - - final PsiClass aClass = result.getElement(); - final PsiClass otherClass = otherResult.getElement(); - if (aClass == null || otherClass == null) { - return aClass == otherClass; - } - return aClass.getManager().areElementsEquivalent(aClass, otherClass) && - (PsiUtil.isRawSubstitutor(aClass, result.getSubstitutor()) || - PsiUtil.equalOnEquivalentClasses(result.getSubstitutor(), aClass, otherResult.getSubstitutor(), otherClass)); - } - - /** - * Checks if the class type has any parameters with no substituted arguments. - * - * @return true if the class type has non-substituted parameters, false otherwise - */ - public boolean hasParameters() { - final ClassResolveResult resolveResult = resolveGenerics(); - PsiClass aClass = resolveResult.getElement(); - if (aClass == null) return false; - boolean hasParams = false; - for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(aClass)) { - if (resolveResult.getSubstitutor().substitute(parameter) == null) return false; - hasParams = true; - } - return hasParams; - } - - /** - * Checks if the class type has any parameters which are not unbounded wildcards - * and do not have substituted arguments. - * - * @return true if the class type has nontrivial non-substituted parameters, false otherwise - */ - public boolean hasNonTrivialParameters() { - final ClassResolveResult resolveResult = resolveGenerics(); - PsiClass aClass = resolveResult.getElement(); - if (aClass == null) return false; - for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(aClass)) { - PsiType type = resolveResult.getSubstitutor().substitute(parameter); - if (type != null) { - if (!(type instanceof PsiWildcardType) || ((PsiWildcardType)type).getBound() != null) { - return true; - } - } - } - return false; - } - - public int hashCode() { - final String className = getClassName(); - if (className == null) return 0; - return className.hashCode(); - } - - @NotNull - public PsiType[] getSuperTypes() { - final ClassResolveResult resolveResult = resolveGenerics(); - final PsiClass aClass = resolveResult.getElement(); - if (aClass == null) return EMPTY_ARRAY; - final PsiClassType[] superTypes = aClass.getSuperTypes(); - - final PsiType[] subtitutionResults = new PsiType[superTypes.length]; - for (int i = 0; i < superTypes.length; i++) { - subtitutionResults[i] = resolveResult.getSubstitutor().substitute(superTypes[i]); - } - return subtitutionResults; - } - - /** - * Checks whether the specified resolve result representss a raw type.
- * Raw type is a class type for a class with type parameters which does not assign - * any value to them. If a class does not have any type parameters, it cannot generate any raw type. - */ - public static boolean isRaw(ClassResolveResult resolveResult) { - PsiClass psiClass = resolveResult.getElement(); - return psiClass != null && PsiUtil.isRawSubstitutor(psiClass, resolveResult.getSubstitutor()); - } - - /** - * Checks whether this type is a raw type.
- * Raw type is a class type for a class with type parameters which does not assign - * any value to them. If a class does not have any type parameters, it cannot generate any raw type. - */ - public boolean isRaw() { - return isRaw(resolveGenerics()); - } - - /** - * Returns the resolve result containing the class referenced by the class type and the - * substitutor which can substitute the values of type arguments used in the class type - * declaration. - * - * @return the resolve result instance. - */ - @NotNull public abstract ClassResolveResult resolveGenerics(); - - /** - * Returns the raw type (with no values assigned to type parameters) corresponding to this type. - * - * @return the raw type instance. - */ - @NotNull public abstract PsiClassType rawType(); - - /** - * Overrides {@link com.intellij.psi.PsiType#getResolveScope()} to narrow specify @NotNull. - */ - @NotNull - public abstract GlobalSearchScope getResolveScope(); - - - public A accept(PsiTypeVisitor visitor) { - return visitor.visitClassType(this); - } - - @NotNull - public abstract LanguageLevel getLanguageLevel(); - - /** - * Functional style setter preserving original type's language level - * @param languageLevel level to obtain class type with - * @return type with requested language level - */ - public abstract PsiClassType setLanguageLevel(final LanguageLevel languageLevel); - - /** - * Represents the result of resolving a reference to a Java class. - */ - public interface ClassResolveResult extends JavaResolveResult { - PsiClass getElement(); - - ClassResolveResult EMPTY = new ClassResolveResult() { - public PsiClass getElement() { - return null; - } - - public PsiSubstitutor getSubstitutor() { - return PsiSubstitutor.EMPTY; - } - - public boolean isValidResult(){ - return false; - } - - public boolean isAccessible(){ - return false; - } - - public boolean isStaticsScopeCorrect(){ - return false; - } - - public PsiElement getCurrentFileResolveScope() { - return null; - } - - public boolean isPackagePrefixPackageReference() { - return false; - } - }; - } -} +/* + * 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; + +import com.intellij.openapi.util.Comparing; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a class type. + * + * @author max + */ +public abstract class PsiClassType extends PsiType { + /** + * The empty array of PSI class types which can be reused to avoid unnecessary allocations. + */ + public static final PsiClassType[] EMPTY_ARRAY = new PsiClassType[0]; + protected final LanguageLevel myLanguageLevel; + + protected PsiClassType(LanguageLevel languageLevel) { + this(languageLevel, PsiAnnotation.EMPTY_ARRAY); + } + protected PsiClassType(LanguageLevel languageLevel, PsiAnnotation[] annotations) { + super(annotations); + myLanguageLevel = languageLevel; + } + + /** + * Resolves the class reference and returns the resulting class. + * + * @return the class instance, or null if the reference resolve failed. + */ + @Nullable + public abstract PsiClass resolve(); + + /** + * Returns the non-qualified name of the class referenced by the type. + * + * @return the name of the class. + */ + public abstract String getClassName(); + + /** + * Returns the list of type arguments for the type. + * + * @return the array of type arguments, or an empty array if the type does not point to a generic class or interface. + */ + @NotNull public abstract PsiType[] getParameters(); + + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof PsiClassType)) return false; + PsiClassType otherClassType = (PsiClassType) obj; + //if (!isValid() || !otherClassType.isValid()) return false; + + String className = getClassName(); + String otherClassName = otherClassType.getClassName(); + if (!Comparing.equal(className, otherClassName)) return false; + final PsiType[] parameters = getParameters(); + final PsiType[] otherParameters = otherClassType.getParameters(); + if (parameters.length != otherParameters.length) return false; + final ClassResolveResult result = resolveGenerics(); + final ClassResolveResult otherResult = otherClassType.resolveGenerics(); + if (result == otherResult) return true; + + final PsiClass aClass = result.getElement(); + final PsiClass otherClass = otherResult.getElement(); + if (aClass == null || otherClass == null) { + return aClass == otherClass; + } + return aClass.getManager().areElementsEquivalent(aClass, otherClass) && + (PsiUtil.isRawSubstitutor(aClass, result.getSubstitutor()) || + PsiUtil.equalOnEquivalentClasses(result.getSubstitutor(), aClass, otherResult.getSubstitutor(), otherClass)); + } + + /** + * Checks if the class type has any parameters with no substituted arguments. + * + * @return true if the class type has non-substituted parameters, false otherwise + */ + public boolean hasParameters() { + final ClassResolveResult resolveResult = resolveGenerics(); + PsiClass aClass = resolveResult.getElement(); + if (aClass == null) return false; + boolean hasParams = false; + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(aClass)) { + if (resolveResult.getSubstitutor().substitute(parameter) == null) return false; + hasParams = true; + } + return hasParams; + } + + /** + * Checks if the class type has any parameters which are not unbounded wildcards + * and do not have substituted arguments. + * + * @return true if the class type has nontrivial non-substituted parameters, false otherwise + */ + public boolean hasNonTrivialParameters() { + final ClassResolveResult resolveResult = resolveGenerics(); + PsiClass aClass = resolveResult.getElement(); + if (aClass == null) return false; + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(aClass)) { + PsiType type = resolveResult.getSubstitutor().substitute(parameter); + if (type != null) { + if (!(type instanceof PsiWildcardType) || ((PsiWildcardType)type).getBound() != null) { + return true; + } + } + } + return false; + } + + public int hashCode() { + final String className = getClassName(); + if (className == null) return 0; + return className.hashCode(); + } + + @NotNull + public PsiType[] getSuperTypes() { + final ClassResolveResult resolveResult = resolveGenerics(); + final PsiClass aClass = resolveResult.getElement(); + if (aClass == null) return EMPTY_ARRAY; + final PsiClassType[] superTypes = aClass.getSuperTypes(); + + final PsiType[] subtitutionResults = new PsiType[superTypes.length]; + for (int i = 0; i < superTypes.length; i++) { + subtitutionResults[i] = resolveResult.getSubstitutor().substitute(superTypes[i]); + } + return subtitutionResults; + } + + /** + * Checks whether the specified resolve result representss a raw type.
+ * Raw type is a class type for a class with type parameters which does not assign + * any value to them. If a class does not have any type parameters, it cannot generate any raw type. + */ + public static boolean isRaw(ClassResolveResult resolveResult) { + PsiClass psiClass = resolveResult.getElement(); + return psiClass != null && PsiUtil.isRawSubstitutor(psiClass, resolveResult.getSubstitutor()); + } + + /** + * Checks whether this type is a raw type.
+ * Raw type is a class type for a class with type parameters which does not assign + * any value to them. If a class does not have any type parameters, it cannot generate any raw type. + */ + public boolean isRaw() { + return isRaw(resolveGenerics()); + } + + /** + * Returns the resolve result containing the class referenced by the class type and the + * substitutor which can substitute the values of type arguments used in the class type + * declaration. + * + * @return the resolve result instance. + */ + @NotNull public abstract ClassResolveResult resolveGenerics(); + + /** + * Returns the raw type (with no values assigned to type parameters) corresponding to this type. + * + * @return the raw type instance. + */ + @NotNull public abstract PsiClassType rawType(); + + /** + * Overrides {@link com.intellij.psi.PsiType#getResolveScope()} to narrow specify @NotNull. + */ + @NotNull + public abstract GlobalSearchScope getResolveScope(); + + + public
A accept(PsiTypeVisitor visitor) { + return visitor.visitClassType(this); + } + + @NotNull + public abstract LanguageLevel getLanguageLevel(); + + /** + * Functional style setter preserving original type's language level + * @param languageLevel level to obtain class type with + * @return type with requested language level + */ + public abstract PsiClassType setLanguageLevel(final LanguageLevel languageLevel); + + /** + * Represents the result of resolving a reference to a Java class. + */ + public interface ClassResolveResult extends JavaResolveResult { + PsiClass getElement(); + + ClassResolveResult EMPTY = new ClassResolveResult() { + public PsiClass getElement() { + return null; + } + + public PsiSubstitutor getSubstitutor() { + return PsiSubstitutor.EMPTY; + } + + public boolean isValidResult(){ + return false; + } + + public boolean isAccessible(){ + return false; + } + + public boolean isStaticsScopeCorrect(){ + return false; + } + + public PsiElement getCurrentFileResolveScope() { + return null; + } + + public boolean isPackagePrefixPackageReference() { + return false; + } + }; + } +} diff --git a/java/testFramework/src/com/intellij/codeInsight/CodeInsightTestCase.java b/java/testFramework/src/com/intellij/codeInsight/CodeInsightTestCase.java index b0d3815a1b35..4ee177217e57 100644 --- a/java/testFramework/src/com/intellij/codeInsight/CodeInsightTestCase.java +++ b/java/testFramework/src/com/intellij/codeInsight/CodeInsightTestCase.java @@ -1,689 +1,689 @@ -/* - * 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.codeInsight; - -import com.intellij.codeInsight.highlighting.HighlightUsagesHandler; -import com.intellij.ide.DataManager; -import com.intellij.injected.editor.EditorWindow; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.application.ex.PathManagerEx; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.editor.actionSystem.EditorActionManager; -import com.intellij.openapi.editor.actionSystem.TypedAction; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.impl.EditorImpl; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.roots.ContentEntry; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.testFramework.PsiTestCase; -import com.intellij.testFramework.PsiTestData; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; - -/** - * @author Mike - */ -public abstract class CodeInsightTestCase extends PsiTestCase { - protected Editor myEditor; - - public CodeInsightTestCase() { - myRunCommandForTest = true; - } - - protected Editor createEditor(VirtualFile file) { - final FileEditorManager instance = FileEditorManager.getInstance(myProject); - - if (file.getFileType().isBinary()) return null; - - Editor editor = instance.openTextEditor(new OpenFileDescriptor(myProject, file, 0), false); - ((EditorImpl)editor).setCaretActive(); - return editor; - } - - @Override - protected void tearDown() throws Exception { - FileEditorManager editorManager = FileEditorManager.getInstance(myProject); - VirtualFile[] openFiles = editorManager.getOpenFiles(); - for (VirtualFile openFile : openFiles) { - editorManager.closeFile(openFile); - } - myEditor = null; - super.tearDown(); - } - - @Override - protected PsiTestData createData() { - return new CodeInsightTestData(); - } - - public static final String CARET_MARKER = ""; - @NonNls public static final String SELECTION_START_MARKER = ""; - @NonNls public static final String SELECTION_END_MARKER = ""; - - protected void configureByFile(@NonNls String filePath) throws Exception { - configureByFile(filePath, null); - } - protected VirtualFile configureByFiles(String projectRoot,String... files) throws Exception { - final VirtualFile[] vFiles = new VirtualFile[files.length]; - for (int i = 0; i < files.length; i++) { - String path = files[i]; - final String fullPath = FileUtil.toSystemIndependentName(getTestDataPath() + path); - VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath); - vFiles[i] = vFile; - assertNotNull("file " + fullPath + " not found", vFile); - } - - File projectFile = projectRoot == null ? null : new File(getTestDataPath() + projectRoot); - - return configureByFiles(projectFile, vFiles); - } - protected VirtualFile configureByFile(@NonNls String filePath, String projectRoot) throws Exception { - String fullPath = getTestDataPath() + filePath; - - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); - assertNotNull("file " + fullPath + " not found", vFile); - - File projectFile = projectRoot == null ? null : new File(getTestDataPath() + projectRoot); - - return configureByFile(vFile, projectFile); - } - - protected PsiFile configureByText(final FileType fileType, @NonNls final String text) throws Throwable { - return configureByText(fileType, text, null); - } - - protected PsiFile configureByText(final FileType fileType, @NonNls final String text, final String _extension) throws Throwable { - final String extension = _extension == null ? fileType.getDefaultExtension():_extension; - - File dir = createTempDirectory(); - final File tempFile = FileUtil.createTempFile(dir, "aaa", "." + extension, true); - final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); - if (fileTypeManager.getFileTypeByExtension(extension) != fileType) { - new WriteCommandAction(getProject()) { - @Override - protected void run(Result result) throws Throwable { - fileTypeManager.associateExtension(fileType, extension); - } - }.execute(); - } - final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempFile); - assert vFile != null; - VfsUtil.saveText(vFile, text); - - final VirtualFile vdir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(dir); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule); - final ModifiableRootModel rootModel = rootManager.getModifiableModel(); - final ContentEntry contentEntry = rootModel.addContentEntry(vdir); - contentEntry.addSourceFolder(vdir, false); - rootModel.commit(); - } - }); - - configureByExistingFile(vFile); - - assertEquals(fileType, myFile.getVirtualFile().getFileType()); - return myFile; - } - - - @Override - protected String getTestDataPath() { - return PathManagerEx.getTestDataPath(); - } - - protected void configureByFile(final VirtualFile vFile) throws IOException { - configureByFile(vFile, null); - } - - protected void configureByExistingFile(final VirtualFile virtualFile) { - myFile = null; - myEditor = null; - - final Editor editor = createEditor(virtualFile); - - final Document document = editor.getDocument(); - final EditorInfo editorInfo = new EditorInfo(document.getText()); - - final String newFileText = editorInfo.getNewFileText(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - if (!document.getText().equals(newFileText)) { - document.setText(newFileText); - } - - PsiFile file = myPsiManager.findFile(virtualFile); - if (myFile == null) myFile = file; - - if (myEditor == null) myEditor = editor; - - editorInfo.applyToEditor(editor); - } - }); - - - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - } - - protected VirtualFile configureByFiles(final File rawProjectRoot, final VirtualFile... vFiles) throws IOException { - myFile = null; - myEditor = null; - - final File toDirIO = createTempDirectory(); - final VirtualFile toDir = getVirtualFile(toDirIO); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule); - final ModifiableRootModel rootModel = rootManager.getModifiableModel(); - if (clearModelBeforeConfiguring()) { - rootModel.clear(); - } - - // auxiliary files should be copied first - VirtualFile[] reversed = ArrayUtil.reverseArray(vFiles); - final LinkedHashMap editorInfos; - if (rawProjectRoot != null) { - final File projectRoot = rawProjectRoot.getCanonicalFile(); - FileUtil.copyDir(projectRoot, toDirIO); - VirtualFile fromDir = getVirtualFile(projectRoot); - editorInfos = - copyFilesFillingEditorInfos(fromDir, toDir, ContainerUtil.map2Array(reversed, String.class, new Function() { - @Override - public String fun(final VirtualFile s) { - return s.getPath().substring(projectRoot.getPath().length()); - } - })); - - VirtualFileManager.getInstance().refresh(false); - } - else { - editorInfos = new LinkedHashMap(); - for (final VirtualFile vFile : reversed) { - editorInfos.putAll(copyFilesFillingEditorInfos(vFile.getParent(), toDir, vFile.getName())); - } - } - - boolean sourceRootAdded = false; - if (isAddDirToContentRoot()) { - final ContentEntry contentEntry = rootModel.addContentEntry(toDir); - if (isAddDirToSource()) { - sourceRootAdded = true; - contentEntry.addSourceFolder(toDir, false); - } - } - doCommitModel(rootModel); - if (sourceRootAdded) { - sourceRootAdded(toDir); - } - - openEditorsAndActivateLast(editorInfos); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - - - return toDir; - } - - protected void doCommitModel(final ModifiableRootModel rootModel) { - rootModel.commit(); - } - - protected void sourceRootAdded(final VirtualFile dir) { - } - - protected LinkedHashMap copyFilesFillingEditorInfos(String testDataFromDir, - final VirtualFile toDir, - final String... relativePaths) throws IOException { - if (!testDataFromDir.startsWith("/")) testDataFromDir = "/" + testDataFromDir; - return copyFilesFillingEditorInfos(LocalFileSystem.getInstance().refreshAndFindFileByPath(getTestDataPath() + testDataFromDir), toDir, relativePaths); - } - - protected LinkedHashMap copyFilesFillingEditorInfos(final VirtualFile fromDir, final VirtualFile toDir, final String... relativePaths) throws IOException { - LinkedHashMap editorInfos = new LinkedHashMap(); - - List streamsToClose = new ArrayList(); - - for (String relativePath : relativePaths) { - if (relativePath.startsWith("/")) { - relativePath = relativePath.substring(1); - } - final VirtualFile fromFile = fromDir.findFileByRelativePath(relativePath); - assertNotNull(fromDir.getPath() + "/" + relativePath, fromFile); - VirtualFile toFile = toDir.findFileByRelativePath(relativePath); - if (toFile == null) { - final File file = new File(toDir.getPath(), relativePath); - FileUtil.createIfDoesntExist(file); - toFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); - assertNotNull(file.getCanonicalPath(), toFile); - } - - editorInfos.put(toFile, copyContent(fromFile, toFile, streamsToClose)); - } - - for(int i = streamsToClose.size() -1; i >= 0 ; --i) { - streamsToClose.get(i).close(); - } - return editorInfos; - } - - /*protected LinkedHashMap copyFilesFillingEditorInfos(final VirtualFile fromDir, final VirtualFile toDir) throws IOException { - final LinkedHashMap map = new LinkedHashMap(); - copyFilesFillingEditorInfos(fromDir, toDir, map); - return map; - } - - - private void copyFilesFillingEditorInfos(final VirtualFile fromDir, final VirtualFile toDir, LinkedHashMap editorInfos) throws IOException { - - List streamsToClose = new ArrayList(); - - final VirtualFile[] files = fromDir.getChildren(); - for (final VirtualFile fromFile : files) { - if (fromFile.isDirectory()) { - copyFilesFillingEditorInfos(fromFile, toDir.createChildDirectory(this, fromFile.getName()), editorInfos); - } else { - final VirtualFile toFile = toDir.createChildData(this, fromFile.getName()); - editorInfos.put(toFile, copyContent(fromFile, toFile, streamsToClose)); - } - } - - for(int i = streamsToClose.size() -1; i >= 0 ; --i) { - streamsToClose.get(i).close(); - } - }*/ - - private EditorInfo copyContent(final VirtualFile from, final VirtualFile to, final List streamsToClose) throws IOException { - byte[] content = from.getFileType().isBinary() ? from.contentsToByteArray(): null; - final String fileText = from.getFileType().isBinary() ? null : StringUtil.convertLineSeparators(VfsUtil.loadText(from)); - - EditorInfo editorInfo = fileText != null ? new EditorInfo(fileText) : null; - String newFileText = fileText != null ? editorInfo.getNewFileText() : null; - doWrite(newFileText, to, content, streamsToClose); - return editorInfo; - } - - protected final void setActiveEditor(Editor editor) { - myEditor = editor; - myFile = getPsiFile(editor.getDocument()); - } - - protected List openEditorsAndActivateLast(final LinkedHashMap editorInfos) { - final List list = openEditors(editorInfos); - setActiveEditor(list.get(list.size() - 1)); - return list; - } - - protected final List openEditors(final LinkedHashMap editorInfos) { - return ContainerUtil.map(editorInfos.keySet(), new Function() { - @Override - public Editor fun(final VirtualFile newVFile) { - PsiFile file = myPsiManager.findFile(newVFile); - if (myFile == null) myFile = file; - - Editor editor = createEditor(newVFile); - if (myEditor == null) myEditor = editor; - - EditorInfo editorInfo = editorInfos.get(newVFile); - if (editorInfo != null) { - editorInfo.applyToEditor(editor); - } - return editor; - } - }); - } - - private void doWrite(final String newFileText, final VirtualFile newVFile, final byte[] content, final List streamsToClose) throws IOException { - if (newFileText != null) { - VfsUtil.saveText(newVFile, newFileText); - } else { - final OutputStream outputStream = newVFile.getOutputStream(this, -1, -1); - outputStream.write(content); - streamsToClose.add(outputStream); - } - } - - protected boolean isAddDirToContentRoot() { - return true; - } - - protected boolean isAddDirToSource() { - return true; - } - - protected VirtualFile configureByFile(final VirtualFile vFile, final File projectRoot) throws IOException { - return configureByFiles(projectRoot, vFile); - } - - protected boolean clearModelBeforeConfiguring() { - return false; - } - - protected void setupCursorAndSelection(Editor editor) { - Document document = editor.getDocument(); - final String text = document.getText(); - - int caretIndex = text.indexOf(CARET_MARKER); - int selStartIndex = text.indexOf(SELECTION_START_MARKER); - int selEndIndex = text.indexOf(SELECTION_END_MARKER); - - final RangeMarker caretMarker = caretIndex >= 0 ? document.createRangeMarker(caretIndex, caretIndex) : null; - final RangeMarker selStartMarker = selStartIndex >= 0 ? document.createRangeMarker(selStartIndex, selStartIndex) : null; - final RangeMarker selEndMarker = selEndIndex >= 0 ? document.createRangeMarker(selEndIndex, selEndIndex) : null; - - if (caretMarker != null) { - document.deleteString(caretMarker.getStartOffset(), caretMarker.getStartOffset() + CARET_MARKER.length()); - } - if (selStartMarker != null) { - document.deleteString(selStartMarker.getStartOffset(), selStartMarker.getStartOffset() + SELECTION_START_MARKER.length()); - } - if (selEndMarker != null) { - document.deleteString(selEndMarker.getStartOffset(), selEndMarker.getStartOffset() + SELECTION_END_MARKER.length()); - } - - final String newText = document.getText(); - - if (caretMarker != null) { - int caretLine = StringUtil.offsetToLineNumber(newText, caretMarker.getStartOffset()); - int caretCol = caretMarker.getStartOffset() - StringUtil.lineColToOffset(newText, caretLine, 0); - LogicalPosition pos = new LogicalPosition(caretLine, caretCol); - editor.getCaretModel().moveToLogicalPosition(pos); - } - - if (selStartMarker != null) { - editor.getSelectionModel().setSelection(selStartMarker.getStartOffset(), selEndMarker.getStartOffset()); - } - - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - } - - @Override - protected void configure(String path, String dataName) throws Exception { - super.configure(path, dataName); - - myEditor = createEditor(myFile.getVirtualFile()); - - CodeInsightTestData data = (CodeInsightTestData) myTestDataBefore; - - LogicalPosition pos = new LogicalPosition(data.getLineNumber() - 1, data.getColumnNumber() - 1); - myEditor.getCaretModel().moveToLogicalPosition(pos); - - int selectionEnd; - int selectionStart = selectionEnd = myEditor.getCaretModel().getOffset(); - - if (data.getSelectionStartColumnNumber() >= 0) { - selectionStart = myEditor.logicalPositionToOffset(new LogicalPosition(data.getSelectionEndLineNumber() - 1, data.getSelectionStartColumnNumber() - 1)); - selectionEnd = myEditor.logicalPositionToOffset(new LogicalPosition(data.getSelectionEndLineNumber() - 1, data.getSelectionEndColumnNumber() - 1)); - } - - myEditor.getSelectionModel().setSelection(selectionStart, selectionEnd); - } - - protected void checkResultByFile(@NonNls String filePath) throws Exception { - checkResultByFile(filePath, false); - } - - protected void checkResultByFile(@NonNls final String filePath, final boolean stripTrailingSpaces) throws Exception { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - getProject().getComponent(PostprocessReformattingAspect.class).doPostponedFormatting(); - if (stripTrailingSpaces) { - ((DocumentEx)myEditor.getDocument()).stripTrailingSpaces(false); - } - - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - - String fullPath = getTestDataPath() + filePath; - - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); - assertNotNull("Cannot find file " + fullPath, vFile); - String ft = null; - try { - ft = VfsUtil.loadText(vFile); - } - catch (IOException e) { - throw new RuntimeException(e); - } - - String fileText = StringUtil.convertLineSeparators(ft); - Document document = EditorFactory.getInstance().createDocument(fileText); - - int caretIndex = fileText.indexOf(CARET_MARKER); - int selStartIndex = fileText.indexOf(SELECTION_START_MARKER); - int selEndIndex = fileText.indexOf(SELECTION_END_MARKER); - - final RangeMarker caretMarker = caretIndex >= 0 ? document.createRangeMarker(caretIndex, caretIndex) : null; - final RangeMarker selStartMarker = selStartIndex >= 0 ? document.createRangeMarker(selStartIndex, selStartIndex) : null; - final RangeMarker selEndMarker = selEndIndex >= 0 ? document.createRangeMarker(selEndIndex, selEndIndex) : null; - - if (caretMarker != null) { - document.deleteString(caretMarker.getStartOffset(), caretMarker.getStartOffset() + CARET_MARKER.length()); - } - if (selStartMarker != null) { - document.deleteString(selStartMarker.getStartOffset(), selStartMarker.getStartOffset() + SELECTION_START_MARKER.length()); - } - if (selEndMarker != null) { - document.deleteString(selEndMarker.getStartOffset(), selEndMarker.getStartOffset() + SELECTION_END_MARKER.length()); - } - - String newFileText = document.getText(); - String newFileText1 = newFileText; - if (stripTrailingSpaces) { - Document document1 = EditorFactory.getInstance().createDocument(newFileText); - ((DocumentEx)document1).stripTrailingSpaces(false); - newFileText1 = document1.getText(); - } - - if (myEditor instanceof EditorWindow) { - myEditor = ((EditorWindow)myEditor).getDelegate(); - myFile = PsiDocumentManager.getInstance(getProject()).getPsiFile(myEditor.getDocument()); - } - - String text = myFile.getText(); - text = StringUtil.convertLineSeparators(text); - - assertEquals("Text mismatch in file " + filePath, newFileText1, text); - - if (caretMarker != null) { - int caretLine = StringUtil.offsetToLineNumber(newFileText, caretMarker.getStartOffset()); - int caretCol = caretMarker.getStartOffset() - StringUtil.lineColToOffset(newFileText, caretLine, 0); - - assertEquals("caretLine", caretLine + 1, myEditor.getCaretModel().getLogicalPosition().line + 1); - assertEquals("caretColumn", caretCol + 1, myEditor.getCaretModel().getLogicalPosition().column + 1); - } - - if (selStartMarker != null && selEndMarker != null) { - int selStartLine = StringUtil.offsetToLineNumber(newFileText, selStartMarker.getStartOffset()); - int selStartCol = selStartMarker.getStartOffset() - StringUtil.lineColToOffset(newFileText, selStartLine, 0); - - int selEndLine = StringUtil.offsetToLineNumber(newFileText, selEndMarker.getEndOffset()); - int selEndCol = selEndMarker.getEndOffset() - StringUtil.lineColToOffset(newFileText, selEndLine, 0); - - assertEquals("selectionStartLine", selStartLine + 1, - StringUtil.offsetToLineNumber(newFileText, myEditor.getSelectionModel().getSelectionStart()) + 1); - - assertEquals("selectionStartCol", selStartCol + 1, - myEditor.getSelectionModel().getSelectionStart() - StringUtil.lineColToOffset(newFileText, selStartLine, 0) + 1); - - assertEquals("selectionEndLine", selEndLine + 1, - StringUtil.offsetToLineNumber(newFileText, myEditor.getSelectionModel().getSelectionEnd()) + 1); - - assertEquals("selectionEndCol", selEndCol + 1, - myEditor.getSelectionModel().getSelectionEnd() - StringUtil.lineColToOffset(newFileText, selEndLine, 0) + 1); - } - else { - assertTrue("has no selection", !myEditor.getSelectionModel().hasSelection()); - } - } - }); - } - - @Override - protected void checkResult(String dataName) throws Exception { - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - super.checkResult(dataName); - - CodeInsightTestData data = (CodeInsightTestData) myTestDataAfter; - - if (data.getColumnNumber() >= 0) { - assertEquals(dataName + ":caretColumn", data.getColumnNumber(), myEditor.getCaretModel().getLogicalPosition().column + 1); - } - if (data.getLineNumber() >= 0) { - assertEquals(dataName + ":caretLine", data.getLineNumber(), myEditor.getCaretModel().getLogicalPosition().line + 1); - } - - int selectionStart = myEditor.getSelectionModel().getSelectionStart(); - int selectionEnd = myEditor.getSelectionModel().getSelectionEnd(); - LogicalPosition startPosition = myEditor.offsetToLogicalPosition(selectionStart); - LogicalPosition endPosition = myEditor.offsetToLogicalPosition(selectionEnd); - - if (data.getSelectionStartColumnNumber() >= 0) { - assertEquals(dataName + ":selectionStartColumn", data.getSelectionStartColumnNumber(), startPosition.column + 1); - } - if (data.getSelectionStartLineNumber() >= 0) { - assertEquals(dataName + ":selectionStartLine", data.getSelectionStartLineNumber(), startPosition.line + 1); - } - if (data.getSelectionEndColumnNumber() >= 0) { - assertEquals(dataName + ":selectionEndColumn", data.getSelectionEndColumnNumber(), endPosition.column + 1); - } - if (data.getSelectionEndLineNumber() >= 0) { - assertEquals(dataName + ":selectionEndLine", data.getSelectionEndLineNumber(), endPosition.line + 1); - } - } - - @Override - public Object getData(String dataId) { - return PlatformDataKeys.EDITOR.is(dataId) ? myEditor : super.getData(dataId); - } - - protected VirtualFile getVirtualFile(@NonNls String filePath) { - String fullPath = getTestDataPath() + filePath; - - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); - assertNotNull("file " + fullPath + " not found", vFile); - return vFile; - } - - protected String getTestRoot(){ - return FileUtil.toSystemIndependentName(getTestDataPath()); - } - - public Editor getEditor() { - return myEditor; - } - - protected void type(char c) { - type(c, getEditor()); - } - - protected static void type(char c, Editor editor) { - EditorActionManager actionManager = EditorActionManager.getInstance(); - DataContext dataContext = DataManager.getInstance().getDataContext(); - if (c == '\n') { - actionManager.getActionHandler(IdeActions.ACTION_EDITOR_ENTER).execute(editor, dataContext); - return; - } - TypedAction action = actionManager.getTypedAction(); - action.actionPerformed(editor, c, dataContext); - } - - protected void caretRight() { - EditorActionManager actionManager = EditorActionManager.getInstance(); - EditorActionHandler action = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT); - action.execute(getEditor(), DataManager.getInstance().getDataContext()); - } - protected void deleteLine() { - EditorActionManager actionManager = EditorActionManager.getInstance(); - EditorActionHandler action = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_DELETE_LINE); - action.execute(getEditor(), DataManager.getInstance().getDataContext()); - } - - protected void type(@NonNls String s) { - for (char c : s.toCharArray()) { - type(c); - } - } - - protected void undo() { - UndoManager undoManager = UndoManager.getInstance(myProject); - TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor()); - undoManager.undo(textEditor); - } - - protected void backspace() { - backspace(getEditor()); - } - protected void backspace(final Editor editor) { - CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { - @Override - public void run() { - EditorActionManager actionManager = EditorActionManager.getInstance(); - EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE); - - actionHandler.execute(editor, DataManager.getInstance().getDataContext()); - } - }, "backspace", editor.getDocument()); - } - - protected void ctrlShiftF7() { - HighlightUsagesHandler.invoke(getProject(), getEditor(), getFile()); - } - - public static void ctrlW() { - AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET); - DataContext dataContext = DataManager.getInstance().getDataContext(); - AnActionEvent event = new AnActionEvent(null, dataContext, "", action.getTemplatePresentation(), ActionManager.getInstance(), 0); - event.setInjectedContext(true); - action.actionPerformed(event); - } -} +/* + * 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.codeInsight; + +import com.intellij.codeInsight.highlighting.HighlightUsagesHandler; +import com.intellij.ide.DataManager; +import com.intellij.injected.editor.EditorWindow; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.command.undo.UndoManager; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.actionSystem.TypedAction; +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.roots.ContentEntry; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.testFramework.PsiTestCase; +import com.intellij.testFramework.PsiTestData; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * @author Mike + */ +public abstract class CodeInsightTestCase extends PsiTestCase { + protected Editor myEditor; + + public CodeInsightTestCase() { + myRunCommandForTest = true; + } + + protected Editor createEditor(VirtualFile file) { + final FileEditorManager instance = FileEditorManager.getInstance(myProject); + + if (file.getFileType().isBinary()) return null; + + Editor editor = instance.openTextEditor(new OpenFileDescriptor(myProject, file, 0), false); + ((EditorImpl)editor).setCaretActive(); + return editor; + } + + @Override + protected void tearDown() throws Exception { + FileEditorManager editorManager = FileEditorManager.getInstance(myProject); + VirtualFile[] openFiles = editorManager.getOpenFiles(); + for (VirtualFile openFile : openFiles) { + editorManager.closeFile(openFile); + } + myEditor = null; + super.tearDown(); + } + + @Override + protected PsiTestData createData() { + return new CodeInsightTestData(); + } + + public static final String CARET_MARKER = ""; + @NonNls public static final String SELECTION_START_MARKER = ""; + @NonNls public static final String SELECTION_END_MARKER = ""; + + protected void configureByFile(@NonNls String filePath) throws Exception { + configureByFile(filePath, null); + } + protected VirtualFile configureByFiles(String projectRoot,String... files) throws Exception { + final VirtualFile[] vFiles = new VirtualFile[files.length]; + for (int i = 0; i < files.length; i++) { + String path = files[i]; + final String fullPath = FileUtil.toSystemIndependentName(getTestDataPath() + path); + VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath); + vFiles[i] = vFile; + assertNotNull("file " + fullPath + " not found", vFile); + } + + File projectFile = projectRoot == null ? null : new File(getTestDataPath() + projectRoot); + + return configureByFiles(projectFile, vFiles); + } + protected VirtualFile configureByFile(@NonNls String filePath, String projectRoot) throws Exception { + String fullPath = getTestDataPath() + filePath; + + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); + assertNotNull("file " + fullPath + " not found", vFile); + + File projectFile = projectRoot == null ? null : new File(getTestDataPath() + projectRoot); + + return configureByFile(vFile, projectFile); + } + + protected PsiFile configureByText(final FileType fileType, @NonNls final String text) throws Throwable { + return configureByText(fileType, text, null); + } + + protected PsiFile configureByText(final FileType fileType, @NonNls final String text, final String _extension) throws Throwable { + final String extension = _extension == null ? fileType.getDefaultExtension():_extension; + + File dir = createTempDirectory(); + final File tempFile = FileUtil.createTempFile(dir, "aaa", "." + extension, true); + final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); + if (fileTypeManager.getFileTypeByExtension(extension) != fileType) { + new WriteCommandAction(getProject()) { + @Override + protected void run(Result result) throws Throwable { + fileTypeManager.associateExtension(fileType, extension); + } + }.execute(); + } + final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempFile); + assert vFile != null; + VfsUtil.saveText(vFile, text); + + final VirtualFile vdir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(dir); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule); + final ModifiableRootModel rootModel = rootManager.getModifiableModel(); + final ContentEntry contentEntry = rootModel.addContentEntry(vdir); + contentEntry.addSourceFolder(vdir, false); + rootModel.commit(); + } + }); + + configureByExistingFile(vFile); + + assertEquals(fileType, myFile.getVirtualFile().getFileType()); + return myFile; + } + + + @Override + protected String getTestDataPath() { + return PathManagerEx.getTestDataPath(); + } + + protected void configureByFile(final VirtualFile vFile) throws IOException { + configureByFile(vFile, null); + } + + protected void configureByExistingFile(final VirtualFile virtualFile) { + myFile = null; + myEditor = null; + + final Editor editor = createEditor(virtualFile); + + final Document document = editor.getDocument(); + final EditorInfo editorInfo = new EditorInfo(document.getText()); + + final String newFileText = editorInfo.getNewFileText(); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + if (!document.getText().equals(newFileText)) { + document.setText(newFileText); + } + + PsiFile file = myPsiManager.findFile(virtualFile); + if (myFile == null) myFile = file; + + if (myEditor == null) myEditor = editor; + + editorInfo.applyToEditor(editor); + } + }); + + + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + } + + protected VirtualFile configureByFiles(final File rawProjectRoot, final VirtualFile... vFiles) throws IOException { + myFile = null; + myEditor = null; + + final File toDirIO = createTempDirectory(); + final VirtualFile toDir = getVirtualFile(toDirIO); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule); + final ModifiableRootModel rootModel = rootManager.getModifiableModel(); + if (clearModelBeforeConfiguring()) { + rootModel.clear(); + } + + // auxiliary files should be copied first + VirtualFile[] reversed = ArrayUtil.reverseArray(vFiles); + final LinkedHashMap editorInfos; + if (rawProjectRoot != null) { + final File projectRoot = rawProjectRoot.getCanonicalFile(); + FileUtil.copyDir(projectRoot, toDirIO); + VirtualFile fromDir = getVirtualFile(projectRoot); + editorInfos = + copyFilesFillingEditorInfos(fromDir, toDir, ContainerUtil.map2Array(reversed, String.class, new Function() { + @Override + public String fun(final VirtualFile s) { + return s.getPath().substring(projectRoot.getPath().length()); + } + })); + + VirtualFileManager.getInstance().refresh(false); + } + else { + editorInfos = new LinkedHashMap(); + for (final VirtualFile vFile : reversed) { + editorInfos.putAll(copyFilesFillingEditorInfos(vFile.getParent(), toDir, vFile.getName())); + } + } + + boolean sourceRootAdded = false; + if (isAddDirToContentRoot()) { + final ContentEntry contentEntry = rootModel.addContentEntry(toDir); + if (isAddDirToSource()) { + sourceRootAdded = true; + contentEntry.addSourceFolder(toDir, false); + } + } + doCommitModel(rootModel); + if (sourceRootAdded) { + sourceRootAdded(toDir); + } + + openEditorsAndActivateLast(editorInfos); + } + catch (IOException e) { + LOG.error(e); + } + } + }); + + + return toDir; + } + + protected void doCommitModel(final ModifiableRootModel rootModel) { + rootModel.commit(); + } + + protected void sourceRootAdded(final VirtualFile dir) { + } + + protected LinkedHashMap copyFilesFillingEditorInfos(String testDataFromDir, + final VirtualFile toDir, + final String... relativePaths) throws IOException { + if (!testDataFromDir.startsWith("/")) testDataFromDir = "/" + testDataFromDir; + return copyFilesFillingEditorInfos(LocalFileSystem.getInstance().refreshAndFindFileByPath(getTestDataPath() + testDataFromDir), toDir, relativePaths); + } + + protected LinkedHashMap copyFilesFillingEditorInfos(final VirtualFile fromDir, final VirtualFile toDir, final String... relativePaths) throws IOException { + LinkedHashMap editorInfos = new LinkedHashMap(); + + List streamsToClose = new ArrayList(); + + for (String relativePath : relativePaths) { + if (relativePath.startsWith("/")) { + relativePath = relativePath.substring(1); + } + final VirtualFile fromFile = fromDir.findFileByRelativePath(relativePath); + assertNotNull(fromDir.getPath() + "/" + relativePath, fromFile); + VirtualFile toFile = toDir.findFileByRelativePath(relativePath); + if (toFile == null) { + final File file = new File(toDir.getPath(), relativePath); + FileUtil.createIfDoesntExist(file); + toFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + assertNotNull(file.getCanonicalPath(), toFile); + } + + editorInfos.put(toFile, copyContent(fromFile, toFile, streamsToClose)); + } + + for(int i = streamsToClose.size() -1; i >= 0 ; --i) { + streamsToClose.get(i).close(); + } + return editorInfos; + } + + /*protected LinkedHashMap copyFilesFillingEditorInfos(final VirtualFile fromDir, final VirtualFile toDir) throws IOException { + final LinkedHashMap map = new LinkedHashMap(); + copyFilesFillingEditorInfos(fromDir, toDir, map); + return map; + } + + + private void copyFilesFillingEditorInfos(final VirtualFile fromDir, final VirtualFile toDir, LinkedHashMap editorInfos) throws IOException { + + List streamsToClose = new ArrayList(); + + final VirtualFile[] files = fromDir.getChildren(); + for (final VirtualFile fromFile : files) { + if (fromFile.isDirectory()) { + copyFilesFillingEditorInfos(fromFile, toDir.createChildDirectory(this, fromFile.getName()), editorInfos); + } else { + final VirtualFile toFile = toDir.createChildData(this, fromFile.getName()); + editorInfos.put(toFile, copyContent(fromFile, toFile, streamsToClose)); + } + } + + for(int i = streamsToClose.size() -1; i >= 0 ; --i) { + streamsToClose.get(i).close(); + } + }*/ + + private EditorInfo copyContent(final VirtualFile from, final VirtualFile to, final List streamsToClose) throws IOException { + byte[] content = from.getFileType().isBinary() ? from.contentsToByteArray(): null; + final String fileText = from.getFileType().isBinary() ? null : StringUtil.convertLineSeparators(VfsUtil.loadText(from)); + + EditorInfo editorInfo = fileText != null ? new EditorInfo(fileText) : null; + String newFileText = fileText != null ? editorInfo.getNewFileText() : null; + doWrite(newFileText, to, content, streamsToClose); + return editorInfo; + } + + protected final void setActiveEditor(Editor editor) { + myEditor = editor; + myFile = getPsiFile(editor.getDocument()); + } + + protected List openEditorsAndActivateLast(final LinkedHashMap editorInfos) { + final List list = openEditors(editorInfos); + setActiveEditor(list.get(list.size() - 1)); + return list; + } + + protected final List openEditors(final LinkedHashMap editorInfos) { + return ContainerUtil.map(editorInfos.keySet(), new Function() { + @Override + public Editor fun(final VirtualFile newVFile) { + PsiFile file = myPsiManager.findFile(newVFile); + if (myFile == null) myFile = file; + + Editor editor = createEditor(newVFile); + if (myEditor == null) myEditor = editor; + + EditorInfo editorInfo = editorInfos.get(newVFile); + if (editorInfo != null) { + editorInfo.applyToEditor(editor); + } + return editor; + } + }); + } + + private void doWrite(final String newFileText, final VirtualFile newVFile, final byte[] content, final List streamsToClose) throws IOException { + if (newFileText != null) { + VfsUtil.saveText(newVFile, newFileText); + } else { + final OutputStream outputStream = newVFile.getOutputStream(this, -1, -1); + outputStream.write(content); + streamsToClose.add(outputStream); + } + } + + protected boolean isAddDirToContentRoot() { + return true; + } + + protected boolean isAddDirToSource() { + return true; + } + + protected VirtualFile configureByFile(final VirtualFile vFile, final File projectRoot) throws IOException { + return configureByFiles(projectRoot, vFile); + } + + protected boolean clearModelBeforeConfiguring() { + return false; + } + + protected void setupCursorAndSelection(Editor editor) { + Document document = editor.getDocument(); + final String text = document.getText(); + + int caretIndex = text.indexOf(CARET_MARKER); + int selStartIndex = text.indexOf(SELECTION_START_MARKER); + int selEndIndex = text.indexOf(SELECTION_END_MARKER); + + final RangeMarker caretMarker = caretIndex >= 0 ? document.createRangeMarker(caretIndex, caretIndex) : null; + final RangeMarker selStartMarker = selStartIndex >= 0 ? document.createRangeMarker(selStartIndex, selStartIndex) : null; + final RangeMarker selEndMarker = selEndIndex >= 0 ? document.createRangeMarker(selEndIndex, selEndIndex) : null; + + if (caretMarker != null) { + document.deleteString(caretMarker.getStartOffset(), caretMarker.getStartOffset() + CARET_MARKER.length()); + } + if (selStartMarker != null) { + document.deleteString(selStartMarker.getStartOffset(), selStartMarker.getStartOffset() + SELECTION_START_MARKER.length()); + } + if (selEndMarker != null) { + document.deleteString(selEndMarker.getStartOffset(), selEndMarker.getStartOffset() + SELECTION_END_MARKER.length()); + } + + final String newText = document.getText(); + + if (caretMarker != null) { + int caretLine = StringUtil.offsetToLineNumber(newText, caretMarker.getStartOffset()); + int caretCol = caretMarker.getStartOffset() - StringUtil.lineColToOffset(newText, caretLine, 0); + LogicalPosition pos = new LogicalPosition(caretLine, caretCol); + editor.getCaretModel().moveToLogicalPosition(pos); + } + + if (selStartMarker != null) { + editor.getSelectionModel().setSelection(selStartMarker.getStartOffset(), selEndMarker.getStartOffset()); + } + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + } + + @Override + protected void configure(String path, String dataName) throws Exception { + super.configure(path, dataName); + + myEditor = createEditor(myFile.getVirtualFile()); + + CodeInsightTestData data = (CodeInsightTestData) myTestDataBefore; + + LogicalPosition pos = new LogicalPosition(data.getLineNumber() - 1, data.getColumnNumber() - 1); + myEditor.getCaretModel().moveToLogicalPosition(pos); + + int selectionEnd; + int selectionStart = selectionEnd = myEditor.getCaretModel().getOffset(); + + if (data.getSelectionStartColumnNumber() >= 0) { + selectionStart = myEditor.logicalPositionToOffset(new LogicalPosition(data.getSelectionEndLineNumber() - 1, data.getSelectionStartColumnNumber() - 1)); + selectionEnd = myEditor.logicalPositionToOffset(new LogicalPosition(data.getSelectionEndLineNumber() - 1, data.getSelectionEndColumnNumber() - 1)); + } + + myEditor.getSelectionModel().setSelection(selectionStart, selectionEnd); + } + + protected void checkResultByFile(@NonNls String filePath) throws Exception { + checkResultByFile(filePath, false); + } + + protected void checkResultByFile(@NonNls final String filePath, final boolean stripTrailingSpaces) throws Exception { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + getProject().getComponent(PostprocessReformattingAspect.class).doPostponedFormatting(); + if (stripTrailingSpaces) { + ((DocumentEx)myEditor.getDocument()).stripTrailingSpaces(false); + } + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + String fullPath = getTestDataPath() + filePath; + + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); + assertNotNull("Cannot find file " + fullPath, vFile); + String ft = null; + try { + ft = VfsUtil.loadText(vFile); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + String fileText = StringUtil.convertLineSeparators(ft); + Document document = EditorFactory.getInstance().createDocument(fileText); + + int caretIndex = fileText.indexOf(CARET_MARKER); + int selStartIndex = fileText.indexOf(SELECTION_START_MARKER); + int selEndIndex = fileText.indexOf(SELECTION_END_MARKER); + + final RangeMarker caretMarker = caretIndex >= 0 ? document.createRangeMarker(caretIndex, caretIndex) : null; + final RangeMarker selStartMarker = selStartIndex >= 0 ? document.createRangeMarker(selStartIndex, selStartIndex) : null; + final RangeMarker selEndMarker = selEndIndex >= 0 ? document.createRangeMarker(selEndIndex, selEndIndex) : null; + + if (caretMarker != null) { + document.deleteString(caretMarker.getStartOffset(), caretMarker.getStartOffset() + CARET_MARKER.length()); + } + if (selStartMarker != null) { + document.deleteString(selStartMarker.getStartOffset(), selStartMarker.getStartOffset() + SELECTION_START_MARKER.length()); + } + if (selEndMarker != null) { + document.deleteString(selEndMarker.getStartOffset(), selEndMarker.getStartOffset() + SELECTION_END_MARKER.length()); + } + + String newFileText = document.getText(); + String newFileText1 = newFileText; + if (stripTrailingSpaces) { + Document document1 = EditorFactory.getInstance().createDocument(newFileText); + ((DocumentEx)document1).stripTrailingSpaces(false); + newFileText1 = document1.getText(); + } + + if (myEditor instanceof EditorWindow) { + myEditor = ((EditorWindow)myEditor).getDelegate(); + myFile = PsiDocumentManager.getInstance(getProject()).getPsiFile(myEditor.getDocument()); + } + + String text = myFile.getText(); + text = StringUtil.convertLineSeparators(text); + + assertEquals("Text mismatch in file " + filePath, newFileText1, text); + + if (caretMarker != null) { + int caretLine = StringUtil.offsetToLineNumber(newFileText, caretMarker.getStartOffset()); + int caretCol = caretMarker.getStartOffset() - StringUtil.lineColToOffset(newFileText, caretLine, 0); + + assertEquals("caretLine", caretLine + 1, myEditor.getCaretModel().getLogicalPosition().line + 1); + assertEquals("caretColumn", caretCol + 1, myEditor.getCaretModel().getLogicalPosition().column + 1); + } + + if (selStartMarker != null && selEndMarker != null) { + int selStartLine = StringUtil.offsetToLineNumber(newFileText, selStartMarker.getStartOffset()); + int selStartCol = selStartMarker.getStartOffset() - StringUtil.lineColToOffset(newFileText, selStartLine, 0); + + int selEndLine = StringUtil.offsetToLineNumber(newFileText, selEndMarker.getEndOffset()); + int selEndCol = selEndMarker.getEndOffset() - StringUtil.lineColToOffset(newFileText, selEndLine, 0); + + assertEquals("selectionStartLine", selStartLine + 1, + StringUtil.offsetToLineNumber(newFileText, myEditor.getSelectionModel().getSelectionStart()) + 1); + + assertEquals("selectionStartCol", selStartCol + 1, + myEditor.getSelectionModel().getSelectionStart() - StringUtil.lineColToOffset(newFileText, selStartLine, 0) + 1); + + assertEquals("selectionEndLine", selEndLine + 1, + StringUtil.offsetToLineNumber(newFileText, myEditor.getSelectionModel().getSelectionEnd()) + 1); + + assertEquals("selectionEndCol", selEndCol + 1, + myEditor.getSelectionModel().getSelectionEnd() - StringUtil.lineColToOffset(newFileText, selEndLine, 0) + 1); + } + else { + assertTrue("has no selection", !myEditor.getSelectionModel().hasSelection()); + } + } + }); + } + + @Override + protected void checkResult(String dataName) throws Exception { + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + super.checkResult(dataName); + + CodeInsightTestData data = (CodeInsightTestData) myTestDataAfter; + + if (data.getColumnNumber() >= 0) { + assertEquals(dataName + ":caretColumn", data.getColumnNumber(), myEditor.getCaretModel().getLogicalPosition().column + 1); + } + if (data.getLineNumber() >= 0) { + assertEquals(dataName + ":caretLine", data.getLineNumber(), myEditor.getCaretModel().getLogicalPosition().line + 1); + } + + int selectionStart = myEditor.getSelectionModel().getSelectionStart(); + int selectionEnd = myEditor.getSelectionModel().getSelectionEnd(); + LogicalPosition startPosition = myEditor.offsetToLogicalPosition(selectionStart); + LogicalPosition endPosition = myEditor.offsetToLogicalPosition(selectionEnd); + + if (data.getSelectionStartColumnNumber() >= 0) { + assertEquals(dataName + ":selectionStartColumn", data.getSelectionStartColumnNumber(), startPosition.column + 1); + } + if (data.getSelectionStartLineNumber() >= 0) { + assertEquals(dataName + ":selectionStartLine", data.getSelectionStartLineNumber(), startPosition.line + 1); + } + if (data.getSelectionEndColumnNumber() >= 0) { + assertEquals(dataName + ":selectionEndColumn", data.getSelectionEndColumnNumber(), endPosition.column + 1); + } + if (data.getSelectionEndLineNumber() >= 0) { + assertEquals(dataName + ":selectionEndLine", data.getSelectionEndLineNumber(), endPosition.line + 1); + } + } + + @Override + public Object getData(String dataId) { + return PlatformDataKeys.EDITOR.is(dataId) ? myEditor : super.getData(dataId); + } + + protected VirtualFile getVirtualFile(@NonNls String filePath) { + String fullPath = getTestDataPath() + filePath; + + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); + assertNotNull("file " + fullPath + " not found", vFile); + return vFile; + } + + protected String getTestRoot(){ + return FileUtil.toSystemIndependentName(getTestDataPath()); + } + + public Editor getEditor() { + return myEditor; + } + + protected void type(char c) { + type(c, getEditor()); + } + + protected static void type(char c, Editor editor) { + EditorActionManager actionManager = EditorActionManager.getInstance(); + DataContext dataContext = DataManager.getInstance().getDataContext(); + if (c == '\n') { + actionManager.getActionHandler(IdeActions.ACTION_EDITOR_ENTER).execute(editor, dataContext); + return; + } + TypedAction action = actionManager.getTypedAction(); + action.actionPerformed(editor, c, dataContext); + } + + protected void caretRight() { + EditorActionManager actionManager = EditorActionManager.getInstance(); + EditorActionHandler action = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT); + action.execute(getEditor(), DataManager.getInstance().getDataContext()); + } + protected void deleteLine() { + EditorActionManager actionManager = EditorActionManager.getInstance(); + EditorActionHandler action = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_DELETE_LINE); + action.execute(getEditor(), DataManager.getInstance().getDataContext()); + } + + protected void type(@NonNls String s) { + for (char c : s.toCharArray()) { + type(c); + } + } + + protected void undo() { + UndoManager undoManager = UndoManager.getInstance(myProject); + TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor()); + undoManager.undo(textEditor); + } + + protected void backspace() { + backspace(getEditor()); + } + protected void backspace(final Editor editor) { + CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { + @Override + public void run() { + EditorActionManager actionManager = EditorActionManager.getInstance(); + EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE); + + actionHandler.execute(editor, DataManager.getInstance().getDataContext()); + } + }, "backspace", editor.getDocument()); + } + + protected void ctrlShiftF7() { + HighlightUsagesHandler.invoke(getProject(), getEditor(), getFile()); + } + + public static void ctrlW() { + AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET); + DataContext dataContext = DataManager.getInstance().getDataContext(); + AnActionEvent event = new AnActionEvent(null, dataContext, "", action.getTemplatePresentation(), ActionManager.getInstance(), 0); + event.setInjectedContext(true); + action.actionPerformed(event); + } +} diff --git a/platform/bootstrap/src/com/intellij/idea/Main.java b/platform/bootstrap/src/com/intellij/idea/Main.java index ab379b2a6f8e..6e6e0111a3a5 100644 --- a/platform/bootstrap/src/com/intellij/idea/Main.java +++ b/platform/bootstrap/src/com/intellij/idea/Main.java @@ -1,212 +1,212 @@ -/* - * 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.idea; - -import com.intellij.ide.Bootstrap; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.io.FileUtil; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import java.awt.*; -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class Main { - private static boolean isHeadless; - - private Main() { - } - - public static void main(final String[] args) { - if (installPatch()) { - JOptionPane.showMessageDialog(null, "The application cannot start right away since some critical files have been changed, " + - "please restart it manually."); - return; - } - - isHeadless = isHeadless(args); - if (isHeadless) { - System.setProperty("java.awt.headless", Boolean.TRUE.toString()); - } - else if (GraphicsEnvironment.isHeadless()) { - throw new HeadlessException("Unable to detect graphics environment"); - } - - Bootstrap.main(args, Main.class.getName() + "Impl", "start"); - } - - public static boolean isHeadless(final String[] args) { - @NonNls final String inspectAppCode = "inspect"; - @NonNls final String antAppCode = "ant"; - @NonNls final String duplocateCode = "duplocate"; - @NonNls final String traverseUI = "traverseUI"; - return args.length > 0 && (Comparing.strEqual(args[0], inspectAppCode) || - Comparing.strEqual(args[0], antAppCode) || - Comparing.strEqual(args[0], duplocateCode) || - Comparing.strEqual(args[0], traverseUI)); - } - - public static boolean isCommandLine(final String[] args) { - if (isHeadless(args)) return true; - @NonNls final String diffAppCode = "diff"; - return args.length > 0 && Comparing.strEqual(args[0], diffAppCode); - } - - public static boolean isHeadless() { - return isHeadless; - } - - private static boolean installPatch() { - try { - File ideaHomeDir = getIdeaHomeDir(); - if (ideaHomeDir == null) return false; - - String platform = System.getProperty("idea.platform.prefix", "idea"); - String patchFileName = ("jetbrains.patch.jar." + platform).toLowerCase(); - File patchFile = new File(System.getProperty("java.io.tmpdir"), patchFileName); - - if (!patchFile.exists()) return false; - - try { - List args = new ArrayList(); - if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { - File launcherFile = new File(ideaHomeDir, "bin/vistalauncher.exe"); - File launcherCopy = FileUtil.createTempFile("vistalauncher", ".exe"); - launcherCopy.deleteOnExit(); - copyFile(launcherFile, launcherCopy); - args.add(launcherCopy.getPath()); - } - - Collections.addAll(args, - System.getProperty("java.home") + "/bin/java", - "-classpath", - patchFile.getPath(), - "com.intellij.updater.Runner", - "install", - ideaHomeDir.getPath()); - Process process = Runtime.getRuntime().exec(args.toArray(new String[args.size()])); - - Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out)); - Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err)); - outThread.start(); - errThread.start(); - - try { - boolean requiresRestart = process.waitFor() == 42; - return requiresRestart; - } - finally { - outThread.join(); - errThread.join(); - } - } - finally { - patchFile.delete(); - } - } - catch (Exception e) { - e.printStackTrace(); - } - return false; - } - - private static class StreamRedirector implements Runnable { - private final InputStream myIn; - private final OutputStream myOut; - - private StreamRedirector(InputStream in, OutputStream out) { - myIn = in; - myOut = out; - } - - public void run() { - try { - copyStream(myIn, myOut); - } - catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static File getIdeaHomeDir() throws IOException { - URL url = Bootstrap.class.getResource(""); - if (url == null || !"jar".equals(url.getProtocol())) return null; - - String path = url.getPath(); - - int start = path.indexOf("file:/"); - int end = path.indexOf("!/"); - if (start == -1 || end == -1) return null; - - String jarFileUrl = path.substring(start, end); - - try { - File bootstrapJar = new File(new URI(jarFileUrl)); - return bootstrapJar.getParentFile().getParentFile(); - } - catch (URISyntaxException e) { - return null; - } - } - - public static void copyFile(File from, File to) throws IOException { - to.getParentFile().mkdirs(); - - FileInputStream is = null; - FileOutputStream os = null; - try { - is = new FileInputStream(from); - os = new FileOutputStream(to); - - copyStream(is, os); - } - finally { - if (is != null) { - try { - is.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - if (os != null) { - try { - os.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - public static void copyStream(InputStream from, OutputStream to) throws IOException { - byte[] buffer = new byte[65536]; - while (true) { - int read = from.read(buffer); - if (read < 0) break; - to.write(buffer, 0, read); - } - } -} +/* + * 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.idea; + +import com.intellij.ide.Bootstrap; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import java.awt.*; +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Main { + private static boolean isHeadless; + + private Main() { + } + + public static void main(final String[] args) { + if (installPatch()) { + JOptionPane.showMessageDialog(null, "The application cannot start right away since some critical files have been changed, " + + "please restart it manually."); + return; + } + + isHeadless = isHeadless(args); + if (isHeadless) { + System.setProperty("java.awt.headless", Boolean.TRUE.toString()); + } + else if (GraphicsEnvironment.isHeadless()) { + throw new HeadlessException("Unable to detect graphics environment"); + } + + Bootstrap.main(args, Main.class.getName() + "Impl", "start"); + } + + public static boolean isHeadless(final String[] args) { + @NonNls final String inspectAppCode = "inspect"; + @NonNls final String antAppCode = "ant"; + @NonNls final String duplocateCode = "duplocate"; + @NonNls final String traverseUI = "traverseUI"; + return args.length > 0 && (Comparing.strEqual(args[0], inspectAppCode) || + Comparing.strEqual(args[0], antAppCode) || + Comparing.strEqual(args[0], duplocateCode) || + Comparing.strEqual(args[0], traverseUI)); + } + + public static boolean isCommandLine(final String[] args) { + if (isHeadless(args)) return true; + @NonNls final String diffAppCode = "diff"; + return args.length > 0 && Comparing.strEqual(args[0], diffAppCode); + } + + public static boolean isHeadless() { + return isHeadless; + } + + private static boolean installPatch() { + try { + File ideaHomeDir = getIdeaHomeDir(); + if (ideaHomeDir == null) return false; + + String platform = System.getProperty("idea.platform.prefix", "idea"); + String patchFileName = ("jetbrains.patch.jar." + platform).toLowerCase(); + File patchFile = new File(System.getProperty("java.io.tmpdir"), patchFileName); + + if (!patchFile.exists()) return false; + + try { + List args = new ArrayList(); + if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { + File launcherFile = new File(ideaHomeDir, "bin/vistalauncher.exe"); + File launcherCopy = FileUtil.createTempFile("vistalauncher", ".exe"); + launcherCopy.deleteOnExit(); + copyFile(launcherFile, launcherCopy); + args.add(launcherCopy.getPath()); + } + + Collections.addAll(args, + System.getProperty("java.home") + "/bin/java", + "-classpath", + patchFile.getPath(), + "com.intellij.updater.Runner", + "install", + ideaHomeDir.getPath()); + Process process = Runtime.getRuntime().exec(args.toArray(new String[args.size()])); + + Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out)); + Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err)); + outThread.start(); + errThread.start(); + + try { + boolean requiresRestart = process.waitFor() == 42; + return requiresRestart; + } + finally { + outThread.join(); + errThread.join(); + } + } + finally { + patchFile.delete(); + } + } + catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private static class StreamRedirector implements Runnable { + private final InputStream myIn; + private final OutputStream myOut; + + private StreamRedirector(InputStream in, OutputStream out) { + myIn = in; + myOut = out; + } + + public void run() { + try { + copyStream(myIn, myOut); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static File getIdeaHomeDir() throws IOException { + URL url = Bootstrap.class.getResource(""); + if (url == null || !"jar".equals(url.getProtocol())) return null; + + String path = url.getPath(); + + int start = path.indexOf("file:/"); + int end = path.indexOf("!/"); + if (start == -1 || end == -1) return null; + + String jarFileUrl = path.substring(start, end); + + try { + File bootstrapJar = new File(new URI(jarFileUrl)); + return bootstrapJar.getParentFile().getParentFile(); + } + catch (URISyntaxException e) { + return null; + } + } + + public static void copyFile(File from, File to) throws IOException { + to.getParentFile().mkdirs(); + + FileInputStream is = null; + FileOutputStream os = null; + try { + is = new FileInputStream(from); + os = new FileOutputStream(to); + + copyStream(is, os); + } + finally { + if (is != null) { + try { + is.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + if (os != null) { + try { + os.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static void copyStream(InputStream from, OutputStream to) throws IOException { + byte[] buffer = new byte[65536]; + while (true) { + int read = from.read(buffer); + if (read < 0) break; + to.write(buffer, 0, read); + } + } +} diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionConfidence.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionConfidence.java index d394d6bcd6c2..84b44292f99f 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionConfidence.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionConfidence.java @@ -1,35 +1,35 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author peter - */ -public abstract class CompletionConfidence { - - @Nullable - public abstract Boolean shouldFocusLookup(@NotNull CompletionParameters parameters); - - @Nullable - public Boolean shouldSkipAutopopup(@Nullable PsiElement contextElement, @NotNull PsiFile psiFile, int offset) { - return null; - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author peter + */ +public abstract class CompletionConfidence { + + @Nullable + public abstract Boolean shouldFocusLookup(@NotNull CompletionParameters parameters); + + @Nullable + public Boolean shouldSkipAutopopup(@Nullable PsiElement contextElement, @NotNull PsiFile psiFile, int offset) { + return null; + } +} diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java index e3ad0941197e..e4911ca18ca1 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java @@ -1,152 +1,152 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.util.Key; -import com.intellij.psi.Weigher; -import com.intellij.reference.SoftReference; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; - -/** - * For completion FAQ, see {@link CompletionContributor}. - * - * @author peter - */ -public abstract class CompletionService { - private static final Key> INVOLVED_IN_COMPLETION_KEY = Key.create("INVOLVED_IN_COMPLETION_KEY"); - public static final Key STATISTICS_KEY = Key.create("completion"); - /** - * A "weigher" extension key (see {@link Weigher}) to sort completion items by priority and move the heaviest to the top of the Lookup. - */ - public static final Key RELEVANCE_KEY = Key.create("completion"); - /** - * A "weigher" extension key (see {@link Weigher}) to sort the whole lookup descending. - */ - public static final Key SORTING_KEY = Key.create("completionSorting"); - - public static CompletionService getCompletionService() { - return ServiceManager.getService(CompletionService.class); - } - - /** - * @return Current lookup advertisement text (at the bottom). - */ - @Nullable - public abstract String getAdvertisementText(); - - /** - * Set lookup advertisement text (at the bottom) at any time. Will do nothing if no completion process is in progress. - * @param text - */ - public abstract void setAdvertisementText(@Nullable String text); - - /** - * Run all contributors until any of them returns false or the list is exhausted. If from parameter is not null, contributors - * will be run starting from the next one after that. - * @param parameters - * @param from - * @param consumer - * @return - */ - public void getVariantsFromContributors(final CompletionParameters parameters, - @Nullable final CompletionContributor from, - final Consumer consumer) { - final List contributors = CompletionContributor.forParameters(parameters); - final boolean dumb = DumbService.getInstance(parameters.getPosition().getProject()).isDumb(); - - for (int i = contributors.indexOf(from) + 1; i < contributors.size(); i++) { - final CompletionContributor contributor = contributors.get(i); - if (dumb && !DumbService.isDumbAware(contributor)) continue; - - final CompletionResultSet result = createResultSet(parameters, consumer, contributor); - contributor.fillCompletionVariants(parameters, result); - if (result.isStopped()) { - return; - } - } - } - - /** - * Create a {@link com.intellij.codeInsight.completion.CompletionResultSet} that will filter variants based on default camel-hump - * {@link com.intellij.codeInsight.completion.PrefixMatcher} and give the filtered variants to consumer. - * @param parameters - * @param consumer - * @param contributor - * @return - */ - public abstract CompletionResultSet createResultSet(CompletionParameters parameters, Consumer consumer, - @NotNull CompletionContributor contributor); - - @Nullable - public abstract CompletionProcess getCurrentCompletion(); - - /** - * Checks if a lookup element matches a given prefix matcher. If this element has already been matched successfully during this completion, - * returns true. If it was matched successfully during another completion, forget about this and re-match with (possibly) new prefix. - * @param element - * @param matcher - * @return should a lookup element be presented to user based on entered prefix? - */ - public boolean prefixMatches(@NotNull LookupElement element, @NotNull PrefixMatcher matcher) { - final SoftReference data = element.getUserData(INVOLVED_IN_COMPLETION_KEY); - final CompletionProcess currentCompletion = getCurrentCompletion(); - if (currentCompletion != null) { - element.putUserData(INVOLVED_IN_COMPLETION_KEY, new SoftReference(currentCompletion)); - if (data != null) { - final CompletionProcess oldCompletion = data.get(); - if (oldCompletion != null && oldCompletion != currentCompletion) { - return element.setPrefixMatcher(matcher); - } - } - } else { - element.putUserData(INVOLVED_IN_COMPLETION_KEY, null); - } - return element.isPrefixMatched() || element.setPrefixMatcher(matcher); - } - - /** - * The main method that is invoked to collect all the completion variants - * @param parameters Parameters specifying current completion environment - * @param consumer This consumer will directly add lookup elements to the lookup - * @return all suitable lookup elements - */ - @NotNull - public LookupElement[] performCompletion(final CompletionParameters parameters, final Consumer consumer) { - final Collection lookupSet = new LinkedHashSet(); - - getVariantsFromContributors(parameters, null, new Consumer() { - public void consume(final LookupElement lookupElement) { - if (lookupSet.add(lookupElement)) { - consumer.consume(lookupElement); - } - } - }); - return lookupSet.toArray(new LookupElement[lookupSet.size()]); - } - - public abstract void correctCaseInsensitiveString(@NotNull final LookupElement element, InsertionContext context); -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.util.Key; +import com.intellij.psi.Weigher; +import com.intellij.reference.SoftReference; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * For completion FAQ, see {@link CompletionContributor}. + * + * @author peter + */ +public abstract class CompletionService { + private static final Key> INVOLVED_IN_COMPLETION_KEY = Key.create("INVOLVED_IN_COMPLETION_KEY"); + public static final Key STATISTICS_KEY = Key.create("completion"); + /** + * A "weigher" extension key (see {@link Weigher}) to sort completion items by priority and move the heaviest to the top of the Lookup. + */ + public static final Key RELEVANCE_KEY = Key.create("completion"); + /** + * A "weigher" extension key (see {@link Weigher}) to sort the whole lookup descending. + */ + public static final Key SORTING_KEY = Key.create("completionSorting"); + + public static CompletionService getCompletionService() { + return ServiceManager.getService(CompletionService.class); + } + + /** + * @return Current lookup advertisement text (at the bottom). + */ + @Nullable + public abstract String getAdvertisementText(); + + /** + * Set lookup advertisement text (at the bottom) at any time. Will do nothing if no completion process is in progress. + * @param text + */ + public abstract void setAdvertisementText(@Nullable String text); + + /** + * Run all contributors until any of them returns false or the list is exhausted. If from parameter is not null, contributors + * will be run starting from the next one after that. + * @param parameters + * @param from + * @param consumer + * @return + */ + public void getVariantsFromContributors(final CompletionParameters parameters, + @Nullable final CompletionContributor from, + final Consumer consumer) { + final List contributors = CompletionContributor.forParameters(parameters); + final boolean dumb = DumbService.getInstance(parameters.getPosition().getProject()).isDumb(); + + for (int i = contributors.indexOf(from) + 1; i < contributors.size(); i++) { + final CompletionContributor contributor = contributors.get(i); + if (dumb && !DumbService.isDumbAware(contributor)) continue; + + final CompletionResultSet result = createResultSet(parameters, consumer, contributor); + contributor.fillCompletionVariants(parameters, result); + if (result.isStopped()) { + return; + } + } + } + + /** + * Create a {@link com.intellij.codeInsight.completion.CompletionResultSet} that will filter variants based on default camel-hump + * {@link com.intellij.codeInsight.completion.PrefixMatcher} and give the filtered variants to consumer. + * @param parameters + * @param consumer + * @param contributor + * @return + */ + public abstract CompletionResultSet createResultSet(CompletionParameters parameters, Consumer consumer, + @NotNull CompletionContributor contributor); + + @Nullable + public abstract CompletionProcess getCurrentCompletion(); + + /** + * Checks if a lookup element matches a given prefix matcher. If this element has already been matched successfully during this completion, + * returns true. If it was matched successfully during another completion, forget about this and re-match with (possibly) new prefix. + * @param element + * @param matcher + * @return should a lookup element be presented to user based on entered prefix? + */ + public boolean prefixMatches(@NotNull LookupElement element, @NotNull PrefixMatcher matcher) { + final SoftReference data = element.getUserData(INVOLVED_IN_COMPLETION_KEY); + final CompletionProcess currentCompletion = getCurrentCompletion(); + if (currentCompletion != null) { + element.putUserData(INVOLVED_IN_COMPLETION_KEY, new SoftReference(currentCompletion)); + if (data != null) { + final CompletionProcess oldCompletion = data.get(); + if (oldCompletion != null && oldCompletion != currentCompletion) { + return element.setPrefixMatcher(matcher); + } + } + } else { + element.putUserData(INVOLVED_IN_COMPLETION_KEY, null); + } + return element.isPrefixMatched() || element.setPrefixMatcher(matcher); + } + + /** + * The main method that is invoked to collect all the completion variants + * @param parameters Parameters specifying current completion environment + * @param consumer This consumer will directly add lookup elements to the lookup + * @return all suitable lookup elements + */ + @NotNull + public LookupElement[] performCompletion(final CompletionParameters parameters, final Consumer consumer) { + final Collection lookupSet = new LinkedHashSet(); + + getVariantsFromContributors(parameters, null, new Consumer() { + public void consume(final LookupElement lookupElement) { + if (lookupSet.add(lookupElement)) { + consumer.consume(lookupElement); + } + } + }); + return lookupSet.toArray(new LookupElement[lookupSet.size()]); + } + + public abstract void correctCaseInsensitiveString(@NotNull final LookupElement element, InsertionContext context); +} diff --git a/platform/lang-api/src/com/intellij/execution/RunnerAndConfigurationSettings.java b/platform/lang-api/src/com/intellij/execution/RunnerAndConfigurationSettings.java index 73a5ce5018eb..26a0944f9267 100644 --- a/platform/lang-api/src/com/intellij/execution/RunnerAndConfigurationSettings.java +++ b/platform/lang-api/src/com/intellij/execution/RunnerAndConfigurationSettings.java @@ -1,59 +1,59 @@ -/* - * 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.execution; - -import com.intellij.execution.configurations.*; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.openapi.util.Factory; -import org.jetbrains.annotations.Nullable; - -/** - * User: anna - * Date: Jan 25, 2005 - */ -public interface RunnerAndConfigurationSettings { - @Nullable - ConfigurationFactory getFactory(); - - boolean isTemplate(); - - boolean isTemporary(); - - RunConfiguration getConfiguration(); - - void setName(String name); - - String getName(); - - RunnerSettings getRunnerSettings(ProgramRunner runner); - - ConfigurationPerRunnerSettings getConfigurationSettings(ProgramRunner runner); - - @Nullable - ConfigurationType getType(); - - void checkSettings() throws RuntimeConfigurationException; - - void checkSettings(@Nullable Executor executor) throws RuntimeConfigurationException; - - void setTemporary(boolean temporary); - - Factory createFactory(); - - void setEditBeforeRun(boolean b); - - boolean isEditBeforeRun(); -} +/* + * 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.execution; + +import com.intellij.execution.configurations.*; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.openapi.util.Factory; +import org.jetbrains.annotations.Nullable; + +/** + * User: anna + * Date: Jan 25, 2005 + */ +public interface RunnerAndConfigurationSettings { + @Nullable + ConfigurationFactory getFactory(); + + boolean isTemplate(); + + boolean isTemporary(); + + RunConfiguration getConfiguration(); + + void setName(String name); + + String getName(); + + RunnerSettings getRunnerSettings(ProgramRunner runner); + + ConfigurationPerRunnerSettings getConfigurationSettings(ProgramRunner runner); + + @Nullable + ConfigurationType getType(); + + void checkSettings() throws RuntimeConfigurationException; + + void checkSettings(@Nullable Executor executor) throws RuntimeConfigurationException; + + void setTemporary(boolean temporary); + + Factory createFactory(); + + void setEditBeforeRun(boolean b); + + boolean isEditBeforeRun(); +} diff --git a/platform/lang-api/src/com/intellij/facet/FacetType.java b/platform/lang-api/src/com/intellij/facet/FacetType.java index 37e33b9a5c8d..e1a96fcc79e7 100644 --- a/platform/lang-api/src/com/intellij/facet/FacetType.java +++ b/platform/lang-api/src/com/intellij/facet/FacetType.java @@ -1,175 +1,175 @@ -/* - * 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.facet; - -import com.intellij.facet.autodetecting.FacetDetectorRegistry; -import com.intellij.facet.ui.DefaultFacetSettingsEditor; -import com.intellij.facet.ui.FacetEditor; -import com.intellij.facet.ui.MultipleFacetSettingsEditor; -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * Override this class to provide custom type of facets. The implementation should be registered in the plugin.xml: - *

- * <extensions defaultExtensionNs="com.intellij">
- *   <facetType implementation="qualified-class-name"/>
- * </extensions> - * - * @author nik - */ -public abstract class FacetType { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.facetType"); - - private final @NotNull FacetTypeId myId; - private final @NotNull String myStringId; - private final @NotNull String myPresentableName; - private final @Nullable FacetTypeId myUnderlyingFacetType; - - public static T findInstance(Class aClass) { - return EP_NAME.findExtension(aClass); - } - - /** - * @param id unique instance of {@link FacetTypeId} - * @param stringId unique string id of the facet type - * @param presentableName name of this facet type which will be shown in UI - * @param underlyingFacetType if this parameter is not null then you will be able to add facets of this type only as - * subfacets to a facet of the specified type. If this parameter is null it will be possible to add facet of this type - * directly to a module - */ - public FacetType(final @NotNull FacetTypeId id, final @NotNull @NonNls String stringId, final @NotNull String presentableName, - final @Nullable FacetTypeId underlyingFacetType) { - myId = id; - myStringId = stringId; - myPresentableName = presentableName; - myUnderlyingFacetType = underlyingFacetType; - } - - - /** - * @param id unique instance of {@link FacetTypeId} - * @param stringId unique string id of the facet type - * @param presentableName name of this facet type which will be shown in UI - */ - public FacetType(final @NotNull FacetTypeId id, final @NotNull @NonNls String stringId, final @NotNull String presentableName) { - this(id, stringId, presentableName, null); - } - - @NotNull - public final FacetTypeId getId() { - return myId; - } - - @NotNull - public final String getStringId() { - return myStringId; - } - - @NotNull - public final String getPresentableName() { - return myPresentableName; - } - - /** - * Default name wich will be used then user creates a facet of this type - * @return - */ - @NotNull @NonNls - public String getDefaultFacetName() { - return myPresentableName; - } - - @Nullable - public final FacetTypeId getUnderlyingFacetType() { - return myUnderlyingFacetType; - } - - /** - * Override this method to support auto-detection for facets of this type - * @param registry a {@link com.intellij.facet.autodetecting.FacetDetectorRegistry} instance to register facet detectors - */ - public void registerDetectors(FacetDetectorRegistry registry) { - } - - /** - * Create default configuration of facet. See {@link FacetConfiguration} for details - * @return - */ - public abstract C createDefaultConfiguration(); - - /** - * Create a new facet instance - * @param module parent module for facet. Must be passed to {@link Facet} constructor - * @param name name of facet. Must be passed to {@link Facet} constructor - * @param configuration facet configuration. Must be passed to {@link Facet} constructor - * @param underlyingFacet underlying facet. Must be passed to {@link Facet} constructor - * @return a created facet - */ - public abstract F createFacet(@NotNull Module module, final String name, @NotNull C configuration, @Nullable Facet underlyingFacet); - - /** - * @return true if only one facet of this type is allowed in single module - */ - public boolean isOnlyOneFacetAllowed() { - return true; - } - - /** - * @param moduleType type of module - * @return true if facet of this type are allowed in module of type moduleType - */ - public abstract boolean isSuitableModuleType(ModuleType moduleType); - - @Nullable - public Icon getIcon() { - return null; - } - - /** - * Returns the topic in the help file which is shown when help for this facet type is requested - * - * @return the help topic, or null if no help is available. - */ - @Nullable @NonNls - public String getHelpTopic() { - return null; - } - - @Nullable - public DefaultFacetSettingsEditor createDefaultConfigurationEditor(@NotNull Project project, @NotNull C configuration) { - return null; - } - - /** - * Override to allow editing several facets at once - * @param project project - * @param editors editors of selected facets - * @return editor - */ - @Nullable - public MultipleFacetSettingsEditor createMultipleConfigurationsEditor(@NotNull Project project, @NotNull FacetEditor[] editors) { - return null; - } -} +/* + * 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.facet; + +import com.intellij.facet.autodetecting.FacetDetectorRegistry; +import com.intellij.facet.ui.DefaultFacetSettingsEditor; +import com.intellij.facet.ui.FacetEditor; +import com.intellij.facet.ui.MultipleFacetSettingsEditor; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * Override this class to provide custom type of facets. The implementation should be registered in the plugin.xml: + *

+ * <extensions defaultExtensionNs="com.intellij">
+ *   <facetType implementation="qualified-class-name"/>
+ * </extensions> + * + * @author nik + */ +public abstract class FacetType { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.facetType"); + + private final @NotNull FacetTypeId myId; + private final @NotNull String myStringId; + private final @NotNull String myPresentableName; + private final @Nullable FacetTypeId myUnderlyingFacetType; + + public static T findInstance(Class aClass) { + return EP_NAME.findExtension(aClass); + } + + /** + * @param id unique instance of {@link FacetTypeId} + * @param stringId unique string id of the facet type + * @param presentableName name of this facet type which will be shown in UI + * @param underlyingFacetType if this parameter is not null then you will be able to add facets of this type only as + * subfacets to a facet of the specified type. If this parameter is null it will be possible to add facet of this type + * directly to a module + */ + public FacetType(final @NotNull FacetTypeId id, final @NotNull @NonNls String stringId, final @NotNull String presentableName, + final @Nullable FacetTypeId underlyingFacetType) { + myId = id; + myStringId = stringId; + myPresentableName = presentableName; + myUnderlyingFacetType = underlyingFacetType; + } + + + /** + * @param id unique instance of {@link FacetTypeId} + * @param stringId unique string id of the facet type + * @param presentableName name of this facet type which will be shown in UI + */ + public FacetType(final @NotNull FacetTypeId id, final @NotNull @NonNls String stringId, final @NotNull String presentableName) { + this(id, stringId, presentableName, null); + } + + @NotNull + public final FacetTypeId getId() { + return myId; + } + + @NotNull + public final String getStringId() { + return myStringId; + } + + @NotNull + public final String getPresentableName() { + return myPresentableName; + } + + /** + * Default name wich will be used then user creates a facet of this type + * @return + */ + @NotNull @NonNls + public String getDefaultFacetName() { + return myPresentableName; + } + + @Nullable + public final FacetTypeId getUnderlyingFacetType() { + return myUnderlyingFacetType; + } + + /** + * Override this method to support auto-detection for facets of this type + * @param registry a {@link com.intellij.facet.autodetecting.FacetDetectorRegistry} instance to register facet detectors + */ + public void registerDetectors(FacetDetectorRegistry registry) { + } + + /** + * Create default configuration of facet. See {@link FacetConfiguration} for details + * @return + */ + public abstract C createDefaultConfiguration(); + + /** + * Create a new facet instance + * @param module parent module for facet. Must be passed to {@link Facet} constructor + * @param name name of facet. Must be passed to {@link Facet} constructor + * @param configuration facet configuration. Must be passed to {@link Facet} constructor + * @param underlyingFacet underlying facet. Must be passed to {@link Facet} constructor + * @return a created facet + */ + public abstract F createFacet(@NotNull Module module, final String name, @NotNull C configuration, @Nullable Facet underlyingFacet); + + /** + * @return true if only one facet of this type is allowed in single module + */ + public boolean isOnlyOneFacetAllowed() { + return true; + } + + /** + * @param moduleType type of module + * @return true if facet of this type are allowed in module of type moduleType + */ + public abstract boolean isSuitableModuleType(ModuleType moduleType); + + @Nullable + public Icon getIcon() { + return null; + } + + /** + * Returns the topic in the help file which is shown when help for this facet type is requested + * + * @return the help topic, or null if no help is available. + */ + @Nullable @NonNls + public String getHelpTopic() { + return null; + } + + @Nullable + public DefaultFacetSettingsEditor createDefaultConfigurationEditor(@NotNull Project project, @NotNull C configuration) { + return null; + } + + /** + * Override to allow editing several facets at once + * @param project project + * @param editors editors of selected facets + * @return editor + */ + @Nullable + public MultipleFacetSettingsEditor createMultipleConfigurationsEditor(@NotNull Project project, @NotNull FacetEditor[] editors) { + return null; + } +} diff --git a/platform/lang-api/src/com/intellij/openapi/roots/ui/util/OrderEntryCellAppearanceUtils.java b/platform/lang-api/src/com/intellij/openapi/roots/ui/util/OrderEntryCellAppearanceUtils.java index a009d8223ba6..4aa20293ad0f 100644 --- a/platform/lang-api/src/com/intellij/openapi/roots/ui/util/OrderEntryCellAppearanceUtils.java +++ b/platform/lang-api/src/com/intellij/openapi/roots/ui/util/OrderEntryCellAppearanceUtils.java @@ -1,208 +1,208 @@ -/* - * 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.roots.ui.util; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.ui.LightFilePointer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.Icons; - -import javax.swing.*; -import java.io.File; - -public class OrderEntryCellAppearanceUtils { - public static final Icon EXCLUDE_FOLDER_ICON = CellAppearanceUtils.excludeIcon(Icons.FOLDER_ICON); - public static final Icon GENERIC_JDK_ICON = IconLoader.getIcon("/general/jdk.png"); - public static final String NO_JDK = ProjectBundle.message("jdk.missing.item"); - - private OrderEntryCellAppearanceUtils() { - } - - public static CellAppearance forOrderEntry(OrderEntry orderEntry, boolean selected) { - if (orderEntry instanceof JdkOrderEntry) { - JdkOrderEntry jdkLibraryEntry = (JdkOrderEntry)orderEntry; - Sdk jdk = jdkLibraryEntry.getJdk(); - if (!orderEntry.isValid()) { - final String oldJdkName = jdkLibraryEntry.getJdkName(); - return SimpleTextCellAppearance.invalid(oldJdkName != null ? oldJdkName : NO_JDK, - CellAppearanceUtils.INVALID_ICON); - } - return forJdk(jdk, false, selected); - } - else if (!orderEntry.isValid()) { - return SimpleTextCellAppearance.invalid(orderEntry.getPresentableName(), CellAppearanceUtils.INVALID_ICON); - } - else if (orderEntry instanceof LibraryOrderEntry) { - LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry; - final Library library = libraryOrderEntry.getLibrary(); - if (!libraryOrderEntry.isValid()){ //library can be removed - return SimpleTextCellAppearance.invalid(orderEntry.getPresentableName(), CellAppearanceUtils.INVALID_ICON); - } - return forLibrary(library); - } - else if (orderEntry.isSynthetic()) { - String presentableName = orderEntry.getPresentableName(); - Icon icon = orderEntry instanceof ModuleSourceOrderEntry ? sourceFolderIcon(false) : null; - return new SimpleTextCellAppearance(presentableName, icon, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - } - else if (orderEntry instanceof ModuleOrderEntry) { - final Icon icon = ((ModuleOrderEntry)orderEntry).getModule().getModuleType().getNodeIcon(false); - return SimpleTextCellAppearance.normal(orderEntry.getPresentableName(), icon); - } - else return CompositeAppearance.single(orderEntry.getPresentableName()); - } - - public static CellAppearance forLibrary(Library library) { - String name = library.getName(); - if (name != null) { - return SimpleTextCellAppearance.normal(name, Icons.LIBRARY_ICON); - } - String[] files = library.getUrls(OrderRootType.CLASSES); - if (files.length == 0) { - return SimpleTextCellAppearance.invalid(ProjectBundle.message("library.empty.library.item"), Icons.LIBRARY_ICON); - } - if (files.length == 1) { - return forVirtualFilePointer(new LightFilePointer(files[0])); - } - String url = files[0]; - if (url.endsWith(JarFileSystem.JAR_SEPARATOR)) { - url = url.substring(0, url.length() - JarFileSystem.JAR_SEPARATOR.length()); - } - int startIndex = Math.min(url.lastIndexOf('/') + 1, url.length() - 1); - return SimpleTextCellAppearance.normal(url.substring(startIndex), Icons.LIBRARY_ICON); - } - - public static Icon sourceFolderIcon(boolean testSource) { - return testSource ? Icons.TEST_SOURCE_FOLDER : Icons.SOURCE_FOLDERS_ICON; - } - - public static ModifiableCellAppearance forJdk(Sdk jdk, boolean isInComboBox, final boolean selected, final boolean showVersion) { - if (jdk == null) { - return SimpleTextCellAppearance.invalid(NO_JDK, CellAppearanceUtils.INVALID_ICON); - } - String name = jdk.getName(); - CompositeAppearance appearance = new CompositeAppearance(); - appearance.setIcon(jdk.getSdkType().getIcon()); - VirtualFile homeDirectory = jdk.getHomeDirectory(); - SimpleTextAttributes attributes = - homeDirectory != null && homeDirectory.isValid() ? CellAppearanceUtils.createSimpleCellAttributes(selected) : SimpleTextAttributes.ERROR_ATTRIBUTES; - CompositeAppearance.DequeEnd ending = appearance.getEnding(); - ending.addText(name, attributes); - if (showVersion) { - String versionString = jdk.getVersionString(); - if (versionString != null && !versionString.equals(name)) { - SimpleTextAttributes textAttributes = isInComboBox ? SimpleTextAttributes.SYNTHETIC_ATTRIBUTES : SimpleTextAttributes.GRAY_ATTRIBUTES; - ending.addComment(versionString, textAttributes); - } - } - return ending.getAppearance(); - } - - public static CellAppearance forJdk(Sdk jdk, boolean isInComboBox, final boolean selected) { - return forJdk(jdk, isInComboBox, selected, true); - } - - public static SimpleTextCellAppearance forSourceFolder(SourceFolder folder) { - return formatRelativePath(folder, Icons.FOLDER_ICON); - } - - public static CellAppearance forExcludeFolder(ExcludeFolder folder) { - return formatRelativePath(folder, EXCLUDE_FOLDER_ICON); - } - - public static CellAppearance forContentFolder(ContentFolder folder) { - if (folder instanceof SourceFolder) { - return forSourceFolder((SourceFolder)folder); - } - else if (folder instanceof ExcludeFolder) { - return forExcludeFolder((ExcludeFolder)folder); - } - else { - throw new RuntimeException(folder.getClass().getName()); - } - } - - public static CellAppearance forModule(Module module) { - return SimpleTextCellAppearance.normal(module.getName(), module.getModuleType().getNodeIcon(false)); - } - - public static CellAppearance forContentEntry(ContentEntry contentEntry) { - return forVirtualFilePointer(new LightFilePointer(contentEntry.getUrl())); - } - - public static SimpleTextCellAppearance formatRelativePath(ContentFolder folder, Icon icon) { - LightFilePointer folderFile = new LightFilePointer(folder.getUrl()); - VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(folder.getContentEntry().getUrl()); - if (file == null) return forInvalidVirtualFilePointer(folderFile); - String contentPath = file.getPath(); - String relativePath; - SimpleTextAttributes textAttributes; - if (!folderFile.isValid()) { - textAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES; - String absolutePath = folderFile.getPresentableUrl(); - relativePath = absolutePath.startsWith(contentPath) ? absolutePath.substring(contentPath.length()) : absolutePath; - } - else { - relativePath = VfsUtil.getRelativePath(folderFile.getFile(), file, File.separatorChar); - textAttributes = SimpleTextAttributes.REGULAR_ATTRIBUTES; - } - if (relativePath == null) relativePath = ""; - relativePath = relativePath.length() == 0 ? "." + File.separatorChar : relativePath; - return new SimpleTextCellAppearance(relativePath, icon, textAttributes); - } - - public static CellAppearance forProjectJdk(final Project project) { - final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project); - final Sdk projectJdk = projectRootManager.getProjectSdk(); - final CellAppearance appearance; - if (projectJdk != null) { - appearance = forJdk(projectJdk, false, false); - } - else { - // probably invalid JDK - final String projectJdkName = projectRootManager.getProjectSdkName(); - if (projectJdkName != null) { - appearance = SimpleTextCellAppearance.invalid(ProjectBundle.message("jdk.combo.box.invalid.item", projectJdkName), - CellAppearanceUtils.INVALID_ICON); - } - else { - appearance = forJdk(null, false, false); - } - } - return appearance; - } - - public static CellAppearance forVirtualFilePointer(LightFilePointer filePointer) { - return filePointer.isValid() ? - CellAppearanceUtils.forValidVirtualFile(filePointer.getFile()) : - forInvalidVirtualFilePointer(filePointer); - } - - static SimpleTextCellAppearance forInvalidVirtualFilePointer(LightFilePointer filePointer) { - return SimpleTextCellAppearance.invalid(filePointer.getPresentableUrl(), CellAppearanceUtils.INVALID_ICON); - } -} +/* + * 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.roots.ui.util; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.ui.LightFilePointer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Icons; + +import javax.swing.*; +import java.io.File; + +public class OrderEntryCellAppearanceUtils { + public static final Icon EXCLUDE_FOLDER_ICON = CellAppearanceUtils.excludeIcon(Icons.FOLDER_ICON); + public static final Icon GENERIC_JDK_ICON = IconLoader.getIcon("/general/jdk.png"); + public static final String NO_JDK = ProjectBundle.message("jdk.missing.item"); + + private OrderEntryCellAppearanceUtils() { + } + + public static CellAppearance forOrderEntry(OrderEntry orderEntry, boolean selected) { + if (orderEntry instanceof JdkOrderEntry) { + JdkOrderEntry jdkLibraryEntry = (JdkOrderEntry)orderEntry; + Sdk jdk = jdkLibraryEntry.getJdk(); + if (!orderEntry.isValid()) { + final String oldJdkName = jdkLibraryEntry.getJdkName(); + return SimpleTextCellAppearance.invalid(oldJdkName != null ? oldJdkName : NO_JDK, + CellAppearanceUtils.INVALID_ICON); + } + return forJdk(jdk, false, selected); + } + else if (!orderEntry.isValid()) { + return SimpleTextCellAppearance.invalid(orderEntry.getPresentableName(), CellAppearanceUtils.INVALID_ICON); + } + else if (orderEntry instanceof LibraryOrderEntry) { + LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry; + final Library library = libraryOrderEntry.getLibrary(); + if (!libraryOrderEntry.isValid()){ //library can be removed + return SimpleTextCellAppearance.invalid(orderEntry.getPresentableName(), CellAppearanceUtils.INVALID_ICON); + } + return forLibrary(library); + } + else if (orderEntry.isSynthetic()) { + String presentableName = orderEntry.getPresentableName(); + Icon icon = orderEntry instanceof ModuleSourceOrderEntry ? sourceFolderIcon(false) : null; + return new SimpleTextCellAppearance(presentableName, icon, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + } + else if (orderEntry instanceof ModuleOrderEntry) { + final Icon icon = ((ModuleOrderEntry)orderEntry).getModule().getModuleType().getNodeIcon(false); + return SimpleTextCellAppearance.normal(orderEntry.getPresentableName(), icon); + } + else return CompositeAppearance.single(orderEntry.getPresentableName()); + } + + public static CellAppearance forLibrary(Library library) { + String name = library.getName(); + if (name != null) { + return SimpleTextCellAppearance.normal(name, Icons.LIBRARY_ICON); + } + String[] files = library.getUrls(OrderRootType.CLASSES); + if (files.length == 0) { + return SimpleTextCellAppearance.invalid(ProjectBundle.message("library.empty.library.item"), Icons.LIBRARY_ICON); + } + if (files.length == 1) { + return forVirtualFilePointer(new LightFilePointer(files[0])); + } + String url = files[0]; + if (url.endsWith(JarFileSystem.JAR_SEPARATOR)) { + url = url.substring(0, url.length() - JarFileSystem.JAR_SEPARATOR.length()); + } + int startIndex = Math.min(url.lastIndexOf('/') + 1, url.length() - 1); + return SimpleTextCellAppearance.normal(url.substring(startIndex), Icons.LIBRARY_ICON); + } + + public static Icon sourceFolderIcon(boolean testSource) { + return testSource ? Icons.TEST_SOURCE_FOLDER : Icons.SOURCE_FOLDERS_ICON; + } + + public static ModifiableCellAppearance forJdk(Sdk jdk, boolean isInComboBox, final boolean selected, final boolean showVersion) { + if (jdk == null) { + return SimpleTextCellAppearance.invalid(NO_JDK, CellAppearanceUtils.INVALID_ICON); + } + String name = jdk.getName(); + CompositeAppearance appearance = new CompositeAppearance(); + appearance.setIcon(jdk.getSdkType().getIcon()); + VirtualFile homeDirectory = jdk.getHomeDirectory(); + SimpleTextAttributes attributes = + homeDirectory != null && homeDirectory.isValid() ? CellAppearanceUtils.createSimpleCellAttributes(selected) : SimpleTextAttributes.ERROR_ATTRIBUTES; + CompositeAppearance.DequeEnd ending = appearance.getEnding(); + ending.addText(name, attributes); + if (showVersion) { + String versionString = jdk.getVersionString(); + if (versionString != null && !versionString.equals(name)) { + SimpleTextAttributes textAttributes = isInComboBox ? SimpleTextAttributes.SYNTHETIC_ATTRIBUTES : SimpleTextAttributes.GRAY_ATTRIBUTES; + ending.addComment(versionString, textAttributes); + } + } + return ending.getAppearance(); + } + + public static CellAppearance forJdk(Sdk jdk, boolean isInComboBox, final boolean selected) { + return forJdk(jdk, isInComboBox, selected, true); + } + + public static SimpleTextCellAppearance forSourceFolder(SourceFolder folder) { + return formatRelativePath(folder, Icons.FOLDER_ICON); + } + + public static CellAppearance forExcludeFolder(ExcludeFolder folder) { + return formatRelativePath(folder, EXCLUDE_FOLDER_ICON); + } + + public static CellAppearance forContentFolder(ContentFolder folder) { + if (folder instanceof SourceFolder) { + return forSourceFolder((SourceFolder)folder); + } + else if (folder instanceof ExcludeFolder) { + return forExcludeFolder((ExcludeFolder)folder); + } + else { + throw new RuntimeException(folder.getClass().getName()); + } + } + + public static CellAppearance forModule(Module module) { + return SimpleTextCellAppearance.normal(module.getName(), module.getModuleType().getNodeIcon(false)); + } + + public static CellAppearance forContentEntry(ContentEntry contentEntry) { + return forVirtualFilePointer(new LightFilePointer(contentEntry.getUrl())); + } + + public static SimpleTextCellAppearance formatRelativePath(ContentFolder folder, Icon icon) { + LightFilePointer folderFile = new LightFilePointer(folder.getUrl()); + VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(folder.getContentEntry().getUrl()); + if (file == null) return forInvalidVirtualFilePointer(folderFile); + String contentPath = file.getPath(); + String relativePath; + SimpleTextAttributes textAttributes; + if (!folderFile.isValid()) { + textAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES; + String absolutePath = folderFile.getPresentableUrl(); + relativePath = absolutePath.startsWith(contentPath) ? absolutePath.substring(contentPath.length()) : absolutePath; + } + else { + relativePath = VfsUtil.getRelativePath(folderFile.getFile(), file, File.separatorChar); + textAttributes = SimpleTextAttributes.REGULAR_ATTRIBUTES; + } + if (relativePath == null) relativePath = ""; + relativePath = relativePath.length() == 0 ? "." + File.separatorChar : relativePath; + return new SimpleTextCellAppearance(relativePath, icon, textAttributes); + } + + public static CellAppearance forProjectJdk(final Project project) { + final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project); + final Sdk projectJdk = projectRootManager.getProjectSdk(); + final CellAppearance appearance; + if (projectJdk != null) { + appearance = forJdk(projectJdk, false, false); + } + else { + // probably invalid JDK + final String projectJdkName = projectRootManager.getProjectSdkName(); + if (projectJdkName != null) { + appearance = SimpleTextCellAppearance.invalid(ProjectBundle.message("jdk.combo.box.invalid.item", projectJdkName), + CellAppearanceUtils.INVALID_ICON); + } + else { + appearance = forJdk(null, false, false); + } + } + return appearance; + } + + public static CellAppearance forVirtualFilePointer(LightFilePointer filePointer) { + return filePointer.isValid() ? + CellAppearanceUtils.forValidVirtualFile(filePointer.getFile()) : + forInvalidVirtualFilePointer(filePointer); + } + + static SimpleTextCellAppearance forInvalidVirtualFilePointer(LightFilePointer filePointer) { + return SimpleTextCellAppearance.invalid(filePointer.getPresentableUrl(), CellAppearanceUtils.INVALID_ICON); + } +} diff --git a/platform/lang-api/src/com/intellij/psi/PsiElementRef.java b/platform/lang-api/src/com/intellij/psi/PsiElementRef.java index 94ba9b195486..eeddd9fcc236 100644 --- a/platform/lang-api/src/com/intellij/psi/PsiElementRef.java +++ b/platform/lang-api/src/com/intellij/psi/PsiElementRef.java @@ -1,189 +1,189 @@ -/* - * 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; - -import com.intellij.openapi.diagnostic.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author peter - */ -public final class PsiElementRef { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.PsiElementRef"); - private volatile PsiRefColleague myColleague; - - public PsiElementRef(PsiRefColleague colleague) { - myColleague = colleague; - } - - public final boolean isImaginary() { - return getPsiElement() == null; - } - - @Nullable - public final T getPsiElement() { - return myColleague.getPsiElement(); - } - - @NotNull - public final T ensurePsiElementExists() { - final PsiRefColleague.Real realColleague = myColleague.makeReal(); - myColleague = realColleague; - return realColleague.getPsiElement(); - } - - @NotNull - public final PsiElement getRoot() { - return myColleague.getRoot(); - } - - @Override - public boolean equals(Object o) { - return o instanceof PsiElementRef && myColleague.equals(((PsiElementRef) o).myColleague); - } - - @Override - public int hashCode() { - return myColleague.hashCode(); - } - - public final boolean isValid() { - return myColleague.isValid(); - } - - public static PsiElementRef real(@NotNull final T element) { - return new PsiElementRef(new PsiRefColleague.Real(element)); - } - - public static PsiElementRef imaginary(final PsiElementRef parent, final PsiRefElementCreator creator) { - return new PsiElementRef(new PsiRefColleague.Imaginary(parent, creator)); - } - - public PsiManager getPsiManager() { - return myColleague.getRoot().getManager(); - } - - private interface PsiRefColleague { - - boolean isValid(); - - @Nullable - T getPsiElement(); - - @NotNull - Real makeReal(); - - @NotNull - PsiElement getRoot(); - - class Real implements PsiRefColleague { - private final T myElement; - - public Real(@NotNull T element) { - LOG.assertTrue(element.isValid()); - myElement = element; - } - - @NotNull - public T getPsiElement() { - return myElement; - } - - public boolean isValid() { - return myElement.isValid(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Real real = (Real)o; - - if (!myElement.equals(real.myElement)) return false; - - return true; - } - - @Override - public int hashCode() { - return myElement.hashCode(); - } - - @NotNull - public Real makeReal() { - return this; - } - - @NotNull - public PsiElement getRoot() { - return myElement; - } - } - - class Imaginary implements PsiRefColleague { - private final PsiElementRef myParent; - private final PsiRefElementCreator myCreator; - - public Imaginary(PsiElementRef parent, PsiRefElementCreator creator) { - myParent = parent; - myCreator = creator; - } - - public boolean isValid() { - return myParent.isValid(); - } - - public Child getPsiElement() { - return null; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Imaginary imaginary = (Imaginary)o; - - if (!myCreator.equals(imaginary.myCreator)) return false; - if (!myParent.equals(imaginary.myParent)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myParent.hashCode(); - result = 31 * result + myCreator.hashCode(); - return result; - } - - @NotNull - public Real makeReal() { - return new Real(myCreator.createChild(myParent.ensurePsiElementExists())); - } - - @NotNull - public PsiElement getRoot() { - return myParent.getRoot(); - } - } - - - } - -} +/* + * 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; + +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author peter + */ +public final class PsiElementRef { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.PsiElementRef"); + private volatile PsiRefColleague myColleague; + + public PsiElementRef(PsiRefColleague colleague) { + myColleague = colleague; + } + + public final boolean isImaginary() { + return getPsiElement() == null; + } + + @Nullable + public final T getPsiElement() { + return myColleague.getPsiElement(); + } + + @NotNull + public final T ensurePsiElementExists() { + final PsiRefColleague.Real realColleague = myColleague.makeReal(); + myColleague = realColleague; + return realColleague.getPsiElement(); + } + + @NotNull + public final PsiElement getRoot() { + return myColleague.getRoot(); + } + + @Override + public boolean equals(Object o) { + return o instanceof PsiElementRef && myColleague.equals(((PsiElementRef) o).myColleague); + } + + @Override + public int hashCode() { + return myColleague.hashCode(); + } + + public final boolean isValid() { + return myColleague.isValid(); + } + + public static PsiElementRef real(@NotNull final T element) { + return new PsiElementRef(new PsiRefColleague.Real(element)); + } + + public static PsiElementRef imaginary(final PsiElementRef parent, final PsiRefElementCreator creator) { + return new PsiElementRef(new PsiRefColleague.Imaginary(parent, creator)); + } + + public PsiManager getPsiManager() { + return myColleague.getRoot().getManager(); + } + + private interface PsiRefColleague { + + boolean isValid(); + + @Nullable + T getPsiElement(); + + @NotNull + Real makeReal(); + + @NotNull + PsiElement getRoot(); + + class Real implements PsiRefColleague { + private final T myElement; + + public Real(@NotNull T element) { + LOG.assertTrue(element.isValid()); + myElement = element; + } + + @NotNull + public T getPsiElement() { + return myElement; + } + + public boolean isValid() { + return myElement.isValid(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Real real = (Real)o; + + if (!myElement.equals(real.myElement)) return false; + + return true; + } + + @Override + public int hashCode() { + return myElement.hashCode(); + } + + @NotNull + public Real makeReal() { + return this; + } + + @NotNull + public PsiElement getRoot() { + return myElement; + } + } + + class Imaginary implements PsiRefColleague { + private final PsiElementRef myParent; + private final PsiRefElementCreator myCreator; + + public Imaginary(PsiElementRef parent, PsiRefElementCreator creator) { + myParent = parent; + myCreator = creator; + } + + public boolean isValid() { + return myParent.isValid(); + } + + public Child getPsiElement() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Imaginary imaginary = (Imaginary)o; + + if (!myCreator.equals(imaginary.myCreator)) return false; + if (!myParent.equals(imaginary.myParent)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myParent.hashCode(); + result = 31 * result + myCreator.hashCode(); + return result; + } + + @NotNull + public Real makeReal() { + return new Real(myCreator.createChild(myParent.ensurePsiElementExists())); + } + + @NotNull + public PsiElement getRoot() { + return myParent.getRoot(); + } + } + + + } + +} diff --git a/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java b/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java index 4a6e6a3d7f53..738dd457457a 100644 --- a/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java +++ b/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java @@ -1,258 +1,258 @@ -/* - * 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.impl; - -import com.intellij.navigation.ItemPresentation; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.ui.IconDeferrer; -import com.intellij.ui.LayeredIcon; -import com.intellij.ui.RowIcon; -import com.intellij.util.*; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.update.ComparableObject; -import gnu.trove.TIntObjectHashMap; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.List; - -public abstract class ElementBase extends UserDataHolderBase implements Iconable { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.ElementBase"); - - public static final int FLAGS_LOCKED = 0x800; - private TIntObjectHashMap myBaseIcon; - - private static final Icon VISIBILITY_ICON_PLACHOLDER = new EmptyIcon(Icons.PUBLIC_ICON); - private static final Icon ICON_PLACHOLDER = IconLoader.getIcon("/nodes/nodePlaceholder.png"); - - @Nullable - public Icon getIcon(int flags) { - if (!(this instanceof PsiElement)) return null; - - try { - Icon icon = computeIcon(flags); - LastComputedIcon.put(this, icon, flags); - return icon; - } - catch (ProcessCanceledException e) { - throw e; - } - catch (IndexNotReadyException e) { - throw e; - } - catch(Exception e) { - LOG.error(e); - return null; - } - } - - @Nullable - private Icon computeIcon(final int flags) { - PsiElement psiElement = (PsiElement)this; - Icon baseIcon = LastComputedIcon.get(psiElement, flags); - if (baseIcon == null) { - if (myBaseIcon == null) { - myBaseIcon = new TIntObjectHashMap(3); - } - if (!myBaseIcon.containsKey(flags)) { - myBaseIcon.put(flags, computeBaseIcon(flags)); - } - baseIcon = myBaseIcon.get(flags); - } - - final Project project = psiElement.getProject(); - if (isToDeferIconLoading()) { - return IconDeferrer.getInstance().defer(baseIcon, new ElementIconRequest(psiElement, flags), new NullableFunction() { - public Icon fun(ElementIconRequest request) { - if (project.isDisposed()) return null; - final PsiElement element = request.getElement(); - if (!element.isValid()) return null; - if (element.getProject().isDisposed()) return null; - return computeIconNow(element, request.getFlags()); - } - }); - } else { - if (!psiElement.isValid()) return null; - return computeIconNow(psiElement, flags); - } - } - - protected boolean isToDeferIconLoading() { - return Registry.is("psi.deferIconLoading"); - } - - @Nullable - private Icon computeIconNow(PsiElement element, int flags) { - final Icon providersIcon = PsiIconUtil.getProvidersIcon(element, flags); - if (providersIcon != null) { - return providersIcon instanceof RowIcon ? (RowIcon)providersIcon : createLayeredIcon(providersIcon, flags); - } - return getElementIcon(flags); - } - - protected Icon computeBaseIcon(int flags) { - return isVisibilitySupported() ? getAdjustedBaseIcon(getBaseIcon(), flags) : getBaseIcon(); - } - - protected Icon getBaseIcon() { - return ICON_PLACHOLDER; - } - - protected Icon getAdjustedBaseIcon(Icon icon, int flags) { - Icon result = icon; - if ((flags & ICON_FLAG_VISIBILITY) > 0) { - RowIcon rowIcon = new RowIcon(2); - rowIcon.setIcon(icon, 0); - rowIcon.setIcon(VISIBILITY_ICON_PLACHOLDER, 1); - result = rowIcon; - } - - return result; - } - - protected boolean isVisibilitySupported() { - return false; - } - - public static Icon overlayIcons(Icon ... icons) { - final LayeredIcon icon = new LayeredIcon(icons.length); - int i = 0; - for(Icon ic:icons) icon.setIcon(ic, i++); - return icon; - } - - public static RowIcon buildRowIcon(final Icon baseIcon, Icon visibilityIcon) { - RowIcon icon = new RowIcon(2); - icon.setIcon(baseIcon, 0); - icon.setIcon(visibilityIcon, 1); - return icon; - } - - public static Icon iconWithVisibilityIfNeeded(int flags, Icon baseIcon, Icon visibility) { - return (flags & ICON_FLAG_VISIBILITY) != 0 ? buildRowIcon( - baseIcon, - visibility - ):baseIcon; - } - - public static class ElementIconRequest extends ComparableObject.Impl { - public ElementIconRequest(PsiElement element, int flags) { - super(new Object[] {element, flags}); - } - - public PsiElement getElement() { - return (PsiElement)getEqualityObjects()[0]; - } - - public int getFlags() { - return (Integer)getEqualityObjects()[1]; - } - } - - @Nullable - protected Icon getElementIcon(final int flags) { - final PsiElement element = (PsiElement)this; - - if (!element.isValid()) return null; - - RowIcon baseIcon; - final boolean isLocked = (flags & ICON_FLAG_READ_STATUS) != 0 && !element.isWritable(); - int elementFlags = isLocked ? FLAGS_LOCKED : 0; - if (element instanceof ItemPresentation && ((ItemPresentation)element).getIcon(false) != null) { - baseIcon = createLayeredIcon(((ItemPresentation)element).getIcon(false), elementFlags); - } - else if (element instanceof PsiFile) { - PsiFile file = (PsiFile)element; - - VirtualFile virtualFile = file.getVirtualFile(); - final Icon fileTypeIcon; - if (virtualFile == null) { - fileTypeIcon = file.getFileType().getIcon(); - } - else { - fileTypeIcon = IconUtil.getIcon(virtualFile, flags & ~ICON_FLAG_READ_STATUS, file.getProject()); - } - return createLayeredIcon(fileTypeIcon, elementFlags); - } - else { - return null; - } - return baseIcon; - } - - public static RowIcon createLayeredIcon(Icon icon, int flags) { - if (flags != 0) { - List iconLayers = new SmartList(); - for(IconLayer l: ourIconLayers) { - if ((flags & l.flagMask) != 0) { - iconLayers.add(l.icon); - } - } - LayeredIcon layeredIcon = new LayeredIcon(1 + iconLayers.size()); - layeredIcon.setIcon(icon, 0); - for (int i = 0; i < iconLayers.size(); i++) { - Icon icon1 = iconLayers.get(i); - layeredIcon.setIcon(icon1, i+1); - } - icon = layeredIcon; - } - RowIcon baseIcon = new RowIcon(2); - baseIcon.setIcon(icon, 0); - return baseIcon; - } - - public static int transformFlags(PsiElement element, int _flags) { - int flags = _flags & ~ICON_FLAG_READ_STATUS; - final boolean isLocked = (_flags & ICON_FLAG_READ_STATUS) != 0 && !element.isWritable(); - if (isLocked) flags |= FLAGS_LOCKED; - return flags; - } - - private static class IconLayer { - int flagMask; - Icon icon; - - IconLayer(final int flagMask, final Icon icon) { - this.flagMask = flagMask; - this.icon = icon; - } - } - - private static final List ourIconLayers = new ArrayList(); - - public static void registerIconLayer(int flagMask, Icon icon) { - for(IconLayer iconLayer: ourIconLayers) { - if (iconLayer.flagMask == flagMask) return; - } - ourIconLayers.add(new IconLayer(flagMask, icon)); - } - - static { - registerIconLayer(FLAGS_LOCKED, Icons.LOCKED_ICON); - } -} +/* + * 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.impl; + +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.ui.IconDeferrer; +import com.intellij.ui.LayeredIcon; +import com.intellij.ui.RowIcon; +import com.intellij.util.*; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.update.ComparableObject; +import gnu.trove.TIntObjectHashMap; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.List; + +public abstract class ElementBase extends UserDataHolderBase implements Iconable { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.ElementBase"); + + public static final int FLAGS_LOCKED = 0x800; + private TIntObjectHashMap myBaseIcon; + + private static final Icon VISIBILITY_ICON_PLACHOLDER = new EmptyIcon(Icons.PUBLIC_ICON); + private static final Icon ICON_PLACHOLDER = IconLoader.getIcon("/nodes/nodePlaceholder.png"); + + @Nullable + public Icon getIcon(int flags) { + if (!(this instanceof PsiElement)) return null; + + try { + Icon icon = computeIcon(flags); + LastComputedIcon.put(this, icon, flags); + return icon; + } + catch (ProcessCanceledException e) { + throw e; + } + catch (IndexNotReadyException e) { + throw e; + } + catch(Exception e) { + LOG.error(e); + return null; + } + } + + @Nullable + private Icon computeIcon(final int flags) { + PsiElement psiElement = (PsiElement)this; + Icon baseIcon = LastComputedIcon.get(psiElement, flags); + if (baseIcon == null) { + if (myBaseIcon == null) { + myBaseIcon = new TIntObjectHashMap(3); + } + if (!myBaseIcon.containsKey(flags)) { + myBaseIcon.put(flags, computeBaseIcon(flags)); + } + baseIcon = myBaseIcon.get(flags); + } + + final Project project = psiElement.getProject(); + if (isToDeferIconLoading()) { + return IconDeferrer.getInstance().defer(baseIcon, new ElementIconRequest(psiElement, flags), new NullableFunction() { + public Icon fun(ElementIconRequest request) { + if (project.isDisposed()) return null; + final PsiElement element = request.getElement(); + if (!element.isValid()) return null; + if (element.getProject().isDisposed()) return null; + return computeIconNow(element, request.getFlags()); + } + }); + } else { + if (!psiElement.isValid()) return null; + return computeIconNow(psiElement, flags); + } + } + + protected boolean isToDeferIconLoading() { + return Registry.is("psi.deferIconLoading"); + } + + @Nullable + private Icon computeIconNow(PsiElement element, int flags) { + final Icon providersIcon = PsiIconUtil.getProvidersIcon(element, flags); + if (providersIcon != null) { + return providersIcon instanceof RowIcon ? (RowIcon)providersIcon : createLayeredIcon(providersIcon, flags); + } + return getElementIcon(flags); + } + + protected Icon computeBaseIcon(int flags) { + return isVisibilitySupported() ? getAdjustedBaseIcon(getBaseIcon(), flags) : getBaseIcon(); + } + + protected Icon getBaseIcon() { + return ICON_PLACHOLDER; + } + + protected Icon getAdjustedBaseIcon(Icon icon, int flags) { + Icon result = icon; + if ((flags & ICON_FLAG_VISIBILITY) > 0) { + RowIcon rowIcon = new RowIcon(2); + rowIcon.setIcon(icon, 0); + rowIcon.setIcon(VISIBILITY_ICON_PLACHOLDER, 1); + result = rowIcon; + } + + return result; + } + + protected boolean isVisibilitySupported() { + return false; + } + + public static Icon overlayIcons(Icon ... icons) { + final LayeredIcon icon = new LayeredIcon(icons.length); + int i = 0; + for(Icon ic:icons) icon.setIcon(ic, i++); + return icon; + } + + public static RowIcon buildRowIcon(final Icon baseIcon, Icon visibilityIcon) { + RowIcon icon = new RowIcon(2); + icon.setIcon(baseIcon, 0); + icon.setIcon(visibilityIcon, 1); + return icon; + } + + public static Icon iconWithVisibilityIfNeeded(int flags, Icon baseIcon, Icon visibility) { + return (flags & ICON_FLAG_VISIBILITY) != 0 ? buildRowIcon( + baseIcon, + visibility + ):baseIcon; + } + + public static class ElementIconRequest extends ComparableObject.Impl { + public ElementIconRequest(PsiElement element, int flags) { + super(new Object[] {element, flags}); + } + + public PsiElement getElement() { + return (PsiElement)getEqualityObjects()[0]; + } + + public int getFlags() { + return (Integer)getEqualityObjects()[1]; + } + } + + @Nullable + protected Icon getElementIcon(final int flags) { + final PsiElement element = (PsiElement)this; + + if (!element.isValid()) return null; + + RowIcon baseIcon; + final boolean isLocked = (flags & ICON_FLAG_READ_STATUS) != 0 && !element.isWritable(); + int elementFlags = isLocked ? FLAGS_LOCKED : 0; + if (element instanceof ItemPresentation && ((ItemPresentation)element).getIcon(false) != null) { + baseIcon = createLayeredIcon(((ItemPresentation)element).getIcon(false), elementFlags); + } + else if (element instanceof PsiFile) { + PsiFile file = (PsiFile)element; + + VirtualFile virtualFile = file.getVirtualFile(); + final Icon fileTypeIcon; + if (virtualFile == null) { + fileTypeIcon = file.getFileType().getIcon(); + } + else { + fileTypeIcon = IconUtil.getIcon(virtualFile, flags & ~ICON_FLAG_READ_STATUS, file.getProject()); + } + return createLayeredIcon(fileTypeIcon, elementFlags); + } + else { + return null; + } + return baseIcon; + } + + public static RowIcon createLayeredIcon(Icon icon, int flags) { + if (flags != 0) { + List iconLayers = new SmartList(); + for(IconLayer l: ourIconLayers) { + if ((flags & l.flagMask) != 0) { + iconLayers.add(l.icon); + } + } + LayeredIcon layeredIcon = new LayeredIcon(1 + iconLayers.size()); + layeredIcon.setIcon(icon, 0); + for (int i = 0; i < iconLayers.size(); i++) { + Icon icon1 = iconLayers.get(i); + layeredIcon.setIcon(icon1, i+1); + } + icon = layeredIcon; + } + RowIcon baseIcon = new RowIcon(2); + baseIcon.setIcon(icon, 0); + return baseIcon; + } + + public static int transformFlags(PsiElement element, int _flags) { + int flags = _flags & ~ICON_FLAG_READ_STATUS; + final boolean isLocked = (_flags & ICON_FLAG_READ_STATUS) != 0 && !element.isWritable(); + if (isLocked) flags |= FLAGS_LOCKED; + return flags; + } + + private static class IconLayer { + int flagMask; + Icon icon; + + IconLayer(final int flagMask, final Icon icon) { + this.flagMask = flagMask; + this.icon = icon; + } + } + + private static final List ourIconLayers = new ArrayList(); + + public static void registerIconLayer(int flagMask, Icon icon) { + for(IconLayer iconLayer: ourIconLayers) { + if (iconLayer.flagMask == flagMask) return; + } + ourIconLayers.add(new IconLayer(flagMask, icon)); + } + + static { + registerIconLayer(FLAGS_LOCKED, Icons.LOCKED_ICON); + } +} diff --git a/platform/lang-api/src/com/intellij/psi/search/scope/ProjectFilesScope.java b/platform/lang-api/src/com/intellij/psi/search/scope/ProjectFilesScope.java index b56706886ee1..f989d8972b94 100644 --- a/platform/lang-api/src/com/intellij/psi/search/scope/ProjectFilesScope.java +++ b/platform/lang-api/src/com/intellij/psi/search/scope/ProjectFilesScope.java @@ -1,43 +1,43 @@ -/* - * 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.psi.search.scope; - -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.search.scope.packageSet.AbstractPackageSet; -import com.intellij.psi.search.scope.packageSet.NamedScope; -import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; - -/** - * @author Konstantin Bulenkov - */ -public class ProjectFilesScope extends NamedScope { - public static final String NAME = "Project Files"; - public ProjectFilesScope() { - super(NAME, new AbstractPackageSet("ProjectFiles") { - public boolean contains(PsiFile psiFile, NamedScopesHolder holder) { - final VirtualFile file = psiFile.getVirtualFile(); - if (file == null) return false; - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(holder.getProject()).getFileIndex(); - return holder.getProject().isInitialized() - && !fileIndex.isIgnored(file) - && fileIndex.getContentRootForFile(file) != null; - } - }); - } -} +/* + * 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.psi.search.scope; + +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.scope.packageSet.AbstractPackageSet; +import com.intellij.psi.search.scope.packageSet.NamedScope; +import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; + +/** + * @author Konstantin Bulenkov + */ +public class ProjectFilesScope extends NamedScope { + public static final String NAME = "Project Files"; + public ProjectFilesScope() { + super(NAME, new AbstractPackageSet("ProjectFiles") { + public boolean contains(PsiFile psiFile, NamedScopesHolder holder) { + final VirtualFile file = psiFile.getVirtualFile(); + if (file == null) return false; + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(holder.getProject()).getFileIndex(); + return holder.getProject().isInitialized() + && !fileIndex.isIgnored(file) + && fileIndex.getContentRootForFile(file) != null; + } + }); + } +} diff --git a/platform/lang-api/src/com/intellij/psi/stubs/SerializationManagerImpl.java b/platform/lang-api/src/com/intellij/psi/stubs/SerializationManagerImpl.java index 6cd042b457a0..e90ab4138623 100644 --- a/platform/lang-api/src/com/intellij/psi/stubs/SerializationManagerImpl.java +++ b/platform/lang-api/src/com/intellij/psi/stubs/SerializationManagerImpl.java @@ -1,241 +1,241 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.psi.stubs; - -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.IStubFileElementType; -import com.intellij.util.io.DataInputOutputUtil; -import com.intellij.util.io.PersistentStringEnumerator; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -public class SerializationManagerImpl extends SerializationManager implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.SerializationManagerImpl"); - - private PersistentStringEnumerator myNameStorage; - - private final Map> myIdToSerializer = new HashMap>(); - private final Map, Integer> mySerializerToId = new HashMap, Integer>(); - private final List> myAllSerializers = new ArrayList>(); - private final AtomicBoolean myNameStorageCrashed = new AtomicBoolean(false); - private final File myFile = new File(PathManager.getIndexRoot(), "rep.names"); - private boolean mySerializersLoaded = false; - - public SerializationManagerImpl() { - myFile.getParentFile().mkdirs(); - try { - myNameStorage = new PersistentStringEnumerator(myFile); - } - catch (IOException e) { - myNameStorageCrashed.set(true); - LOG.info(e); - repairNameStorage(); // need this in order for myNameStorage not to be null - myNameStorageCrashed.set(true); - } - registerSerializer(PsiFileStubImpl.TYPE); - } - - public boolean isNameStorageCorrupted() { - return myNameStorageCrashed.get(); - } - - public void repairNameStorage() { - if (myNameStorageCrashed.getAndSet(false)) { - try { - if (myNameStorage != null) { - myNameStorage.close(); - } - - final File[] files = myFile.getParentFile().listFiles(); - if (files != null) { - for (File file : files) { - if (file.getName().startsWith(myFile.getName())) { - FileUtil.delete(file); - } - } - } - myNameStorage = new PersistentStringEnumerator(myFile); - - mySerializerToId.clear(); - myIdToSerializer.clear(); - for (StubSerializer serializer : myAllSerializers) { - assignId(serializer); - } - } - catch (IOException e) { - LOG.info(e); - myNameStorageCrashed.set(true); - } - } - } - - public void registerSerializer(@NotNull StubSerializer serializer) { - myAllSerializers.add(serializer); - try { - assignId(serializer); - } - catch (IOException e) { - LOG.info(e); - myNameStorageCrashed.set(true); - } - } - - private void assignId(@NotNull final StubSerializer serializer) throws IOException { - final int id = persistentId(serializer); - final StubSerializer old = myIdToSerializer.put(id, serializer); - assert old == null : "ID: " + serializer.getExternalId() + " is not unique; Already registered serializer with this ID: " + old.getClass().getName(); - - final Integer oldId = mySerializerToId.put(serializer, id); - assert oldId == null : "Serializer " + serializer + " is already registered; Old ID:" + oldId; - } - - private int persistentId(@NotNull final StubSerializer serializer) throws IOException { - if (myNameStorage == null) { - throw new IOException("SerializationManager's name storage failed to initialize"); - } - return myNameStorage.enumerate(serializer.getExternalId()); - } - - private synchronized void initSerializers() { - if (mySerializersLoaded) return; - mySerializersLoaded = true; - for(StubElementTypeHolderEP holderEP: Extensions.getExtensions(StubElementTypeHolderEP.EP_NAME)) { - holderEP.initialize(); - } - final IElementType[] stubElementTypes = IElementType.enumerate(new IElementType.Predicate() { - public boolean matches(final IElementType type) { - return type instanceof StubSerializer; - } - }); - for(IElementType type: stubElementTypes) { - if (type instanceof IStubFileElementType && ((IStubFileElementType) type).getExternalId().equals(PsiFileStubImpl.TYPE.getExternalId())) { - continue; - } - StubSerializer stubSerializer = (StubSerializer) type; - - if (!myAllSerializers.contains(stubSerializer)) { - registerSerializer(stubSerializer); - } - } - } - - public void serialize(StubElement rootStub, OutputStream stream) { - StubOutputStream stubOutputStream = new StubOutputStream(stream, myNameStorage); - doSerialize(rootStub, stubOutputStream); - } - - private void doSerialize(final StubElement rootStub, final StubOutputStream stream) { - initSerializers(); - try { - final StubSerializer serializer = getSerializer(rootStub); - - DataInputOutputUtil.writeINT(stream, getClassId(serializer)); - serializer.serialize(rootStub, stream); - - final List children = rootStub.getChildrenStubs(); - DataInputOutputUtil.writeINT(stream, children.size()); - for (StubElement child : children) { - doSerialize(child, stream); - } - } - catch (IOException e) { - LOG.info(e); - myNameStorageCrashed.set(true); - } - } - - public StubSerializer getSerializer(final StubElement rootStub) { - if (rootStub instanceof PsiFileStub) { - final PsiFileStub fileStub = (PsiFileStub)rootStub; - return fileStub.getType(); - } - - return rootStub.getStubType(); - } - - public StubElement deserialize(InputStream stream) { - StubInputStream inputStream = new StubInputStream(stream, myNameStorage); - initSerializers(); - try { - return deserialize(inputStream, null); - } - catch (IOException e) { - myNameStorageCrashed.set(true); - LOG.info(e); - throw new RuntimeException(e); - } - } - - private StubElement deserialize(StubInputStream stream, StubElement parentStub) throws IOException { - final int id = DataInputOutputUtil.readINT(stream); - final StubSerializer serializer = getClassById(id); - - assert serializer != null : "No serializer registered for stub: ID=" + id + "; parent stub class=" + (parentStub != null? parentStub.getClass().getName() : "null"); - - StubElement stub = serializer.deserialize(stream, parentStub); - int childCount = DataInputOutputUtil.readINT(stream); - for (int i = 0; i < childCount; i++) { - deserialize(stream, stub); - } - return stub; - } - - private int getClassId(final StubSerializer serializer) { - final Integer idValue = mySerializerToId.get(serializer); - assert idValue != null: "No ID found for serializer " + serializer; - return idValue.intValue(); - } - - private StubSerializer getClassById(int id) { - return myIdToSerializer.get(id); - } - - @NotNull - public String getComponentName() { - return "PSI.SerializationManager"; - } - - public void initComponent() { - } - - public void disposeComponent() { - try { - myNameStorage.close(); - } - catch (IOException e) { - LOG.error(e); - } - } -} - +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.psi.stubs; + +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.IStubFileElementType; +import com.intellij.util.io.DataInputOutputUtil; +import com.intellij.util.io.PersistentStringEnumerator; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SerializationManagerImpl extends SerializationManager implements ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.SerializationManagerImpl"); + + private PersistentStringEnumerator myNameStorage; + + private final Map> myIdToSerializer = new HashMap>(); + private final Map, Integer> mySerializerToId = new HashMap, Integer>(); + private final List> myAllSerializers = new ArrayList>(); + private final AtomicBoolean myNameStorageCrashed = new AtomicBoolean(false); + private final File myFile = new File(PathManager.getIndexRoot(), "rep.names"); + private boolean mySerializersLoaded = false; + + public SerializationManagerImpl() { + myFile.getParentFile().mkdirs(); + try { + myNameStorage = new PersistentStringEnumerator(myFile); + } + catch (IOException e) { + myNameStorageCrashed.set(true); + LOG.info(e); + repairNameStorage(); // need this in order for myNameStorage not to be null + myNameStorageCrashed.set(true); + } + registerSerializer(PsiFileStubImpl.TYPE); + } + + public boolean isNameStorageCorrupted() { + return myNameStorageCrashed.get(); + } + + public void repairNameStorage() { + if (myNameStorageCrashed.getAndSet(false)) { + try { + if (myNameStorage != null) { + myNameStorage.close(); + } + + final File[] files = myFile.getParentFile().listFiles(); + if (files != null) { + for (File file : files) { + if (file.getName().startsWith(myFile.getName())) { + FileUtil.delete(file); + } + } + } + myNameStorage = new PersistentStringEnumerator(myFile); + + mySerializerToId.clear(); + myIdToSerializer.clear(); + for (StubSerializer serializer : myAllSerializers) { + assignId(serializer); + } + } + catch (IOException e) { + LOG.info(e); + myNameStorageCrashed.set(true); + } + } + } + + public void registerSerializer(@NotNull StubSerializer serializer) { + myAllSerializers.add(serializer); + try { + assignId(serializer); + } + catch (IOException e) { + LOG.info(e); + myNameStorageCrashed.set(true); + } + } + + private void assignId(@NotNull final StubSerializer serializer) throws IOException { + final int id = persistentId(serializer); + final StubSerializer old = myIdToSerializer.put(id, serializer); + assert old == null : "ID: " + serializer.getExternalId() + " is not unique; Already registered serializer with this ID: " + old.getClass().getName(); + + final Integer oldId = mySerializerToId.put(serializer, id); + assert oldId == null : "Serializer " + serializer + " is already registered; Old ID:" + oldId; + } + + private int persistentId(@NotNull final StubSerializer serializer) throws IOException { + if (myNameStorage == null) { + throw new IOException("SerializationManager's name storage failed to initialize"); + } + return myNameStorage.enumerate(serializer.getExternalId()); + } + + private synchronized void initSerializers() { + if (mySerializersLoaded) return; + mySerializersLoaded = true; + for(StubElementTypeHolderEP holderEP: Extensions.getExtensions(StubElementTypeHolderEP.EP_NAME)) { + holderEP.initialize(); + } + final IElementType[] stubElementTypes = IElementType.enumerate(new IElementType.Predicate() { + public boolean matches(final IElementType type) { + return type instanceof StubSerializer; + } + }); + for(IElementType type: stubElementTypes) { + if (type instanceof IStubFileElementType && ((IStubFileElementType) type).getExternalId().equals(PsiFileStubImpl.TYPE.getExternalId())) { + continue; + } + StubSerializer stubSerializer = (StubSerializer) type; + + if (!myAllSerializers.contains(stubSerializer)) { + registerSerializer(stubSerializer); + } + } + } + + public void serialize(StubElement rootStub, OutputStream stream) { + StubOutputStream stubOutputStream = new StubOutputStream(stream, myNameStorage); + doSerialize(rootStub, stubOutputStream); + } + + private void doSerialize(final StubElement rootStub, final StubOutputStream stream) { + initSerializers(); + try { + final StubSerializer serializer = getSerializer(rootStub); + + DataInputOutputUtil.writeINT(stream, getClassId(serializer)); + serializer.serialize(rootStub, stream); + + final List children = rootStub.getChildrenStubs(); + DataInputOutputUtil.writeINT(stream, children.size()); + for (StubElement child : children) { + doSerialize(child, stream); + } + } + catch (IOException e) { + LOG.info(e); + myNameStorageCrashed.set(true); + } + } + + public StubSerializer getSerializer(final StubElement rootStub) { + if (rootStub instanceof PsiFileStub) { + final PsiFileStub fileStub = (PsiFileStub)rootStub; + return fileStub.getType(); + } + + return rootStub.getStubType(); + } + + public StubElement deserialize(InputStream stream) { + StubInputStream inputStream = new StubInputStream(stream, myNameStorage); + initSerializers(); + try { + return deserialize(inputStream, null); + } + catch (IOException e) { + myNameStorageCrashed.set(true); + LOG.info(e); + throw new RuntimeException(e); + } + } + + private StubElement deserialize(StubInputStream stream, StubElement parentStub) throws IOException { + final int id = DataInputOutputUtil.readINT(stream); + final StubSerializer serializer = getClassById(id); + + assert serializer != null : "No serializer registered for stub: ID=" + id + "; parent stub class=" + (parentStub != null? parentStub.getClass().getName() : "null"); + + StubElement stub = serializer.deserialize(stream, parentStub); + int childCount = DataInputOutputUtil.readINT(stream); + for (int i = 0; i < childCount; i++) { + deserialize(stream, stub); + } + return stub; + } + + private int getClassId(final StubSerializer serializer) { + final Integer idValue = mySerializerToId.get(serializer); + assert idValue != null: "No ID found for serializer " + serializer; + return idValue.intValue(); + } + + private StubSerializer getClassById(int id) { + return myIdToSerializer.get(id); + } + + @NotNull + public String getComponentName() { + return "PSI.SerializationManager"; + } + + public void initComponent() { + } + + public void disposeComponent() { + try { + myNameStorage.close(); + } + catch (IOException e) { + LOG.error(e); + } + } +} + diff --git a/platform/lang-api/src/com/intellij/psi/util/ProximityLocation.java b/platform/lang-api/src/com/intellij/psi/util/ProximityLocation.java index f7ed483edd37..f1554c54e86c 100644 --- a/platform/lang-api/src/com/intellij/psi/util/ProximityLocation.java +++ b/platform/lang-api/src/com/intellij/psi/util/ProximityLocation.java @@ -1,69 +1,69 @@ -/* - * 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.util; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.UserDataHolder; -import com.intellij.psi.PsiElement; -import com.intellij.util.ProcessingContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author peter - */ -public class ProximityLocation implements UserDataHolder { - private final PsiElement myPosition; - private final Module myPositionModule; - private final ProcessingContext myContext; - - public ProximityLocation(@Nullable final PsiElement position, final Module positionModule) { - this(position, positionModule, new ProcessingContext()); - } - - public ProximityLocation(PsiElement position, Module positionModule, ProcessingContext context) { - myPosition = position; - myPositionModule = positionModule; - myContext = context; - } - - @Nullable - public Module getPositionModule() { - return myPositionModule; - } - - @Nullable - public PsiElement getPosition() { - return myPosition; - } - - @Nullable - public Project getProject() { - return myPosition != null ? myPosition.getProject() : null; - } - - @Override - public T getUserData(@NotNull Key key) { - return myContext.get(key); - } - - @Override - public void putUserData(@NotNull Key key, @Nullable T value) { - myContext.put(key, value); - } -} +/* + * 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.util; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolder; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author peter + */ +public class ProximityLocation implements UserDataHolder { + private final PsiElement myPosition; + private final Module myPositionModule; + private final ProcessingContext myContext; + + public ProximityLocation(@Nullable final PsiElement position, final Module positionModule) { + this(position, positionModule, new ProcessingContext()); + } + + public ProximityLocation(PsiElement position, Module positionModule, ProcessingContext context) { + myPosition = position; + myPositionModule = positionModule; + myContext = context; + } + + @Nullable + public Module getPositionModule() { + return myPositionModule; + } + + @Nullable + public PsiElement getPosition() { + return myPosition; + } + + @Nullable + public Project getProject() { + return myPosition != null ? myPosition.getProject() : null; + } + + @Override + public T getUserData(@NotNull Key key) { + return myContext.get(key); + } + + @Override + public void putUserData(@NotNull Key key, @Nullable T value) { + myContext.put(key, value); + } +} diff --git a/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java b/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java index 00ac82278f9d..4e564a232413 100644 --- a/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java +++ b/platform/lang-api/src/com/intellij/psi/util/PsiTreeUtil.java @@ -1,851 +1,851 @@ -/* - * 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.util; - -import com.google.common.collect.Lists; -import com.intellij.lang.Language; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.scope.PsiScopeProcessor; -import com.intellij.psi.search.PsiElementProcessor; -import com.intellij.psi.stubs.StubBase; -import com.intellij.psi.stubs.StubElement; -import com.intellij.psi.templateLanguages.OuterLanguageElement; -import com.intellij.util.ArrayUtil; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class PsiTreeUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.util.PsiTreeUtil"); - - private static final Key INDEX = Key.create("PsiTreeUtil.copyElements.INDEX"); - private static final Key MARKER = Key.create("PsiTreeUtil.copyElements.MARKER"); - - private PsiTreeUtil() { - } - - /** - * Checks whether one element in the psi tree is under another. - * - * @param ancestor parent candidate. false will be returned if ancestor is null. - * @param element child candidate - * @param strict whether return true if ancestor and parent are the same. - * @return true if element has ancestor as its parent somewhere in the hierarchy and false otherwise. - */ - public static boolean isAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { - if (ancestor == null) return false; - // fast path to avoid loading tree - if (ancestor instanceof StubBasedPsiElement && ((StubBasedPsiElement)ancestor).getStub() != null || - element instanceof StubBasedPsiElement && ((StubBasedPsiElement)element).getStub() != null) { - if (ancestor.getContainingFile() != element.getContainingFile()) return false; - } - - boolean stopAtFileLevel = !(ancestor instanceof PsiFile || ancestor instanceof PsiDirectory); - - PsiElement parent = strict ? element.getParent() : element; - while (true) { - if (parent == null) return false; - if (parent.equals(ancestor)) return true; - if (stopAtFileLevel && parent instanceof PsiFile) return false; - parent = parent.getParent(); - } - } - - /** - * Checks whether one element in the psi tree is under another in {@link com.intellij.psi.PsiElement#getContext()} hierarchy. - * - * @param ancestor parent candidate. false will be returned if ancestor is null. - * @param element child candidate - * @param strict whether return true if ancestor and parent are the same. - * @return true if element has ancestor as its parent somewhere in the hierarchy and false otherwise. - */ - public static boolean isContextAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { - if (ancestor == null) return false; - boolean stopAtFileLevel = !(ancestor instanceof PsiFile || ancestor instanceof PsiDirectory); - PsiElement parent = strict ? element.getContext() : element; - while (true) { - if (parent == null) return false; - if (parent.equals(ancestor)) return true; - if (stopAtFileLevel && parent instanceof PsiFile) { - final PsiElement context = parent.getContext(); - if (context == null) return false; - } - parent = parent.getContext(); - } - } - - @Nullable - public static PsiElement findCommonParent(@NotNull List elements) { - if (elements.isEmpty()) return null; - PsiElement toReturn = null; - for (PsiElement element : elements) { - if (element == null) continue; - toReturn = toReturn == null ? element : findCommonParent(toReturn, element); - if (toReturn == null) return null; - } - - return toReturn; - } - - @Nullable - public static PsiElement findCommonParent(@NotNull PsiElement... elements) { - if (elements.length == 0) return null; - PsiElement toReturn = null; - for (PsiElement element : elements) { - if (element == null) continue; - toReturn = toReturn == null ? element : findCommonParent(toReturn, element); - if (toReturn == null) return null; - } - - return toReturn; - } - - @Nullable - public static PsiElement findCommonParent(@NotNull PsiElement element1, @NotNull PsiElement element2) { - // optimization - if (element1 == element2) return element1; - final PsiFile containingFile = element1.getContainingFile(); - final PsiElement topLevel = containingFile == element2.getContainingFile() ? containingFile : null; - - ArrayList parents1 = getParents(element1, topLevel); - ArrayList parents2 = getParents(element2, topLevel); - int size = Math.min(parents1.size(), parents2.size()); - PsiElement parent = topLevel; - for (int i = 1; i <= size; i++) { - PsiElement parent1 = parents1.get(parents1.size() - i); - PsiElement parent2 = parents2.get(parents2.size() - i); - if (!parent1.equals(parent2)) break; - parent = parent1; - } - return parent; - } - - @NotNull - private static ArrayList getParents(@NotNull PsiElement element, @Nullable PsiElement topLevel) { - ArrayList parents = new ArrayList(); - PsiElement parent = element; - while (parent != topLevel && parent != null) { - parents.add(parent); - parent = parent.getParent(); - } - return parents; - } - - @Nullable - public static PsiElement findCommonContext(@NotNull PsiElement... elements) { - return findCommonContext(Arrays.asList(elements)); - } - - @Nullable - public static PsiElement findCommonContext(@NotNull Collection elements) { - if (elements.isEmpty()) return null; - PsiElement toReturn = null; - for (PsiElement element : elements) { - if (element == null) continue; - toReturn = toReturn == null ? element : findCommonContext(toReturn, element); - if (toReturn == null) return null; - } - return toReturn; - } - - @Nullable - public static PsiElement findCommonContext(@NotNull PsiElement element1, @NotNull PsiElement element2) { - // optimization - if (element1 == element2) return element1; - final PsiFile containingFile = element1.getContainingFile(); - final PsiElement topLevel = containingFile == element2.getContainingFile() ? containingFile : null; - - ArrayList parents1 = getContexts(element1, topLevel); - ArrayList parents2 = getContexts(element2, topLevel); - int size = Math.min(parents1.size(), parents2.size()); - PsiElement parent = topLevel; - for (int i = 1; i <= size; i++) { - PsiElement parent1 = parents1.get(parents1.size() - i); - PsiElement parent2 = parents2.get(parents2.size() - i); - if (!parent1.equals(parent2)) break; - parent = parent1; - } - return parent; - } - - @NotNull - private static ArrayList getContexts(@NotNull PsiElement element, @Nullable PsiElement topLevel) { - ArrayList parents = new ArrayList(); - PsiElement parent = element; - while (parent != topLevel && parent != null) { - parents.add(parent); - parent = parent.getContext(); - } - return parents; - } - - @Nullable - public static T findChildOfType(@Nullable final PsiElement element, @NotNull final Class aClass) { - return findChildOfType(element, aClass, true); - } - - @Nullable - public static T findChildOfType(@Nullable final PsiElement element, - @NotNull final Class aClass, - final boolean strict) { - return findChildOfAnyType(element, strict, aClass); - } - - @Nullable - public static T findChildOfAnyType(@Nullable final PsiElement element, @NotNull final Class... classes) { - return findChildOfAnyType(element, true, classes); - } - - @Nullable - public static T findChildOfAnyType(@Nullable final PsiElement element, - final boolean strict, - @NotNull final Class... classes) { - PsiElementProcessor.FindElement processor = new PsiElementProcessor.FindElement() { - @Override - public boolean execute(PsiElement each) { - if (strict && each == element) return true; - if (instanceOf(each, classes)) { - return setFound(each); - } - return true; - } - }; - - processElements(element, processor); - //noinspection unchecked - return (T)processor.getFoundElement(); - } - - @Nullable - public static T getChildOfType(@Nullable PsiElement element, @NotNull Class aClass) { - if (element == null) return null; - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - if (aClass.isInstance(child)) { - //noinspection unchecked - return (T)child; - } - } - return null; - } - - @NotNull - public static T getRequiredChildOfType(@NotNull PsiElement element, @NotNull Class aClass) { - final T child = getChildOfType(element, aClass); - assert child != null : "Missing required child of type " + aClass.getName(); - return child; - } - - @Nullable - public static T[] getChildrenOfType(@Nullable PsiElement element, @NotNull Class aClass) { - if (element == null) return null; - - List result = null; - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - if (aClass.isInstance(child)) { - if (result == null) result = new SmartList(); - //noinspection unchecked - result.add((T)child); - } - } - return result == null ? null : ArrayUtil.toObjectArray(result, aClass); - } - - @NotNull - public static List getChildrenOfTypeAsList(@Nullable PsiElement element, @NotNull Class aClass) { - if (element == null) return Collections.emptyList(); - - List result = new SmartList(); - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - if (aClass.isInstance(child)) { - //noinspection unchecked - result.add((T)child); - } - } - return result; - } - - private static boolean instanceOf(final PsiElement child, final Class... classes) { - for (final Class each : classes) { - if (each.isInstance(child)) return true; - } - return false; - } - - /** - * Returns a direct child of the specified element which has any of the specified classes. - * - * @param element the element to get the child for. - * @param classes the array of classes. - * @return the element, or null if none was found. - * @since 5.1 - */ - @Nullable - public static T getChildOfAnyType(@Nullable PsiElement element, @NotNull Class... classes) { - if (element == null) return null; - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - for (Class aClass : classes) { - if (aClass.isInstance(child)) { - //noinspection unchecked - return (T)child; - } - } - } - return null; - } - - @Nullable - public static T getNextSiblingOfType(@Nullable PsiElement sibling, @NotNull Class aClass) { - if (sibling == null) return null; - for (PsiElement child = sibling.getNextSibling(); child != null; child = child.getNextSibling()) { - if (aClass.isInstance(child)) { - //noinspection unchecked - return (T)child; - } - } - return null; - } - - @Nullable - public static T getPrevSiblingOfType(@Nullable PsiElement sibling, @NotNull Class aClass) { - if (sibling == null) return null; - for (PsiElement child = sibling.getPrevSibling(); child != null; child = child.getPrevSibling()) { - if (aClass.isInstance(child)) { - //noinspection unchecked - return (T)child; - } - } - return null; - } - - @Nullable - public static T getTopmostParentOfType(@Nullable PsiElement element, @NotNull Class aClass) { - T answer = getParentOfType(element, aClass); - - do { - T next = getParentOfType(answer, aClass); - if (next == null) break; - answer = next; - } - while (true); - - return answer; - } - - @Nullable - public static T getParentOfType(@Nullable PsiElement element, @NotNull Class aClass) { - return getParentOfType(element, aClass, true); - } - - @Nullable - public static PsiElement getStubOrPsiParent(@Nullable PsiElement element) { - if (element instanceof StubBasedPsiElement) { - StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub(); - if (stub != null) { - //noinspection unchecked - final StubElement parentStub = stub.getParentStub(); - return parentStub != null ? parentStub.getPsi() : null; - } - } - return element != null ? element.getParent() : null; - } - - @Nullable - public static E getStubOrPsiParentOfType(@Nullable PsiElement element, @NotNull Class parentClass) { - if (element instanceof StubBasedPsiElement) { - StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub(); - if (stub != null) { - //noinspection unchecked - return (E)stub.getParentStubOfType(parentClass); - } - } - return getParentOfType(element, parentClass); - } - - @Nullable - public static T getContextOfType(@Nullable PsiElement element, - @NotNull Class aClass, - boolean strict, - Class... stopAt) { - if (element == null) return null; - if (strict) { - element = element.getContext(); - } - - while (element != null && !aClass.isInstance(element)) { - if (instanceOf(element, stopAt)) return null; - element = element.getContext(); - } - - //noinspection unchecked - return (T)element; - } - - @Nullable - public static T getContextOfType(@Nullable PsiElement element, - @NotNull Class aClass, - boolean strict) { - return getContextOfType(element, strict, aClass); - } - - @Nullable - public static T getContextOfType(@Nullable PsiElement element, @NotNull Class... classes) { - return getContextOfType(element, true, classes); - } - - @Nullable - public static T getContextOfType(@Nullable PsiElement element, - boolean strict, - @NotNull Class... classes) { - if (element == null) return null; - if (strict) { - element = element.getContext(); - } - - while (element != null && !instanceOf(element, classes)) { - element = element.getContext(); - } - - //noinspection unchecked - return (T)element; - } - - @Nullable - public static T getParentOfType(@Nullable PsiElement element, @NotNull Class aClass, boolean strict) { - if (element == null) return null; - if (strict) { - element = element.getParent(); - } - - while (element != null) { - if (aClass.isInstance(element)) { - //noinspection unchecked - return (T)element; - } - if (element instanceof PsiFile) return null; - element = element.getParent(); - } - - return null; - } - - @Nullable - public static T getParentOfType(@Nullable PsiElement element, - @NotNull Class aClass, - boolean strict, - @NotNull Class... stopAt) { - if (element == null) return null; - if (strict) { - element = element.getParent(); - } - - while (element != null && !aClass.isInstance(element)) { - if (instanceOf(element, stopAt)) return null; - if (element instanceof PsiFile) return null; - element = element.getParent(); - } - - //noinspection unchecked - return (T)element; - } - - @Nullable - public static PsiElement skipSiblingsForward(@Nullable PsiElement element, @NotNull Class... elementClasses) { - if (element == null) return null; - NextSibling: - for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) { - if (instanceOf(e, elementClasses)) continue NextSibling; - return e; - } - return null; - } - - @Nullable - public static PsiElement skipSiblingsBackward(@Nullable PsiElement element, @NotNull Class... elementClasses) { - if (element == null) return null; - NextSibling: - for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) { - if (instanceOf(e, elementClasses)) continue NextSibling; - return e; - } - return null; - } - - @Nullable - public static PsiElement skipParentsOfType(@Nullable PsiElement element, @NotNull Class... parentClasses) { - if (element == null) return null; - NextSibling: - for (PsiElement e = element.getParent(); e != null; e = e.getParent()) { - if (instanceOf(e, parentClasses)) continue NextSibling; - return e; - } - return null; - } - - @Nullable - public static T getParentOfType(PsiElement element, @NotNull Class... classes) { - if (element == null) return null; - PsiElement parent = element.getParent(); - if (parent == null) return null; - return getNonStrictParentOfType(parent, classes); - } - - @Nullable - public static T getNonStrictParentOfType(@NotNull PsiElement element, @NotNull Class... classes) { - PsiElement run = element; - while (run != null) { - if (instanceOf(run, classes)) { - //noinspection unchecked - return (T)run; - } - if (run instanceof PsiFile) break; - run = run.getParent(); - } - - return null; - } - - @NotNull - public static PsiElement[] collectElements(@Nullable PsiElement element, @NotNull PsiElementFilter filter) { - PsiElementProcessor.CollectFilteredElements processor = new PsiElementProcessor.CollectFilteredElements(filter); - processElements(element, processor); - return processor.toArray(); - } - - @NotNull - public static Collection collectElementsOfType(@Nullable PsiElement element, - final @NotNull Class... classes) { - PsiElementProcessor.CollectFilteredElements processor = new PsiElementProcessor.CollectFilteredElements(new PsiElementFilter() { - - @Override - public boolean isAccepted(PsiElement element) { - for (Class clazz : classes) { - if (clazz.isInstance(element)) { - return true; - } - } - - return false; - } - }); - processElements(element, processor); - return processor.getCollection(); - } - - public static boolean processElements(@Nullable PsiElement element, @NotNull PsiElementProcessor processor) { - if (element == null) return true; - //noinspection unchecked - if (!processor.execute(element)) return false; - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - if (!processElements(child, processor)) return false; - } - - return true; - } - - @NotNull - public static PsiElement[] copyElements(@NotNull PsiElement[] elements) { - ArrayList roots = new ArrayList(); - for (int i = 0; i < elements.length; i++) { - PsiElement rootCandidate = elements[i]; - boolean failed = false; - for (int j = 0; j < elements.length; j++) { - PsiElement element = elements[j]; - if (i != j && isAncestor(element, rootCandidate, true)) { - failed = true; - break; - } - } - if (!failed) { - roots.add(rootCandidate); - } - } - for (int i = 0; i < elements.length; i++) { - PsiElement element = elements[i]; - element.putCopyableUserData(INDEX, Integer.valueOf(i)); - } - PsiElement[] newRoots = new PsiElement[roots.size()]; - for (int i = 0; i < roots.size(); i++) { - PsiElement root = roots.get(i); - newRoots[i] = root.copy(); - } - - final PsiElement[] result = new PsiElement[elements.length]; - for (PsiElement newRoot : newRoots) { - decodeIndices(newRoot, result); - } - return result; - } - - private static void decodeIndices(@NotNull PsiElement element, @NotNull PsiElement[] result) { - final Integer data = element.getCopyableUserData(INDEX); - if (data != null) { - element.putCopyableUserData(INDEX, null); - int index = data.intValue(); - result[index] = element; - } - PsiElement child = element.getFirstChild(); - while (child != null) { - decodeIndices(child, result); - child = child.getNextSibling(); - } - } - - public static void mark(@NotNull PsiElement element, @NotNull Object marker) { - element.putCopyableUserData(MARKER, marker); - } - - @Nullable - public static PsiElement releaseMark(@NotNull PsiElement root, @NotNull Object marker) { - if (marker.equals(root.getCopyableUserData(MARKER))) { - root.putCopyableUserData(MARKER, null); - return root; - } - else { - PsiElement child = root.getFirstChild(); - while (child != null) { - final PsiElement result = releaseMark(child, marker); - if (result != null) return result; - child = child.getNextSibling(); - } - return null; - } - } - - @Nullable - public static T findElementOfClassAtOffset(@NotNull PsiFile file, - int offset, - @NotNull Class clazz, - boolean strictStart) { - final PsiElement[] psiRoots = file.getPsiRoots(); - T result = null; - for (PsiElement root : psiRoots) { - final PsiElement elementAt = root.findElementAt(offset); - if (elementAt != null) { - final T parent = getParentOfType(elementAt, clazz, strictStart); - if (parent != null) { - final TextRange range = parent.getTextRange(); - if (!strictStart || range.getStartOffset() == offset) { - if (result == null || result.getTextRange().getEndOffset() > range.getEndOffset()) { - result = parent; - } - } - } - } - } - - return result; - } - - /** - * @return maximal element of specified Class starting at startOffset exactly and ending not farther than endOffset - */ - @Nullable - public static T findElementOfClassAtRange(@NotNull PsiFile file, - int startOffset, - int endOffset, - @NotNull Class clazz) { - final FileViewProvider viewProvider = file.getViewProvider(); - T result = null; - for (Language lang : viewProvider.getLanguages()) { - PsiElement elementAt = viewProvider.findElementAt(startOffset, lang); - T run = getParentOfType(elementAt, clazz, false); - T prev = run; - while (run != null && run.getTextRange().getStartOffset() == startOffset && - run.getTextRange().getEndOffset() <= endOffset) { - prev = run; - run = getParentOfType(run, clazz); - } - - if (prev == null) continue; - final int elementStartOffset = prev.getTextRange().getStartOffset(); - final int elementEndOffset = prev.getTextRange().getEndOffset(); - if (elementStartOffset != startOffset || elementEndOffset > endOffset) continue; - - if (result == null || result.getTextRange().getEndOffset() < elementEndOffset) { - result = prev; - } - } - - return result; - } - - @NotNull - public static PsiElement getDeepestFirst(@NotNull PsiElement elt) { - @NotNull PsiElement res = elt; - do { - final PsiElement firstChild = res.getFirstChild(); - if (firstChild == null) return res; - res = firstChild; - } - while (true); - } - - @NotNull - public static PsiElement getDeepestLast(@NotNull PsiElement elt) { - @NotNull PsiElement res = elt; - do { - final PsiElement lastChild = res.getLastChild(); - if (lastChild == null) return res; - res = lastChild; - } - while (true); - } - - @Nullable - public static PsiElement prevLeaf(@NotNull PsiElement current) { - final PsiElement prevSibling = current.getPrevSibling(); - if (prevSibling != null) return lastChild(prevSibling); - final PsiElement parent = current.getParent(); - if (parent == null || parent instanceof PsiFile) return null; - return prevLeaf(parent); - } - - @Nullable - public static PsiElement nextLeaf(@NotNull PsiElement current) { - final PsiElement nextSibling = current.getNextSibling(); - if (nextSibling != null) return firstChild(nextSibling); - final PsiElement parent = current.getParent(); - if (parent == null || parent instanceof PsiFile) return null; - return nextLeaf(parent); - } - - public static PsiElement lastChild(@NotNull PsiElement element) { - PsiElement lastChild = element.getLastChild(); - if (lastChild != null) return lastChild(lastChild); - return element; - } - - public static PsiElement firstChild(@NotNull final PsiElement element) { - PsiElement child = element.getFirstChild(); - if (child != null) return firstChild(child); - return element; - } - - @Nullable - public static PsiElement prevLeaf(@NotNull final PsiErrorElement element, final boolean skipEmptyElements) { - PsiElement prevLeaf = prevLeaf(element); - while (skipEmptyElements && prevLeaf != null && prevLeaf.getTextLength() == 0) prevLeaf = prevLeaf(prevLeaf); - return prevLeaf; - } - - @Nullable - public static PsiElement nextLeaf(final PsiErrorElement element, final boolean skipEmptyElements) { - PsiElement nextLeaf = nextLeaf(element); - while (skipEmptyElements && nextLeaf != null && nextLeaf.getTextLength() == 0) nextLeaf = nextLeaf(nextLeaf); - return nextLeaf; - } - - public static boolean hasErrorElements(@NotNull final PsiElement element) { - if (element instanceof PsiErrorElement) return true; - - for (PsiElement child : element.getChildren()) { - if (hasErrorElements(child)) return true; - } - - return false; - } - - @NotNull - public static PsiElement[] filterAncestors(@NotNull PsiElement[] elements) { - if (LOG.isDebugEnabled()) { - for (PsiElement element : elements) { - LOG.debug("element = " + element); - } - } - - ArrayList filteredElements = new ArrayList(); - ContainerUtil.addAll(filteredElements, elements); - - int previousSize; - do { - previousSize = filteredElements.size(); - outer: - for (PsiElement element : filteredElements) { - for (PsiElement element2 : filteredElements) { - if (element == element2) continue; - if (isAncestor(element, element2, false)) { - if (LOG.isDebugEnabled()) { - LOG.debug("removing " + element2); - } - filteredElements.remove(element2); - break outer; - } - } - } - } - while (filteredElements.size() != previousSize); - - if (LOG.isDebugEnabled()) { - for (PsiElement element : filteredElements) { - LOG.debug("filtered element = " + element); - } - } - - return filteredElements.toArray(new PsiElement[filteredElements.size()]); - } - - public static boolean treeWalkUp(@NotNull final PsiScopeProcessor processor, - @NotNull final PsiElement entrance, - @Nullable final PsiElement maxScope, - @NotNull final ResolveState state) { - PsiElement prevParent = entrance; - PsiElement scope = entrance; - - while (scope != null) { - if (!scope.processDeclarations(processor, state, prevParent, entrance)) return false; - - if (scope == maxScope) break; - prevParent = scope; - scope = prevParent.getContext(); - } - - return true; - } - - @NotNull - public static PsiElement findPrevParent(@NotNull PsiElement ancestor, @NotNull PsiElement descendant) { - PsiElement cur = descendant; - while (cur != null) { - final PsiElement parent = cur.getParent(); - if (parent == ancestor) { - return cur; - } - cur = parent; - } - throw new AssertionError(descendant + " is not a descendant of " + ancestor); - } - - public static List getInjectedElements(OuterLanguageElement outerLanguageElement) { - PsiElement psi = outerLanguageElement.getContainingFile().getViewProvider().getPsi(outerLanguageElement.getLanguage()); - TextRange injectionRange = outerLanguageElement.getTextRange(); - List res = Lists.newArrayList(); - - assert psi != null : outerLanguageElement; - for (PsiElement element = psi.findElementAt(injectionRange.getStartOffset()); - element != null && injectionRange.intersectsStrict(element.getTextRange()); - element = element.getNextSibling()) { - res.add(element); - } - - return res; - } -} +/* + * 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.util; + +import com.google.common.collect.Lists; +import com.intellij.lang.Language; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.search.PsiElementProcessor; +import com.intellij.psi.stubs.StubBase; +import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.templateLanguages.OuterLanguageElement; +import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class PsiTreeUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.util.PsiTreeUtil"); + + private static final Key INDEX = Key.create("PsiTreeUtil.copyElements.INDEX"); + private static final Key MARKER = Key.create("PsiTreeUtil.copyElements.MARKER"); + + private PsiTreeUtil() { + } + + /** + * Checks whether one element in the psi tree is under another. + * + * @param ancestor parent candidate. false will be returned if ancestor is null. + * @param element child candidate + * @param strict whether return true if ancestor and parent are the same. + * @return true if element has ancestor as its parent somewhere in the hierarchy and false otherwise. + */ + public static boolean isAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { + if (ancestor == null) return false; + // fast path to avoid loading tree + if (ancestor instanceof StubBasedPsiElement && ((StubBasedPsiElement)ancestor).getStub() != null || + element instanceof StubBasedPsiElement && ((StubBasedPsiElement)element).getStub() != null) { + if (ancestor.getContainingFile() != element.getContainingFile()) return false; + } + + boolean stopAtFileLevel = !(ancestor instanceof PsiFile || ancestor instanceof PsiDirectory); + + PsiElement parent = strict ? element.getParent() : element; + while (true) { + if (parent == null) return false; + if (parent.equals(ancestor)) return true; + if (stopAtFileLevel && parent instanceof PsiFile) return false; + parent = parent.getParent(); + } + } + + /** + * Checks whether one element in the psi tree is under another in {@link com.intellij.psi.PsiElement#getContext()} hierarchy. + * + * @param ancestor parent candidate. false will be returned if ancestor is null. + * @param element child candidate + * @param strict whether return true if ancestor and parent are the same. + * @return true if element has ancestor as its parent somewhere in the hierarchy and false otherwise. + */ + public static boolean isContextAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { + if (ancestor == null) return false; + boolean stopAtFileLevel = !(ancestor instanceof PsiFile || ancestor instanceof PsiDirectory); + PsiElement parent = strict ? element.getContext() : element; + while (true) { + if (parent == null) return false; + if (parent.equals(ancestor)) return true; + if (stopAtFileLevel && parent instanceof PsiFile) { + final PsiElement context = parent.getContext(); + if (context == null) return false; + } + parent = parent.getContext(); + } + } + + @Nullable + public static PsiElement findCommonParent(@NotNull List elements) { + if (elements.isEmpty()) return null; + PsiElement toReturn = null; + for (PsiElement element : elements) { + if (element == null) continue; + toReturn = toReturn == null ? element : findCommonParent(toReturn, element); + if (toReturn == null) return null; + } + + return toReturn; + } + + @Nullable + public static PsiElement findCommonParent(@NotNull PsiElement... elements) { + if (elements.length == 0) return null; + PsiElement toReturn = null; + for (PsiElement element : elements) { + if (element == null) continue; + toReturn = toReturn == null ? element : findCommonParent(toReturn, element); + if (toReturn == null) return null; + } + + return toReturn; + } + + @Nullable + public static PsiElement findCommonParent(@NotNull PsiElement element1, @NotNull PsiElement element2) { + // optimization + if (element1 == element2) return element1; + final PsiFile containingFile = element1.getContainingFile(); + final PsiElement topLevel = containingFile == element2.getContainingFile() ? containingFile : null; + + ArrayList parents1 = getParents(element1, topLevel); + ArrayList parents2 = getParents(element2, topLevel); + int size = Math.min(parents1.size(), parents2.size()); + PsiElement parent = topLevel; + for (int i = 1; i <= size; i++) { + PsiElement parent1 = parents1.get(parents1.size() - i); + PsiElement parent2 = parents2.get(parents2.size() - i); + if (!parent1.equals(parent2)) break; + parent = parent1; + } + return parent; + } + + @NotNull + private static ArrayList getParents(@NotNull PsiElement element, @Nullable PsiElement topLevel) { + ArrayList parents = new ArrayList(); + PsiElement parent = element; + while (parent != topLevel && parent != null) { + parents.add(parent); + parent = parent.getParent(); + } + return parents; + } + + @Nullable + public static PsiElement findCommonContext(@NotNull PsiElement... elements) { + return findCommonContext(Arrays.asList(elements)); + } + + @Nullable + public static PsiElement findCommonContext(@NotNull Collection elements) { + if (elements.isEmpty()) return null; + PsiElement toReturn = null; + for (PsiElement element : elements) { + if (element == null) continue; + toReturn = toReturn == null ? element : findCommonContext(toReturn, element); + if (toReturn == null) return null; + } + return toReturn; + } + + @Nullable + public static PsiElement findCommonContext(@NotNull PsiElement element1, @NotNull PsiElement element2) { + // optimization + if (element1 == element2) return element1; + final PsiFile containingFile = element1.getContainingFile(); + final PsiElement topLevel = containingFile == element2.getContainingFile() ? containingFile : null; + + ArrayList parents1 = getContexts(element1, topLevel); + ArrayList parents2 = getContexts(element2, topLevel); + int size = Math.min(parents1.size(), parents2.size()); + PsiElement parent = topLevel; + for (int i = 1; i <= size; i++) { + PsiElement parent1 = parents1.get(parents1.size() - i); + PsiElement parent2 = parents2.get(parents2.size() - i); + if (!parent1.equals(parent2)) break; + parent = parent1; + } + return parent; + } + + @NotNull + private static ArrayList getContexts(@NotNull PsiElement element, @Nullable PsiElement topLevel) { + ArrayList parents = new ArrayList(); + PsiElement parent = element; + while (parent != topLevel && parent != null) { + parents.add(parent); + parent = parent.getContext(); + } + return parents; + } + + @Nullable + public static T findChildOfType(@Nullable final PsiElement element, @NotNull final Class aClass) { + return findChildOfType(element, aClass, true); + } + + @Nullable + public static T findChildOfType(@Nullable final PsiElement element, + @NotNull final Class aClass, + final boolean strict) { + return findChildOfAnyType(element, strict, aClass); + } + + @Nullable + public static T findChildOfAnyType(@Nullable final PsiElement element, @NotNull final Class... classes) { + return findChildOfAnyType(element, true, classes); + } + + @Nullable + public static T findChildOfAnyType(@Nullable final PsiElement element, + final boolean strict, + @NotNull final Class... classes) { + PsiElementProcessor.FindElement processor = new PsiElementProcessor.FindElement() { + @Override + public boolean execute(PsiElement each) { + if (strict && each == element) return true; + if (instanceOf(each, classes)) { + return setFound(each); + } + return true; + } + }; + + processElements(element, processor); + //noinspection unchecked + return (T)processor.getFoundElement(); + } + + @Nullable + public static T getChildOfType(@Nullable PsiElement element, @NotNull Class aClass) { + if (element == null) return null; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (aClass.isInstance(child)) { + //noinspection unchecked + return (T)child; + } + } + return null; + } + + @NotNull + public static T getRequiredChildOfType(@NotNull PsiElement element, @NotNull Class aClass) { + final T child = getChildOfType(element, aClass); + assert child != null : "Missing required child of type " + aClass.getName(); + return child; + } + + @Nullable + public static T[] getChildrenOfType(@Nullable PsiElement element, @NotNull Class aClass) { + if (element == null) return null; + + List result = null; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (aClass.isInstance(child)) { + if (result == null) result = new SmartList(); + //noinspection unchecked + result.add((T)child); + } + } + return result == null ? null : ArrayUtil.toObjectArray(result, aClass); + } + + @NotNull + public static List getChildrenOfTypeAsList(@Nullable PsiElement element, @NotNull Class aClass) { + if (element == null) return Collections.emptyList(); + + List result = new SmartList(); + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (aClass.isInstance(child)) { + //noinspection unchecked + result.add((T)child); + } + } + return result; + } + + private static boolean instanceOf(final PsiElement child, final Class... classes) { + for (final Class each : classes) { + if (each.isInstance(child)) return true; + } + return false; + } + + /** + * Returns a direct child of the specified element which has any of the specified classes. + * + * @param element the element to get the child for. + * @param classes the array of classes. + * @return the element, or null if none was found. + * @since 5.1 + */ + @Nullable + public static T getChildOfAnyType(@Nullable PsiElement element, @NotNull Class... classes) { + if (element == null) return null; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + for (Class aClass : classes) { + if (aClass.isInstance(child)) { + //noinspection unchecked + return (T)child; + } + } + } + return null; + } + + @Nullable + public static T getNextSiblingOfType(@Nullable PsiElement sibling, @NotNull Class aClass) { + if (sibling == null) return null; + for (PsiElement child = sibling.getNextSibling(); child != null; child = child.getNextSibling()) { + if (aClass.isInstance(child)) { + //noinspection unchecked + return (T)child; + } + } + return null; + } + + @Nullable + public static T getPrevSiblingOfType(@Nullable PsiElement sibling, @NotNull Class aClass) { + if (sibling == null) return null; + for (PsiElement child = sibling.getPrevSibling(); child != null; child = child.getPrevSibling()) { + if (aClass.isInstance(child)) { + //noinspection unchecked + return (T)child; + } + } + return null; + } + + @Nullable + public static T getTopmostParentOfType(@Nullable PsiElement element, @NotNull Class aClass) { + T answer = getParentOfType(element, aClass); + + do { + T next = getParentOfType(answer, aClass); + if (next == null) break; + answer = next; + } + while (true); + + return answer; + } + + @Nullable + public static T getParentOfType(@Nullable PsiElement element, @NotNull Class aClass) { + return getParentOfType(element, aClass, true); + } + + @Nullable + public static PsiElement getStubOrPsiParent(@Nullable PsiElement element) { + if (element instanceof StubBasedPsiElement) { + StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub(); + if (stub != null) { + //noinspection unchecked + final StubElement parentStub = stub.getParentStub(); + return parentStub != null ? parentStub.getPsi() : null; + } + } + return element != null ? element.getParent() : null; + } + + @Nullable + public static E getStubOrPsiParentOfType(@Nullable PsiElement element, @NotNull Class parentClass) { + if (element instanceof StubBasedPsiElement) { + StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub(); + if (stub != null) { + //noinspection unchecked + return (E)stub.getParentStubOfType(parentClass); + } + } + return getParentOfType(element, parentClass); + } + + @Nullable + public static T getContextOfType(@Nullable PsiElement element, + @NotNull Class aClass, + boolean strict, + Class... stopAt) { + if (element == null) return null; + if (strict) { + element = element.getContext(); + } + + while (element != null && !aClass.isInstance(element)) { + if (instanceOf(element, stopAt)) return null; + element = element.getContext(); + } + + //noinspection unchecked + return (T)element; + } + + @Nullable + public static T getContextOfType(@Nullable PsiElement element, + @NotNull Class aClass, + boolean strict) { + return getContextOfType(element, strict, aClass); + } + + @Nullable + public static T getContextOfType(@Nullable PsiElement element, @NotNull Class... classes) { + return getContextOfType(element, true, classes); + } + + @Nullable + public static T getContextOfType(@Nullable PsiElement element, + boolean strict, + @NotNull Class... classes) { + if (element == null) return null; + if (strict) { + element = element.getContext(); + } + + while (element != null && !instanceOf(element, classes)) { + element = element.getContext(); + } + + //noinspection unchecked + return (T)element; + } + + @Nullable + public static T getParentOfType(@Nullable PsiElement element, @NotNull Class aClass, boolean strict) { + if (element == null) return null; + if (strict) { + element = element.getParent(); + } + + while (element != null) { + if (aClass.isInstance(element)) { + //noinspection unchecked + return (T)element; + } + if (element instanceof PsiFile) return null; + element = element.getParent(); + } + + return null; + } + + @Nullable + public static T getParentOfType(@Nullable PsiElement element, + @NotNull Class aClass, + boolean strict, + @NotNull Class... stopAt) { + if (element == null) return null; + if (strict) { + element = element.getParent(); + } + + while (element != null && !aClass.isInstance(element)) { + if (instanceOf(element, stopAt)) return null; + if (element instanceof PsiFile) return null; + element = element.getParent(); + } + + //noinspection unchecked + return (T)element; + } + + @Nullable + public static PsiElement skipSiblingsForward(@Nullable PsiElement element, @NotNull Class... elementClasses) { + if (element == null) return null; + NextSibling: + for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) { + if (instanceOf(e, elementClasses)) continue NextSibling; + return e; + } + return null; + } + + @Nullable + public static PsiElement skipSiblingsBackward(@Nullable PsiElement element, @NotNull Class... elementClasses) { + if (element == null) return null; + NextSibling: + for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) { + if (instanceOf(e, elementClasses)) continue NextSibling; + return e; + } + return null; + } + + @Nullable + public static PsiElement skipParentsOfType(@Nullable PsiElement element, @NotNull Class... parentClasses) { + if (element == null) return null; + NextSibling: + for (PsiElement e = element.getParent(); e != null; e = e.getParent()) { + if (instanceOf(e, parentClasses)) continue NextSibling; + return e; + } + return null; + } + + @Nullable + public static T getParentOfType(PsiElement element, @NotNull Class... classes) { + if (element == null) return null; + PsiElement parent = element.getParent(); + if (parent == null) return null; + return getNonStrictParentOfType(parent, classes); + } + + @Nullable + public static T getNonStrictParentOfType(@NotNull PsiElement element, @NotNull Class... classes) { + PsiElement run = element; + while (run != null) { + if (instanceOf(run, classes)) { + //noinspection unchecked + return (T)run; + } + if (run instanceof PsiFile) break; + run = run.getParent(); + } + + return null; + } + + @NotNull + public static PsiElement[] collectElements(@Nullable PsiElement element, @NotNull PsiElementFilter filter) { + PsiElementProcessor.CollectFilteredElements processor = new PsiElementProcessor.CollectFilteredElements(filter); + processElements(element, processor); + return processor.toArray(); + } + + @NotNull + public static Collection collectElementsOfType(@Nullable PsiElement element, + final @NotNull Class... classes) { + PsiElementProcessor.CollectFilteredElements processor = new PsiElementProcessor.CollectFilteredElements(new PsiElementFilter() { + + @Override + public boolean isAccepted(PsiElement element) { + for (Class clazz : classes) { + if (clazz.isInstance(element)) { + return true; + } + } + + return false; + } + }); + processElements(element, processor); + return processor.getCollection(); + } + + public static boolean processElements(@Nullable PsiElement element, @NotNull PsiElementProcessor processor) { + if (element == null) return true; + //noinspection unchecked + if (!processor.execute(element)) return false; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (!processElements(child, processor)) return false; + } + + return true; + } + + @NotNull + public static PsiElement[] copyElements(@NotNull PsiElement[] elements) { + ArrayList roots = new ArrayList(); + for (int i = 0; i < elements.length; i++) { + PsiElement rootCandidate = elements[i]; + boolean failed = false; + for (int j = 0; j < elements.length; j++) { + PsiElement element = elements[j]; + if (i != j && isAncestor(element, rootCandidate, true)) { + failed = true; + break; + } + } + if (!failed) { + roots.add(rootCandidate); + } + } + for (int i = 0; i < elements.length; i++) { + PsiElement element = elements[i]; + element.putCopyableUserData(INDEX, Integer.valueOf(i)); + } + PsiElement[] newRoots = new PsiElement[roots.size()]; + for (int i = 0; i < roots.size(); i++) { + PsiElement root = roots.get(i); + newRoots[i] = root.copy(); + } + + final PsiElement[] result = new PsiElement[elements.length]; + for (PsiElement newRoot : newRoots) { + decodeIndices(newRoot, result); + } + return result; + } + + private static void decodeIndices(@NotNull PsiElement element, @NotNull PsiElement[] result) { + final Integer data = element.getCopyableUserData(INDEX); + if (data != null) { + element.putCopyableUserData(INDEX, null); + int index = data.intValue(); + result[index] = element; + } + PsiElement child = element.getFirstChild(); + while (child != null) { + decodeIndices(child, result); + child = child.getNextSibling(); + } + } + + public static void mark(@NotNull PsiElement element, @NotNull Object marker) { + element.putCopyableUserData(MARKER, marker); + } + + @Nullable + public static PsiElement releaseMark(@NotNull PsiElement root, @NotNull Object marker) { + if (marker.equals(root.getCopyableUserData(MARKER))) { + root.putCopyableUserData(MARKER, null); + return root; + } + else { + PsiElement child = root.getFirstChild(); + while (child != null) { + final PsiElement result = releaseMark(child, marker); + if (result != null) return result; + child = child.getNextSibling(); + } + return null; + } + } + + @Nullable + public static T findElementOfClassAtOffset(@NotNull PsiFile file, + int offset, + @NotNull Class clazz, + boolean strictStart) { + final PsiElement[] psiRoots = file.getPsiRoots(); + T result = null; + for (PsiElement root : psiRoots) { + final PsiElement elementAt = root.findElementAt(offset); + if (elementAt != null) { + final T parent = getParentOfType(elementAt, clazz, strictStart); + if (parent != null) { + final TextRange range = parent.getTextRange(); + if (!strictStart || range.getStartOffset() == offset) { + if (result == null || result.getTextRange().getEndOffset() > range.getEndOffset()) { + result = parent; + } + } + } + } + } + + return result; + } + + /** + * @return maximal element of specified Class starting at startOffset exactly and ending not farther than endOffset + */ + @Nullable + public static T findElementOfClassAtRange(@NotNull PsiFile file, + int startOffset, + int endOffset, + @NotNull Class clazz) { + final FileViewProvider viewProvider = file.getViewProvider(); + T result = null; + for (Language lang : viewProvider.getLanguages()) { + PsiElement elementAt = viewProvider.findElementAt(startOffset, lang); + T run = getParentOfType(elementAt, clazz, false); + T prev = run; + while (run != null && run.getTextRange().getStartOffset() == startOffset && + run.getTextRange().getEndOffset() <= endOffset) { + prev = run; + run = getParentOfType(run, clazz); + } + + if (prev == null) continue; + final int elementStartOffset = prev.getTextRange().getStartOffset(); + final int elementEndOffset = prev.getTextRange().getEndOffset(); + if (elementStartOffset != startOffset || elementEndOffset > endOffset) continue; + + if (result == null || result.getTextRange().getEndOffset() < elementEndOffset) { + result = prev; + } + } + + return result; + } + + @NotNull + public static PsiElement getDeepestFirst(@NotNull PsiElement elt) { + @NotNull PsiElement res = elt; + do { + final PsiElement firstChild = res.getFirstChild(); + if (firstChild == null) return res; + res = firstChild; + } + while (true); + } + + @NotNull + public static PsiElement getDeepestLast(@NotNull PsiElement elt) { + @NotNull PsiElement res = elt; + do { + final PsiElement lastChild = res.getLastChild(); + if (lastChild == null) return res; + res = lastChild; + } + while (true); + } + + @Nullable + public static PsiElement prevLeaf(@NotNull PsiElement current) { + final PsiElement prevSibling = current.getPrevSibling(); + if (prevSibling != null) return lastChild(prevSibling); + final PsiElement parent = current.getParent(); + if (parent == null || parent instanceof PsiFile) return null; + return prevLeaf(parent); + } + + @Nullable + public static PsiElement nextLeaf(@NotNull PsiElement current) { + final PsiElement nextSibling = current.getNextSibling(); + if (nextSibling != null) return firstChild(nextSibling); + final PsiElement parent = current.getParent(); + if (parent == null || parent instanceof PsiFile) return null; + return nextLeaf(parent); + } + + public static PsiElement lastChild(@NotNull PsiElement element) { + PsiElement lastChild = element.getLastChild(); + if (lastChild != null) return lastChild(lastChild); + return element; + } + + public static PsiElement firstChild(@NotNull final PsiElement element) { + PsiElement child = element.getFirstChild(); + if (child != null) return firstChild(child); + return element; + } + + @Nullable + public static PsiElement prevLeaf(@NotNull final PsiErrorElement element, final boolean skipEmptyElements) { + PsiElement prevLeaf = prevLeaf(element); + while (skipEmptyElements && prevLeaf != null && prevLeaf.getTextLength() == 0) prevLeaf = prevLeaf(prevLeaf); + return prevLeaf; + } + + @Nullable + public static PsiElement nextLeaf(final PsiErrorElement element, final boolean skipEmptyElements) { + PsiElement nextLeaf = nextLeaf(element); + while (skipEmptyElements && nextLeaf != null && nextLeaf.getTextLength() == 0) nextLeaf = nextLeaf(nextLeaf); + return nextLeaf; + } + + public static boolean hasErrorElements(@NotNull final PsiElement element) { + if (element instanceof PsiErrorElement) return true; + + for (PsiElement child : element.getChildren()) { + if (hasErrorElements(child)) return true; + } + + return false; + } + + @NotNull + public static PsiElement[] filterAncestors(@NotNull PsiElement[] elements) { + if (LOG.isDebugEnabled()) { + for (PsiElement element : elements) { + LOG.debug("element = " + element); + } + } + + ArrayList filteredElements = new ArrayList(); + ContainerUtil.addAll(filteredElements, elements); + + int previousSize; + do { + previousSize = filteredElements.size(); + outer: + for (PsiElement element : filteredElements) { + for (PsiElement element2 : filteredElements) { + if (element == element2) continue; + if (isAncestor(element, element2, false)) { + if (LOG.isDebugEnabled()) { + LOG.debug("removing " + element2); + } + filteredElements.remove(element2); + break outer; + } + } + } + } + while (filteredElements.size() != previousSize); + + if (LOG.isDebugEnabled()) { + for (PsiElement element : filteredElements) { + LOG.debug("filtered element = " + element); + } + } + + return filteredElements.toArray(new PsiElement[filteredElements.size()]); + } + + public static boolean treeWalkUp(@NotNull final PsiScopeProcessor processor, + @NotNull final PsiElement entrance, + @Nullable final PsiElement maxScope, + @NotNull final ResolveState state) { + PsiElement prevParent = entrance; + PsiElement scope = entrance; + + while (scope != null) { + if (!scope.processDeclarations(processor, state, prevParent, entrance)) return false; + + if (scope == maxScope) break; + prevParent = scope; + scope = prevParent.getContext(); + } + + return true; + } + + @NotNull + public static PsiElement findPrevParent(@NotNull PsiElement ancestor, @NotNull PsiElement descendant) { + PsiElement cur = descendant; + while (cur != null) { + final PsiElement parent = cur.getParent(); + if (parent == ancestor) { + return cur; + } + cur = parent; + } + throw new AssertionError(descendant + " is not a descendant of " + ancestor); + } + + public static List getInjectedElements(OuterLanguageElement outerLanguageElement) { + PsiElement psi = outerLanguageElement.getContainingFile().getViewProvider().getPsi(outerLanguageElement.getLanguage()); + TextRange injectionRange = outerLanguageElement.getTextRange(); + List res = Lists.newArrayList(); + + assert psi != null : outerLanguageElement; + for (PsiElement element = psi.findElementAt(injectionRange.getStartOffset()); + element != null && injectionRange.intersectsStrict(element.getTextRange()); + element = element.getNextSibling()) { + res.add(element); + } + + return res; + } +} diff --git a/platform/lang-api/src/com/intellij/util/indexing/ID.java b/platform/lang-api/src/com/intellij/util/indexing/ID.java index 144f0f93b30a..e400793c745c 100644 --- a/platform/lang-api/src/com/intellij/util/indexing/ID.java +++ b/platform/lang-api/src/com/intellij/util/indexing/ID.java @@ -1,140 +1,140 @@ -/* - * 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.util.indexing; - -import com.intellij.openapi.application.PathManager; -import gnu.trove.TIntObjectHashMap; -import gnu.trove.TObjectIntHashMap; -import gnu.trove.TObjectIntProcedure; -import org.jetbrains.annotations.NonNls; - -import java.io.*; - -/** - * @author Eugene Zhuravlev - * Date: Feb 12, 2008 - */ -public class ID { - private static final TIntObjectHashMap ourRegistry = new TIntObjectHashMap(); - private static final TObjectIntHashMap ourNameToIdRegistry = new TObjectIntHashMap(); - - private final String myName; - private final short myUniqueId; - - static { - final File indicies = getEnumFile(); - try { - final BufferedReader reader = new BufferedReader(new FileReader(indicies)); - try { - int cnt = 0; - do { - cnt++; - final String name = reader.readLine(); - if (name == null) break; - ourNameToIdRegistry.put(name, cnt); - } - while (true); - } - finally { - reader.close(); - } - } - catch (IOException e) { - ourNameToIdRegistry.clear(); - writeEnumFile(); - } - } - - private static File getEnumFile() { - final File indexFolder = PathManager.getIndexRoot(); - return new File(indexFolder, "indices.enum"); - } - - protected ID(String name) { - myName = name; - myUniqueId = stringToId(name); - - final ID old = ourRegistry.put(myUniqueId, this); - assert old == null; - } - - private static short stringToId(String name) { - if (ourNameToIdRegistry.containsKey(name)) { - return (short)ourNameToIdRegistry.get(name); - } - - int n = ourNameToIdRegistry.size() + 1; - ourNameToIdRegistry.put(name, n); - - writeEnumFile(); - - return (short)n; - } - - private static void writeEnumFile() { - try { - final File f = getEnumFile(); - final BufferedWriter w = new BufferedWriter(new FileWriter(f)); - try { - final String[] names = new String[ourNameToIdRegistry.size()]; - - ourNameToIdRegistry.forEachEntry(new TObjectIntProcedure() { - public boolean execute(final String key, final int value) { - names[value - 1] = key; - return true; - } - }); - - for (String name : names) { - w.write(name); - w.newLine(); - } - } - finally { - w.close(); - } - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static ID create(@NonNls String name) { - short id = stringToId(name); - - final ID found = (ID)findById(id); - if (found != null) return found; - - return new ID(name); - } - - public int hashCode() { - return (int)myUniqueId; - } - - public String toString() { - return myName; - } - - public int getUniqueId() { - return myUniqueId; - } - - public static ID findById(int id) { - return ourRegistry.get(id); - } -} +/* + * 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.util.indexing; + +import com.intellij.openapi.application.PathManager; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TObjectIntHashMap; +import gnu.trove.TObjectIntProcedure; +import org.jetbrains.annotations.NonNls; + +import java.io.*; + +/** + * @author Eugene Zhuravlev + * Date: Feb 12, 2008 + */ +public class ID { + private static final TIntObjectHashMap ourRegistry = new TIntObjectHashMap(); + private static final TObjectIntHashMap ourNameToIdRegistry = new TObjectIntHashMap(); + + private final String myName; + private final short myUniqueId; + + static { + final File indicies = getEnumFile(); + try { + final BufferedReader reader = new BufferedReader(new FileReader(indicies)); + try { + int cnt = 0; + do { + cnt++; + final String name = reader.readLine(); + if (name == null) break; + ourNameToIdRegistry.put(name, cnt); + } + while (true); + } + finally { + reader.close(); + } + } + catch (IOException e) { + ourNameToIdRegistry.clear(); + writeEnumFile(); + } + } + + private static File getEnumFile() { + final File indexFolder = PathManager.getIndexRoot(); + return new File(indexFolder, "indices.enum"); + } + + protected ID(String name) { + myName = name; + myUniqueId = stringToId(name); + + final ID old = ourRegistry.put(myUniqueId, this); + assert old == null; + } + + private static short stringToId(String name) { + if (ourNameToIdRegistry.containsKey(name)) { + return (short)ourNameToIdRegistry.get(name); + } + + int n = ourNameToIdRegistry.size() + 1; + ourNameToIdRegistry.put(name, n); + + writeEnumFile(); + + return (short)n; + } + + private static void writeEnumFile() { + try { + final File f = getEnumFile(); + final BufferedWriter w = new BufferedWriter(new FileWriter(f)); + try { + final String[] names = new String[ourNameToIdRegistry.size()]; + + ourNameToIdRegistry.forEachEntry(new TObjectIntProcedure() { + public boolean execute(final String key, final int value) { + names[value - 1] = key; + return true; + } + }); + + for (String name : names) { + w.write(name); + w.newLine(); + } + } + finally { + w.close(); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static ID create(@NonNls String name) { + short id = stringToId(name); + + final ID found = (ID)findById(id); + if (found != null) return found; + + return new ID(name); + } + + public int hashCode() { + return (int)myUniqueId; + } + + public String toString() { + return myName; + } + + public int getUniqueId() { + return myUniqueId; + } + + public static ID findById(int id) { + return ourRegistry.get(id); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightUtilBase.java b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightUtilBase.java index 3a23065e858c..2c3e9fa0f14b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightUtilBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightUtilBase.java @@ -1,142 +1,142 @@ -/* - * 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.codeInsight; - -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.lang.Language; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ex.RangeMarkerEx; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.ReadonlyStatusHandler; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiWhiteSpace; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.ReflectionCache; -import gnu.trove.THashSet; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Set; - -public class CodeInsightUtilBase { - private CodeInsightUtilBase() { - } - - static T findElementInRange(final PsiFile file, - int startOffset, - int endOffset, - final Class klass, - final Language language) { - PsiElement element1 = file.getViewProvider().findElementAt(startOffset, language); - PsiElement element2 = file.getViewProvider().findElementAt(endOffset - 1, language); - if (element1 instanceof PsiWhiteSpace) { - startOffset = element1.getTextRange().getEndOffset(); - element1 = file.getViewProvider().findElementAt(startOffset, language); - } - if (element2 instanceof PsiWhiteSpace) { - endOffset = element2.getTextRange().getStartOffset(); - element2 = file.getViewProvider().findElementAt(endOffset - 1, language); - } - if (element2 == null || element1 == null) return null; - final PsiElement commonParent = PsiTreeUtil.findCommonParent(element1, element2); - final T element = - ReflectionCache.isAssignable(klass, commonParent.getClass()) - ? (T)commonParent : PsiTreeUtil.getParentOfType(commonParent, klass); - if (element == null || element.getTextRange().getStartOffset() != startOffset || element.getTextRange().getEndOffset() != endOffset) { - return null; - } - return element; - } - - public static T forcePsiPostprocessAndRestoreElement(final T element) { - final PsiFile psiFile = element.getContainingFile(); - final Document document = psiFile.getViewProvider().getDocument(); - if (document == null) return element; - final Language language = PsiUtilBase.getDialect(element); - final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(psiFile.getProject()); - final RangeMarker rangeMarker = document.createRangeMarker(element.getTextRange()); - documentManager.doPostponedOperationsAndUnblockDocument(document); - documentManager.commitDocument(document); - - T elementInRange = findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), - (Class)element.getClass(), - language); - rangeMarker.dispose(); - return elementInRange; - } - - public static boolean prepareFileForWrite(final PsiFile psiFile) { - if (psiFile == null) return false; - final VirtualFile file = psiFile.getVirtualFile(); - final Project project = psiFile.getProject(); - - final Editor editor = - psiFile.isWritable() ? null : FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file), true); - if (!ReadonlyStatusHandler.ensureFilesWritable(project, file)) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (editor != null && editor.getComponent().isDisplayable()) { - HintManager.getInstance() - .showErrorHint(editor, CodeInsightBundle.message("error.hint.file.is.readonly", file.getPresentableUrl())); - } - } - }); - - return false; - } - - return true; - } - - public static boolean preparePsiElementForWrite(PsiElement element) { - PsiFile file = element == null ? null : element.getContainingFile(); - return prepareFileForWrite(file); - } - - public static boolean preparePsiElementsForWrite(PsiElement... elements) { - return preparePsiElementsForWrite(Arrays.asList(elements)); - } - public static boolean preparePsiElementsForWrite(Collection elements) { - if (elements.isEmpty()) return true; - Set files = new THashSet(); - Project project = null; - for (PsiElement element : elements) { - project = element.getProject(); - PsiFile file = element.getContainingFile(); - if (file == null) continue; - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) continue; - files.add(virtualFile); - } - if (!files.isEmpty()) { - VirtualFile[] virtualFiles = VfsUtil.toVirtualFileArray(files); - ReadonlyStatusHandler.OperationStatus status = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(virtualFiles); - return !status.hasReadonlyFiles(); - } - return true; - } -} +/* + * 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.codeInsight; + +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.lang.Language; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.ex.RangeMarkerEx; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.ReflectionCache; +import gnu.trove.THashSet; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; + +public class CodeInsightUtilBase { + private CodeInsightUtilBase() { + } + + static T findElementInRange(final PsiFile file, + int startOffset, + int endOffset, + final Class klass, + final Language language) { + PsiElement element1 = file.getViewProvider().findElementAt(startOffset, language); + PsiElement element2 = file.getViewProvider().findElementAt(endOffset - 1, language); + if (element1 instanceof PsiWhiteSpace) { + startOffset = element1.getTextRange().getEndOffset(); + element1 = file.getViewProvider().findElementAt(startOffset, language); + } + if (element2 instanceof PsiWhiteSpace) { + endOffset = element2.getTextRange().getStartOffset(); + element2 = file.getViewProvider().findElementAt(endOffset - 1, language); + } + if (element2 == null || element1 == null) return null; + final PsiElement commonParent = PsiTreeUtil.findCommonParent(element1, element2); + final T element = + ReflectionCache.isAssignable(klass, commonParent.getClass()) + ? (T)commonParent : PsiTreeUtil.getParentOfType(commonParent, klass); + if (element == null || element.getTextRange().getStartOffset() != startOffset || element.getTextRange().getEndOffset() != endOffset) { + return null; + } + return element; + } + + public static T forcePsiPostprocessAndRestoreElement(final T element) { + final PsiFile psiFile = element.getContainingFile(); + final Document document = psiFile.getViewProvider().getDocument(); + if (document == null) return element; + final Language language = PsiUtilBase.getDialect(element); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(psiFile.getProject()); + final RangeMarker rangeMarker = document.createRangeMarker(element.getTextRange()); + documentManager.doPostponedOperationsAndUnblockDocument(document); + documentManager.commitDocument(document); + + T elementInRange = findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), + (Class)element.getClass(), + language); + rangeMarker.dispose(); + return elementInRange; + } + + public static boolean prepareFileForWrite(final PsiFile psiFile) { + if (psiFile == null) return false; + final VirtualFile file = psiFile.getVirtualFile(); + final Project project = psiFile.getProject(); + + final Editor editor = + psiFile.isWritable() ? null : FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file), true); + if (!ReadonlyStatusHandler.ensureFilesWritable(project, file)) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (editor != null && editor.getComponent().isDisplayable()) { + HintManager.getInstance() + .showErrorHint(editor, CodeInsightBundle.message("error.hint.file.is.readonly", file.getPresentableUrl())); + } + } + }); + + return false; + } + + return true; + } + + public static boolean preparePsiElementForWrite(PsiElement element) { + PsiFile file = element == null ? null : element.getContainingFile(); + return prepareFileForWrite(file); + } + + public static boolean preparePsiElementsForWrite(PsiElement... elements) { + return preparePsiElementsForWrite(Arrays.asList(elements)); + } + public static boolean preparePsiElementsForWrite(Collection elements) { + if (elements.isEmpty()) return true; + Set files = new THashSet(); + Project project = null; + for (PsiElement element : elements) { + project = element.getProject(); + PsiFile file = element.getContainingFile(); + if (file == null) continue; + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) continue; + files.add(virtualFile); + } + if (!files.isEmpty()) { + VirtualFile[] virtualFiles = VfsUtil.toVirtualFileArray(files); + ReadonlyStatusHandler.OperationStatus status = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(virtualFiles); + return !status.hasReadonlyFiles(); + } + return true; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java index 72953e407680..7d3dd6825fc0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -1,743 +1,743 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.CodeInsightActionHandler; -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler; -import com.intellij.codeInsight.hint.EditorHintListener; -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.codeInsight.lookup.*; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.extapi.psi.MetadataPsiElementBase; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.injected.editor.EditorWindow; -import com.intellij.lang.Language; -import com.intellij.lang.injection.InjectedLanguageManager; -import com.intellij.openapi.application.ApplicationAdapter; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.application.ex.ApplicationEx; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.ScrollType; -import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressWrapper; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.impl.PsiFileEx; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.reference.SoftReference; -import com.intellij.ui.LightweightHint; -import com.intellij.util.Consumer; -import com.intellij.util.concurrency.Semaphore; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class CodeCompletionHandlerBase implements CodeInsightActionHandler { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CodeCompletionHandlerBase"); - private final CompletionType myCompletionType; - final boolean invokedExplicitly; - final boolean autopopup; - - public CodeCompletionHandlerBase(final CompletionType completionType) { - this(completionType, true, false); - } - - public CodeCompletionHandlerBase(CompletionType completionType, boolean invokedExplicitly, boolean autopopup) { - myCompletionType = completionType; - this.invokedExplicitly = invokedExplicitly; - this.autopopup = autopopup; - } - - public final void invoke(final Project project, final Editor editor) { - invoke(project, editor, PsiUtilBase.getPsiFileInEditor(editor, project)); - } - - public final void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile psiFile) { - if (editor.isViewer()) { - editor.getDocument().fireReadOnlyModificationAttempt(); - return; - } - - try { - invokeCompletion(project, editor, psiFile, autopopup ? 0 : 1); - } - catch (IndexNotReadyException e) { - DumbService.getInstance(project).showDumbModeNotification("Code completion is not available here while indices are being built"); - } - } - - public void invokeCompletion(final Project project, final Editor editor, final PsiFile psiFile, int time) { - if (!ApplicationManager.getApplication().isUnitTestMode()) { - assert !ApplicationManager.getApplication().isWriteAccessAllowed() : "Completion should not be invoked inside write action"; - } - - final Document document = editor.getDocument(); - if (!FileDocumentManager.getInstance().requestWriting(document, project)) { - return; - } - - psiFile.putUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING, Boolean.TRUE); - - final CompletionProgressIndicator indicator = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); - if (indicator != null) { - if (indicator.isRepeatedInvocation(myCompletionType, editor)) { - if (!indicator.isRunning() && (!isAutocompleteCommonPrefixOnInvocation() || indicator.fillInCommonPrefix(true))) { - return; - } - else { - time = indicator.getParameters().getInvocationCount() + 1; - new WriteCommandAction(project) { - protected void run(Result result) throws Throwable { - indicator.restorePrefix(); - } - }.execute(); - } - } - indicator.closeAndFinish(false); - } - - if (time > 1) { - if (myCompletionType == CompletionType.CLASS_NAME) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.SECOND_CLASS_NAME_COMPLETION); - } - else if (myCompletionType == CompletionType.BASIC) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.SECOND_BASIC_COMPLETION); - } - } - - final CompletionInitializationContext[] initializationContext = {null}; - - - Runnable initCmd = new Runnable() { - @Override - public void run() { - - Runnable runnable = new Runnable() { - public void run() { - final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); - - EditorUtil.fillVirtualSpaceUntilCaret(editor); - documentManager.commitAllDocuments(); - - final Ref current = Ref.create(null); - initializationContext[0] = new CompletionInitializationContext(editor, psiFile, myCompletionType) { - CompletionContributor dummyIdentifierChanger; - @Override - public void setFileCopyPatcher(@NotNull FileCopyPatcher fileCopyPatcher) { - super.setFileCopyPatcher(fileCopyPatcher); - - if (dummyIdentifierChanger != null) { - LOG.error("Changing the dummy identifier twice, already changed by " + dummyIdentifierChanger); - } - dummyIdentifierChanger = current.get(); - } - }; - for (final CompletionContributor contributor : CompletionContributor.forLanguage(initializationContext[0].getPositionLanguage())) { - if (DumbService.getInstance(project).isDumb() && !DumbService.isDumbAware(contributor)) { - continue; - } - - current.set(contributor); - contributor.beforeCompletion(initializationContext[0]); - assert !documentManager.isUncommited(document) : "Contributor " + contributor + " left the document uncommitted"; - } - } - }; - ApplicationManager.getApplication().runWriteAction(runnable); - } - }; - if (autopopup) { - CommandProcessor.getInstance().runUndoTransparentAction(initCmd); - - int offset = editor.getCaretModel().getOffset(); - - PsiElement elementAt = InjectedLanguageUtil.findInjectedElementNoCommit(psiFile, offset); - if (elementAt == null) { - elementAt = psiFile.findElementAt(offset); - if (elementAt == null && offset > 0) { - elementAt = psiFile.findElementAt(offset - 1); - } - } - - Language language = elementAt != null ? PsiUtilBase.findLanguageFromElement(elementAt):psiFile.getLanguage(); - - for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { - final Boolean result = confidence.shouldSkipAutopopup(elementAt, psiFile, offset); // TODO: Peter Lazy API - if (result == Boolean.TRUE) return; - } - } else { - CommandProcessor.getInstance().executeCommand(project, initCmd, null, null); - } - - doComplete(time, initializationContext[0]); - } - - private boolean shouldFocusLookup(CompletionParameters parameters) { - if (!autopopup) { - return true; - } - - final Language language = PsiUtilBase.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset()); - for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { - final Boolean result = confidence.shouldFocusLookup(parameters); - if (result != null) { - return result; - } - } - return false; - } - - @NotNull - private LookupImpl obtainLookup(Editor editor, CompletionParameters parameters) { - LookupImpl existing = (LookupImpl)LookupManager.getActiveLookup(editor); - if (existing != null && existing.isCompletion()) { - existing.markReused(); - if (!autopopup) { - existing.setFocused(true); - } - return existing; - } - - LookupImpl lookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).createLookup(editor, LookupElement.EMPTY_ARRAY, "", LookupArranger.DEFAULT); - if (editor.isOneLineMode()) { - lookup.setCancelOnClickOutside(true); - lookup.setCancelOnOtherWindowOpen(true); - lookup.setResizable(false); - lookup.setForceLightweightPopup(false); - } - lookup.setFocused(shouldFocusLookup(parameters)); - return lookup; - } - - private void doComplete(final int invocationCount, CompletionInitializationContext initContext) { - final Editor editor = initContext.getEditor(); - final OffsetMap offsetMap = initContext.getOffsetMap(); - final CompletionContext context = new CompletionContext(initContext.getProject(), editor, initContext.getFile(), offsetMap); - - final CompletionParameters parameters = createCompletionParameters(context, initContext.getFileCopyPatcher(), invocationCount); - - final LookupImpl lookup = obtainLookup(editor, parameters); - - final Semaphore freezeSemaphore = new Semaphore(); - freezeSemaphore.down(); - final CompletionProgressIndicator indicator = new CompletionProgressIndicator(editor, parameters, this, freezeSemaphore, offsetMap, lookup); - - final AtomicReference data = startCompletionThread(parameters, indicator, initContext); - - if (!invokedExplicitly) { - indicator.notifyBackgrounded(); - return; - } - - if (freezeSemaphore.waitFor(2000)) { - final LookupElement[] allItems = data.get(); - if (allItems != null) { // the completion is really finished, now we may auto-insert or show lookup - completionFinished(initContext.getStartOffset(), initContext.getSelectionEndOffset(), indicator, allItems); - return; - } - } - - indicator.notifyBackgrounded(); - indicator.showLookup(); - } - - private static AtomicReference startCompletionThread(final CompletionParameters parameters, - final CompletionProgressIndicator indicator, - final CompletionInitializationContext initContext) { - - final ApplicationAdapter listener = new ApplicationAdapter() { - @Override - public void beforeWriteActionStart(Object action) { - indicator.cancelByWriteAction(); - } - }; - ApplicationManager.getApplication().addApplicationListener(listener); - - final Semaphore startSemaphore = new Semaphore(); - startSemaphore.down(); - startSemaphore.down(); - - spawnProcess(ProgressWrapper.wrap(indicator), new Runnable() { - public void run() { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - startSemaphore.up(); - indicator.duringCompletion(initContext); - } - }); - } - }); - - final AtomicReference data = new AtomicReference(null); - spawnProcess(indicator, new Runnable() { - public void run() { - try { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - startSemaphore.up(); - ProgressManager.checkCanceled(); - - final LookupElement[] result = CompletionService.getCompletionService().performCompletion(parameters, new Consumer() { - public void consume(final LookupElement lookupElement) { - indicator.addItem(lookupElement); - } - }); - - indicator.ensureDuringCompletionPassed(); - - data.set(result); - } - finally { - ApplicationManager.getApplication().removeApplicationListener(listener); - } - } - }); - } - catch (ProcessCanceledException ignored) { - } - } - }); - - startSemaphore.waitFor(); - return data; - } - - - - private static void spawnProcess(final ProgressIndicator indicator, final Runnable process) { - final Runnable computeRunnable = new Runnable() { - public void run() { - ProgressManager.getInstance().runProcess(process, indicator); - } - }; - - if (ApplicationManager.getApplication().isUnitTestMode()) { - computeRunnable.run(); - } else { - ApplicationManager.getApplication().executeOnPooledThread(computeRunnable); - } - } - - private CompletionParameters createCompletionParameters(final CompletionContext context, final FileCopyPatcher patcher, int invocationCount) { - final Ref> ref = Ref.create(null); - CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - ref.set(insertDummyIdentifier(context, patcher, context.file, context.editor)); - } - }); - } - }); - - final PsiElement insertedElement = ref.get().getSecond(); - final CompletionContext newContext = ref.get().getFirst(); - insertedElement.putUserData(CompletionContext.COMPLETION_CONTEXT_KEY, newContext); - - return new CompletionParameters(insertedElement, newContext.file, myCompletionType, newContext.getStartOffset(), invocationCount); - } - - private AutoCompletionDecision shouldAutoComplete( - final CompletionProgressIndicator indicator, - final LookupElement[] items) { - if (!invokedExplicitly) { - return AutoCompletionDecision.SHOW_LOOKUP; - } - final CompletionParameters parameters = indicator.getParameters(); - final LookupElement item = items[0]; - if (items.length == 1) { - final AutoCompletionPolicy policy = getAutocompletionPolicy(item); - if (policy == AutoCompletionPolicy.NEVER_AUTOCOMPLETE) return AutoCompletionDecision.SHOW_LOOKUP; - if (policy == AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE) return AutoCompletionDecision.insertItem(item); - } - if (!isAutocompleteOnInvocation(parameters.getCompletionType())) { - return AutoCompletionDecision.SHOW_LOOKUP; - } - if (isInsideIdentifier(indicator.getOffsetMap())) { - return AutoCompletionDecision.SHOW_LOOKUP; - } - if (items.length == 1 && getAutocompletionPolicy(item) == AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE) { - return AutoCompletionDecision.insertItem(item); - } - - for (final CompletionContributor contributor : CompletionContributor.forParameters(parameters)) { - final AutoCompletionDecision decision = contributor.handleAutoCompletionPossibility(new AutoCompletionContext(parameters, items, indicator.getOffsetMap())); - if (decision != null) { - return decision; - } - } - - return AutoCompletionDecision.SHOW_LOOKUP; - } - - @Nullable - private static AutoCompletionPolicy getAutocompletionPolicy(LookupElement element) { - final AutoCompletionPolicy policy = AutoCompletionPolicy.getPolicy(element); - if (policy != null) { - return policy; - } - - final LookupItem item = element.as(LookupItem.class); - if (item != null) { - return item.getAutoCompletionPolicy(); - } - - return null; - } - - private static boolean isInsideIdentifier(final OffsetMap offsetMap) { - return offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != offsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); - } - - - protected void completionFinished(final int offset1, - final int offset2, - final CompletionProgressIndicator indicator, - final LookupElement[] items) { - if (CompletionAutoPopupHandler.ourTestingAutopopup) { - System.out.println("CodeCompletionHandlerBase.completionFinished"); - System.out.println("items " + Arrays.asList(items)); - } - - if (items.length == 0) { - LookupManager.getInstance(indicator.getProject()).hideActiveLookup(); - handleEmptyLookup(indicator.getProject(), indicator.getEditor(), indicator.getParameters(), indicator); - return; - } - - final AutoCompletionDecision decision = shouldAutoComplete(indicator, items); - if (decision == AutoCompletionDecision.SHOW_LOOKUP) { - indicator.showLookup(); - if (isAutocompleteCommonPrefixOnInvocation() && items.length > 1) { - indicator.fillInCommonPrefix(false); - } - } - else if (decision instanceof AutoCompletionDecision.InsertItem) { - final LookupElement item = ((AutoCompletionDecision.InsertItem)decision).getElement(); - indicator.closeAndFinish(true); - indicator.rememberDocumentState(); - indicator.getOffsetMap() - .addOffset(CompletionInitializationContext.START_OFFSET, (offset1 - item.getPrefixMatcher().getPrefix().length())); - handleSingleItem(offset2, indicator, items, item.getLookupString(), item); - - // the insert handler may have started a live template with completion - if (CompletionService.getCompletionService() == null) { - indicator.liveAfterDeath(null); - } - } - } - - protected static void handleSingleItem(final int offset2, final CompletionProgressIndicator context, final LookupElement[] items, final String _uniqueText, final LookupElement item) { - new WriteCommandAction(context.getProject()) { - protected void run(Result result) throws Throwable { - String uniqueText = _uniqueText; - - if (item.getObject() instanceof DeferredUserLookupValue && item.as(LookupItem.class) != null) { - if (!((DeferredUserLookupValue)item.getObject()).handleUserSelection(item.as(LookupItem.class), context.getProject())) { - return; - } - - uniqueText = item.getLookupString(); // text may be not ready yet - } - - if (!StringUtil.startsWithIgnoreCase(uniqueText, item.getPrefixMatcher().getPrefix())) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS); - } - - insertLookupString(offset2, uniqueText, context.getEditor(), context.getOffsetMap()); - context.getEditor().getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - - lookupItemSelected(context, item, Lookup.AUTO_INSERT_SELECT_CHAR, Arrays.asList(items)); - } - }.execute(); - } - - private static void insertLookupString(final int currentOffset, final String newText, final Editor editor, final OffsetMap offsetMap) { - editor.getDocument().replaceString(offsetMap.getOffset(CompletionInitializationContext.START_OFFSET), currentOffset, newText); - editor.getCaretModel().moveToOffset(offsetMap.getOffset(CompletionInitializationContext.START_OFFSET) + newText.length()); - editor.getSelectionModel().removeSelection(); - } - - protected static void selectLookupItem(final LookupElement item, final char completionChar, final CompletionProgressIndicator context, final List items) { - final int caretOffset = context.getEditor().getCaretModel().getOffset(); - - context.getOffsetMap().addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, caretOffset); - final int idEnd = context.getIdentifierEndOffset(); - final int identifierEndOffset = - CompletionUtil.isOverwrite(item, completionChar) && context.getSelectionEndOffset() == idEnd ? - caretOffset : - Math.max(caretOffset, idEnd); - context.getOffsetMap().addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, identifierEndOffset); - lookupItemSelected(context, item, completionChar, items); - } - - private Pair insertDummyIdentifier(final CompletionContext context, - final FileCopyPatcher patcher, - final PsiFile file, final Editor originalEditor) { - PsiFile oldFileCopy = createFileCopy(file); - PsiFile hostFile = InjectedLanguageUtil.getTopLevelFile(oldFileCopy); - boolean wasInjected = hostFile != oldFileCopy; - Project project = hostFile.getProject(); - InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(project); - // is null in tests - int hostStartOffset = injectedLanguageManager == null - ? context.getStartOffset() - : injectedLanguageManager.injectedToHost(oldFileCopy, context.getStartOffset()); - - Document document = oldFileCopy.getViewProvider().getDocument(); - assert document != null; - patcher.patchFileCopy(oldFileCopy, document, context.getOffsetMap()); - PsiDocumentManager.getInstance(project).commitDocument(document); - PsiFile fileCopy = InjectedLanguageUtil.findInjectedPsiNoCommit(hostFile, hostStartOffset); - if (fileCopy == null) { - PsiElement elementAfterCommit = findElementAt(hostFile, hostStartOffset); - if (wasInjected) { - LOG.error("No injected fragment found at offset " + hostStartOffset + " in the patched file copy, found: " + elementAfterCommit); - } - fileCopy = elementAfterCommit == null ? oldFileCopy : elementAfterCommit.getContainingFile(); - } - - if (oldFileCopy != fileCopy && !wasInjected) { - // newly inserted identifier can well end up in the injected language region - Editor editor = EditorFactory.getInstance().createEditor(document, project); - Editor newEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, hostFile, context.getStartOffset()); - if (newEditor instanceof EditorWindow) { - EditorWindow injectedEditor = (EditorWindow)newEditor; - PsiFile injectedFile = injectedEditor.getInjectedFile(); - final OffsetMap map = new OffsetMap(newEditor.getDocument()); - final OffsetMap oldMap = context.getOffsetMap(); - for (final OffsetKey key : oldMap.keySet()) { - map.addOffset(key, injectedEditor.logicalPositionToOffset(injectedEditor.hostToInjected(originalEditor.offsetToLogicalPosition(oldMap.getOffset(key))))); - } - CompletionContext newContext = new CompletionContext(file.getProject(), injectedEditor, injectedFile, map); - int injectedOffset = newContext.getStartOffset(); - PsiElement element = findElementAt(injectedFile, injectedOffset); - - int toHost = injectedLanguageManager == null ? hostStartOffset : injectedLanguageManager.injectedToHost(injectedFile, injectedOffset); - // maybe injected fragment is ended before hostStartOffset - if (element != null && toHost == hostStartOffset) { - EditorFactory.getInstance().releaseEditor(editor); - return Pair.create(newContext, element); - } - - PsiElement elementAfterCommit = findElementAt(hostFile, hostStartOffset); - fileCopy = elementAfterCommit == null ? oldFileCopy : elementAfterCommit.getContainingFile(); - } - EditorFactory.getInstance().releaseEditor(editor); - } - PsiElement element = findElementAt(fileCopy, context.getStartOffset()); - if (element == null) { - LOG.error("offset " + context.getStartOffset() + " at:\ntext=\"" + fileCopy.getText() + "\"\ninstance=" + fileCopy); - } - return Pair.create(context, element); - } - - private static PsiElement findElementAt(final PsiFile fileCopy, int startOffset) { - PsiElement element = fileCopy.findElementAt(startOffset); - if (element instanceof MetadataPsiElementBase) { - final PsiElement source = ((MetadataPsiElementBase)element).getSourceElement(); - if (source != null) { - return source.findElementAt(startOffset - source.getTextRange().getStartOffset()); - } - } - return element; - } - - - private boolean isAutocompleteCommonPrefixOnInvocation() { - return invokedExplicitly && CodeInsightSettings.getInstance().AUTOCOMPLETE_COMMON_PREFIX; - } - - protected void handleEmptyLookup(Project project, Editor editor, final CompletionParameters parameters, final CompletionProgressIndicator indicator) { - if (ApplicationManager.getApplication().isUnitTestMode()) return; - if (!invokedExplicitly) { - return; - } - - for (final CompletionContributor contributor : CompletionContributor.forParameters(parameters)) { - final String text = contributor.handleEmptyLookup(parameters, editor); - if (StringUtil.isNotEmpty(text)) { - final EditorHintListener listener = new EditorHintListener() { - public void hintShown(final Project project, final LightweightHint hint, final int flags) { - indicator.liveAfterDeath(hint); - } - }; - final MessageBusConnection connection = project.getMessageBus().connect(); - connection.subscribe(EditorHintListener.TOPIC, listener); - HintManager.getInstance().showErrorHint(editor, text); - connection.disconnect(); - break; - } - } - DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project); - if (codeAnalyzer != null) { - codeAnalyzer.updateVisibleHighlighters(editor); - } - } - - private static void lookupItemSelected(final CompletionProgressIndicator context, @NotNull final LookupElement item, final char completionChar, - final List items) { - if (context.getHandler().autopopup) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_BASIC); - } - - final Editor editor = context.getEditor(); - final PsiFile file = context.getParameters().getOriginalFile(); - final InsertionContext context1 = new InsertionContext(context.getOffsetMap(), completionChar, items.toArray(new LookupElement[items.size()]), file, editor); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - final int idEndOffset = context.getIdentifierEndOffset(); - if (idEndOffset != context.getSelectionEndOffset() && CompletionUtil.isOverwrite(item, completionChar)) { - editor.getDocument().deleteString(context.getSelectionEndOffset(), idEndOffset); - } - - PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); - item.handleInsert(context1); - PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); - - if (context1.shouldAddCompletionChar() && - completionChar != Lookup.AUTO_INSERT_SELECT_CHAR && completionChar != Lookup.REPLACE_SELECT_CHAR && - completionChar != Lookup.NORMAL_SELECT_CHAR && completionChar != Lookup.COMPLETE_STATEMENT_SELECT_CHAR) { - TailType.insertChar(editor, context1.getTailOffset(), completionChar); - } - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - }); - final Runnable runnable = context1.getLaterRunnable(); - if (runnable != null) { - final Runnable runnable1 = new Runnable() { - public void run() { - final Project project = context1.getProject(); - if (project.isDisposed()) return; - runnable.run(); - } - }; - if (!ApplicationManager.getApplication().isUnitTestMode()) { - ApplicationManager.getApplication().invokeLater(runnable1); - } else { - runnable1.run(); - } - } - } - - public boolean startInWriteAction() { - return false; - } - - public static final Key> FILE_COPY_KEY = Key.create("CompletionFileCopy"); - - protected PsiFile createFileCopy(PsiFile file) { - final VirtualFile virtualFile = file.getVirtualFile(); - if (file.isPhysical() && virtualFile != null && virtualFile.getFileSystem() == LocalFileSystem.getInstance() - // must not cache injected file copy, since it does not reflect changes in host document - && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) { - final SoftReference reference = file.getUserData(FILE_COPY_KEY); - if (reference != null) { - final PsiFile copy = reference.get(); - if (copy != null && copy.isValid() && copy.getClass().equals(file.getClass())) { - final Document document = copy.getViewProvider().getDocument(); - assert document != null; - final String oldDocumentText = document.getText(); - final String oldCopyText = copy.getText(); - final String newText = file.getText(); - document.setText(newText); - try { - PsiDocumentManager.getInstance(copy.getProject()).commitDocument(document); - return copy; - } - catch (Throwable e) { - document.setText(""); - if (((ApplicationEx)ApplicationManager.getApplication()).isInternal()) { - final StringBuilder sb = new StringBuilder(); - boolean oldsAreSame = Comparing.equal(oldCopyText, oldDocumentText); - if (oldsAreSame) { - sb.append("oldCopyText == oldDocumentText"); - } - else { - sb.append("oldCopyText != oldDocumentText"); - sb.append("\n--- oldCopyText ------------------------------------------------\n").append(oldCopyText); - sb.append("\n--- oldDocumentText ------------------------------------------------\n").append(oldDocumentText); - } - if (Comparing.equal(oldCopyText, newText)) { - sb.insert(0, "newText == oldCopyText; "); - } - else if (!oldsAreSame && Comparing.equal(oldDocumentText, newText)) { - sb.insert(0, "newText == oldDocumentText; "); - } - else { - sb.insert(0, "newText != oldCopyText, oldDocumentText; "); - if (oldsAreSame) { - sb.append("\n--- oldCopyText ------------------------------------------------\n").append(oldCopyText); - } - sb.append("\n--- newText ------------------------------------------------\n").append(newText); - } - LOG.error(sb.toString(), e); - } - } - } - } - } - - final PsiFile copy = (PsiFile)file.copy(); - file.putUserData(FILE_COPY_KEY, new SoftReference(copy)); - return copy; - } - - private static boolean isAutocompleteOnInvocation(final CompletionType type) { - final CodeInsightSettings settings = CodeInsightSettings.getInstance(); - switch (type) { - case CLASS_NAME: return settings.AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION; - case SMART: return settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION; - case BASIC: - default: return settings.AUTOCOMPLETE_ON_CODE_COMPLETION; - } - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.CodeInsightActionHandler; +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler; +import com.intellij.codeInsight.hint.EditorHintListener; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.lookup.*; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.extapi.psi.MetadataPsiElementBase; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.injected.editor.EditorWindow; +import com.intellij.lang.Language; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.application.ApplicationAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.ex.ApplicationEx; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.util.ProgressWrapper; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.PsiFileEx; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.reference.SoftReference; +import com.intellij.ui.LightweightHint; +import com.intellij.util.Consumer; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class CodeCompletionHandlerBase implements CodeInsightActionHandler { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CodeCompletionHandlerBase"); + private final CompletionType myCompletionType; + final boolean invokedExplicitly; + final boolean autopopup; + + public CodeCompletionHandlerBase(final CompletionType completionType) { + this(completionType, true, false); + } + + public CodeCompletionHandlerBase(CompletionType completionType, boolean invokedExplicitly, boolean autopopup) { + myCompletionType = completionType; + this.invokedExplicitly = invokedExplicitly; + this.autopopup = autopopup; + } + + public final void invoke(final Project project, final Editor editor) { + invoke(project, editor, PsiUtilBase.getPsiFileInEditor(editor, project)); + } + + public final void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile psiFile) { + if (editor.isViewer()) { + editor.getDocument().fireReadOnlyModificationAttempt(); + return; + } + + try { + invokeCompletion(project, editor, psiFile, autopopup ? 0 : 1); + } + catch (IndexNotReadyException e) { + DumbService.getInstance(project).showDumbModeNotification("Code completion is not available here while indices are being built"); + } + } + + public void invokeCompletion(final Project project, final Editor editor, final PsiFile psiFile, int time) { + if (!ApplicationManager.getApplication().isUnitTestMode()) { + assert !ApplicationManager.getApplication().isWriteAccessAllowed() : "Completion should not be invoked inside write action"; + } + + final Document document = editor.getDocument(); + if (!FileDocumentManager.getInstance().requestWriting(document, project)) { + return; + } + + psiFile.putUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING, Boolean.TRUE); + + final CompletionProgressIndicator indicator = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); + if (indicator != null) { + if (indicator.isRepeatedInvocation(myCompletionType, editor)) { + if (!indicator.isRunning() && (!isAutocompleteCommonPrefixOnInvocation() || indicator.fillInCommonPrefix(true))) { + return; + } + else { + time = indicator.getParameters().getInvocationCount() + 1; + new WriteCommandAction(project) { + protected void run(Result result) throws Throwable { + indicator.restorePrefix(); + } + }.execute(); + } + } + indicator.closeAndFinish(false); + } + + if (time > 1) { + if (myCompletionType == CompletionType.CLASS_NAME) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.SECOND_CLASS_NAME_COMPLETION); + } + else if (myCompletionType == CompletionType.BASIC) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.SECOND_BASIC_COMPLETION); + } + } + + final CompletionInitializationContext[] initializationContext = {null}; + + + Runnable initCmd = new Runnable() { + @Override + public void run() { + + Runnable runnable = new Runnable() { + public void run() { + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + + EditorUtil.fillVirtualSpaceUntilCaret(editor); + documentManager.commitAllDocuments(); + + final Ref current = Ref.create(null); + initializationContext[0] = new CompletionInitializationContext(editor, psiFile, myCompletionType) { + CompletionContributor dummyIdentifierChanger; + @Override + public void setFileCopyPatcher(@NotNull FileCopyPatcher fileCopyPatcher) { + super.setFileCopyPatcher(fileCopyPatcher); + + if (dummyIdentifierChanger != null) { + LOG.error("Changing the dummy identifier twice, already changed by " + dummyIdentifierChanger); + } + dummyIdentifierChanger = current.get(); + } + }; + for (final CompletionContributor contributor : CompletionContributor.forLanguage(initializationContext[0].getPositionLanguage())) { + if (DumbService.getInstance(project).isDumb() && !DumbService.isDumbAware(contributor)) { + continue; + } + + current.set(contributor); + contributor.beforeCompletion(initializationContext[0]); + assert !documentManager.isUncommited(document) : "Contributor " + contributor + " left the document uncommitted"; + } + } + }; + ApplicationManager.getApplication().runWriteAction(runnable); + } + }; + if (autopopup) { + CommandProcessor.getInstance().runUndoTransparentAction(initCmd); + + int offset = editor.getCaretModel().getOffset(); + + PsiElement elementAt = InjectedLanguageUtil.findInjectedElementNoCommit(psiFile, offset); + if (elementAt == null) { + elementAt = psiFile.findElementAt(offset); + if (elementAt == null && offset > 0) { + elementAt = psiFile.findElementAt(offset - 1); + } + } + + Language language = elementAt != null ? PsiUtilBase.findLanguageFromElement(elementAt):psiFile.getLanguage(); + + for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { + final Boolean result = confidence.shouldSkipAutopopup(elementAt, psiFile, offset); // TODO: Peter Lazy API + if (result == Boolean.TRUE) return; + } + } else { + CommandProcessor.getInstance().executeCommand(project, initCmd, null, null); + } + + doComplete(time, initializationContext[0]); + } + + private boolean shouldFocusLookup(CompletionParameters parameters) { + if (!autopopup) { + return true; + } + + final Language language = PsiUtilBase.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset()); + for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { + final Boolean result = confidence.shouldFocusLookup(parameters); + if (result != null) { + return result; + } + } + return false; + } + + @NotNull + private LookupImpl obtainLookup(Editor editor, CompletionParameters parameters) { + LookupImpl existing = (LookupImpl)LookupManager.getActiveLookup(editor); + if (existing != null && existing.isCompletion()) { + existing.markReused(); + if (!autopopup) { + existing.setFocused(true); + } + return existing; + } + + LookupImpl lookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).createLookup(editor, LookupElement.EMPTY_ARRAY, "", LookupArranger.DEFAULT); + if (editor.isOneLineMode()) { + lookup.setCancelOnClickOutside(true); + lookup.setCancelOnOtherWindowOpen(true); + lookup.setResizable(false); + lookup.setForceLightweightPopup(false); + } + lookup.setFocused(shouldFocusLookup(parameters)); + return lookup; + } + + private void doComplete(final int invocationCount, CompletionInitializationContext initContext) { + final Editor editor = initContext.getEditor(); + final OffsetMap offsetMap = initContext.getOffsetMap(); + final CompletionContext context = new CompletionContext(initContext.getProject(), editor, initContext.getFile(), offsetMap); + + final CompletionParameters parameters = createCompletionParameters(context, initContext.getFileCopyPatcher(), invocationCount); + + final LookupImpl lookup = obtainLookup(editor, parameters); + + final Semaphore freezeSemaphore = new Semaphore(); + freezeSemaphore.down(); + final CompletionProgressIndicator indicator = new CompletionProgressIndicator(editor, parameters, this, freezeSemaphore, offsetMap, lookup); + + final AtomicReference data = startCompletionThread(parameters, indicator, initContext); + + if (!invokedExplicitly) { + indicator.notifyBackgrounded(); + return; + } + + if (freezeSemaphore.waitFor(2000)) { + final LookupElement[] allItems = data.get(); + if (allItems != null) { // the completion is really finished, now we may auto-insert or show lookup + completionFinished(initContext.getStartOffset(), initContext.getSelectionEndOffset(), indicator, allItems); + return; + } + } + + indicator.notifyBackgrounded(); + indicator.showLookup(); + } + + private static AtomicReference startCompletionThread(final CompletionParameters parameters, + final CompletionProgressIndicator indicator, + final CompletionInitializationContext initContext) { + + final ApplicationAdapter listener = new ApplicationAdapter() { + @Override + public void beforeWriteActionStart(Object action) { + indicator.cancelByWriteAction(); + } + }; + ApplicationManager.getApplication().addApplicationListener(listener); + + final Semaphore startSemaphore = new Semaphore(); + startSemaphore.down(); + startSemaphore.down(); + + spawnProcess(ProgressWrapper.wrap(indicator), new Runnable() { + public void run() { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + startSemaphore.up(); + indicator.duringCompletion(initContext); + } + }); + } + }); + + final AtomicReference data = new AtomicReference(null); + spawnProcess(indicator, new Runnable() { + public void run() { + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + startSemaphore.up(); + ProgressManager.checkCanceled(); + + final LookupElement[] result = CompletionService.getCompletionService().performCompletion(parameters, new Consumer() { + public void consume(final LookupElement lookupElement) { + indicator.addItem(lookupElement); + } + }); + + indicator.ensureDuringCompletionPassed(); + + data.set(result); + } + finally { + ApplicationManager.getApplication().removeApplicationListener(listener); + } + } + }); + } + catch (ProcessCanceledException ignored) { + } + } + }); + + startSemaphore.waitFor(); + return data; + } + + + + private static void spawnProcess(final ProgressIndicator indicator, final Runnable process) { + final Runnable computeRunnable = new Runnable() { + public void run() { + ProgressManager.getInstance().runProcess(process, indicator); + } + }; + + if (ApplicationManager.getApplication().isUnitTestMode()) { + computeRunnable.run(); + } else { + ApplicationManager.getApplication().executeOnPooledThread(computeRunnable); + } + } + + private CompletionParameters createCompletionParameters(final CompletionContext context, final FileCopyPatcher patcher, int invocationCount) { + final Ref> ref = Ref.create(null); + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + ref.set(insertDummyIdentifier(context, patcher, context.file, context.editor)); + } + }); + } + }); + + final PsiElement insertedElement = ref.get().getSecond(); + final CompletionContext newContext = ref.get().getFirst(); + insertedElement.putUserData(CompletionContext.COMPLETION_CONTEXT_KEY, newContext); + + return new CompletionParameters(insertedElement, newContext.file, myCompletionType, newContext.getStartOffset(), invocationCount); + } + + private AutoCompletionDecision shouldAutoComplete( + final CompletionProgressIndicator indicator, + final LookupElement[] items) { + if (!invokedExplicitly) { + return AutoCompletionDecision.SHOW_LOOKUP; + } + final CompletionParameters parameters = indicator.getParameters(); + final LookupElement item = items[0]; + if (items.length == 1) { + final AutoCompletionPolicy policy = getAutocompletionPolicy(item); + if (policy == AutoCompletionPolicy.NEVER_AUTOCOMPLETE) return AutoCompletionDecision.SHOW_LOOKUP; + if (policy == AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE) return AutoCompletionDecision.insertItem(item); + } + if (!isAutocompleteOnInvocation(parameters.getCompletionType())) { + return AutoCompletionDecision.SHOW_LOOKUP; + } + if (isInsideIdentifier(indicator.getOffsetMap())) { + return AutoCompletionDecision.SHOW_LOOKUP; + } + if (items.length == 1 && getAutocompletionPolicy(item) == AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE) { + return AutoCompletionDecision.insertItem(item); + } + + for (final CompletionContributor contributor : CompletionContributor.forParameters(parameters)) { + final AutoCompletionDecision decision = contributor.handleAutoCompletionPossibility(new AutoCompletionContext(parameters, items, indicator.getOffsetMap())); + if (decision != null) { + return decision; + } + } + + return AutoCompletionDecision.SHOW_LOOKUP; + } + + @Nullable + private static AutoCompletionPolicy getAutocompletionPolicy(LookupElement element) { + final AutoCompletionPolicy policy = AutoCompletionPolicy.getPolicy(element); + if (policy != null) { + return policy; + } + + final LookupItem item = element.as(LookupItem.class); + if (item != null) { + return item.getAutoCompletionPolicy(); + } + + return null; + } + + private static boolean isInsideIdentifier(final OffsetMap offsetMap) { + return offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != offsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); + } + + + protected void completionFinished(final int offset1, + final int offset2, + final CompletionProgressIndicator indicator, + final LookupElement[] items) { + if (CompletionAutoPopupHandler.ourTestingAutopopup) { + System.out.println("CodeCompletionHandlerBase.completionFinished"); + System.out.println("items " + Arrays.asList(items)); + } + + if (items.length == 0) { + LookupManager.getInstance(indicator.getProject()).hideActiveLookup(); + handleEmptyLookup(indicator.getProject(), indicator.getEditor(), indicator.getParameters(), indicator); + return; + } + + final AutoCompletionDecision decision = shouldAutoComplete(indicator, items); + if (decision == AutoCompletionDecision.SHOW_LOOKUP) { + indicator.showLookup(); + if (isAutocompleteCommonPrefixOnInvocation() && items.length > 1) { + indicator.fillInCommonPrefix(false); + } + } + else if (decision instanceof AutoCompletionDecision.InsertItem) { + final LookupElement item = ((AutoCompletionDecision.InsertItem)decision).getElement(); + indicator.closeAndFinish(true); + indicator.rememberDocumentState(); + indicator.getOffsetMap() + .addOffset(CompletionInitializationContext.START_OFFSET, (offset1 - item.getPrefixMatcher().getPrefix().length())); + handleSingleItem(offset2, indicator, items, item.getLookupString(), item); + + // the insert handler may have started a live template with completion + if (CompletionService.getCompletionService() == null) { + indicator.liveAfterDeath(null); + } + } + } + + protected static void handleSingleItem(final int offset2, final CompletionProgressIndicator context, final LookupElement[] items, final String _uniqueText, final LookupElement item) { + new WriteCommandAction(context.getProject()) { + protected void run(Result result) throws Throwable { + String uniqueText = _uniqueText; + + if (item.getObject() instanceof DeferredUserLookupValue && item.as(LookupItem.class) != null) { + if (!((DeferredUserLookupValue)item.getObject()).handleUserSelection(item.as(LookupItem.class), context.getProject())) { + return; + } + + uniqueText = item.getLookupString(); // text may be not ready yet + } + + if (!StringUtil.startsWithIgnoreCase(uniqueText, item.getPrefixMatcher().getPrefix())) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS); + } + + insertLookupString(offset2, uniqueText, context.getEditor(), context.getOffsetMap()); + context.getEditor().getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + + lookupItemSelected(context, item, Lookup.AUTO_INSERT_SELECT_CHAR, Arrays.asList(items)); + } + }.execute(); + } + + private static void insertLookupString(final int currentOffset, final String newText, final Editor editor, final OffsetMap offsetMap) { + editor.getDocument().replaceString(offsetMap.getOffset(CompletionInitializationContext.START_OFFSET), currentOffset, newText); + editor.getCaretModel().moveToOffset(offsetMap.getOffset(CompletionInitializationContext.START_OFFSET) + newText.length()); + editor.getSelectionModel().removeSelection(); + } + + protected static void selectLookupItem(final LookupElement item, final char completionChar, final CompletionProgressIndicator context, final List items) { + final int caretOffset = context.getEditor().getCaretModel().getOffset(); + + context.getOffsetMap().addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, caretOffset); + final int idEnd = context.getIdentifierEndOffset(); + final int identifierEndOffset = + CompletionUtil.isOverwrite(item, completionChar) && context.getSelectionEndOffset() == idEnd ? + caretOffset : + Math.max(caretOffset, idEnd); + context.getOffsetMap().addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, identifierEndOffset); + lookupItemSelected(context, item, completionChar, items); + } + + private Pair insertDummyIdentifier(final CompletionContext context, + final FileCopyPatcher patcher, + final PsiFile file, final Editor originalEditor) { + PsiFile oldFileCopy = createFileCopy(file); + PsiFile hostFile = InjectedLanguageUtil.getTopLevelFile(oldFileCopy); + boolean wasInjected = hostFile != oldFileCopy; + Project project = hostFile.getProject(); + InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(project); + // is null in tests + int hostStartOffset = injectedLanguageManager == null + ? context.getStartOffset() + : injectedLanguageManager.injectedToHost(oldFileCopy, context.getStartOffset()); + + Document document = oldFileCopy.getViewProvider().getDocument(); + assert document != null; + patcher.patchFileCopy(oldFileCopy, document, context.getOffsetMap()); + PsiDocumentManager.getInstance(project).commitDocument(document); + PsiFile fileCopy = InjectedLanguageUtil.findInjectedPsiNoCommit(hostFile, hostStartOffset); + if (fileCopy == null) { + PsiElement elementAfterCommit = findElementAt(hostFile, hostStartOffset); + if (wasInjected) { + LOG.error("No injected fragment found at offset " + hostStartOffset + " in the patched file copy, found: " + elementAfterCommit); + } + fileCopy = elementAfterCommit == null ? oldFileCopy : elementAfterCommit.getContainingFile(); + } + + if (oldFileCopy != fileCopy && !wasInjected) { + // newly inserted identifier can well end up in the injected language region + Editor editor = EditorFactory.getInstance().createEditor(document, project); + Editor newEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, hostFile, context.getStartOffset()); + if (newEditor instanceof EditorWindow) { + EditorWindow injectedEditor = (EditorWindow)newEditor; + PsiFile injectedFile = injectedEditor.getInjectedFile(); + final OffsetMap map = new OffsetMap(newEditor.getDocument()); + final OffsetMap oldMap = context.getOffsetMap(); + for (final OffsetKey key : oldMap.keySet()) { + map.addOffset(key, injectedEditor.logicalPositionToOffset(injectedEditor.hostToInjected(originalEditor.offsetToLogicalPosition(oldMap.getOffset(key))))); + } + CompletionContext newContext = new CompletionContext(file.getProject(), injectedEditor, injectedFile, map); + int injectedOffset = newContext.getStartOffset(); + PsiElement element = findElementAt(injectedFile, injectedOffset); + + int toHost = injectedLanguageManager == null ? hostStartOffset : injectedLanguageManager.injectedToHost(injectedFile, injectedOffset); + // maybe injected fragment is ended before hostStartOffset + if (element != null && toHost == hostStartOffset) { + EditorFactory.getInstance().releaseEditor(editor); + return Pair.create(newContext, element); + } + + PsiElement elementAfterCommit = findElementAt(hostFile, hostStartOffset); + fileCopy = elementAfterCommit == null ? oldFileCopy : elementAfterCommit.getContainingFile(); + } + EditorFactory.getInstance().releaseEditor(editor); + } + PsiElement element = findElementAt(fileCopy, context.getStartOffset()); + if (element == null) { + LOG.error("offset " + context.getStartOffset() + " at:\ntext=\"" + fileCopy.getText() + "\"\ninstance=" + fileCopy); + } + return Pair.create(context, element); + } + + private static PsiElement findElementAt(final PsiFile fileCopy, int startOffset) { + PsiElement element = fileCopy.findElementAt(startOffset); + if (element instanceof MetadataPsiElementBase) { + final PsiElement source = ((MetadataPsiElementBase)element).getSourceElement(); + if (source != null) { + return source.findElementAt(startOffset - source.getTextRange().getStartOffset()); + } + } + return element; + } + + + private boolean isAutocompleteCommonPrefixOnInvocation() { + return invokedExplicitly && CodeInsightSettings.getInstance().AUTOCOMPLETE_COMMON_PREFIX; + } + + protected void handleEmptyLookup(Project project, Editor editor, final CompletionParameters parameters, final CompletionProgressIndicator indicator) { + if (ApplicationManager.getApplication().isUnitTestMode()) return; + if (!invokedExplicitly) { + return; + } + + for (final CompletionContributor contributor : CompletionContributor.forParameters(parameters)) { + final String text = contributor.handleEmptyLookup(parameters, editor); + if (StringUtil.isNotEmpty(text)) { + final EditorHintListener listener = new EditorHintListener() { + public void hintShown(final Project project, final LightweightHint hint, final int flags) { + indicator.liveAfterDeath(hint); + } + }; + final MessageBusConnection connection = project.getMessageBus().connect(); + connection.subscribe(EditorHintListener.TOPIC, listener); + HintManager.getInstance().showErrorHint(editor, text); + connection.disconnect(); + break; + } + } + DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project); + if (codeAnalyzer != null) { + codeAnalyzer.updateVisibleHighlighters(editor); + } + } + + private static void lookupItemSelected(final CompletionProgressIndicator context, @NotNull final LookupElement item, final char completionChar, + final List items) { + if (context.getHandler().autopopup) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_BASIC); + } + + final Editor editor = context.getEditor(); + final PsiFile file = context.getParameters().getOriginalFile(); + final InsertionContext context1 = new InsertionContext(context.getOffsetMap(), completionChar, items.toArray(new LookupElement[items.size()]), file, editor); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + final int idEndOffset = context.getIdentifierEndOffset(); + if (idEndOffset != context.getSelectionEndOffset() && CompletionUtil.isOverwrite(item, completionChar)) { + editor.getDocument().deleteString(context.getSelectionEndOffset(), idEndOffset); + } + + PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); + item.handleInsert(context1); + PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); + + if (context1.shouldAddCompletionChar() && + completionChar != Lookup.AUTO_INSERT_SELECT_CHAR && completionChar != Lookup.REPLACE_SELECT_CHAR && + completionChar != Lookup.NORMAL_SELECT_CHAR && completionChar != Lookup.COMPLETE_STATEMENT_SELECT_CHAR) { + TailType.insertChar(editor, context1.getTailOffset(), completionChar); + } + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + }); + final Runnable runnable = context1.getLaterRunnable(); + if (runnable != null) { + final Runnable runnable1 = new Runnable() { + public void run() { + final Project project = context1.getProject(); + if (project.isDisposed()) return; + runnable.run(); + } + }; + if (!ApplicationManager.getApplication().isUnitTestMode()) { + ApplicationManager.getApplication().invokeLater(runnable1); + } else { + runnable1.run(); + } + } + } + + public boolean startInWriteAction() { + return false; + } + + public static final Key> FILE_COPY_KEY = Key.create("CompletionFileCopy"); + + protected PsiFile createFileCopy(PsiFile file) { + final VirtualFile virtualFile = file.getVirtualFile(); + if (file.isPhysical() && virtualFile != null && virtualFile.getFileSystem() == LocalFileSystem.getInstance() + // must not cache injected file copy, since it does not reflect changes in host document + && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) { + final SoftReference reference = file.getUserData(FILE_COPY_KEY); + if (reference != null) { + final PsiFile copy = reference.get(); + if (copy != null && copy.isValid() && copy.getClass().equals(file.getClass())) { + final Document document = copy.getViewProvider().getDocument(); + assert document != null; + final String oldDocumentText = document.getText(); + final String oldCopyText = copy.getText(); + final String newText = file.getText(); + document.setText(newText); + try { + PsiDocumentManager.getInstance(copy.getProject()).commitDocument(document); + return copy; + } + catch (Throwable e) { + document.setText(""); + if (((ApplicationEx)ApplicationManager.getApplication()).isInternal()) { + final StringBuilder sb = new StringBuilder(); + boolean oldsAreSame = Comparing.equal(oldCopyText, oldDocumentText); + if (oldsAreSame) { + sb.append("oldCopyText == oldDocumentText"); + } + else { + sb.append("oldCopyText != oldDocumentText"); + sb.append("\n--- oldCopyText ------------------------------------------------\n").append(oldCopyText); + sb.append("\n--- oldDocumentText ------------------------------------------------\n").append(oldDocumentText); + } + if (Comparing.equal(oldCopyText, newText)) { + sb.insert(0, "newText == oldCopyText; "); + } + else if (!oldsAreSame && Comparing.equal(oldDocumentText, newText)) { + sb.insert(0, "newText == oldDocumentText; "); + } + else { + sb.insert(0, "newText != oldCopyText, oldDocumentText; "); + if (oldsAreSame) { + sb.append("\n--- oldCopyText ------------------------------------------------\n").append(oldCopyText); + } + sb.append("\n--- newText ------------------------------------------------\n").append(newText); + } + LOG.error(sb.toString(), e); + } + } + } + } + } + + final PsiFile copy = (PsiFile)file.copy(); + file.putUserData(FILE_COPY_KEY, new SoftReference(copy)); + return copy; + } + + private static boolean isAutocompleteOnInvocation(final CompletionType type) { + final CodeInsightSettings settings = CodeInsightSettings.getInstance(); + switch (type) { + case CLASS_NAME: return settings.AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION; + case SMART: return settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION; + case BASIC: + default: return settings.AUTOCOMPLETE_ON_CODE_COMPLETION; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionData.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionData.java index 91fd21cb2155..4384f57240b9 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionData.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionData.java @@ -1,372 +1,372 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.lookup.*; -import com.intellij.codeInsight.template.Template; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.paths.PsiDynaReference; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.TextRange; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.*; -import com.intellij.psi.filters.ContextGetter; -import com.intellij.psi.filters.ElementFilter; -import com.intellij.psi.filters.TrueFilter; -import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; -import com.intellij.psi.meta.PsiMetaData; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.ReflectionCache; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -import static com.intellij.patterns.StandardPatterns.character; -import static com.intellij.patterns.StandardPatterns.not; - -/** - * @deprecated see {@link com.intellij.codeInsight.completion.CompletionContributor} - */ -public class CompletionData { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionData"); - private final Set myFinalScopes = new HashSet(); - private final List myCompletionVariants = new ArrayList(); - - protected CompletionData(){ } - - protected final void declareFinalScope(Class scopeClass){ - myFinalScopes.add(scopeClass); - } - - protected boolean isScopeFinal(Class scopeClass){ - if(myFinalScopes.contains(scopeClass)) - return true; - - for (final Class myFinalScope : myFinalScopes) { - if (ReflectionCache.isAssignable(myFinalScope, scopeClass)) { - return true; - } - } - return false; - } - - private boolean isScopeAcceptable(PsiElement scope){ - - for (final CompletionVariant variant : myCompletionVariants) { - if (variant.isScopeAcceptable(scope)) { - return true; - } - } - return false; - } - - protected void defineScopeEquivalence(Class scopeClass, Class equivClass){ - final Iterator iter = myCompletionVariants.iterator(); - if(isScopeFinal(scopeClass)){ - declareFinalScope(equivClass); - } - - while(iter.hasNext()){ - final CompletionVariant variant = iter.next(); - if(variant.isScopeClassAcceptable(scopeClass)){ - variant.includeScopeClass(equivClass, variant.isScopeClassFinal(scopeClass)); - } - } - } - - /** - * @deprecated - * @see com.intellij.codeInsight.completion.CompletionContributor - */ - protected void registerVariant(CompletionVariant variant){ - myCompletionVariants.add(variant); - } - - public void completeReference(final PsiReference reference, final Set set, @NotNull final PsiElement position, final PsiFile file, - final int offset){ - final CompletionVariant[] variants = findVariants(position, file); - boolean hasApplicableVariants = false; - for (CompletionVariant variant : variants) { - if (variant.hasReferenceFilter()) { - variant.addReferenceCompletions(reference, position, set, file, CompletionData.this); - hasApplicableVariants = true; - } - } - - if (!hasApplicableVariants) { - myGenericVariant.addReferenceCompletions(reference, position, set, file, CompletionData.this); - } - } - - public void addKeywordVariants(Set set, PsiElement position, final PsiFile file) { - ContainerUtil.addAll(set, findVariants(position, file)); - } - - public void completeKeywordsBySet(final Set set, Set variants, final PsiElement position, - final PrefixMatcher matcher, - final PsiFile file){ - for (final CompletionVariant variant : variants) { - variant.addKeywords(set, position, matcher, file, CompletionData.this); - } - } - - public String findPrefix(PsiElement insertedElement, int offsetInFile){ - return findPrefixStatic(insertedElement, offsetInFile); - } - - public CompletionVariant[] findVariants(final PsiElement position, final PsiFile file){ - final List variants = new ArrayList(); - PsiElement scope = position; - if(scope == null){ - scope = file; - } - while (scope != null) { - boolean breakFlag = false; - if (isScopeAcceptable(scope)){ - - for (final CompletionVariant variant : myCompletionVariants) { - if (variant.isVariantApplicable(position, scope) && !variants.contains(variant)) { - variants.add(variant); - if (variant.isScopeFinal(scope)) { - breakFlag = true; - } - } - } - } - if(breakFlag || isScopeFinal(scope.getClass())) - break; - scope = scope.getContext(); - if (scope instanceof PsiDirectory) break; - } - return variants.toArray(new CompletionVariant[variants.size()]); - } - - protected final CompletionVariant myGenericVariant = new CompletionVariant() { - public void addReferenceCompletions(PsiReference reference, PsiElement position, Set set, final PsiFile file, - final CompletionData completionData) { - completeReference(reference, position, set, TailType.NONE, file, TrueFilter.INSTANCE, this); - } - }; - - @Nullable - public static String getReferencePrefix(@NotNull PsiElement insertedElement, int offsetInFile) { - final PsiReference ref = insertedElement.getContainingFile().findReferenceAt(offsetInFile); - if(ref != null) { - final List ranges = ReferenceRange.getRanges(ref); - final PsiElement element = ref.getElement(); - final int elementStart = element.getTextRange().getStartOffset(); - for (TextRange refRange : ranges) { - if (refRange.contains(offsetInFile - elementStart)) { - final int endIndex = offsetInFile - elementStart; - final int beginIndex = refRange.getStartOffset(); - if (beginIndex > endIndex) { - LOG.error("Inconsistent reference (found at offset not included in its range): ref=" + ref + " element=" + element + " text=" + element.getText()); - } - if (beginIndex < 0) { - LOG.error("Inconsistent reference (begin < 0): ref=" + ref + " element=" + element + "; begin=" + beginIndex + " text=" + element.getText()); - } - LOG.assertTrue(endIndex >= 0); - return element.getText().substring(beginIndex, endIndex); - } - } - } - return null; - } - - public static String findPrefixStatic(final PsiElement insertedElement, final int offsetInFile, ElementPattern prefixStartTrim) { - if(insertedElement == null) return ""; - - final String prefix = getReferencePrefix(insertedElement, offsetInFile); - if (prefix != null) return prefix; - - if (insertedElement instanceof PsiPlainText || insertedElement instanceof PsiComment) { - return CompletionUtil.findJavaIdentifierPrefix(insertedElement, offsetInFile); - } - - return findPrefixDefault(insertedElement, offsetInFile, prefixStartTrim); - } - - public static String findPrefixStatic(final PsiElement insertedElement, final int offsetInFile) { - return findPrefixStatic(insertedElement, offsetInFile, not(character().javaIdentifierPart())); - } - - protected static String findPrefixDefault(final PsiElement insertedElement, final int offset, @NotNull final ElementPattern trimStart) { - String substr = insertedElement.getText().substring(0, offset - insertedElement.getTextRange().getStartOffset()); - if (substr.length() == 0 || Character.isWhitespace(substr.charAt(substr.length() - 1))) return ""; - - substr = substr.trim(); - - int i = 0; - while (substr.length() > i && trimStart.accepts(substr.charAt(i))) i++; - return substr.substring(i).trim(); - } - - public static LookupElement objectToLookupItem(Object object) { - if (object instanceof LookupElement) return (LookupElement)object; - - String s = null; - TailType tailType = TailType.NONE; - if (object instanceof PsiElement){ - s = PsiUtilBase.getName((PsiElement) object); - } - else if (object instanceof PsiMetaData) { - s = ((PsiMetaData)object).getName(); - } - else if (object instanceof String) { - s = (String)object; - } - else if (object instanceof Template) { - s = ((Template) object).getKey(); - } - else if (object instanceof PresentableLookupValue) { - s = ((PresentableLookupValue)object).getPresentation(); - } - else { - LOG.error("Null string for object: " + object + " of class " + (object != null ? object.getClass() : null)); - } - - LookupItem item = new LookupItem(object, s); - - if (object instanceof LookupValueWithUIHint && ((LookupValueWithUIHint) object).isBold()) { - item.setBold(); - } - if (object instanceof LookupValueWithTail) { - item.setAttribute(LookupItem.TAIL_TEXT_ATTR, " " + ((LookupValueWithTail)object).getTailText()); - } - item.setAttribute(CompletionUtil.TAIL_TYPE_ATTR, tailType); - return item; - } - - - protected void addLookupItem(Set set, TailType tailType, @NotNull Object completion, final PsiFile file, - final CompletionVariant variant) { - LookupElement ret = objectToLookupItem(completion); - if (ret == null) return; - if (!(ret instanceof LookupItem)) { - set.add(ret); - return; - } - - LookupItem item = (LookupItem)ret; - - final InsertHandler insertHandler = variant.getInsertHandler(); - if(insertHandler != null && item.getInsertHandler() == null) { - item.setInsertHandler(insertHandler); - item.setTailType(TailType.UNKNOWN); - } - else if (tailType != TailType.NONE) { - item.setTailType(tailType); - } - final Map itemProperties = variant.getItemProperties(); - for (final Object key : itemProperties.keySet()) { - item.setAttribute(key, itemProperties.get(key)); - } - - set.add(ret); - } - - protected void completeReference(final PsiReference reference, final PsiElement position, final Set set, final TailType tailType, - final PsiFile file, - final ElementFilter filter, - final CompletionVariant variant) { - if (reference instanceof PsiMultiReference) { - for (PsiReference ref : getReferences((PsiMultiReference)reference)) { - completeReference(ref, position, set, tailType, file, filter, variant); - } - } - else if (reference instanceof PsiDynaReference) { - for (PsiReference ref : ((PsiDynaReference)reference).getReferences()) { - completeReference(ref, position, set, tailType, file, filter, variant); - } - } - else{ - final Object[] completions = reference.getVariants(); - if(completions == null) return; - - for (Object completion : completions) { - if (completion == null) { - LOG.error("Position=" + position + "\n;Reference=" + reference + "\n;variants=" + Arrays.toString(completions)); - } - if (completion instanceof PsiElement) { - final PsiElement psiElement = (PsiElement)completion; - if (filter.isClassAcceptable(psiElement.getClass()) && filter.isAcceptable(psiElement, position)) { - addLookupItem(set, tailType, completion, file, variant); - } - } - else { - if (completion instanceof LookupItem) { - final Object o = ((LookupItem)completion).getObject(); - if (o instanceof PsiElement) { - if (!filter.isClassAcceptable(o.getClass()) || !filter.isAcceptable(o, position)) continue; - } - } - addLookupItem(set, tailType, completion, file, variant); - } - } - } - } - - protected PsiReference[] getReferences(final PsiMultiReference multiReference) { - final PsiReference[] references = multiReference.getReferences(); - final List hard = ContainerUtil.findAll(references, new Condition() { - public boolean value(final PsiReference object) { - return !object.isSoft(); - } - }); - if (!hard.isEmpty()) { - return hard.toArray(new PsiReference[hard.size()]); - } - return references; - } - - protected void addKeywords(final Set set, final PsiElement position, final PrefixMatcher matcher, final PsiFile file, - final CompletionVariant variant, final Object comp, final TailType tailType) { - if (comp instanceof String) { - addKeyword(set, tailType, comp, matcher, file, variant); - } - else { - final CompletionContext context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY); - if (comp instanceof ContextGetter) { - final Object[] elements = ((ContextGetter)comp).get(position, context); - for (Object element : elements) { - addLookupItem(set, tailType, element, file, variant); - } - } - // TODO: KeywordChooser -> ContextGetter - else if (comp instanceof KeywordChooser) { - final String[] keywords = ((KeywordChooser)comp).getKeywords(context, position); - for (String keyword : keywords) { - addKeyword(set, tailType, keyword, matcher, file, variant); - } - } - } - } - - private void addKeyword(Set set, final TailType tailType, final Object comp, final PrefixMatcher matcher, - final PsiFile file, - final CompletionVariant variant) { - for (final LookupElement item : set) { - if (item.getObject().toString().equals(comp.toString())) { - return; - } - } - addLookupItem(set, tailType, comp, file, variant); - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.lookup.*; +import com.intellij.codeInsight.template.Template; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.paths.PsiDynaReference; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.TextRange; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.*; +import com.intellij.psi.filters.ContextGetter; +import com.intellij.psi.filters.ElementFilter; +import com.intellij.psi.filters.TrueFilter; +import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; +import com.intellij.psi.meta.PsiMetaData; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.ReflectionCache; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +import static com.intellij.patterns.StandardPatterns.character; +import static com.intellij.patterns.StandardPatterns.not; + +/** + * @deprecated see {@link com.intellij.codeInsight.completion.CompletionContributor} + */ +public class CompletionData { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionData"); + private final Set myFinalScopes = new HashSet(); + private final List myCompletionVariants = new ArrayList(); + + protected CompletionData(){ } + + protected final void declareFinalScope(Class scopeClass){ + myFinalScopes.add(scopeClass); + } + + protected boolean isScopeFinal(Class scopeClass){ + if(myFinalScopes.contains(scopeClass)) + return true; + + for (final Class myFinalScope : myFinalScopes) { + if (ReflectionCache.isAssignable(myFinalScope, scopeClass)) { + return true; + } + } + return false; + } + + private boolean isScopeAcceptable(PsiElement scope){ + + for (final CompletionVariant variant : myCompletionVariants) { + if (variant.isScopeAcceptable(scope)) { + return true; + } + } + return false; + } + + protected void defineScopeEquivalence(Class scopeClass, Class equivClass){ + final Iterator iter = myCompletionVariants.iterator(); + if(isScopeFinal(scopeClass)){ + declareFinalScope(equivClass); + } + + while(iter.hasNext()){ + final CompletionVariant variant = iter.next(); + if(variant.isScopeClassAcceptable(scopeClass)){ + variant.includeScopeClass(equivClass, variant.isScopeClassFinal(scopeClass)); + } + } + } + + /** + * @deprecated + * @see com.intellij.codeInsight.completion.CompletionContributor + */ + protected void registerVariant(CompletionVariant variant){ + myCompletionVariants.add(variant); + } + + public void completeReference(final PsiReference reference, final Set set, @NotNull final PsiElement position, final PsiFile file, + final int offset){ + final CompletionVariant[] variants = findVariants(position, file); + boolean hasApplicableVariants = false; + for (CompletionVariant variant : variants) { + if (variant.hasReferenceFilter()) { + variant.addReferenceCompletions(reference, position, set, file, CompletionData.this); + hasApplicableVariants = true; + } + } + + if (!hasApplicableVariants) { + myGenericVariant.addReferenceCompletions(reference, position, set, file, CompletionData.this); + } + } + + public void addKeywordVariants(Set set, PsiElement position, final PsiFile file) { + ContainerUtil.addAll(set, findVariants(position, file)); + } + + public void completeKeywordsBySet(final Set set, Set variants, final PsiElement position, + final PrefixMatcher matcher, + final PsiFile file){ + for (final CompletionVariant variant : variants) { + variant.addKeywords(set, position, matcher, file, CompletionData.this); + } + } + + public String findPrefix(PsiElement insertedElement, int offsetInFile){ + return findPrefixStatic(insertedElement, offsetInFile); + } + + public CompletionVariant[] findVariants(final PsiElement position, final PsiFile file){ + final List variants = new ArrayList(); + PsiElement scope = position; + if(scope == null){ + scope = file; + } + while (scope != null) { + boolean breakFlag = false; + if (isScopeAcceptable(scope)){ + + for (final CompletionVariant variant : myCompletionVariants) { + if (variant.isVariantApplicable(position, scope) && !variants.contains(variant)) { + variants.add(variant); + if (variant.isScopeFinal(scope)) { + breakFlag = true; + } + } + } + } + if(breakFlag || isScopeFinal(scope.getClass())) + break; + scope = scope.getContext(); + if (scope instanceof PsiDirectory) break; + } + return variants.toArray(new CompletionVariant[variants.size()]); + } + + protected final CompletionVariant myGenericVariant = new CompletionVariant() { + public void addReferenceCompletions(PsiReference reference, PsiElement position, Set set, final PsiFile file, + final CompletionData completionData) { + completeReference(reference, position, set, TailType.NONE, file, TrueFilter.INSTANCE, this); + } + }; + + @Nullable + public static String getReferencePrefix(@NotNull PsiElement insertedElement, int offsetInFile) { + final PsiReference ref = insertedElement.getContainingFile().findReferenceAt(offsetInFile); + if(ref != null) { + final List ranges = ReferenceRange.getRanges(ref); + final PsiElement element = ref.getElement(); + final int elementStart = element.getTextRange().getStartOffset(); + for (TextRange refRange : ranges) { + if (refRange.contains(offsetInFile - elementStart)) { + final int endIndex = offsetInFile - elementStart; + final int beginIndex = refRange.getStartOffset(); + if (beginIndex > endIndex) { + LOG.error("Inconsistent reference (found at offset not included in its range): ref=" + ref + " element=" + element + " text=" + element.getText()); + } + if (beginIndex < 0) { + LOG.error("Inconsistent reference (begin < 0): ref=" + ref + " element=" + element + "; begin=" + beginIndex + " text=" + element.getText()); + } + LOG.assertTrue(endIndex >= 0); + return element.getText().substring(beginIndex, endIndex); + } + } + } + return null; + } + + public static String findPrefixStatic(final PsiElement insertedElement, final int offsetInFile, ElementPattern prefixStartTrim) { + if(insertedElement == null) return ""; + + final String prefix = getReferencePrefix(insertedElement, offsetInFile); + if (prefix != null) return prefix; + + if (insertedElement instanceof PsiPlainText || insertedElement instanceof PsiComment) { + return CompletionUtil.findJavaIdentifierPrefix(insertedElement, offsetInFile); + } + + return findPrefixDefault(insertedElement, offsetInFile, prefixStartTrim); + } + + public static String findPrefixStatic(final PsiElement insertedElement, final int offsetInFile) { + return findPrefixStatic(insertedElement, offsetInFile, not(character().javaIdentifierPart())); + } + + protected static String findPrefixDefault(final PsiElement insertedElement, final int offset, @NotNull final ElementPattern trimStart) { + String substr = insertedElement.getText().substring(0, offset - insertedElement.getTextRange().getStartOffset()); + if (substr.length() == 0 || Character.isWhitespace(substr.charAt(substr.length() - 1))) return ""; + + substr = substr.trim(); + + int i = 0; + while (substr.length() > i && trimStart.accepts(substr.charAt(i))) i++; + return substr.substring(i).trim(); + } + + public static LookupElement objectToLookupItem(Object object) { + if (object instanceof LookupElement) return (LookupElement)object; + + String s = null; + TailType tailType = TailType.NONE; + if (object instanceof PsiElement){ + s = PsiUtilBase.getName((PsiElement) object); + } + else if (object instanceof PsiMetaData) { + s = ((PsiMetaData)object).getName(); + } + else if (object instanceof String) { + s = (String)object; + } + else if (object instanceof Template) { + s = ((Template) object).getKey(); + } + else if (object instanceof PresentableLookupValue) { + s = ((PresentableLookupValue)object).getPresentation(); + } + else { + LOG.error("Null string for object: " + object + " of class " + (object != null ? object.getClass() : null)); + } + + LookupItem item = new LookupItem(object, s); + + if (object instanceof LookupValueWithUIHint && ((LookupValueWithUIHint) object).isBold()) { + item.setBold(); + } + if (object instanceof LookupValueWithTail) { + item.setAttribute(LookupItem.TAIL_TEXT_ATTR, " " + ((LookupValueWithTail)object).getTailText()); + } + item.setAttribute(CompletionUtil.TAIL_TYPE_ATTR, tailType); + return item; + } + + + protected void addLookupItem(Set set, TailType tailType, @NotNull Object completion, final PsiFile file, + final CompletionVariant variant) { + LookupElement ret = objectToLookupItem(completion); + if (ret == null) return; + if (!(ret instanceof LookupItem)) { + set.add(ret); + return; + } + + LookupItem item = (LookupItem)ret; + + final InsertHandler insertHandler = variant.getInsertHandler(); + if(insertHandler != null && item.getInsertHandler() == null) { + item.setInsertHandler(insertHandler); + item.setTailType(TailType.UNKNOWN); + } + else if (tailType != TailType.NONE) { + item.setTailType(tailType); + } + final Map itemProperties = variant.getItemProperties(); + for (final Object key : itemProperties.keySet()) { + item.setAttribute(key, itemProperties.get(key)); + } + + set.add(ret); + } + + protected void completeReference(final PsiReference reference, final PsiElement position, final Set set, final TailType tailType, + final PsiFile file, + final ElementFilter filter, + final CompletionVariant variant) { + if (reference instanceof PsiMultiReference) { + for (PsiReference ref : getReferences((PsiMultiReference)reference)) { + completeReference(ref, position, set, tailType, file, filter, variant); + } + } + else if (reference instanceof PsiDynaReference) { + for (PsiReference ref : ((PsiDynaReference)reference).getReferences()) { + completeReference(ref, position, set, tailType, file, filter, variant); + } + } + else{ + final Object[] completions = reference.getVariants(); + if(completions == null) return; + + for (Object completion : completions) { + if (completion == null) { + LOG.error("Position=" + position + "\n;Reference=" + reference + "\n;variants=" + Arrays.toString(completions)); + } + if (completion instanceof PsiElement) { + final PsiElement psiElement = (PsiElement)completion; + if (filter.isClassAcceptable(psiElement.getClass()) && filter.isAcceptable(psiElement, position)) { + addLookupItem(set, tailType, completion, file, variant); + } + } + else { + if (completion instanceof LookupItem) { + final Object o = ((LookupItem)completion).getObject(); + if (o instanceof PsiElement) { + if (!filter.isClassAcceptable(o.getClass()) || !filter.isAcceptable(o, position)) continue; + } + } + addLookupItem(set, tailType, completion, file, variant); + } + } + } + } + + protected PsiReference[] getReferences(final PsiMultiReference multiReference) { + final PsiReference[] references = multiReference.getReferences(); + final List hard = ContainerUtil.findAll(references, new Condition() { + public boolean value(final PsiReference object) { + return !object.isSoft(); + } + }); + if (!hard.isEmpty()) { + return hard.toArray(new PsiReference[hard.size()]); + } + return references; + } + + protected void addKeywords(final Set set, final PsiElement position, final PrefixMatcher matcher, final PsiFile file, + final CompletionVariant variant, final Object comp, final TailType tailType) { + if (comp instanceof String) { + addKeyword(set, tailType, comp, matcher, file, variant); + } + else { + final CompletionContext context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY); + if (comp instanceof ContextGetter) { + final Object[] elements = ((ContextGetter)comp).get(position, context); + for (Object element : elements) { + addLookupItem(set, tailType, element, file, variant); + } + } + // TODO: KeywordChooser -> ContextGetter + else if (comp instanceof KeywordChooser) { + final String[] keywords = ((KeywordChooser)comp).getKeywords(context, position); + for (String keyword : keywords) { + addKeyword(set, tailType, keyword, matcher, file, variant); + } + } + } + } + + private void addKeyword(Set set, final TailType tailType, final Object comp, final PrefixMatcher matcher, + final PsiFile file, + final CompletionVariant variant) { + for (final LookupElement item : set) { + if (item.getObject().toString().equals(comp.toString())) { + return; + } + } + addLookupItem(set, tailType, comp, file, variant); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 81a3ad3b165e..e842a0efbe29 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -1,678 +1,678 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; -import com.intellij.codeInsight.lookup.LookupAdapter; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupEvent; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.CaretModel; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.SelectionModel; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiReference; -import com.intellij.psi.ReferenceRange; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.ui.HintListener; -import com.intellij.ui.LightweightHint; -import com.intellij.util.ObjectUtils; -import com.intellij.util.concurrency.Semaphore; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.update.MergingUpdateQueue; -import com.intellij.util.ui.update.Update; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import javax.swing.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.Collections; -import java.util.EventObject; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * @author peter - */ -public class CompletionProgressIndicator extends ProgressIndicatorBase implements CompletionProcess{ - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionProgressIndicator"); - private final Editor myEditor; - private final CompletionParameters myParameters; - private final CodeCompletionHandlerBase myHandler; - private final LookupImpl myLookup; - private final MergingUpdateQueue myQueue; - private boolean myDisposed; - private boolean myShownLookup; - private volatile int myCount; - private final Update myUpdate = new Update("update") { - public void run() { - updateLookup(); - } - }; - private LightweightHint myHint; - private final Semaphore myFreezeSemaphore; - private Boolean myToRestart; - - private boolean myModifiersReleased; - - private String myOldDocumentText; - private int myOldCaret; - private int myOldStart; - private int myOldEnd; - private boolean myBackgrounded = true; - private OffsetMap myOffsetMap; - private final CopyOnWriteArrayList>> myRestartingPrefixConditions = ContainerUtil.createEmptyCOWList(); - private final LookupAdapter myLookupListener = new LookupAdapter() { - public void itemSelected(LookupEvent event) { - ensureDuringCompletionPassed(); - - finishCompletionProcess(); - - LookupElement item = event.getItem(); - if (item == null) return; - - setMergeCommand(); - - myOffsetMap.addOffset(CompletionInitializationContext.START_OFFSET, myEditor.getCaretModel().getOffset() - item.getLookupString().length()); - CodeCompletionHandlerBase.selectLookupItem(item, event.getCompletionChar(), CompletionProgressIndicator.this, myLookup.getItems()); - } - - - public void lookupCanceled(final LookupEvent event) { - finishCompletionProcess(); - } - }; - private final Semaphore myDuringCompletionSemaphore = new Semaphore(); - - public CompletionProgressIndicator(final Editor editor, CompletionParameters parameters, CodeCompletionHandlerBase handler, Semaphore freezeSemaphore, - final OffsetMap offsetMap, LookupImpl lookup) { - myEditor = editor; - myParameters = parameters; - myHandler = handler; - myFreezeSemaphore = freezeSemaphore; - myOffsetMap = offsetMap; - myLookup = lookup; - - myLookup.setArranger(new CompletionLookupArranger(parameters)); - myShownLookup = lookup.isShown(); - - myLookup.addLookupListener(myLookupListener); - myLookup.setCalculating(true); - - myQueue = new MergingUpdateQueue("completion lookup progress", 100, true, myEditor.getContentComponent()); - - ApplicationManager.getApplication().assertIsDispatchThread(); - registerItself(); - - if (!ApplicationManager.getApplication().isUnitTestMode() && !lookup.isShown()) { - scheduleAdvertising(); - } - - trackModifiers(); - - myDuringCompletionSemaphore.down(); - } - - public OffsetMap getOffsetMap() { - return myOffsetMap; - } - - public int getSelectionEndOffset() { - return getOffsetMap().getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); - } - - void notifyBackgrounded() { - ApplicationManager.getApplication().assertIsDispatchThread(); - myBackgrounded = true; - } - - boolean isBackgrounded() { - ApplicationManager.getApplication().assertIsDispatchThread(); - return myBackgrounded; - } - - void duringCompletion(CompletionInitializationContext initContext) { - try { - ProgressManager.checkCanceled(); - - if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) { - try { - final int selectionEndOffset = initContext.getSelectionEndOffset(); - final PsiReference reference = initContext.getFile().findReferenceAt(selectionEndOffset); - if (reference != null) { - initContext.setReplacementOffset(findReplacementOffset(selectionEndOffset, reference)); - } - } - catch (IndexNotReadyException ignored) { - } - } - - for (CompletionContributor contributor : CompletionContributor.forLanguage(initContext.getPositionLanguage())) { - if (DumbService.getInstance(initContext.getProject()).isDumb() && !DumbService.isDumbAware(contributor)) { - continue; - } - - contributor.duringCompletion(initContext); - } - } catch (ProcessCanceledException ignored) { - } finally { - myDuringCompletionSemaphore.up(); - } - } - - void ensureDuringCompletionPassed() { - myDuringCompletionSemaphore.waitFor(); - } - - private static int findReplacementOffset(int selectionEndOffset, PsiReference reference) { - final List ranges = ReferenceRange.getAbsoluteRanges(reference); - for (TextRange range : ranges) { - if (range.contains(selectionEndOffset)) { - return range.getEndOffset(); - } - } - - return reference.getElement().getTextRange().getStartOffset() + reference.getRangeInElement().getEndOffset(); - } - - - private void scheduleAdvertising() { - ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { - public void run() { - if (isOutdated()) return; //tests? - final List list = ApplicationManager.getApplication().runReadAction(new Computable>() { - public List compute() { - if (isOutdated()) { - return Collections.emptyList(); - } - - return CompletionContributor.forParameters(myParameters); - } - }); - for (final CompletionContributor contributor : list) { - if (myLookup.getAdvertisementText() != null) return; - if (!myLookup.isCalculating() && !myLookup.isVisible()) return; - - String s = ApplicationManager.getApplication().runReadAction(new Computable() { - @Nullable - public String compute() { - if (isOutdated()) { - return null; - } - - return contributor.advertise(myParameters); - } - }); - if (myLookup.getAdvertisementText() != null) return; - - if (s != null) { - myLookup.setAdvertisementText(s); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (isOutdated()) { - return; - } - if (isAutopopupCompletion() && !myShownLookup) { - return; - } - if (!isBackgrounded()) { - return; - } - updateLookup(); - } - }, myQueue.getModalityState()); - return; - } - } - } - }); - } - - private boolean isOutdated() { - return myDisposed || - myEditor.isDisposed() || - !ApplicationManager.getApplication().isUnitTestMode() && myEditor.getComponent().getRootPane() == null; - } - - private void trackModifiers() { - if (isAutopopupCompletion()) { - return; - } - - final JComponent contentComponent = myEditor.getContentComponent(); - contentComponent.addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent e) { - processModifier(e); - } - - public void keyReleased(KeyEvent e) { - processModifier(e); - } - - private void processModifier(KeyEvent e) { - final int code = e.getKeyCode(); - if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT || code == KeyEvent.VK_SHIFT) { - myModifiersReleased = true; - if (myOldDocumentText != null) { - cleanup(); - } - contentComponent.removeKeyListener(this); - } - } - }); - } - - private void setMergeCommand() { - CommandProcessor.getInstance().setCurrentCommandGroupId("Completion" + hashCode()); - } - - public void showLookup() { - updateLookup(); - } - - public CompletionParameters getParameters() { - return myParameters; - } - - private void registerItself() { - CompletionServiceImpl.getCompletionService().setCurrentCompletion(this); - } - - public void liveAfterDeath(@Nullable final LightweightHint hint) { - if (myModifiersReleased || ApplicationManager.getApplication().isUnitTestMode()) { - return; - } - - registerItself(); - myHint = hint; - if (hint != null) { - hint.addHintListener(new HintListener() { - public void hintHidden(final EventObject event) { - hint.removeHintListener(this); - cleanup(); - } - }); - } - final Document document = myEditor.getDocument(); - document.addDocumentListener(new DocumentAdapter() { - @Override - public void beforeDocumentChange(DocumentEvent e) { - document.removeDocumentListener(this); - cleanup(); - } - }); - final SelectionModel selectionModel = myEditor.getSelectionModel(); - selectionModel.addSelectionListener(new SelectionListener() { - public void selectionChanged(SelectionEvent e) { - selectionModel.removeSelectionListener(this); - cleanup(); - } - }); - final CaretModel caretModel = myEditor.getCaretModel(); - caretModel.addCaretListener(new CaretListener() { - public void caretPositionChanged(CaretEvent e) { - caretModel.removeCaretListener(this); - cleanup(); - } - }); - } - - public CodeCompletionHandlerBase getHandler() { - return myHandler; - } - - public LookupImpl getLookup() { - return myLookup; - } - - private void updateLookup() { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (isOutdated()) return; - - if (!myShownLookup) { - myShownLookup = true; - - if (StringUtil.isEmpty(myLookup.getAdvertisementText()) && !isAutopopupCompletion()) { - final String text = DefaultCompletionContributor.getDefaultAdvertisementText(myParameters); - if (text != null) { - myLookup.setAdvertisementText(text); - } - } - - myLookup.show(); - } - myLookup.refreshUi(); - } - - final boolean isInsideIdentifier() { - return getIdentifierEndOffset() != getSelectionEndOffset(); - } - - public int getIdentifierEndOffset() { - return myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET); - } - - - public synchronized void addItem(final LookupElement item) { - if (!isRunning()) return; - ProgressManager.checkCanceled(); - - final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode(); - if (!unitTestMode) { - assert !ApplicationManager.getApplication().isDispatchThread(); - } - - myLookup.addItem(item); - myCount++; - if (unitTestMode) return; - - if (myCount == 1) { - ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { - public void run() { - try { - Thread.sleep(300); - } - catch (InterruptedException e) { - LOG.error(e); - } - myFreezeSemaphore.up(); - } - }); - } - myQueue.queue(myUpdate); - } - - public void closeAndFinish(boolean hideLookup) { - if (myHint != null) { - myHint.hide(); - } - - if (LookupManager.getActiveLookup(myEditor) == myLookup) { - myLookup.removeLookupListener(myLookupListener); - finishCompletionProcess(); - - if (hideLookup) { - LookupManager.getInstance(getProject()).hideActiveLookup(); - } - } - - } - - private void finishCompletionProcess() { - myToRestart = false; - cancel(); - - assert !myDisposed; - myDisposed = true; - - ApplicationManager.getApplication().assertIsDispatchThread(); - Disposer.dispose(myQueue); - cleanup(); - } - - @TestOnly - public static void cleanupForNextTest() { - CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); - if (currentCompletion != null) { - currentCompletion.finishCompletionProcess(); - } - } - - private void cleanup() { - assert ApplicationManager.getApplication().isDispatchThread(); - myHint = null; - myOldDocumentText = null; - unregisterItself(); - } - - private static void unregisterItself() { - CompletionServiceImpl.getCompletionService().setCurrentCompletion(null); - } - - public void stop() { - super.stop(); - - myQueue.cancelAllUpdates(); - myFreezeSemaphore.up(); - - invokeLaterIfNotDispatch(new Runnable() { - public void run() { - if (isOutdated()) return; - if (isCanceled() && myToRestart != Boolean.TRUE) return; - - //what if a new completion was invoked by the user before this 'later'? - if (CompletionProgressIndicator.this != CompletionServiceImpl.getCompletionService().getCurrentCompletion()) return; - - myLookup.setCalculating(false); - - if (!isBackgrounded()) return; - - if (hideAutopopupIfMeaningless()) { - return; - } - - if (myCount == 0) { - LookupManager.getInstance(getProject()).hideActiveLookup(); - - final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); - assert current == null : current + "!=" + CompletionProgressIndicator.this; - - if (!isAutopopupCompletion() ) { - myHandler.handleEmptyLookup(getProject(), myEditor, myParameters, CompletionProgressIndicator.this); - } - } else { - updateLookup(); - } - - } - }); - - } - - public boolean hideAutopopupIfMeaningless() { - if (isAutopopupCompletion() && !myLookup.isCalculating()) { - myLookup.refreshUi(); - final List items = myLookup.getItems(); - if (items.size() == 0 || items.size() == 1 && (items.get(0).getPrefixMatcher().getPrefix() + myLookup.getAdditionalPrefix()).equals(items.get(0).getLookupString())) { - myLookup.hideLookup(false); - assert CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null; - return true; - } - } - return false; - } - - private void invokeLaterIfNotDispatch(final Runnable runnable) { - final Application application = ApplicationManager.getApplication(); - if (application.isUnitTestMode()) { - runnable.run(); - } else { - application.invokeLater(runnable, myQueue.getModalityState()); - } - } - - public void cancelByWriteAction() { - if (myToRestart != null) { - LOG.assertTrue(myToRestart == Boolean.FALSE); //explicit completionFinished was invoked before this write action - return; - } - - myToRestart = true; - cancel(); - - scheduleRestart(); - } - - public boolean fillInCommonPrefix(final boolean explicit) { - if (isInsideIdentifier()) { - return false; - } - - final Boolean aBoolean = new WriteCommandAction(getProject()) { - protected void run(Result result) throws Throwable { - if (!explicit) { - setMergeCommand(); - } - try { - result.setResult(myLookup.fillInCommonPrefix(explicit)); - } - catch (Exception e) { - LOG.error(e); - } - } - }.execute().getResultObject(); - return aBoolean.booleanValue(); - } - - public void restorePrefix() { - setMergeCommand(); - - if (myOldDocumentText != null) { - myEditor.getDocument().setText(myOldDocumentText); - myEditor.getSelectionModel().setSelection(myOldStart, myOldEnd); - myEditor.getCaretModel().moveToOffset(myOldCaret); - myOldDocumentText = null; - return; - } - - getLookup().restorePrefix(); - } - - public Editor getEditor() { - return myEditor; - } - - public void rememberDocumentState() { - if (myModifiersReleased) { - return; - } - - myOldDocumentText = myEditor.getDocument().getText(); - myOldCaret = myEditor.getCaretModel().getOffset(); - myOldStart = myEditor.getSelectionModel().getSelectionStart(); - myOldEnd = myEditor.getSelectionModel().getSelectionEnd(); - } - - public boolean isRepeatedInvocation(CompletionType completionType, Editor editor) { - return completionType == myParameters.getCompletionType() && editor == myEditor; - } - - @Override - public boolean isAutopopupCompletion() { - return myHandler.autopopup; - } - - @NotNull - public Project getProject() { - return ObjectUtils.assertNotNull(myEditor.getProject()); - } - - public void addWatchedPrefix(int startOffset, ElementPattern restartCondition) { - if (isAutopopupCompletion()) { - myRestartingPrefixConditions.add(Pair.create(startOffset, restartCondition)); - } - } - - public void prefixUpdated() { - if (myToRestart == Boolean.TRUE) { - scheduleRestart(); - return; - } - - final CharSequence text = myEditor.getDocument().getCharsSequence(); - final int caretOffset = myEditor.getCaretModel().getOffset(); - for (Pair> pair : myRestartingPrefixConditions) { - final String newPrefix = text.subSequence(pair.first, caretOffset).toString(); - if (pair.second.accepts(newPrefix)) { - scheduleRestart(); - myRestartingPrefixConditions.clear(); - return; - } - } - - hideAutopopupIfMeaningless(); - } - - public void scheduleRestart() { - ApplicationManager.getApplication().assertIsDispatchThread(); - - final int offset = myEditor.getCaretModel().getOffset(); - final long stamp = myEditor.getDocument().getModificationStamp(); - final Project project = getProject(); - final boolean wasDumb = DumbService.getInstance(project).isDumb(); - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - if (ApplicationManager.getApplication().isUnitTestMode()) { - System.out.println("later"); - } - - if (myEditor.isDisposed() || project.isDisposed()) { - return; - } - - if (!wasDumb && DumbService.getInstance(project).isDumb()) { - return; - } - - if (myEditor.getCaretModel().getOffset() != offset || myEditor.getDocument().getModificationStamp() != stamp) { - return; - } - - if (ApplicationManager.getApplication().isUnitTestMode()) { - System.out.println("invoking"); - } - - - if (!isOutdated()) { - closeAndFinish(false); - } - - final CodeCompletionHandlerBase newHandler = new CodeCompletionHandlerBase(myParameters.getCompletionType(), false, isAutopopupCompletion()); - final PsiFile psiFileInEditor = PsiUtilBase.getPsiFileInEditor(myEditor, project); - try { - newHandler.invokeCompletion(project, myEditor, psiFileInEditor, myParameters.getInvocationCount()); - } - catch (IndexNotReadyException ignored) { - } - } - }); - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; +import com.intellij.codeInsight.lookup.LookupAdapter; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupEvent; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.CaretModel; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.ReferenceRange; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.HintListener; +import com.intellij.ui.LightweightHint; +import com.intellij.util.ObjectUtils; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import javax.swing.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.Collections; +import java.util.EventObject; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author peter + */ +public class CompletionProgressIndicator extends ProgressIndicatorBase implements CompletionProcess{ + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionProgressIndicator"); + private final Editor myEditor; + private final CompletionParameters myParameters; + private final CodeCompletionHandlerBase myHandler; + private final LookupImpl myLookup; + private final MergingUpdateQueue myQueue; + private boolean myDisposed; + private boolean myShownLookup; + private volatile int myCount; + private final Update myUpdate = new Update("update") { + public void run() { + updateLookup(); + } + }; + private LightweightHint myHint; + private final Semaphore myFreezeSemaphore; + private Boolean myToRestart; + + private boolean myModifiersReleased; + + private String myOldDocumentText; + private int myOldCaret; + private int myOldStart; + private int myOldEnd; + private boolean myBackgrounded = true; + private OffsetMap myOffsetMap; + private final CopyOnWriteArrayList>> myRestartingPrefixConditions = ContainerUtil.createEmptyCOWList(); + private final LookupAdapter myLookupListener = new LookupAdapter() { + public void itemSelected(LookupEvent event) { + ensureDuringCompletionPassed(); + + finishCompletionProcess(); + + LookupElement item = event.getItem(); + if (item == null) return; + + setMergeCommand(); + + myOffsetMap.addOffset(CompletionInitializationContext.START_OFFSET, myEditor.getCaretModel().getOffset() - item.getLookupString().length()); + CodeCompletionHandlerBase.selectLookupItem(item, event.getCompletionChar(), CompletionProgressIndicator.this, myLookup.getItems()); + } + + + public void lookupCanceled(final LookupEvent event) { + finishCompletionProcess(); + } + }; + private final Semaphore myDuringCompletionSemaphore = new Semaphore(); + + public CompletionProgressIndicator(final Editor editor, CompletionParameters parameters, CodeCompletionHandlerBase handler, Semaphore freezeSemaphore, + final OffsetMap offsetMap, LookupImpl lookup) { + myEditor = editor; + myParameters = parameters; + myHandler = handler; + myFreezeSemaphore = freezeSemaphore; + myOffsetMap = offsetMap; + myLookup = lookup; + + myLookup.setArranger(new CompletionLookupArranger(parameters)); + myShownLookup = lookup.isShown(); + + myLookup.addLookupListener(myLookupListener); + myLookup.setCalculating(true); + + myQueue = new MergingUpdateQueue("completion lookup progress", 100, true, myEditor.getContentComponent()); + + ApplicationManager.getApplication().assertIsDispatchThread(); + registerItself(); + + if (!ApplicationManager.getApplication().isUnitTestMode() && !lookup.isShown()) { + scheduleAdvertising(); + } + + trackModifiers(); + + myDuringCompletionSemaphore.down(); + } + + public OffsetMap getOffsetMap() { + return myOffsetMap; + } + + public int getSelectionEndOffset() { + return getOffsetMap().getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); + } + + void notifyBackgrounded() { + ApplicationManager.getApplication().assertIsDispatchThread(); + myBackgrounded = true; + } + + boolean isBackgrounded() { + ApplicationManager.getApplication().assertIsDispatchThread(); + return myBackgrounded; + } + + void duringCompletion(CompletionInitializationContext initContext) { + try { + ProgressManager.checkCanceled(); + + if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) { + try { + final int selectionEndOffset = initContext.getSelectionEndOffset(); + final PsiReference reference = initContext.getFile().findReferenceAt(selectionEndOffset); + if (reference != null) { + initContext.setReplacementOffset(findReplacementOffset(selectionEndOffset, reference)); + } + } + catch (IndexNotReadyException ignored) { + } + } + + for (CompletionContributor contributor : CompletionContributor.forLanguage(initContext.getPositionLanguage())) { + if (DumbService.getInstance(initContext.getProject()).isDumb() && !DumbService.isDumbAware(contributor)) { + continue; + } + + contributor.duringCompletion(initContext); + } + } catch (ProcessCanceledException ignored) { + } finally { + myDuringCompletionSemaphore.up(); + } + } + + void ensureDuringCompletionPassed() { + myDuringCompletionSemaphore.waitFor(); + } + + private static int findReplacementOffset(int selectionEndOffset, PsiReference reference) { + final List ranges = ReferenceRange.getAbsoluteRanges(reference); + for (TextRange range : ranges) { + if (range.contains(selectionEndOffset)) { + return range.getEndOffset(); + } + } + + return reference.getElement().getTextRange().getStartOffset() + reference.getRangeInElement().getEndOffset(); + } + + + private void scheduleAdvertising() { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + public void run() { + if (isOutdated()) return; //tests? + final List list = ApplicationManager.getApplication().runReadAction(new Computable>() { + public List compute() { + if (isOutdated()) { + return Collections.emptyList(); + } + + return CompletionContributor.forParameters(myParameters); + } + }); + for (final CompletionContributor contributor : list) { + if (myLookup.getAdvertisementText() != null) return; + if (!myLookup.isCalculating() && !myLookup.isVisible()) return; + + String s = ApplicationManager.getApplication().runReadAction(new Computable() { + @Nullable + public String compute() { + if (isOutdated()) { + return null; + } + + return contributor.advertise(myParameters); + } + }); + if (myLookup.getAdvertisementText() != null) return; + + if (s != null) { + myLookup.setAdvertisementText(s); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (isOutdated()) { + return; + } + if (isAutopopupCompletion() && !myShownLookup) { + return; + } + if (!isBackgrounded()) { + return; + } + updateLookup(); + } + }, myQueue.getModalityState()); + return; + } + } + } + }); + } + + private boolean isOutdated() { + return myDisposed || + myEditor.isDisposed() || + !ApplicationManager.getApplication().isUnitTestMode() && myEditor.getComponent().getRootPane() == null; + } + + private void trackModifiers() { + if (isAutopopupCompletion()) { + return; + } + + final JComponent contentComponent = myEditor.getContentComponent(); + contentComponent.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + processModifier(e); + } + + public void keyReleased(KeyEvent e) { + processModifier(e); + } + + private void processModifier(KeyEvent e) { + final int code = e.getKeyCode(); + if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT || code == KeyEvent.VK_SHIFT) { + myModifiersReleased = true; + if (myOldDocumentText != null) { + cleanup(); + } + contentComponent.removeKeyListener(this); + } + } + }); + } + + private void setMergeCommand() { + CommandProcessor.getInstance().setCurrentCommandGroupId("Completion" + hashCode()); + } + + public void showLookup() { + updateLookup(); + } + + public CompletionParameters getParameters() { + return myParameters; + } + + private void registerItself() { + CompletionServiceImpl.getCompletionService().setCurrentCompletion(this); + } + + public void liveAfterDeath(@Nullable final LightweightHint hint) { + if (myModifiersReleased || ApplicationManager.getApplication().isUnitTestMode()) { + return; + } + + registerItself(); + myHint = hint; + if (hint != null) { + hint.addHintListener(new HintListener() { + public void hintHidden(final EventObject event) { + hint.removeHintListener(this); + cleanup(); + } + }); + } + final Document document = myEditor.getDocument(); + document.addDocumentListener(new DocumentAdapter() { + @Override + public void beforeDocumentChange(DocumentEvent e) { + document.removeDocumentListener(this); + cleanup(); + } + }); + final SelectionModel selectionModel = myEditor.getSelectionModel(); + selectionModel.addSelectionListener(new SelectionListener() { + public void selectionChanged(SelectionEvent e) { + selectionModel.removeSelectionListener(this); + cleanup(); + } + }); + final CaretModel caretModel = myEditor.getCaretModel(); + caretModel.addCaretListener(new CaretListener() { + public void caretPositionChanged(CaretEvent e) { + caretModel.removeCaretListener(this); + cleanup(); + } + }); + } + + public CodeCompletionHandlerBase getHandler() { + return myHandler; + } + + public LookupImpl getLookup() { + return myLookup; + } + + private void updateLookup() { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (isOutdated()) return; + + if (!myShownLookup) { + myShownLookup = true; + + if (StringUtil.isEmpty(myLookup.getAdvertisementText()) && !isAutopopupCompletion()) { + final String text = DefaultCompletionContributor.getDefaultAdvertisementText(myParameters); + if (text != null) { + myLookup.setAdvertisementText(text); + } + } + + myLookup.show(); + } + myLookup.refreshUi(); + } + + final boolean isInsideIdentifier() { + return getIdentifierEndOffset() != getSelectionEndOffset(); + } + + public int getIdentifierEndOffset() { + return myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET); + } + + + public synchronized void addItem(final LookupElement item) { + if (!isRunning()) return; + ProgressManager.checkCanceled(); + + final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode(); + if (!unitTestMode) { + assert !ApplicationManager.getApplication().isDispatchThread(); + } + + myLookup.addItem(item); + myCount++; + if (unitTestMode) return; + + if (myCount == 1) { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + public void run() { + try { + Thread.sleep(300); + } + catch (InterruptedException e) { + LOG.error(e); + } + myFreezeSemaphore.up(); + } + }); + } + myQueue.queue(myUpdate); + } + + public void closeAndFinish(boolean hideLookup) { + if (myHint != null) { + myHint.hide(); + } + + if (LookupManager.getActiveLookup(myEditor) == myLookup) { + myLookup.removeLookupListener(myLookupListener); + finishCompletionProcess(); + + if (hideLookup) { + LookupManager.getInstance(getProject()).hideActiveLookup(); + } + } + + } + + private void finishCompletionProcess() { + myToRestart = false; + cancel(); + + assert !myDisposed; + myDisposed = true; + + ApplicationManager.getApplication().assertIsDispatchThread(); + Disposer.dispose(myQueue); + cleanup(); + } + + @TestOnly + public static void cleanupForNextTest() { + CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); + if (currentCompletion != null) { + currentCompletion.finishCompletionProcess(); + } + } + + private void cleanup() { + assert ApplicationManager.getApplication().isDispatchThread(); + myHint = null; + myOldDocumentText = null; + unregisterItself(); + } + + private static void unregisterItself() { + CompletionServiceImpl.getCompletionService().setCurrentCompletion(null); + } + + public void stop() { + super.stop(); + + myQueue.cancelAllUpdates(); + myFreezeSemaphore.up(); + + invokeLaterIfNotDispatch(new Runnable() { + public void run() { + if (isOutdated()) return; + if (isCanceled() && myToRestart != Boolean.TRUE) return; + + //what if a new completion was invoked by the user before this 'later'? + if (CompletionProgressIndicator.this != CompletionServiceImpl.getCompletionService().getCurrentCompletion()) return; + + myLookup.setCalculating(false); + + if (!isBackgrounded()) return; + + if (hideAutopopupIfMeaningless()) { + return; + } + + if (myCount == 0) { + LookupManager.getInstance(getProject()).hideActiveLookup(); + + final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); + assert current == null : current + "!=" + CompletionProgressIndicator.this; + + if (!isAutopopupCompletion() ) { + myHandler.handleEmptyLookup(getProject(), myEditor, myParameters, CompletionProgressIndicator.this); + } + } else { + updateLookup(); + } + + } + }); + + } + + public boolean hideAutopopupIfMeaningless() { + if (isAutopopupCompletion() && !myLookup.isCalculating()) { + myLookup.refreshUi(); + final List items = myLookup.getItems(); + if (items.size() == 0 || items.size() == 1 && (items.get(0).getPrefixMatcher().getPrefix() + myLookup.getAdditionalPrefix()).equals(items.get(0).getLookupString())) { + myLookup.hideLookup(false); + assert CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null; + return true; + } + } + return false; + } + + private void invokeLaterIfNotDispatch(final Runnable runnable) { + final Application application = ApplicationManager.getApplication(); + if (application.isUnitTestMode()) { + runnable.run(); + } else { + application.invokeLater(runnable, myQueue.getModalityState()); + } + } + + public void cancelByWriteAction() { + if (myToRestart != null) { + LOG.assertTrue(myToRestart == Boolean.FALSE); //explicit completionFinished was invoked before this write action + return; + } + + myToRestart = true; + cancel(); + + scheduleRestart(); + } + + public boolean fillInCommonPrefix(final boolean explicit) { + if (isInsideIdentifier()) { + return false; + } + + final Boolean aBoolean = new WriteCommandAction(getProject()) { + protected void run(Result result) throws Throwable { + if (!explicit) { + setMergeCommand(); + } + try { + result.setResult(myLookup.fillInCommonPrefix(explicit)); + } + catch (Exception e) { + LOG.error(e); + } + } + }.execute().getResultObject(); + return aBoolean.booleanValue(); + } + + public void restorePrefix() { + setMergeCommand(); + + if (myOldDocumentText != null) { + myEditor.getDocument().setText(myOldDocumentText); + myEditor.getSelectionModel().setSelection(myOldStart, myOldEnd); + myEditor.getCaretModel().moveToOffset(myOldCaret); + myOldDocumentText = null; + return; + } + + getLookup().restorePrefix(); + } + + public Editor getEditor() { + return myEditor; + } + + public void rememberDocumentState() { + if (myModifiersReleased) { + return; + } + + myOldDocumentText = myEditor.getDocument().getText(); + myOldCaret = myEditor.getCaretModel().getOffset(); + myOldStart = myEditor.getSelectionModel().getSelectionStart(); + myOldEnd = myEditor.getSelectionModel().getSelectionEnd(); + } + + public boolean isRepeatedInvocation(CompletionType completionType, Editor editor) { + return completionType == myParameters.getCompletionType() && editor == myEditor; + } + + @Override + public boolean isAutopopupCompletion() { + return myHandler.autopopup; + } + + @NotNull + public Project getProject() { + return ObjectUtils.assertNotNull(myEditor.getProject()); + } + + public void addWatchedPrefix(int startOffset, ElementPattern restartCondition) { + if (isAutopopupCompletion()) { + myRestartingPrefixConditions.add(Pair.create(startOffset, restartCondition)); + } + } + + public void prefixUpdated() { + if (myToRestart == Boolean.TRUE) { + scheduleRestart(); + return; + } + + final CharSequence text = myEditor.getDocument().getCharsSequence(); + final int caretOffset = myEditor.getCaretModel().getOffset(); + for (Pair> pair : myRestartingPrefixConditions) { + final String newPrefix = text.subSequence(pair.first, caretOffset).toString(); + if (pair.second.accepts(newPrefix)) { + scheduleRestart(); + myRestartingPrefixConditions.clear(); + return; + } + } + + hideAutopopupIfMeaningless(); + } + + public void scheduleRestart() { + ApplicationManager.getApplication().assertIsDispatchThread(); + + final int offset = myEditor.getCaretModel().getOffset(); + final long stamp = myEditor.getDocument().getModificationStamp(); + final Project project = getProject(); + final boolean wasDumb = DumbService.getInstance(project).isDumb(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (ApplicationManager.getApplication().isUnitTestMode()) { + System.out.println("later"); + } + + if (myEditor.isDisposed() || project.isDisposed()) { + return; + } + + if (!wasDumb && DumbService.getInstance(project).isDumb()) { + return; + } + + if (myEditor.getCaretModel().getOffset() != offset || myEditor.getDocument().getModificationStamp() != stamp) { + return; + } + + if (ApplicationManager.getApplication().isUnitTestMode()) { + System.out.println("invoking"); + } + + + if (!isOutdated()) { + closeAndFinish(false); + } + + final CodeCompletionHandlerBase newHandler = new CodeCompletionHandlerBase(myParameters.getCompletionType(), false, isAutopopupCompletion()); + final PsiFile psiFileInEditor = PsiUtilBase.getPsiFileInEditor(myEditor, project); + try { + newHandler.invokeCompletion(project, myEditor, psiFileInEditor, myParameters.getInvocationCount()); + } + catch (IndexNotReadyException ignored) { + } + } + }); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionVariant.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionVariant.java index 5e145ef1e052..737e43c8c0b8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionVariant.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionVariant.java @@ -1,254 +1,254 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItem; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiReference; -import com.intellij.psi.filters.ContextGetter; -import com.intellij.psi.filters.ElementFilter; -import com.intellij.psi.filters.position.PatternFilter; -import com.intellij.util.ReflectionCache; -import com.intellij.util.containers.HashMap; -import org.jetbrains.annotations.NonNls; - -import java.util.*; - -/** - * @author ik - */ -public class CompletionVariant { - protected static final TailType DEFAULT_TAIL_TYPE = TailType.SPACE; - - private final Set myScopeClasses = new HashSet(); - private ElementFilter myPosition; - private final List myCompletionsList = new ArrayList(); - private final Set myScopeClassExceptions = new HashSet(); - private InsertHandler myInsertHandler = null; - private final Map myItemProperties = new HashMap(); - - public CompletionVariant() { - } - - public CompletionVariant(Class scopeClass, ElementPattern position){ - this(scopeClass, new PatternFilter(position)); - } - public CompletionVariant(Class scopeClass, ElementFilter position){ - includeScopeClass(scopeClass); - myPosition = position; - } - - public CompletionVariant(ElementPattern position){ - this(new PatternFilter(position)); - } - public CompletionVariant(ElementFilter position){ - myPosition = position; - } - - public boolean isScopeAcceptable(PsiElement scope){ - return isScopeClassAcceptable(scope.getClass()); - } - - public boolean isScopeFinal(PsiElement scope){ - return isScopeClassFinal(scope.getClass()); - } - - public InsertHandler getInsertHandler(){ - return myInsertHandler; - } - - public void setInsertHandler(InsertHandler handler){ - myInsertHandler = handler; - } - - public void setItemProperty(Object id, Object value){ - myItemProperties.put(id, value); - } - - public Map getItemProperties() { - return myItemProperties; - } - - public boolean isScopeClassFinal(Class scopeClass){ - for (final Object myScopeClass : myScopeClasses) { - Scope scope = (Scope)myScopeClass; - if (ReflectionCache.isAssignable(scope.myClass, scopeClass) && scope.myIsFinalScope) { - return true; - } - } - return false; - } - - public boolean isScopeClassAcceptable(Class scopeClass){ - boolean ret = false; - - for (final Object myScopeClass : myScopeClasses) { - final Class aClass = ((Scope)myScopeClass).myClass; - if (ReflectionCache.isAssignable(aClass, scopeClass)) { - ret = true; - break; - } - } - - if(ret){ - for (final Class aClass: myScopeClassExceptions) { - if (ReflectionCache.isAssignable(aClass, scopeClass)) { - ret = false; - break; - } - } - } - return ret; - } - - public void excludeScopeClass(Class aClass){ - myScopeClassExceptions.add(aClass); - } - - public void includeScopeClass(Class aClass){ - myScopeClasses.add(new Scope(aClass, false)); - } - - public void includeScopeClass(Class aClass, boolean isFinalScope){ - myScopeClasses.add(new Scope(aClass, isFinalScope)); - } - - public void addCompletionFilter(ElementFilter filter, TailType tailType){ - addCompletion(filter, tailType); - } - - public void addCompletionFilter(ElementFilter filter){ - addCompletionFilter(filter, TailType.NONE); - } - - public void addCompletion(@NonNls String keyword){ - addCompletion(keyword, DEFAULT_TAIL_TYPE); - } - - public void addCompletion(@NonNls String keyword, TailType tailType){ - addCompletion((Object)keyword, tailType); - } - - public void addCompletion(KeywordChooser chooser){ - addCompletion(chooser, DEFAULT_TAIL_TYPE); - } - - public void addCompletion(KeywordChooser chooser, TailType tailType){ - addCompletion((Object)chooser, tailType); - } - - public void addCompletion(ContextGetter chooser){ - addCompletion(chooser, DEFAULT_TAIL_TYPE); - } - - public void addCompletion(ContextGetter chooser, TailType tailType){ - addCompletion((Object)chooser, tailType); - } - - private void addCompletion(Object completion, TailType tail){ - myCompletionsList.add(new CompletionVariantItem(completion, tail)); - } - - public void addCompletion(@NonNls String[] keywordList){ - addCompletion(keywordList, DEFAULT_TAIL_TYPE); - } - - public void addCompletion(String[] keywordList, TailType tailType){ - for (String aKeywordList : keywordList) { - addCompletion(aKeywordList, tailType); - } - } - - public boolean isVariantApplicable(PsiElement position, PsiElement scope){ - return isScopeAcceptable(scope) && myPosition.isAcceptable(position, scope); - } - - public void addReferenceCompletions(PsiReference reference, PsiElement position, Set set, final PsiFile file, - final CompletionData completionData){ - for (final CompletionVariantItem ce : myCompletionsList) { - if(ce.myCompletion instanceof ElementFilter){ - final ElementFilter filter = (ElementFilter)ce.myCompletion; - completionData.completeReference(reference, position, set, ce.myTailType, file, filter, this); - } - } - } - - public void addKeywords(Set set, PsiElement position, final PrefixMatcher matcher, final PsiFile file, - final CompletionData completionData){ - - for (final CompletionVariantItem ce : myCompletionsList) { - completionData.addKeywords(set, position, matcher, file, this, ce.myCompletion, ce.myTailType); - } - } - - public boolean hasReferenceFilter(){ - for (final CompletionVariantItem item: myCompletionsList) { - if (item.myCompletion instanceof ElementFilter) { - return true; - } - } - return false; - } - - public boolean hasKeywordCompletions(){ - for (final CompletionVariantItem item : myCompletionsList) { - if (!(item.myCompletion instanceof ElementFilter)) { - return true; - } - } - return false; - } - - - private static class Scope{ - Class myClass; - boolean myIsFinalScope; - - Scope(Class aClass, boolean isFinalScope){ - myClass = aClass; - myIsFinalScope = isFinalScope; - } - } - - protected static class CompletionVariantItem{ - public Object myCompletion; - public TailType myTailType; - - public CompletionVariantItem(Object completion, TailType tailtype){ - myCompletion = completion; - myTailType = tailtype; - } - - public String toString(){ - return myCompletion.toString(); - } - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - public String toString(){ - return "completion variant at " + myPosition.toString() + " completions: " + myCompletionsList; - } - - public void setCaseInsensitive(boolean caseInsensitive) { - setItemProperty(LookupItem.CASE_INSENSITIVE, caseInsensitive); - } - -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupItem; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.filters.ContextGetter; +import com.intellij.psi.filters.ElementFilter; +import com.intellij.psi.filters.position.PatternFilter; +import com.intellij.util.ReflectionCache; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NonNls; + +import java.util.*; + +/** + * @author ik + */ +public class CompletionVariant { + protected static final TailType DEFAULT_TAIL_TYPE = TailType.SPACE; + + private final Set myScopeClasses = new HashSet(); + private ElementFilter myPosition; + private final List myCompletionsList = new ArrayList(); + private final Set myScopeClassExceptions = new HashSet(); + private InsertHandler myInsertHandler = null; + private final Map myItemProperties = new HashMap(); + + public CompletionVariant() { + } + + public CompletionVariant(Class scopeClass, ElementPattern position){ + this(scopeClass, new PatternFilter(position)); + } + public CompletionVariant(Class scopeClass, ElementFilter position){ + includeScopeClass(scopeClass); + myPosition = position; + } + + public CompletionVariant(ElementPattern position){ + this(new PatternFilter(position)); + } + public CompletionVariant(ElementFilter position){ + myPosition = position; + } + + public boolean isScopeAcceptable(PsiElement scope){ + return isScopeClassAcceptable(scope.getClass()); + } + + public boolean isScopeFinal(PsiElement scope){ + return isScopeClassFinal(scope.getClass()); + } + + public InsertHandler getInsertHandler(){ + return myInsertHandler; + } + + public void setInsertHandler(InsertHandler handler){ + myInsertHandler = handler; + } + + public void setItemProperty(Object id, Object value){ + myItemProperties.put(id, value); + } + + public Map getItemProperties() { + return myItemProperties; + } + + public boolean isScopeClassFinal(Class scopeClass){ + for (final Object myScopeClass : myScopeClasses) { + Scope scope = (Scope)myScopeClass; + if (ReflectionCache.isAssignable(scope.myClass, scopeClass) && scope.myIsFinalScope) { + return true; + } + } + return false; + } + + public boolean isScopeClassAcceptable(Class scopeClass){ + boolean ret = false; + + for (final Object myScopeClass : myScopeClasses) { + final Class aClass = ((Scope)myScopeClass).myClass; + if (ReflectionCache.isAssignable(aClass, scopeClass)) { + ret = true; + break; + } + } + + if(ret){ + for (final Class aClass: myScopeClassExceptions) { + if (ReflectionCache.isAssignable(aClass, scopeClass)) { + ret = false; + break; + } + } + } + return ret; + } + + public void excludeScopeClass(Class aClass){ + myScopeClassExceptions.add(aClass); + } + + public void includeScopeClass(Class aClass){ + myScopeClasses.add(new Scope(aClass, false)); + } + + public void includeScopeClass(Class aClass, boolean isFinalScope){ + myScopeClasses.add(new Scope(aClass, isFinalScope)); + } + + public void addCompletionFilter(ElementFilter filter, TailType tailType){ + addCompletion(filter, tailType); + } + + public void addCompletionFilter(ElementFilter filter){ + addCompletionFilter(filter, TailType.NONE); + } + + public void addCompletion(@NonNls String keyword){ + addCompletion(keyword, DEFAULT_TAIL_TYPE); + } + + public void addCompletion(@NonNls String keyword, TailType tailType){ + addCompletion((Object)keyword, tailType); + } + + public void addCompletion(KeywordChooser chooser){ + addCompletion(chooser, DEFAULT_TAIL_TYPE); + } + + public void addCompletion(KeywordChooser chooser, TailType tailType){ + addCompletion((Object)chooser, tailType); + } + + public void addCompletion(ContextGetter chooser){ + addCompletion(chooser, DEFAULT_TAIL_TYPE); + } + + public void addCompletion(ContextGetter chooser, TailType tailType){ + addCompletion((Object)chooser, tailType); + } + + private void addCompletion(Object completion, TailType tail){ + myCompletionsList.add(new CompletionVariantItem(completion, tail)); + } + + public void addCompletion(@NonNls String[] keywordList){ + addCompletion(keywordList, DEFAULT_TAIL_TYPE); + } + + public void addCompletion(String[] keywordList, TailType tailType){ + for (String aKeywordList : keywordList) { + addCompletion(aKeywordList, tailType); + } + } + + public boolean isVariantApplicable(PsiElement position, PsiElement scope){ + return isScopeAcceptable(scope) && myPosition.isAcceptable(position, scope); + } + + public void addReferenceCompletions(PsiReference reference, PsiElement position, Set set, final PsiFile file, + final CompletionData completionData){ + for (final CompletionVariantItem ce : myCompletionsList) { + if(ce.myCompletion instanceof ElementFilter){ + final ElementFilter filter = (ElementFilter)ce.myCompletion; + completionData.completeReference(reference, position, set, ce.myTailType, file, filter, this); + } + } + } + + public void addKeywords(Set set, PsiElement position, final PrefixMatcher matcher, final PsiFile file, + final CompletionData completionData){ + + for (final CompletionVariantItem ce : myCompletionsList) { + completionData.addKeywords(set, position, matcher, file, this, ce.myCompletion, ce.myTailType); + } + } + + public boolean hasReferenceFilter(){ + for (final CompletionVariantItem item: myCompletionsList) { + if (item.myCompletion instanceof ElementFilter) { + return true; + } + } + return false; + } + + public boolean hasKeywordCompletions(){ + for (final CompletionVariantItem item : myCompletionsList) { + if (!(item.myCompletion instanceof ElementFilter)) { + return true; + } + } + return false; + } + + + private static class Scope{ + Class myClass; + boolean myIsFinalScope; + + Scope(Class aClass, boolean isFinalScope){ + myClass = aClass; + myIsFinalScope = isFinalScope; + } + } + + protected static class CompletionVariantItem{ + public Object myCompletion; + public TailType myTailType; + + public CompletionVariantItem(Object completion, TailType tailtype){ + myCompletion = completion; + myTailType = tailtype; + } + + public String toString(){ + return myCompletion.toString(); + } + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + public String toString(){ + return "completion variant at " + myPosition.toString() + " completions: " + myCompletionsList; + } + + public void setCaseInsensitive(boolean caseInsensitive) { + setItemProperty(LookupItem.CASE_INSENSITIVE, caseInsensitive); + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java index 48b6c07a582f..fc38b698fa13 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java @@ -1,355 +1,355 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.CodeInsightBundle; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementPresentation; -import com.intellij.navigation.ChooseByNameContributor; -import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileNameMatcher; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiFileSystemItem; -import com.intellij.psi.PsiReference; -import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; -import com.intellij.psi.impl.source.resolve.reference.impl.providers.*; -import com.intellij.psi.search.FilenameIndex; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.ProjectScope; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ProcessingContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.*; - -import static com.intellij.patterns.PlatformPatterns.psiElement; - -/** - * @author spleaner - */ -public class FilePathCompletionContributor extends CompletionContributor { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.FilePathCompletionContributor"); - - public FilePathCompletionContributor() { - extend(CompletionType.BASIC, psiElement(), new CompletionProvider() { - @Override - protected void addCompletions(@NotNull CompletionParameters parameters, - ProcessingContext context, - @NotNull CompletionResultSet result) { - final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset()); - if (getReference(psiReference) != null) { - final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION); - final CompletionService service = CompletionService.getCompletionService(); - if (/*StringUtil.isEmpty(service.getAdvertisementText()) &&*/ shortcut != null) { - service.setAdvertisementText(CodeInsightBundle.message("class.completion.file.path", shortcut)); - } - } - } - }); - - extend(CompletionType.CLASS_NAME, psiElement(), new CompletionProvider(false) { - @Override - protected void addCompletions(@NotNull final CompletionParameters parameters, - ProcessingContext context, - @NotNull final CompletionResultSet _result) { - @NotNull final CompletionResultSet result = _result.caseInsensitive(); - final PsiElement e = parameters.getPosition(); - final Project project = e.getProject(); - - final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset()); - - final Pair fileReferencePair = getReference(psiReference); - if (fileReferencePair != null) { - final FileReference first = fileReferencePair.getFirst(); - if (first == null) return; - - final FileReferenceSet set = first.getFileReferenceSet(); - String prefix = set.getPathString().substring(0, parameters.getOffset() - set.getElement().getTextRange().getStartOffset() - set.getStartInElement()); - final String textBeforePosition = e.getContainingFile().getText().substring(0, parameters.getOffset()); - if (!textBeforePosition.endsWith(prefix)) { - final int len = textBeforePosition.length(); - final String fragment = len > 100 ? textBeforePosition.substring(len - 100) : textBeforePosition; - throw new AssertionError("prefix should be some actual file string just before caret: " + prefix + "\n text=" + fragment + ";\npathString=" + set.getPathString() + ";\nelementText=" + e.getParent().getText()); - } - - List pathPrefixParts = null; - int lastSlashIndex; - if ((lastSlashIndex = prefix.lastIndexOf('/')) != -1) { - pathPrefixParts = StringUtil.split(prefix.substring(0, lastSlashIndex), "/"); - prefix = prefix.substring(lastSlashIndex + 1); - } - - final CompletionResultSet __result = result.withPrefixMatcher(prefix).caseInsensitive(); - - final PsiFile originalFile = parameters.getOriginalFile(); - final VirtualFile contextFile = originalFile.getVirtualFile(); - if (contextFile != null) { - final String[] fileNames = getAllNames(project); - final Set resultNames = new TreeSet(); - for (String fileName : fileNames) { - if (filenameMatchesPrefixOrType(fileName, prefix, set.getSuitableFileTypes(), parameters.getInvocationCount())) { - resultNames.add(fileName); - } - } - - final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex(); - - final Module contextModule = index.getModuleForFile(contextFile); - if (contextModule != null) { - final FileReferenceHelper contextHelper = FileReferenceHelperRegistrar.getNotNullHelper(originalFile); - - final GlobalSearchScope scope = ProjectScope.getProjectScope(project); - for (final String name : resultNames) { - ProgressManager.checkCanceled(); - - final PsiFile[] files = FilenameIndex.getFilesByName(project, name, scope); - - if (files.length > 0) { - for (final PsiFile file : files) { - ProgressManager.checkCanceled(); - - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile != null && virtualFile.isValid() && virtualFile != contextFile) { - if (contextHelper.isMine(project, virtualFile)) { - if (pathPrefixParts == null || fileMatchesPathPrefix(contextHelper.getPsiFileSystemItem(project, virtualFile), pathPrefixParts)) { - __result.addElement(new FilePathLookupItem(file, contextHelper)); - } - } - } - } - } - } - } - } - - if (set.getSuitableFileTypes().length > 0 && parameters.getInvocationCount() == 1) { - final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION); - final CompletionService service = CompletionService.getCompletionService(); - if (shortcut != null) { - service.setAdvertisementText(CodeInsightBundle.message("class.completion.file.path.all.variants", shortcut)); - } - } - - if (fileReferencePair.getSecond()) result.stopHere(); - } - } - }); - } - - private static boolean filenameMatchesPrefixOrType(final String fileName, final String prefix, final FileType[] suitableFileTypes, final int invocationCount) { - final boolean prefixMatched = prefix.length() == 0 || StringUtil.startsWithIgnoreCase(fileName, prefix); - if (prefixMatched && (suitableFileTypes.length == 0 || invocationCount > 1)) return true; - - if (prefixMatched) { - final String extension = FileUtil.getExtension(fileName); - if (extension.length() == 0) return false; - - for (final FileType fileType : suitableFileTypes) { - final List matchers = FileTypeManager.getInstance().getAssociations(fileType); - for (final FileNameMatcher matcher : matchers) { - if (matcher.accept(fileName)) return true; - } - } - } - - return false; - } - - private static boolean fileMatchesPathPrefix(@Nullable final PsiFileSystemItem file, @NotNull final List pathPrefix) { - if (file == null) return false; - - final List contextParts = new ArrayList(); - PsiFileSystemItem parentFile = file; - PsiFileSystemItem parent; - while ((parent = parentFile.getParent()) != null) { - if (parent.getName().length() > 0) contextParts.add(0, parent.getName().toLowerCase()); - parentFile = parent; - } - - final String path = StringUtil.join(contextParts, "/"); - - int nextIndex = 0; - for (final String s : pathPrefix) { - if ((nextIndex = path.indexOf(s.toLowerCase(), nextIndex)) == -1) return false; - } - - return true; - } - - private static String[] getAllNames(@NotNull final Project project) { - Set names = new HashSet(); - final ChooseByNameContributor[] nameContributors = ChooseByNameContributor.FILE_EP_NAME.getExtensions(); - for (final ChooseByNameContributor contributor : nameContributors) { - try { - names.addAll(Arrays.asList(contributor.getNames(project, false))); - } - catch (ProcessCanceledException ex) { - // index corruption detected, ignore - } - catch (Exception ex) { - LOG.error(ex); - } - } - - return ArrayUtil.toStringArray(names); - } - - @Nullable - private static Pair getReference(final PsiReference original) { - if (original == null) { - return null; - } - - if (original instanceof PsiMultiReference) { - final PsiMultiReference multiReference = (PsiMultiReference)original; - for (PsiReference reference : multiReference.getReferences()) { - if (reference instanceof FileReference) { - return Pair.create((FileReference) reference, false); - } - } - } - else if (original instanceof FileReferenceOwner) { - final FileReference fileReference = ((FileReferenceOwner)original).getLastFileReference(); - if (fileReference != null) { - return Pair.create(fileReference, true); - } - } - - return null; - } - - public class FilePathLookupItem extends LookupElement { - private final String myName; - private final String myPath; - private final String myInfo; - private final Icon myIcon; - private final PsiFile myFile; - private final FileReferenceHelper myReferenceHelper; - - public FilePathLookupItem(@NotNull final PsiFile file, @NotNull final FileReferenceHelper referenceHelper) { - myName = file.getName(); - myPath = file.getVirtualFile().getPath(); - - myReferenceHelper = referenceHelper; - - myInfo = FileInfoManager.getFileAdditionalInfo(file); - myIcon = file.getFileType().getIcon(); - - myFile = file; - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - @Override - public String toString() { - return String.format("%s%s", myName, myInfo == null ? "" : " (" + myInfo + ")"); - } - - @NotNull - @Override - public Object getObject() { - return myFile; - } - - @NotNull - public String getLookupString() { - return myName; - } - - @Override - public void handleInsert(InsertionContext context) { - if (myFile.isValid()) { - final PsiReference psiReference = context.getFile().findReferenceAt(context.getStartOffset()); - final Pair fileReferencePair = getReference(psiReference); - LOG.assertTrue(fileReferencePair != null); - - fileReferencePair.getFirst().bindToElement(myFile, true); - } - } - - @Override - public void renderElement(LookupElementPresentation presentation) { - final VirtualFile virtualFile = myFile.getVirtualFile(); - LOG.assertTrue(virtualFile != null); - final PsiFileSystemItem root = myReferenceHelper.findRoot(myFile.getProject(), virtualFile); - final String relativePath = PsiFileSystemItemUtil.getRelativePath(root, myReferenceHelper.getPsiFileSystemItem(myFile.getProject(), virtualFile)); - - final StringBuilder sb = new StringBuilder(); - if (myInfo != null) { - sb.append(" (").append(myInfo); - } - - if (relativePath != null && !relativePath.equals(myName)) { - if (myInfo != null) { - sb.append(", "); - } - else { - sb.append(" ("); - } - - sb.append(relativePath); - } - - if (sb.length() > 0) { - sb.append(')'); - } - - presentation.setItemText(myName); - - if (sb.length() > 0) { - presentation.setTailText(sb.toString(), true); - } - - presentation.setIcon(myIcon); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FilePathLookupItem that = (FilePathLookupItem)o; - - if (!myName.equals(that.myName)) return false; - if (!myPath.equals(that.myPath)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myName.hashCode(); - result = 31 * result + myPath.hashCode(); - return result; - } - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.intellij.navigation.ChooseByNameContributor; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileNameMatcher; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileSystemItem; +import com.intellij.psi.PsiReference; +import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.*; +import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.ProjectScope; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.*; + +import static com.intellij.patterns.PlatformPatterns.psiElement; + +/** + * @author spleaner + */ +public class FilePathCompletionContributor extends CompletionContributor { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.FilePathCompletionContributor"); + + public FilePathCompletionContributor() { + extend(CompletionType.BASIC, psiElement(), new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + ProcessingContext context, + @NotNull CompletionResultSet result) { + final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset()); + if (getReference(psiReference) != null) { + final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION); + final CompletionService service = CompletionService.getCompletionService(); + if (/*StringUtil.isEmpty(service.getAdvertisementText()) &&*/ shortcut != null) { + service.setAdvertisementText(CodeInsightBundle.message("class.completion.file.path", shortcut)); + } + } + } + }); + + extend(CompletionType.CLASS_NAME, psiElement(), new CompletionProvider(false) { + @Override + protected void addCompletions(@NotNull final CompletionParameters parameters, + ProcessingContext context, + @NotNull final CompletionResultSet _result) { + @NotNull final CompletionResultSet result = _result.caseInsensitive(); + final PsiElement e = parameters.getPosition(); + final Project project = e.getProject(); + + final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset()); + + final Pair fileReferencePair = getReference(psiReference); + if (fileReferencePair != null) { + final FileReference first = fileReferencePair.getFirst(); + if (first == null) return; + + final FileReferenceSet set = first.getFileReferenceSet(); + String prefix = set.getPathString().substring(0, parameters.getOffset() - set.getElement().getTextRange().getStartOffset() - set.getStartInElement()); + final String textBeforePosition = e.getContainingFile().getText().substring(0, parameters.getOffset()); + if (!textBeforePosition.endsWith(prefix)) { + final int len = textBeforePosition.length(); + final String fragment = len > 100 ? textBeforePosition.substring(len - 100) : textBeforePosition; + throw new AssertionError("prefix should be some actual file string just before caret: " + prefix + "\n text=" + fragment + ";\npathString=" + set.getPathString() + ";\nelementText=" + e.getParent().getText()); + } + + List pathPrefixParts = null; + int lastSlashIndex; + if ((lastSlashIndex = prefix.lastIndexOf('/')) != -1) { + pathPrefixParts = StringUtil.split(prefix.substring(0, lastSlashIndex), "/"); + prefix = prefix.substring(lastSlashIndex + 1); + } + + final CompletionResultSet __result = result.withPrefixMatcher(prefix).caseInsensitive(); + + final PsiFile originalFile = parameters.getOriginalFile(); + final VirtualFile contextFile = originalFile.getVirtualFile(); + if (contextFile != null) { + final String[] fileNames = getAllNames(project); + final Set resultNames = new TreeSet(); + for (String fileName : fileNames) { + if (filenameMatchesPrefixOrType(fileName, prefix, set.getSuitableFileTypes(), parameters.getInvocationCount())) { + resultNames.add(fileName); + } + } + + final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex(); + + final Module contextModule = index.getModuleForFile(contextFile); + if (contextModule != null) { + final FileReferenceHelper contextHelper = FileReferenceHelperRegistrar.getNotNullHelper(originalFile); + + final GlobalSearchScope scope = ProjectScope.getProjectScope(project); + for (final String name : resultNames) { + ProgressManager.checkCanceled(); + + final PsiFile[] files = FilenameIndex.getFilesByName(project, name, scope); + + if (files.length > 0) { + for (final PsiFile file : files) { + ProgressManager.checkCanceled(); + + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && virtualFile.isValid() && virtualFile != contextFile) { + if (contextHelper.isMine(project, virtualFile)) { + if (pathPrefixParts == null || fileMatchesPathPrefix(contextHelper.getPsiFileSystemItem(project, virtualFile), pathPrefixParts)) { + __result.addElement(new FilePathLookupItem(file, contextHelper)); + } + } + } + } + } + } + } + } + + if (set.getSuitableFileTypes().length > 0 && parameters.getInvocationCount() == 1) { + final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION); + final CompletionService service = CompletionService.getCompletionService(); + if (shortcut != null) { + service.setAdvertisementText(CodeInsightBundle.message("class.completion.file.path.all.variants", shortcut)); + } + } + + if (fileReferencePair.getSecond()) result.stopHere(); + } + } + }); + } + + private static boolean filenameMatchesPrefixOrType(final String fileName, final String prefix, final FileType[] suitableFileTypes, final int invocationCount) { + final boolean prefixMatched = prefix.length() == 0 || StringUtil.startsWithIgnoreCase(fileName, prefix); + if (prefixMatched && (suitableFileTypes.length == 0 || invocationCount > 1)) return true; + + if (prefixMatched) { + final String extension = FileUtil.getExtension(fileName); + if (extension.length() == 0) return false; + + for (final FileType fileType : suitableFileTypes) { + final List matchers = FileTypeManager.getInstance().getAssociations(fileType); + for (final FileNameMatcher matcher : matchers) { + if (matcher.accept(fileName)) return true; + } + } + } + + return false; + } + + private static boolean fileMatchesPathPrefix(@Nullable final PsiFileSystemItem file, @NotNull final List pathPrefix) { + if (file == null) return false; + + final List contextParts = new ArrayList(); + PsiFileSystemItem parentFile = file; + PsiFileSystemItem parent; + while ((parent = parentFile.getParent()) != null) { + if (parent.getName().length() > 0) contextParts.add(0, parent.getName().toLowerCase()); + parentFile = parent; + } + + final String path = StringUtil.join(contextParts, "/"); + + int nextIndex = 0; + for (final String s : pathPrefix) { + if ((nextIndex = path.indexOf(s.toLowerCase(), nextIndex)) == -1) return false; + } + + return true; + } + + private static String[] getAllNames(@NotNull final Project project) { + Set names = new HashSet(); + final ChooseByNameContributor[] nameContributors = ChooseByNameContributor.FILE_EP_NAME.getExtensions(); + for (final ChooseByNameContributor contributor : nameContributors) { + try { + names.addAll(Arrays.asList(contributor.getNames(project, false))); + } + catch (ProcessCanceledException ex) { + // index corruption detected, ignore + } + catch (Exception ex) { + LOG.error(ex); + } + } + + return ArrayUtil.toStringArray(names); + } + + @Nullable + private static Pair getReference(final PsiReference original) { + if (original == null) { + return null; + } + + if (original instanceof PsiMultiReference) { + final PsiMultiReference multiReference = (PsiMultiReference)original; + for (PsiReference reference : multiReference.getReferences()) { + if (reference instanceof FileReference) { + return Pair.create((FileReference) reference, false); + } + } + } + else if (original instanceof FileReferenceOwner) { + final FileReference fileReference = ((FileReferenceOwner)original).getLastFileReference(); + if (fileReference != null) { + return Pair.create(fileReference, true); + } + } + + return null; + } + + public class FilePathLookupItem extends LookupElement { + private final String myName; + private final String myPath; + private final String myInfo; + private final Icon myIcon; + private final PsiFile myFile; + private final FileReferenceHelper myReferenceHelper; + + public FilePathLookupItem(@NotNull final PsiFile file, @NotNull final FileReferenceHelper referenceHelper) { + myName = file.getName(); + myPath = file.getVirtualFile().getPath(); + + myReferenceHelper = referenceHelper; + + myInfo = FileInfoManager.getFileAdditionalInfo(file); + myIcon = file.getFileType().getIcon(); + + myFile = file; + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + @Override + public String toString() { + return String.format("%s%s", myName, myInfo == null ? "" : " (" + myInfo + ")"); + } + + @NotNull + @Override + public Object getObject() { + return myFile; + } + + @NotNull + public String getLookupString() { + return myName; + } + + @Override + public void handleInsert(InsertionContext context) { + if (myFile.isValid()) { + final PsiReference psiReference = context.getFile().findReferenceAt(context.getStartOffset()); + final Pair fileReferencePair = getReference(psiReference); + LOG.assertTrue(fileReferencePair != null); + + fileReferencePair.getFirst().bindToElement(myFile, true); + } + } + + @Override + public void renderElement(LookupElementPresentation presentation) { + final VirtualFile virtualFile = myFile.getVirtualFile(); + LOG.assertTrue(virtualFile != null); + final PsiFileSystemItem root = myReferenceHelper.findRoot(myFile.getProject(), virtualFile); + final String relativePath = PsiFileSystemItemUtil.getRelativePath(root, myReferenceHelper.getPsiFileSystemItem(myFile.getProject(), virtualFile)); + + final StringBuilder sb = new StringBuilder(); + if (myInfo != null) { + sb.append(" (").append(myInfo); + } + + if (relativePath != null && !relativePath.equals(myName)) { + if (myInfo != null) { + sb.append(", "); + } + else { + sb.append(" ("); + } + + sb.append(relativePath); + } + + if (sb.length() > 0) { + sb.append(')'); + } + + presentation.setItemText(myName); + + if (sb.length() > 0) { + presentation.setTailText(sb.toString(), true); + } + + presentation.setIcon(myIcon); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FilePathLookupItem that = (FilePathLookupItem)o; + + if (!myName.equals(that.myName)) return false; + if (!myPath.equals(that.myPath)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myName.hashCode(); + result = 31 * result + myPath.hashCode(); + return result; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java index 754f68d32ead..63fff98a87e4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java @@ -1,122 +1,122 @@ -/* - * 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.codeInsight.completion; - -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.lang.ASTNode; -import com.intellij.lang.LanguageWordCompletion; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbService; -import com.intellij.psi.PlainTextTokenTypes; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiReference; -import com.intellij.psi.filters.getters.AllWordsGetter; -import com.intellij.psi.tree.IElementType; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import static com.intellij.patterns.StandardPatterns.character; - -/** - * @author peter - */ -public class WordCompletionContributor extends CompletionContributor implements DumbAware { - - @Override - public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) { - if (parameters.getCompletionType() == CompletionType.BASIC && shouldPerformWordCompletion(parameters)) { - addWordCompletionVariants(result, parameters, Collections.emptySet()); - } - } - - public static void addWordCompletionVariants(CompletionResultSet result, CompletionParameters parameters, Set excludes) { - int startOffset = parameters.getOffset(); - PsiElement insertedElement = parameters.getPosition(); - final CompletionResultSet javaResultSet = result.withPrefixMatcher(CompletionUtil.findJavaIdentifierPrefix(insertedElement, startOffset)); - final CompletionResultSet plainResultSet = result.withPrefixMatcher(CompletionUtil.findIdentifierPrefix(insertedElement, - startOffset, - character().javaIdentifierPart().andNot(character().equalTo('$')), - character().javaIdentifierStart())); - for (final String word : AllWordsGetter.getAllWords(insertedElement, startOffset)) { - if (!excludes.contains(word)) { - final LookupElement item = LookupElementBuilder.create(word); - javaResultSet.addElement(item); - plainResultSet.addElement(item); - } - } - } - - private static boolean shouldPerformWordCompletion(CompletionParameters parameters) { - final PsiElement insertedElement = parameters.getPosition(); - final boolean dumb = DumbService.getInstance(insertedElement.getProject()).isDumb(); - if (dumb) { - return true; - } - - final PsiFile file = insertedElement.getContainingFile(); - final CompletionData data = CompletionUtil.getCompletionDataByElement(insertedElement, file); - if (data != null && !(data instanceof SyntaxTableCompletionData)) { - Set toAdd = new HashSet(); - data.addKeywordVariants(toAdd, insertedElement, file); - for (CompletionVariant completionVariant : toAdd) { - if (completionVariant.hasKeywordCompletions()) { - return false; - } - } - } - - final int startOffset = parameters.getOffset(); - - final PsiReference reference = file.findReferenceAt(startOffset); - if (reference != null) { - return false; - } - - final PsiElement element = file.findElementAt(startOffset - 1); - - ASTNode textContainer = element != null ? element.getNode() : null; - while (textContainer != null) { - final IElementType elementType = textContainer.getElementType(); - if (LanguageWordCompletion.INSTANCE.isEnabledIn(elementType) || - isPlainText(parameters, elementType)) { - return true; - } - textContainer = textContainer.getTreeParent(); - } - return false; - } - - private static boolean isPlainText(CompletionParameters parameters, IElementType elementType) { - if (elementType != PlainTextTokenTypes.PLAIN_TEXT) { - return false; - } - - if (ApplicationManager.getApplication().isUnitTestMode()) { - return true; - } - - if (parameters.getInvocationCount() > 0) { - return true; - } - - return parameters.getOriginalFile().getViewProvider().getVirtualFile().isInLocalFileSystem(); - } -} +/* + * 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.codeInsight.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.lang.ASTNode; +import com.intellij.lang.LanguageWordCompletion; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbService; +import com.intellij.psi.PlainTextTokenTypes; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.filters.getters.AllWordsGetter; +import com.intellij.psi.tree.IElementType; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static com.intellij.patterns.StandardPatterns.character; + +/** + * @author peter + */ +public class WordCompletionContributor extends CompletionContributor implements DumbAware { + + @Override + public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) { + if (parameters.getCompletionType() == CompletionType.BASIC && shouldPerformWordCompletion(parameters)) { + addWordCompletionVariants(result, parameters, Collections.emptySet()); + } + } + + public static void addWordCompletionVariants(CompletionResultSet result, CompletionParameters parameters, Set excludes) { + int startOffset = parameters.getOffset(); + PsiElement insertedElement = parameters.getPosition(); + final CompletionResultSet javaResultSet = result.withPrefixMatcher(CompletionUtil.findJavaIdentifierPrefix(insertedElement, startOffset)); + final CompletionResultSet plainResultSet = result.withPrefixMatcher(CompletionUtil.findIdentifierPrefix(insertedElement, + startOffset, + character().javaIdentifierPart().andNot(character().equalTo('$')), + character().javaIdentifierStart())); + for (final String word : AllWordsGetter.getAllWords(insertedElement, startOffset)) { + if (!excludes.contains(word)) { + final LookupElement item = LookupElementBuilder.create(word); + javaResultSet.addElement(item); + plainResultSet.addElement(item); + } + } + } + + private static boolean shouldPerformWordCompletion(CompletionParameters parameters) { + final PsiElement insertedElement = parameters.getPosition(); + final boolean dumb = DumbService.getInstance(insertedElement.getProject()).isDumb(); + if (dumb) { + return true; + } + + final PsiFile file = insertedElement.getContainingFile(); + final CompletionData data = CompletionUtil.getCompletionDataByElement(insertedElement, file); + if (data != null && !(data instanceof SyntaxTableCompletionData)) { + Set toAdd = new HashSet(); + data.addKeywordVariants(toAdd, insertedElement, file); + for (CompletionVariant completionVariant : toAdd) { + if (completionVariant.hasKeywordCompletions()) { + return false; + } + } + } + + final int startOffset = parameters.getOffset(); + + final PsiReference reference = file.findReferenceAt(startOffset); + if (reference != null) { + return false; + } + + final PsiElement element = file.findElementAt(startOffset - 1); + + ASTNode textContainer = element != null ? element.getNode() : null; + while (textContainer != null) { + final IElementType elementType = textContainer.getElementType(); + if (LanguageWordCompletion.INSTANCE.isEnabledIn(elementType) || + isPlainText(parameters, elementType)) { + return true; + } + textContainer = textContainer.getTreeParent(); + } + return false; + } + + private static boolean isPlainText(CompletionParameters parameters, IElementType elementType) { + if (elementType != PlainTextTokenTypes.PLAIN_TEXT) { + return false; + } + + if (ApplicationManager.getApplication().isUnitTestMode()) { + return true; + } + + if (parameters.getInvocationCount() > 0) { + return true; + } + + return parameters.getOriginalFile().getViewProvider().getVirtualFile().isInLocalFileSystem(); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java index 64af47796c65..42ef142195d2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java @@ -1,163 +1,163 @@ -/* - * 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.codeInsight.completion.impl; - -import com.intellij.codeInsight.completion.*; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author peter - */ -public class CompletionServiceImpl extends CompletionService{ - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.impl.CompletionServiceImpl"); - private Throwable myTrace = null; - private CompletionProgressIndicator myCurrentCompletion; - - public static CompletionServiceImpl getCompletionService() { - return (CompletionServiceImpl)CompletionService.getCompletionService(); - } - - @Override - public String getAdvertisementText() { - final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); - return completion == null ? null : completion.getLookup().getAdvertisementText(); - } - - public void setAdvertisementText(@Nullable final String text) { - final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); - if (completion != null) { - completion.getLookup().setAdvertisementText(text); - } - } - - public CompletionResultSet createResultSet(final CompletionParameters parameters, final Consumer consumer, - @NotNull final CompletionContributor contributor) { - final PsiElement position = parameters.getPosition(); - final String prefix = CompletionData.findPrefixStatic(position, parameters.getOffset()); - final String textBeforePosition = parameters.getPosition().getContainingFile().getText().substring(0, parameters.getOffset()); - return new CompletionResultSetImpl(consumer, textBeforePosition, new CamelHumpMatcher(prefix), contributor); - } - - @Override - public CompletionProgressIndicator getCurrentCompletion() { - return myCurrentCompletion; - } - - public void setCurrentCompletion(@Nullable final CompletionProgressIndicator indicator) { - if (indicator != null) { - final CompletionProgressIndicator oldCompletion = myCurrentCompletion; - final Throwable oldTrace = myTrace; - myCurrentCompletion = indicator; - myTrace = new Throwable(); - if (oldCompletion != null) { - throw new RuntimeException( - "SHe's not dead yet!\nthis=" + indicator + "\ncurrent=" + oldCompletion + "\ntrace=" + StringUtil.getThrowableText(oldTrace)); - } - } else { - myCurrentCompletion = null; - } - } - - private static class CompletionResultSetImpl extends CompletionResultSet { - private final String myTextBeforePosition; - - public CompletionResultSetImpl(final Consumer consumer, final String textBeforePosition, - final PrefixMatcher prefixMatcher, CompletionContributor contributor) { - super(prefixMatcher, consumer, contributor); - myTextBeforePosition = textBeforePosition; - } - - @NotNull - public CompletionResultSet withPrefixMatcher(@NotNull final PrefixMatcher matcher) { - if (!myTextBeforePosition.endsWith(matcher.getPrefix())) { - final int len = myTextBeforePosition.length(); - final String fragment = len > 100 ? myTextBeforePosition.substring(len - 100) : myTextBeforePosition; - LOG.error("prefix should be some actual file string just before caret: " + matcher.getPrefix() + "\n text=" + fragment); - } - return new CompletionResultSetImpl(getConsumer(), myTextBeforePosition, matcher, myContributor) { - @Override - public void stopHere() { - super.stopHere(); - CompletionResultSetImpl.this.stopHere(); - } - }; - } - - @NotNull - public CompletionResultSet withPrefixMatcher(@NotNull final String prefix) { - return withPrefixMatcher(new CamelHumpMatcher(prefix)); - } - - @NotNull - @Override - public CompletionResultSet caseInsensitive() { - return withPrefixMatcher(new CamelHumpMatcher(getPrefixMatcher().getPrefix(), false)); - } - - @Override - public void restartCompletionOnPrefixChange(ElementPattern prefixCondition) { - final CompletionProgressIndicator indicator = getCompletionService().getCurrentCompletion(); - if (indicator != null) { - indicator.addWatchedPrefix(myTextBeforePosition.length() - getPrefixMatcher().getPrefix().length(), prefixCondition); - } - } - } - - public void correctCaseInsensitiveString(@NotNull final LookupElement element, InsertionContext context) { - if (!element.isPrefixMatched()) { - return; - } - - final String prefix = element.getPrefixMatcher().getPrefix(); - final String oldLookupString = element.getLookupString(); - if (StringUtil.startsWithIgnoreCase(oldLookupString, prefix)) { - final String newLookupString = handleCaseInsensitiveVariant(prefix, oldLookupString); - if (!newLookupString.equals(oldLookupString)) { - final Document document = context.getEditor().getDocument(); - document.replaceString(context.getStartOffset(), context.getTailOffset(), newLookupString); - PsiDocumentManager.getInstance(context.getProject()).commitDocument(document); - } - } - } - - private static String handleCaseInsensitiveVariant(final String prefix, @NotNull final String lookupString) { - final int length = prefix.length(); - if (length == 0) return lookupString; - boolean isAllLower = true; - boolean isAllUpper = true; - boolean sameCase = true; - for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) { - final char c = prefix.charAt(i); - isAllLower = isAllLower && Character.isLowerCase(c); - isAllUpper = isAllUpper && Character.isUpperCase(c); - sameCase = sameCase && Character.isLowerCase(c) == Character.isLowerCase(lookupString.charAt(i)); - } - if (sameCase) return lookupString; - if (isAllLower) return lookupString.toLowerCase(); - if (isAllUpper) return lookupString.toUpperCase(); - return lookupString; - } - -} +/* + * 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.codeInsight.completion.impl; + +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author peter + */ +public class CompletionServiceImpl extends CompletionService{ + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.impl.CompletionServiceImpl"); + private Throwable myTrace = null; + private CompletionProgressIndicator myCurrentCompletion; + + public static CompletionServiceImpl getCompletionService() { + return (CompletionServiceImpl)CompletionService.getCompletionService(); + } + + @Override + public String getAdvertisementText() { + final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); + return completion == null ? null : completion.getLookup().getAdvertisementText(); + } + + public void setAdvertisementText(@Nullable final String text) { + final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); + if (completion != null) { + completion.getLookup().setAdvertisementText(text); + } + } + + public CompletionResultSet createResultSet(final CompletionParameters parameters, final Consumer consumer, + @NotNull final CompletionContributor contributor) { + final PsiElement position = parameters.getPosition(); + final String prefix = CompletionData.findPrefixStatic(position, parameters.getOffset()); + final String textBeforePosition = parameters.getPosition().getContainingFile().getText().substring(0, parameters.getOffset()); + return new CompletionResultSetImpl(consumer, textBeforePosition, new CamelHumpMatcher(prefix), contributor); + } + + @Override + public CompletionProgressIndicator getCurrentCompletion() { + return myCurrentCompletion; + } + + public void setCurrentCompletion(@Nullable final CompletionProgressIndicator indicator) { + if (indicator != null) { + final CompletionProgressIndicator oldCompletion = myCurrentCompletion; + final Throwable oldTrace = myTrace; + myCurrentCompletion = indicator; + myTrace = new Throwable(); + if (oldCompletion != null) { + throw new RuntimeException( + "SHe's not dead yet!\nthis=" + indicator + "\ncurrent=" + oldCompletion + "\ntrace=" + StringUtil.getThrowableText(oldTrace)); + } + } else { + myCurrentCompletion = null; + } + } + + private static class CompletionResultSetImpl extends CompletionResultSet { + private final String myTextBeforePosition; + + public CompletionResultSetImpl(final Consumer consumer, final String textBeforePosition, + final PrefixMatcher prefixMatcher, CompletionContributor contributor) { + super(prefixMatcher, consumer, contributor); + myTextBeforePosition = textBeforePosition; + } + + @NotNull + public CompletionResultSet withPrefixMatcher(@NotNull final PrefixMatcher matcher) { + if (!myTextBeforePosition.endsWith(matcher.getPrefix())) { + final int len = myTextBeforePosition.length(); + final String fragment = len > 100 ? myTextBeforePosition.substring(len - 100) : myTextBeforePosition; + LOG.error("prefix should be some actual file string just before caret: " + matcher.getPrefix() + "\n text=" + fragment); + } + return new CompletionResultSetImpl(getConsumer(), myTextBeforePosition, matcher, myContributor) { + @Override + public void stopHere() { + super.stopHere(); + CompletionResultSetImpl.this.stopHere(); + } + }; + } + + @NotNull + public CompletionResultSet withPrefixMatcher(@NotNull final String prefix) { + return withPrefixMatcher(new CamelHumpMatcher(prefix)); + } + + @NotNull + @Override + public CompletionResultSet caseInsensitive() { + return withPrefixMatcher(new CamelHumpMatcher(getPrefixMatcher().getPrefix(), false)); + } + + @Override + public void restartCompletionOnPrefixChange(ElementPattern prefixCondition) { + final CompletionProgressIndicator indicator = getCompletionService().getCurrentCompletion(); + if (indicator != null) { + indicator.addWatchedPrefix(myTextBeforePosition.length() - getPrefixMatcher().getPrefix().length(), prefixCondition); + } + } + } + + public void correctCaseInsensitiveString(@NotNull final LookupElement element, InsertionContext context) { + if (!element.isPrefixMatched()) { + return; + } + + final String prefix = element.getPrefixMatcher().getPrefix(); + final String oldLookupString = element.getLookupString(); + if (StringUtil.startsWithIgnoreCase(oldLookupString, prefix)) { + final String newLookupString = handleCaseInsensitiveVariant(prefix, oldLookupString); + if (!newLookupString.equals(oldLookupString)) { + final Document document = context.getEditor().getDocument(); + document.replaceString(context.getStartOffset(), context.getTailOffset(), newLookupString); + PsiDocumentManager.getInstance(context.getProject()).commitDocument(document); + } + } + } + + private static String handleCaseInsensitiveVariant(final String prefix, @NotNull final String lookupString) { + final int length = prefix.length(); + if (length == 0) return lookupString; + boolean isAllLower = true; + boolean isAllUpper = true; + boolean sameCase = true; + for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) { + final char c = prefix.charAt(i); + isAllLower = isAllLower && Character.isLowerCase(c); + isAllUpper = isAllUpper && Character.isUpperCase(c); + sameCase = sameCase && Character.isLowerCase(c) == Character.isLowerCase(lookupString.charAt(i)); + } + if (sameCase) return lookupString; + if (isAllLower) return lookupString.toLowerCase(); + if (isAllUpper) return lookupString.toUpperCase(); + return lookupString; + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java index 9b7f556e7d1d..d89fb0377d99 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/FileStatusMap.java @@ -1,298 +1,298 @@ - -/* - * 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.codeInsight.daemon.impl; - -import com.intellij.codeHighlighting.DirtyScopeTrackingHighlightingPassFactory; -import com.intellij.codeHighlighting.Pass; -import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ex.RangeMarkerEx; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.util.containers.WeakHashMap; -import gnu.trove.TIntObjectHashMap; -import gnu.trove.TIntObjectProcedure; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.util.Map; - -public class FileStatusMap implements Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.FileStatusMap"); - private final Project myProject; - private final Map myDocumentToStatusMap = new WeakHashMap(); // all dirty if absent - private boolean myAllowDirt = true; - - public FileStatusMap(@NotNull Project project) { - myProject = project; - } - - public void dispose() { - markAllFilesDirty(); - } - - @Nullable - public static TextRange getDirtyTextRange(@NotNull Editor editor, int passId) { - Document document = editor.getDocument(); - - FileStatusMap me = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(editor.getProject())).getFileStatusMap(); - TextRange dirtyScope = me.getFileDirtyScope(document, passId); - if (dirtyScope == null) return null; - TextRange documentRange = TextRange.from(0, document.getTextLength()); - return documentRange.intersection(dirtyScope); - } - - public void setErrorFoundFlag(@NotNull Document document, boolean errorFound) { - //GHP has found error. Flag is used by ExternalToolPass to decide whether to run or not - synchronized(myDocumentToStatusMap) { - FileStatus status = myDocumentToStatusMap.get(document); - if (status == null){ - if (!errorFound) return; - PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); - assert file != null : document; - status = new FileStatus(file,document); - myDocumentToStatusMap.put(document, status); - } - status.errorFound = errorFound; - } - } - - public boolean wasErrorFound(@NotNull Document document) { - synchronized(myDocumentToStatusMap) { - FileStatus status = myDocumentToStatusMap.get(document); - return status != null && status.errorFound; - } - } - - private static class FileStatus { - public boolean defensivelyMarked; // file marked dirty without knowledge of specific dirty region. Subsequent markScopeDirty can refine dirty scope, not extend it - private boolean wolfPassFinfished; - private final TIntObjectHashMap dirtyScopes = new TIntObjectHashMap(); - private boolean errorFound; - - private FileStatus(@NotNull PsiFile file, @NotNull Document document) { - markWholeFile(file, document, file.getProject()); - } - - private void markWholeFile(PsiFile file, Document document, Project project) { - dirtyScopes.put(Pass.UPDATE_ALL, createWholeFileMarker(file, document)); - dirtyScopes.put(Pass.EXTERNAL_TOOLS, createWholeFileMarker(file, document)); - dirtyScopes.put(Pass.LOCAL_INSPECTIONS, createWholeFileMarker(file, document)); - TextEditorHighlightingPassRegistrarImpl registrar = (TextEditorHighlightingPassRegistrarImpl) TextEditorHighlightingPassRegistrar.getInstance(project); - for(DirtyScopeTrackingHighlightingPassFactory factory: registrar.getDirtyScopeTrackingFactories()) { - dirtyScopes.put(factory.getPassId(), createWholeFileMarker(file, document)); - } - } - - private static RangeMarker createWholeFileMarker(PsiFile file, Document document) { - int length = file == null ? -1 : Math.min(file.getTextLength(), document.getTextLength()); - return length == -1 ? null : document.createRangeMarker(0, length); - } - - public boolean allDirtyScopesAreNull() { - for (Object o : dirtyScopes.getValues()) { - if (o != null) return false; - } - return true; - } - - public void combineScopesWith(final TextRange scope, final int fileLength, final Document document) { - dirtyScopes.forEachEntry(new TIntObjectProcedure() { - public boolean execute(int id, RangeMarker oldScope) { - RangeMarker newScope = combineScopes(oldScope, scope, fileLength, document); - if (newScope != oldScope) { - dirtyScopes.put(id, newScope); - } - return true; - } - }); - } - - @Override - public String toString() { - final StringBuilder s = new StringBuilder(); - s.append("defensivelyMarked = " + defensivelyMarked); - s.append("; wolfPassFinfished = " + wolfPassFinfished); - s.append("; errorFound = " + errorFound); - s.append("; dirtyScopes: ("); - dirtyScopes.forEachEntry(new TIntObjectProcedure() { - public boolean execute(int passId, RangeMarker rangeMarker) { - s.append(" pass: " + passId + " -> " + rangeMarker + ";"); - return true; - } - }); - s.append(")"); - return s.toString(); - } - } - - public void markAllFilesDirty() { - assert myAllowDirt; - LOG.debug("********************************* Mark all dirty"); - synchronized (myDocumentToStatusMap) { - myDocumentToStatusMap.clear(); - } - } - - public void markFileUpToDate(@NotNull Document document, @NotNull PsiFile file, int passId) { - synchronized(myDocumentToStatusMap){ - FileStatus status = myDocumentToStatusMap.get(document); - if (status == null){ - status = new FileStatus(file,document); - myDocumentToStatusMap.put(document, status); - } - status.defensivelyMarked=false; - if (passId == Pass.WOLF) { - status.wolfPassFinfished = true; - } - else if (status.dirtyScopes.containsKey(passId)) { - RangeMarker marker = status.dirtyScopes.get(passId); - if (marker != null) { - marker.dispose(); - status.dirtyScopes.put(passId, null); - } - } - } - } - - /** - * @param document - * @param passId - * @return null for processed file, whole file for untouched or entirely dirty file, range(usually code block) for dirty region (optimization) - */ - @Nullable - public TextRange getFileDirtyScope(@NotNull Document document, int passId) { - synchronized(myDocumentToStatusMap){ - PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); - if (!CollectHighlightsUtil.shouldHighlightFile(file)) return null; - FileStatus status = myDocumentToStatusMap.get(document); - if (status == null){ - return file == null ? null : file.getTextRange(); - } - if (status.defensivelyMarked) { - //PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); - status.markWholeFile(file, document, myProject); - status.defensivelyMarked = false; - } - LOG.assertTrue(status.dirtyScopes.containsKey(passId), "Unknown pass " + passId); - RangeMarker marker = status.dirtyScopes.get(passId); - return marker == null ? null : marker.isValid() ? new TextRange(marker.getStartOffset(), marker.getEndOffset()) : new TextRange(0, document.getTextLength()); - } - } - - public void markFileScopeDirty(@NotNull Document document, int passId) { - assert myAllowDirt; - synchronized(myDocumentToStatusMap){ - FileStatus status = myDocumentToStatusMap.get(document); - if (status == null){ - return; - } - if (passId == Pass.WOLF) { - status.wolfPassFinfished = false; - } - else { - LOG.assertTrue(status.dirtyScopes.containsKey(passId)); - RangeMarker marker = status.dirtyScopes.get(passId); - if (marker != null) { - marker.dispose(); - } - marker = document.createRangeMarker(0, document.getTextLength()); - status.dirtyScopes.put(passId, marker); - } - } - } - - public void markFileScopeDirtyDefensively(@NotNull PsiFile file) { - assert myAllowDirt; - if (LOG.isDebugEnabled()) { - LOG.debug("********************************* Mark dirty file defensively: "+file.getName()); - } - // mark whole file dirty in case no subsequent PSI events will come, but file requires rehighlighting nevertheless - // e.g. in the case of quick typing/backspacing char - synchronized(myDocumentToStatusMap){ - Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file); - if (document == null) return; - FileStatus status = myDocumentToStatusMap.get(document); - if (status == null) return; // all dirty already - status.defensivelyMarked = true; - } - } - - public void markFileScopeDirty(@NotNull Document document, @NotNull TextRange scope, int fileLength) { - assert myAllowDirt; - if (LOG.isDebugEnabled()) { - LOG.debug("********************************* Mark dirty: "+scope); - } - synchronized(myDocumentToStatusMap) { - FileStatus status = myDocumentToStatusMap.get(document); - if (status == null) return; // all dirty already - if (status.defensivelyMarked) { - status.defensivelyMarked = false; - } - status.combineScopesWith(scope, fileLength,document); - } - } - - private static RangeMarker combineScopes(RangeMarker old, TextRange scope, int textLength, @NotNull Document document) { - if (scope == null) return old; - if (old == null) { - return document.createRangeMarker(scope); - } - TextRange oldRange = new TextRange(old.getStartOffset(), old.getEndOffset()); - TextRange union = scope.union(oldRange); - if (old.isValid() && union.equals(oldRange)) { - return old; - } - if (union.getEndOffset() > textLength) { - union = union.intersection(new TextRange(0, textLength)); - } - old.dispose(); - return document.createRangeMarker(union); - } - - public boolean allDirtyScopesAreNull(@NotNull Document document) { - synchronized (myDocumentToStatusMap) { - PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); - if (!CollectHighlightsUtil.shouldHighlightFile(file)) return true; - - FileStatus status = myDocumentToStatusMap.get(document); - return status != null && !status.defensivelyMarked && status.wolfPassFinfished && status.allDirtyScopesAreNull(); - } - } - - @TestOnly - public void assertAllDirtyScopesAreNull(@NotNull Document document) { - synchronized (myDocumentToStatusMap) { - FileStatus status = myDocumentToStatusMap.get(document); - assert status != null && !status.defensivelyMarked && status.wolfPassFinfished && status.allDirtyScopesAreNull() : status; - } - } - - @TestOnly - public void allowDirt(boolean allow) { - myAllowDirt = allow; - } -} + +/* + * 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.codeInsight.daemon.impl; + +import com.intellij.codeHighlighting.DirtyScopeTrackingHighlightingPassFactory; +import com.intellij.codeHighlighting.Pass; +import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.ex.RangeMarkerEx; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.util.containers.WeakHashMap; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntObjectProcedure; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.Map; + +public class FileStatusMap implements Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.FileStatusMap"); + private final Project myProject; + private final Map myDocumentToStatusMap = new WeakHashMap(); // all dirty if absent + private boolean myAllowDirt = true; + + public FileStatusMap(@NotNull Project project) { + myProject = project; + } + + public void dispose() { + markAllFilesDirty(); + } + + @Nullable + public static TextRange getDirtyTextRange(@NotNull Editor editor, int passId) { + Document document = editor.getDocument(); + + FileStatusMap me = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(editor.getProject())).getFileStatusMap(); + TextRange dirtyScope = me.getFileDirtyScope(document, passId); + if (dirtyScope == null) return null; + TextRange documentRange = TextRange.from(0, document.getTextLength()); + return documentRange.intersection(dirtyScope); + } + + public void setErrorFoundFlag(@NotNull Document document, boolean errorFound) { + //GHP has found error. Flag is used by ExternalToolPass to decide whether to run or not + synchronized(myDocumentToStatusMap) { + FileStatus status = myDocumentToStatusMap.get(document); + if (status == null){ + if (!errorFound) return; + PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); + assert file != null : document; + status = new FileStatus(file,document); + myDocumentToStatusMap.put(document, status); + } + status.errorFound = errorFound; + } + } + + public boolean wasErrorFound(@NotNull Document document) { + synchronized(myDocumentToStatusMap) { + FileStatus status = myDocumentToStatusMap.get(document); + return status != null && status.errorFound; + } + } + + private static class FileStatus { + public boolean defensivelyMarked; // file marked dirty without knowledge of specific dirty region. Subsequent markScopeDirty can refine dirty scope, not extend it + private boolean wolfPassFinfished; + private final TIntObjectHashMap dirtyScopes = new TIntObjectHashMap(); + private boolean errorFound; + + private FileStatus(@NotNull PsiFile file, @NotNull Document document) { + markWholeFile(file, document, file.getProject()); + } + + private void markWholeFile(PsiFile file, Document document, Project project) { + dirtyScopes.put(Pass.UPDATE_ALL, createWholeFileMarker(file, document)); + dirtyScopes.put(Pass.EXTERNAL_TOOLS, createWholeFileMarker(file, document)); + dirtyScopes.put(Pass.LOCAL_INSPECTIONS, createWholeFileMarker(file, document)); + TextEditorHighlightingPassRegistrarImpl registrar = (TextEditorHighlightingPassRegistrarImpl) TextEditorHighlightingPassRegistrar.getInstance(project); + for(DirtyScopeTrackingHighlightingPassFactory factory: registrar.getDirtyScopeTrackingFactories()) { + dirtyScopes.put(factory.getPassId(), createWholeFileMarker(file, document)); + } + } + + private static RangeMarker createWholeFileMarker(PsiFile file, Document document) { + int length = file == null ? -1 : Math.min(file.getTextLength(), document.getTextLength()); + return length == -1 ? null : document.createRangeMarker(0, length); + } + + public boolean allDirtyScopesAreNull() { + for (Object o : dirtyScopes.getValues()) { + if (o != null) return false; + } + return true; + } + + public void combineScopesWith(final TextRange scope, final int fileLength, final Document document) { + dirtyScopes.forEachEntry(new TIntObjectProcedure() { + public boolean execute(int id, RangeMarker oldScope) { + RangeMarker newScope = combineScopes(oldScope, scope, fileLength, document); + if (newScope != oldScope) { + dirtyScopes.put(id, newScope); + } + return true; + } + }); + } + + @Override + public String toString() { + final StringBuilder s = new StringBuilder(); + s.append("defensivelyMarked = " + defensivelyMarked); + s.append("; wolfPassFinfished = " + wolfPassFinfished); + s.append("; errorFound = " + errorFound); + s.append("; dirtyScopes: ("); + dirtyScopes.forEachEntry(new TIntObjectProcedure() { + public boolean execute(int passId, RangeMarker rangeMarker) { + s.append(" pass: " + passId + " -> " + rangeMarker + ";"); + return true; + } + }); + s.append(")"); + return s.toString(); + } + } + + public void markAllFilesDirty() { + assert myAllowDirt; + LOG.debug("********************************* Mark all dirty"); + synchronized (myDocumentToStatusMap) { + myDocumentToStatusMap.clear(); + } + } + + public void markFileUpToDate(@NotNull Document document, @NotNull PsiFile file, int passId) { + synchronized(myDocumentToStatusMap){ + FileStatus status = myDocumentToStatusMap.get(document); + if (status == null){ + status = new FileStatus(file,document); + myDocumentToStatusMap.put(document, status); + } + status.defensivelyMarked=false; + if (passId == Pass.WOLF) { + status.wolfPassFinfished = true; + } + else if (status.dirtyScopes.containsKey(passId)) { + RangeMarker marker = status.dirtyScopes.get(passId); + if (marker != null) { + marker.dispose(); + status.dirtyScopes.put(passId, null); + } + } + } + } + + /** + * @param document + * @param passId + * @return null for processed file, whole file for untouched or entirely dirty file, range(usually code block) for dirty region (optimization) + */ + @Nullable + public TextRange getFileDirtyScope(@NotNull Document document, int passId) { + synchronized(myDocumentToStatusMap){ + PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); + if (!CollectHighlightsUtil.shouldHighlightFile(file)) return null; + FileStatus status = myDocumentToStatusMap.get(document); + if (status == null){ + return file == null ? null : file.getTextRange(); + } + if (status.defensivelyMarked) { + //PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); + status.markWholeFile(file, document, myProject); + status.defensivelyMarked = false; + } + LOG.assertTrue(status.dirtyScopes.containsKey(passId), "Unknown pass " + passId); + RangeMarker marker = status.dirtyScopes.get(passId); + return marker == null ? null : marker.isValid() ? new TextRange(marker.getStartOffset(), marker.getEndOffset()) : new TextRange(0, document.getTextLength()); + } + } + + public void markFileScopeDirty(@NotNull Document document, int passId) { + assert myAllowDirt; + synchronized(myDocumentToStatusMap){ + FileStatus status = myDocumentToStatusMap.get(document); + if (status == null){ + return; + } + if (passId == Pass.WOLF) { + status.wolfPassFinfished = false; + } + else { + LOG.assertTrue(status.dirtyScopes.containsKey(passId)); + RangeMarker marker = status.dirtyScopes.get(passId); + if (marker != null) { + marker.dispose(); + } + marker = document.createRangeMarker(0, document.getTextLength()); + status.dirtyScopes.put(passId, marker); + } + } + } + + public void markFileScopeDirtyDefensively(@NotNull PsiFile file) { + assert myAllowDirt; + if (LOG.isDebugEnabled()) { + LOG.debug("********************************* Mark dirty file defensively: "+file.getName()); + } + // mark whole file dirty in case no subsequent PSI events will come, but file requires rehighlighting nevertheless + // e.g. in the case of quick typing/backspacing char + synchronized(myDocumentToStatusMap){ + Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file); + if (document == null) return; + FileStatus status = myDocumentToStatusMap.get(document); + if (status == null) return; // all dirty already + status.defensivelyMarked = true; + } + } + + public void markFileScopeDirty(@NotNull Document document, @NotNull TextRange scope, int fileLength) { + assert myAllowDirt; + if (LOG.isDebugEnabled()) { + LOG.debug("********************************* Mark dirty: "+scope); + } + synchronized(myDocumentToStatusMap) { + FileStatus status = myDocumentToStatusMap.get(document); + if (status == null) return; // all dirty already + if (status.defensivelyMarked) { + status.defensivelyMarked = false; + } + status.combineScopesWith(scope, fileLength,document); + } + } + + private static RangeMarker combineScopes(RangeMarker old, TextRange scope, int textLength, @NotNull Document document) { + if (scope == null) return old; + if (old == null) { + return document.createRangeMarker(scope); + } + TextRange oldRange = new TextRange(old.getStartOffset(), old.getEndOffset()); + TextRange union = scope.union(oldRange); + if (old.isValid() && union.equals(oldRange)) { + return old; + } + if (union.getEndOffset() > textLength) { + union = union.intersection(new TextRange(0, textLength)); + } + old.dispose(); + return document.createRangeMarker(union); + } + + public boolean allDirtyScopesAreNull(@NotNull Document document) { + synchronized (myDocumentToStatusMap) { + PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); + if (!CollectHighlightsUtil.shouldHighlightFile(file)) return true; + + FileStatus status = myDocumentToStatusMap.get(document); + return status != null && !status.defensivelyMarked && status.wolfPassFinfished && status.allDirtyScopesAreNull(); + } + } + + @TestOnly + public void assertAllDirtyScopesAreNull(@NotNull Document document) { + synchronized (myDocumentToStatusMap) { + FileStatus status = myDocumentToStatusMap.get(document); + assert status != null && !status.defensivelyMarked && status.wolfPassFinfished && status.allDirtyScopesAreNull() : status; + } + } + + @TestOnly + public void allowDirt(boolean allow) { + myAllowDirt = allow; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/StatusBarUpdater.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/StatusBarUpdater.java index e675c5c309c1..7f1dc2435509 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/StatusBarUpdater.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/StatusBarUpdater.java @@ -1,105 +1,105 @@ - -/* - * 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.codeInsight.daemon.impl; - -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.event.CaretEvent; -import com.intellij.openapi.editor.event.CaretListener; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.project.DumbAwareRunnable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.openapi.wm.ex.StatusBarEx; - -public class StatusBarUpdater implements Disposable { - private final Project myProject; - private final UpdateStatusRunnable myUpdateStatusRunnable = new UpdateStatusRunnable(); - - public StatusBarUpdater(Project project) { - myProject = project; - - CaretListener caretListener = new CaretListener() { - public void caretPositionChanged(CaretEvent e) { - updateLater(); - } - }; - EditorFactory.getInstance().getEventMulticaster().addCaretListener(caretListener, this); - - project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { - @Override - public void selectionChanged(FileEditorManagerEvent event) { - updateLater(); - } - }); - } - - private void updateLater() { - final Application application = ApplicationManager.getApplication(); - if (!application.isUnitTestMode()) { - application.invokeLater(myUpdateStatusRunnable); - } - else { - myUpdateStatusRunnable.run(); - } - } - - public void dispose() { - } - - private void updateStatus() { - Editor editor = FileEditorManager.getInstance(myProject).getSelectedTextEditor(); - if (editor == null || !editor.getContentComponent().hasFocus()){ - return; - } - - final Document document = editor.getDocument(); - if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; - - int offset = editor.getCaretModel().getOffset(); - DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); - HighlightInfo info = ((DaemonCodeAnalyzerImpl)codeAnalyzer).findHighlightByOffset(document, offset, false); - String text = info != null && info.description != null ? info.description : ""; - - StatusBar statusBar = WindowManager.getInstance().getStatusBar(editor.getContentComponent()); - if (statusBar instanceof StatusBarEx) { - StatusBarEx barEx = (StatusBarEx)statusBar; - if (!text.equals(barEx.getInfo())){ - statusBar.setInfo(text, "updater"); - } - } - } - - private class UpdateStatusRunnable implements DumbAwareRunnable { - public void run() { - if (!myProject.isDisposed()) { - updateStatus(); - } - } - } -} + +/* + * 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.codeInsight.daemon.impl; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.event.CaretEvent; +import com.intellij.openapi.editor.event.CaretListener; +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.ex.StatusBarEx; + +public class StatusBarUpdater implements Disposable { + private final Project myProject; + private final UpdateStatusRunnable myUpdateStatusRunnable = new UpdateStatusRunnable(); + + public StatusBarUpdater(Project project) { + myProject = project; + + CaretListener caretListener = new CaretListener() { + public void caretPositionChanged(CaretEvent e) { + updateLater(); + } + }; + EditorFactory.getInstance().getEventMulticaster().addCaretListener(caretListener, this); + + project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { + @Override + public void selectionChanged(FileEditorManagerEvent event) { + updateLater(); + } + }); + } + + private void updateLater() { + final Application application = ApplicationManager.getApplication(); + if (!application.isUnitTestMode()) { + application.invokeLater(myUpdateStatusRunnable); + } + else { + myUpdateStatusRunnable.run(); + } + } + + public void dispose() { + } + + private void updateStatus() { + Editor editor = FileEditorManager.getInstance(myProject).getSelectedTextEditor(); + if (editor == null || !editor.getContentComponent().hasFocus()){ + return; + } + + final Document document = editor.getDocument(); + if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; + + int offset = editor.getCaretModel().getOffset(); + DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); + HighlightInfo info = ((DaemonCodeAnalyzerImpl)codeAnalyzer).findHighlightByOffset(document, offset, false); + String text = info != null && info.description != null ? info.description : ""; + + StatusBar statusBar = WindowManager.getInstance().getStatusBar(editor.getContentComponent()); + if (statusBar instanceof StatusBarEx) { + StatusBarEx barEx = (StatusBarEx)statusBar; + if (!text.equals(barEx.getInfo())){ + statusBar.setInfo(text, "updater"); + } + } + } + + private class UpdateStatusRunnable implements DumbAwareRunnable { + public void run() { + if (!myProject.isDisposed()) { + updateStatus(); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java index 8d6d46274f51..48f71b088b5f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java @@ -1,706 +1,706 @@ -/* - * 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.codeInsight.daemon.impl; - -import com.intellij.codeInsight.daemon.LineMarkerInfo; -import com.intellij.codeInsight.intention.impl.FileLevelIntentionComponent; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.lang.injection.InjectedLanguageManager; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.ex.MarkupModelEx; -import com.intellij.openapi.editor.ex.RangeHighlighterEx; -import com.intellij.openapi.editor.highlighter.HighlighterIterator; -import com.intellij.openapi.editor.impl.RangeMarkerTree; -import com.intellij.openapi.editor.impl.RedBlackTree; -import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.util.Consumer; -import com.intellij.util.Processor; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.MultiMap; -import gnu.trove.THashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.awt.*; -import java.util.*; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -public class UpdateHighlightersUtil { - private static final Comparator BY_START_OFFSET_NODUPS = new Comparator() { - public int compare(HighlightInfo o1, HighlightInfo o2) { - int d = o1.getActualStartOffset() - o2.getActualStartOffset(); - if (d != 0) return d; - d = o1.getActualEndOffset() - o2.getActualEndOffset(); - if (d != 0) return d; - - d = Comparing.compare(o1.getSeverity(), o2.getSeverity()); - if (d != 0) return -d; // higher severity first, to prevent warnings overlap errors - - if (!Comparing.equal(o1.type, o2.type)) { - return String.valueOf(o1.type).compareTo(String.valueOf(o2.type)); - } - - if (!Comparing.equal(o1.getGutterIconRenderer(), o2.getGutterIconRenderer())) { - return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); - } - - if (!Comparing.equal(o1.forcedTextAttributes, o2.forcedTextAttributes)) { - return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); - } - - if (!Comparing.equal(o1.forcedTextAttributesKey, o2.forcedTextAttributesKey)) { - return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); - } - - return Comparing.compare(o1.description, o2.description); - } - }; - - private static final Key> FILE_LEVEL_HIGHLIGHTS = Key.create("FILE_LEVEL_HIGHLIGHTS"); - private static final Comparator BY_START_OFFSET = new Comparator() { - public int compare(final TextRange o1, final TextRange o2) { - return o1.getStartOffset() - o2.getStartOffset(); - } - }; - - static void cleanFileLevelHighlights(@NotNull Project project, final int group, PsiFile psiFile) { - if (psiFile == null || !psiFile.getViewProvider().isPhysical()) return; - VirtualFile vFile = psiFile.getViewProvider().getVirtualFile(); - final FileEditorManager manager = FileEditorManager.getInstance(project); - for (FileEditor fileEditor : manager.getEditors(vFile)) { - final List infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS); - if (infos == null) continue; - List infosToRemove = new ArrayList(); - for (HighlightInfo info : infos) { - if (info.group == group) { - manager.removeTopComponent(fileEditor, info.fileLevelComponent); - infosToRemove.add(info); - } - } - infos.removeAll(infosToRemove); - } - } - - static boolean hasInfo(Collection infos, int start, int end, String desc) { - if (infos == null) return false; - for (HighlightInfo info : infos) { - if (info.startOffset == start && info.endOffset == end && info.description.equals(desc)) return true; - } - return false; - } - - private static class HighlightersRecycler { - private final MultiMap incinerator = new MultiMap(){ - @Override - protected Map> createMap() { - return new THashMap>(); - } - - @Override - protected Collection createCollection() { - return new SmartList(); - } - }; - - void recycleHighlighter(RangeHighlighter highlighter) { - incinerator.putValue(InjectedLanguageUtil.toTextRange(highlighter), highlighter); - } - - RangeHighlighter pickupHighlighterFromGarbageBin(int startOffset, int endOffset, int layer){ - TextRange range = new TextRange(startOffset, endOffset); - Collection collection = incinerator.get(range); - for (RangeHighlighter highlighter : collection) { - if (highlighter.isValid() && highlighter.getLayer() == layer) { - incinerator.removeValue(range, highlighter); - return highlighter; - } - } - return null; - } - Collection forAllInGarbageBin() { - return incinerator.values(); - } - } - - static void addHighlighterToEditorIncrementally(@NotNull Project project, - @NotNull Document document, - @NotNull PsiFile file, - int startOffset, - int endOffset, - @NotNull final HighlightInfo info, - @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used - final int group) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (info.isFileLevelAnnotation || info.getGutterIconRenderer() != null) return; - - MarkupModel markup = document.getMarkupModel(project); - Processor otherHighlightInTheWayProcessor = new Processor() { - public boolean process(HighlightInfo oldInfo) { - return oldInfo.group != group || !oldInfo.equalsByActualOffset(info); - } - }; - if (!DaemonCodeAnalyzerImpl.processHighlights(document, project, - null, info.getActualStartOffset(), info.getActualEndOffset(), - otherHighlightInTheWayProcessor)) { - return; - } - - if (info.getStartOffset() < startOffset || info.getEndOffset() > endOffset) return; - - createOrReuseHighlighterFor(info, colorsScheme, document, group, file, (MarkupModelEx)markup, null, null, - SeverityRegistrar.getInstance(project)); - - clearWhiteSpaceOptimizationFlag(document); - assertMarkupConsistent(markup, project); - } - - public static void setHighlightersToEditor(@NotNull Project project, - @NotNull Document document, - int startOffset, - int endOffset, - @NotNull Collection highlights, - @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used - int group) { - TextRange range = new TextRange(startOffset, endOffset); - ApplicationManager.getApplication().assertIsDispatchThread(); - - PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - cleanFileLevelHighlights(project, group, psiFile); - - MarkupModel markup = document.getMarkupModel(project); - assertMarkupConsistent(markup, project); - - setHighlightersInRange(project, document, range, colorsScheme, highlights, (MarkupModelEx)markup, group); - } - - @Deprecated //for teamcity - public static void setHighlightersToEditor(@NotNull Project project, - @NotNull Document document, - int startOffset, - int endOffset, - @NotNull Collection highlights, - int group) { - setHighlightersToEditor(project, document, startOffset, endOffset, highlights, null, group); - } - - // set highlights inside startOffset,endOffset but outside range - static void setHighlightersOutsideRange(@NotNull final Project project, - @NotNull final Document document, - @NotNull Collection infos, - @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used - int startOffset, int endOffset, - @NotNull final ProperTextRange range, - final int group) { - ApplicationManager.getApplication().assertIsDispatchThread(); - - final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - cleanFileLevelHighlights(project, group, psiFile); - - final MarkupModel markup = document.getMarkupModel(project); - assertMarkupConsistent(markup, project); - - final List highlights = new ArrayList(infos); - - final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); - final HighlightersRecycler infosToRemove = new HighlightersRecycler(); - DaemonCodeAnalyzerImpl.processHighlights(document, project, null, startOffset, endOffset, new Processor() { - @Override - public boolean process(HighlightInfo info) { - if (info.group == group) { - RangeHighlighter highlighter = info.highlighter; - int hiStart = highlighter.getStartOffset(); - int hiEnd = highlighter.getEndOffset(); - boolean willBeRemoved = hiEnd == document.getTextLength() && range.getEndOffset() == document.getTextLength() - || !range.containsRange(hiStart, hiEnd); - if (willBeRemoved) { - infosToRemove.recycleHighlighter(highlighter); - info.highlighter = null; - } - } - return true; - } - }); - - Collections.sort(highlights, BY_START_OFFSET_NODUPS); - final Map ranges2markersCache = new THashMap(10); - final boolean[] changed = {false}; - RangeMarkerTree.sweep(new RangeMarkerTree.Generator(){ - @Override - public boolean generate(Processor processor) { - return ContainerUtil.process(highlights, processor); - } - }, new MarkupModelEx.SweepProcessor() { - @Override - public boolean process(int offset, HighlightInfo info, boolean atStart, Collection overlappingIntervals) { - if (!atStart) { - return true; - } - if (info.isFileLevelAnnotation && psiFile != null && psiFile.getViewProvider().isPhysical()) { - addFileLevelHighlight(project, group, info, psiFile); - changed[0] = true; - return true; - } - if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { - return true; - } - if (info.getStartOffset() < range.getStartOffset() || info.getEndOffset() > range.getEndOffset()) { - createOrReuseHighlighterFor(info, colorsScheme, document, group, psiFile, (MarkupModelEx)markup, infosToRemove, ranges2markersCache, severityRegistrar); - changed[0] = true; - } - return true; - } - }); - for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { - markup.removeHighlighter(highlighter); - changed[0] = true; - } - - if (changed[0]) { - clearWhiteSpaceOptimizationFlag(document); - } - assertMarkupConsistent(markup, project); - } - - static void setHighlightersInRange(@NotNull final Project project, - @NotNull final Document document, - @NotNull final TextRange range, - @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used - @NotNull Collection highlightsCo, - @NotNull final MarkupModelEx markup, - final int group) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final List highlights = new ArrayList(highlightsCo); - - final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); - final HighlightersRecycler infosToRemove = new HighlightersRecycler(); - DaemonCodeAnalyzerImpl.processHighlights(document, project, null, range.getStartOffset(), range.getEndOffset(), new Processor() { - @Override - public boolean process(HighlightInfo info) { - if (info.group == group) { - RangeHighlighter highlighter = info.highlighter; - int hiStart = highlighter.getStartOffset(); - int hiEnd = highlighter.getEndOffset(); - boolean willBeRemoved = hiEnd == document.getTextLength() && range.getEndOffset() == document.getTextLength() - || range.intersectsStrict(hiStart, hiEnd) || range.containsRange(hiStart, hiEnd) || hiStart <= range.getStartOffset() && hiEnd >= range.getEndOffset(); - if (willBeRemoved) { - infosToRemove.recycleHighlighter(highlighter); - info.highlighter = null; - } - } - return true; - } - }); - - Collections.sort(highlights, BY_START_OFFSET_NODUPS); - final Map ranges2markersCache = new THashMap(10); - final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - final boolean[] changed = {false}; - RangeMarkerTree.sweep(new RangeMarkerTree.Generator(){ - @Override - public boolean generate(final Processor processor) { - return ContainerUtil.process(highlights, processor); - } - }, new MarkupModelEx.SweepProcessor() { - @Override - public boolean process(int offset, HighlightInfo info, boolean atStart, Collection overlappingIntervals) { - if (!atStart) { - return true; - } - if (info.isFileLevelAnnotation && psiFile != null && psiFile.getViewProvider().isPhysical()) { - addFileLevelHighlight(project, group, info, psiFile); - changed[0] = true; - return true; - } - if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { - return true; - } - if (info.getStartOffset() >= range.getStartOffset() && info.getEndOffset() <= range.getEndOffset()) { - createOrReuseHighlighterFor(info, colorsScheme, document, group, psiFile, markup, infosToRemove, ranges2markersCache, severityRegistrar); - changed[0] = true; - } - return true; - } - }); - for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { - markup.removeHighlighter(highlighter); - changed[0] = true; - } - - if (changed[0]) { - clearWhiteSpaceOptimizationFlag(document); - } - assertMarkupConsistent(markup, project); - } - - private static boolean isWarningCoveredByError(HighlightInfo info, - Collection overlappingIntervals, - SeverityRegistrar severityRegistrar) { - if (!isError(info, severityRegistrar)) { - for (HighlightInfo overlapping : overlappingIntervals) { - boolean overlapIsError = isError(overlapping, severityRegistrar); - if (overlapIsError && DaemonCodeAnalyzerImpl.isCoveredBy(info, overlapping)) { - return true; - } - } - } - return false; - } - - private static boolean isError(HighlightInfo info, SeverityRegistrar severityRegistrar) { - return severityRegistrar.compare(HighlightSeverity.ERROR, info.getSeverity()) <= 0; - } - - // return true if changed - private static void createOrReuseHighlighterFor(@NotNull final HighlightInfo info, - @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used - @NotNull final Document document, - final int group, - @NotNull final PsiFile psiFile, - @NotNull MarkupModelEx markup, - @Nullable HighlightersRecycler infosToRemove, - @Nullable final Map ranges2markersCache, - SeverityRegistrar severityRegistrar) { - final int infoStartOffset = info.startOffset; - int infoEndOffset = info.endOffset; - - if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine) { - infoEndOffset++; //show something in case of empty highlightinfo - } - final int docLength = document.getTextLength(); - if (infoEndOffset > docLength) { - infoEndOffset = docLength; - } - - info.text = document.getCharsSequence().subSequence(infoStartOffset, infoEndOffset).toString(); - info.group = group; - - int layer = getLayer(info, severityRegistrar); - RangeHighlighterEx highlighter = infosToRemove == null ? null : (RangeHighlighterEx)infosToRemove.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, layer); - - final int finalInfoEndOffset = infoEndOffset; - Consumer changeAttributes = new Consumer() { - @Override - public void consume(RangeHighlighterEx finalHighlighter) { - finalHighlighter.setTextAttributes(info.getTextAttributes(psiFile, colorsScheme)); - - info.highlighter = finalHighlighter; - finalHighlighter.setAfterEndOfLine(info.isAfterEndOfLine); - - Color color = info.getErrorStripeMarkColor(psiFile, colorsScheme); - finalHighlighter.setErrorStripeMarkColor(color); - if (info != finalHighlighter.getErrorStripeTooltip()) { - finalHighlighter.setErrorStripeTooltip(info); - } - GutterIconRenderer renderer = info.getGutterIconRenderer(); - finalHighlighter.setGutterIconRenderer(renderer); - - if (ranges2markersCache != null) ranges2markersCache.put(new TextRange(infoStartOffset, finalInfoEndOffset), info.highlighter); - if (info.quickFixActionRanges != null) { - List> list = - new ArrayList>(info.quickFixActionRanges.size()); - for (Pair pair : info.quickFixActionRanges) { - TextRange textRange = pair.second; - RangeMarker marker = getOrCreate(document, ranges2markersCache, textRange); - list.add(Pair.create(pair.first, marker)); - } - info.quickFixActionMarkers = new CopyOnWriteArrayList>(list); - } - info.fixMarker = getOrCreate(document, ranges2markersCache, new TextRange(info.fixStartOffset, info.fixEndOffset)); - } - }; - - if (highlighter == null) { - highlighter = markup.addRangeHighlighterAndChangeAttributes(infoStartOffset, infoEndOffset, layer, null, - HighlighterTargetArea.EXACT_RANGE, false, changeAttributes); - } - else { - markup.changeAttributesInBatch(highlighter, changeAttributes); - } - - assert Comparing.equal(info.getTextAttributes(psiFile, colorsScheme), highlighter.getTextAttributes()) : "Info: " + - info.getTextAttributes(psiFile, colorsScheme) + - "; colorsSheme: " + (colorsScheme == null ? "[global]" : colorsScheme.getName()) + - "; highlighter:" + - highlighter.getTextAttributes(); - } - - private static int getLayer(HighlightInfo info, SeverityRegistrar severityRegistrar) { - final HighlightSeverity severity = info.getSeverity(); - int layer; - if (severity == HighlightSeverity.WARNING) { - layer = HighlighterLayer.WARNING; - } - else if (severityRegistrar.compare(severity, HighlightSeverity.ERROR) >= 0) { - layer = HighlighterLayer.ERROR; - } - else { - layer = HighlighterLayer.ADDITIONAL_SYNTAX; - } - return layer; - } - - private static RangeMarker getOrCreate(@NotNull Document document, @Nullable Map ranges2markersCache, @NotNull TextRange textRange) { - RangeMarker marker = ranges2markersCache == null ? null : ranges2markersCache.get(textRange); - if (marker == null) { - marker = document.createRangeMarker(textRange); - if (ranges2markersCache != null) { - ranges2markersCache.put(textRange, marker); - } - } - return marker; - } - - private static void addFileLevelHighlight(final Project project, final int group, final HighlightInfo info, final PsiFile psiFile) { - VirtualFile vFile = psiFile.getViewProvider().getVirtualFile(); - final FileEditorManager manager = FileEditorManager.getInstance(project); - for (FileEditor fileEditor : manager.getEditors(vFile)) { - if (fileEditor instanceof TextEditor) { - FileLevelIntentionComponent component = new FileLevelIntentionComponent(info.description, info.severity, info.quickFixActionRanges, - project, psiFile, ((TextEditor)fileEditor).getEditor()); - manager.addTopComponent(fileEditor, component); - List fileLevelInfos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS); - if (fileLevelInfos == null) { - fileLevelInfos = new ArrayList(); - fileEditor.putUserData(FILE_LEVEL_HIGHLIGHTS, fileLevelInfos); - } - info.fileLevelComponent = component; - info.group = group; - fileLevelInfos.add(info); - } - } - } - - static void setLineMarkersToEditor(@NotNull Project project, - @NotNull Document document, - int startOffset, - int endOffset, - @NotNull Collection markers, - int group) { - ApplicationManager.getApplication().assertIsDispatchThread(); - - List oldMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(document, project); - List array = new ArrayList(oldMarkers == null ? markers.size() : oldMarkers.size()); - MarkupModel markupModel = document.getMarkupModel(project); - HighlightersRecycler toReuse = new HighlightersRecycler(); - if (oldMarkers != null) { - for (LineMarkerInfo info : oldMarkers) { - RangeHighlighter highlighter = info.highlighter; - boolean toRemove = !highlighter.isValid() || - info.updatePass == group && - startOffset <= highlighter.getStartOffset() && - (highlighter.getEndOffset() < endOffset || highlighter.getEndOffset() == document.getTextLength()); - - if (toRemove) { - toReuse.recycleHighlighter(highlighter); - } - else { - array.add(info); - } - } - } - - for (LineMarkerInfo info : markers) { - PsiElement element = info.getElement(); - if (element == null) { - continue; - } - - TextRange textRange = element.getTextRange(); - if (textRange == null) continue; - TextRange elementRange = InjectedLanguageManager.getInstance(project).injectedToHost(element, textRange); - if (startOffset > elementRange.getStartOffset() || elementRange.getEndOffset() > endOffset) { - continue; - } - RangeHighlighter marker = toReuse.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX); - if (marker == null) { - marker = markupModel.addRangeHighlighter(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX, null, HighlighterTargetArea.EXACT_RANGE); - } - LineMarkerInfo.LineMarkerGutterIconRenderer renderer = (LineMarkerInfo.LineMarkerGutterIconRenderer)info.createGutterRenderer(); - LineMarkerInfo.LineMarkerGutterIconRenderer oldRenderer = marker.getGutterIconRenderer() instanceof LineMarkerInfo.LineMarkerGutterIconRenderer ? (LineMarkerInfo.LineMarkerGutterIconRenderer)marker.getGutterIconRenderer() : null; - if (oldRenderer == null || renderer == null || !renderer.equals(oldRenderer)) { - marker.setGutterIconRenderer(renderer); - } - if (!Comparing.equal(marker.getLineSeparatorColor(), info.separatorColor)) { - marker.setLineSeparatorColor(info.separatorColor); - } - if (!Comparing.equal(marker.getLineSeparatorPlacement(), info.separatorPlacement)) { - marker.setLineSeparatorPlacement(info.separatorPlacement); - } - info.highlighter = marker; - array.add(info); - } - - for (RangeHighlighter highlighter : toReuse.forAllInGarbageBin()) { - markupModel.removeHighlighter(highlighter); - } - - DaemonCodeAnalyzerImpl.setLineMarkers(document, array, project); - } - - private static final Key TYPING_INSIDE_HIGHLIGHTER_OCCURRED = Key.create("TYPING_INSIDE_HIGHLIGHTER_OCCURRED"); - static boolean isWhitespaceOptimizationAllowed(final Document document) { - return document.getUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED) == null; - } - private static void disableWhiteSpaceOptimization(Document document) { - document.putUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED, Boolean.TRUE); - } - private static void clearWhiteSpaceOptimizationFlag(final Document document) { - document.putUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED, null); - } - - static void updateHighlightersByTyping(@NotNull Project project, @NotNull DocumentEvent e) { - ApplicationManager.getApplication().assertIsDispatchThread(); - - final Document document = e.getDocument(); - if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; - - final MarkupModel markup = document.getMarkupModel(project); - assertMarkupConsistent(markup, project); - - int offset = e.getOffset(); - Editor[] editors = EditorFactory.getInstance().getEditors(document, project); - if (editors.length == 0) return; - Editor editor = editors[0]; // use any editor - just to fetch SelectInEditorManager - HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(Math.max(0, offset - 1)); - if (iterator.atEnd()) return; - final int start = iterator.getStart(); - while (iterator.getEnd() < e.getOffset() + Math.max(e.getOldLength(), e.getNewLength())) { - iterator.advance(); - if (iterator.atEnd()) return; - } - final int end = iterator.getEnd(); - - final boolean[] highlightersChanged = {false}; - final List removed = new ArrayList(); - final boolean[] documentChangedInsideHighlighter = {false}; - DaemonCodeAnalyzerImpl.processHighlights(document, project, null, start, end, new Processor() { - public boolean process(HighlightInfo info) { - RangeHighlighter highlighter = info.highlighter; - boolean toRemove = false; - if (info.needUpdateOnTyping()) { - int highlighterStart = highlighter.getStartOffset(); - int highlighterEnd = highlighter.getEndOffset(); - if (info.isAfterEndOfLine) { - if (highlighterStart < document.getTextLength()) { - highlighterStart += 1; - } - if (highlighterEnd < document.getTextLength()) { - highlighterEnd += 1; - } - } - - if (!highlighter.isValid()) { - toRemove = true; - } - else if (start < highlighterEnd && highlighterStart <= end) { - documentChangedInsideHighlighter[0] = true; - toRemove = true; - } - } - - if (toRemove) { - //if (info.type.equals(HighlightInfoType.WRONG_REF)) { - /* - markup.removeHighlighter(highlighter); - */ - //} - highlightersChanged[0] = true; - removed.add(info); - } - - return true; - } - }); - - for (HighlightInfo info : removed) { - if (!info.highlighter.isValid() || info.type.equals(HighlightInfoType.WRONG_REF)) { - markup.removeHighlighter(info.highlighter); - } - } - - assertMarkupConsistent(markup, project); - - if (highlightersChanged[0] || documentChangedInsideHighlighter[0]) { - disableWhiteSpaceOptimization(document); - } - } - - @NotNull - @TestOnly - static List getFileLeveleHighlights(Project project, PsiFile file) { - VirtualFile vFile = file.getViewProvider().getVirtualFile(); - final FileEditorManager manager = FileEditorManager.getInstance(project); - List result = new ArrayList(); - for (FileEditor fileEditor : manager.getEditors(vFile)) { - final List infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS); - if (infos == null) continue; - for (HighlightInfo info : infos) { - result.add(info); - } - } - return result; - } - - private static void assertMarkupConsistent(final MarkupModel markup, Project project) { - if (!RedBlackTree.VERIFY) { - return; - } - Document document = markup.getDocument(); - DaemonCodeAnalyzerImpl.processHighlights(document, project, null, 0, document.getTextLength(), new Processor() { - public boolean process(HighlightInfo info) { - assert ((MarkupModelEx)markup).containsHighlighter(info.highlighter); - return true; - } - }); - RangeHighlighter[] allHighlighters = markup.getAllHighlighters(); - for (RangeHighlighter highlighter : allHighlighters) { - if (!highlighter.isValid()) continue; - Object tooltip = highlighter.getErrorStripeTooltip(); - if (!(tooltip instanceof HighlightInfo)) { - continue; - } - final HighlightInfo info = (HighlightInfo)tooltip; - boolean contains = !DaemonCodeAnalyzerImpl.processHighlights(document, project, null, info.getActualStartOffset(), info.getActualEndOffset(), new Processor() { - public boolean process(HighlightInfo highlightInfo) { - return UpdateHighlightersUtil.BY_START_OFFSET_NODUPS.compare(highlightInfo, info) != 0; - } - }); - assert contains: info; - } - } -} +/* + * 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.codeInsight.daemon.impl; + +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.intention.impl.FileLevelIntentionComponent; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.ex.MarkupModelEx; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.editor.impl.RangeMarkerTree; +import com.intellij.openapi.editor.impl.RedBlackTree; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.util.Consumer; +import com.intellij.util.Processor; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class UpdateHighlightersUtil { + private static final Comparator BY_START_OFFSET_NODUPS = new Comparator() { + public int compare(HighlightInfo o1, HighlightInfo o2) { + int d = o1.getActualStartOffset() - o2.getActualStartOffset(); + if (d != 0) return d; + d = o1.getActualEndOffset() - o2.getActualEndOffset(); + if (d != 0) return d; + + d = Comparing.compare(o1.getSeverity(), o2.getSeverity()); + if (d != 0) return -d; // higher severity first, to prevent warnings overlap errors + + if (!Comparing.equal(o1.type, o2.type)) { + return String.valueOf(o1.type).compareTo(String.valueOf(o2.type)); + } + + if (!Comparing.equal(o1.getGutterIconRenderer(), o2.getGutterIconRenderer())) { + return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); + } + + if (!Comparing.equal(o1.forcedTextAttributes, o2.forcedTextAttributes)) { + return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); + } + + if (!Comparing.equal(o1.forcedTextAttributesKey, o2.forcedTextAttributesKey)) { + return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); + } + + return Comparing.compare(o1.description, o2.description); + } + }; + + private static final Key> FILE_LEVEL_HIGHLIGHTS = Key.create("FILE_LEVEL_HIGHLIGHTS"); + private static final Comparator BY_START_OFFSET = new Comparator() { + public int compare(final TextRange o1, final TextRange o2) { + return o1.getStartOffset() - o2.getStartOffset(); + } + }; + + static void cleanFileLevelHighlights(@NotNull Project project, final int group, PsiFile psiFile) { + if (psiFile == null || !psiFile.getViewProvider().isPhysical()) return; + VirtualFile vFile = psiFile.getViewProvider().getVirtualFile(); + final FileEditorManager manager = FileEditorManager.getInstance(project); + for (FileEditor fileEditor : manager.getEditors(vFile)) { + final List infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS); + if (infos == null) continue; + List infosToRemove = new ArrayList(); + for (HighlightInfo info : infos) { + if (info.group == group) { + manager.removeTopComponent(fileEditor, info.fileLevelComponent); + infosToRemove.add(info); + } + } + infos.removeAll(infosToRemove); + } + } + + static boolean hasInfo(Collection infos, int start, int end, String desc) { + if (infos == null) return false; + for (HighlightInfo info : infos) { + if (info.startOffset == start && info.endOffset == end && info.description.equals(desc)) return true; + } + return false; + } + + private static class HighlightersRecycler { + private final MultiMap incinerator = new MultiMap(){ + @Override + protected Map> createMap() { + return new THashMap>(); + } + + @Override + protected Collection createCollection() { + return new SmartList(); + } + }; + + void recycleHighlighter(RangeHighlighter highlighter) { + incinerator.putValue(InjectedLanguageUtil.toTextRange(highlighter), highlighter); + } + + RangeHighlighter pickupHighlighterFromGarbageBin(int startOffset, int endOffset, int layer){ + TextRange range = new TextRange(startOffset, endOffset); + Collection collection = incinerator.get(range); + for (RangeHighlighter highlighter : collection) { + if (highlighter.isValid() && highlighter.getLayer() == layer) { + incinerator.removeValue(range, highlighter); + return highlighter; + } + } + return null; + } + Collection forAllInGarbageBin() { + return incinerator.values(); + } + } + + static void addHighlighterToEditorIncrementally(@NotNull Project project, + @NotNull Document document, + @NotNull PsiFile file, + int startOffset, + int endOffset, + @NotNull final HighlightInfo info, + @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used + final int group) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (info.isFileLevelAnnotation || info.getGutterIconRenderer() != null) return; + + MarkupModel markup = document.getMarkupModel(project); + Processor otherHighlightInTheWayProcessor = new Processor() { + public boolean process(HighlightInfo oldInfo) { + return oldInfo.group != group || !oldInfo.equalsByActualOffset(info); + } + }; + if (!DaemonCodeAnalyzerImpl.processHighlights(document, project, + null, info.getActualStartOffset(), info.getActualEndOffset(), + otherHighlightInTheWayProcessor)) { + return; + } + + if (info.getStartOffset() < startOffset || info.getEndOffset() > endOffset) return; + + createOrReuseHighlighterFor(info, colorsScheme, document, group, file, (MarkupModelEx)markup, null, null, + SeverityRegistrar.getInstance(project)); + + clearWhiteSpaceOptimizationFlag(document); + assertMarkupConsistent(markup, project); + } + + public static void setHighlightersToEditor(@NotNull Project project, + @NotNull Document document, + int startOffset, + int endOffset, + @NotNull Collection highlights, + @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used + int group) { + TextRange range = new TextRange(startOffset, endOffset); + ApplicationManager.getApplication().assertIsDispatchThread(); + + PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + cleanFileLevelHighlights(project, group, psiFile); + + MarkupModel markup = document.getMarkupModel(project); + assertMarkupConsistent(markup, project); + + setHighlightersInRange(project, document, range, colorsScheme, highlights, (MarkupModelEx)markup, group); + } + + @Deprecated //for teamcity + public static void setHighlightersToEditor(@NotNull Project project, + @NotNull Document document, + int startOffset, + int endOffset, + @NotNull Collection highlights, + int group) { + setHighlightersToEditor(project, document, startOffset, endOffset, highlights, null, group); + } + + // set highlights inside startOffset,endOffset but outside range + static void setHighlightersOutsideRange(@NotNull final Project project, + @NotNull final Document document, + @NotNull Collection infos, + @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used + int startOffset, int endOffset, + @NotNull final ProperTextRange range, + final int group) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + cleanFileLevelHighlights(project, group, psiFile); + + final MarkupModel markup = document.getMarkupModel(project); + assertMarkupConsistent(markup, project); + + final List highlights = new ArrayList(infos); + + final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); + final HighlightersRecycler infosToRemove = new HighlightersRecycler(); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, startOffset, endOffset, new Processor() { + @Override + public boolean process(HighlightInfo info) { + if (info.group == group) { + RangeHighlighter highlighter = info.highlighter; + int hiStart = highlighter.getStartOffset(); + int hiEnd = highlighter.getEndOffset(); + boolean willBeRemoved = hiEnd == document.getTextLength() && range.getEndOffset() == document.getTextLength() + || !range.containsRange(hiStart, hiEnd); + if (willBeRemoved) { + infosToRemove.recycleHighlighter(highlighter); + info.highlighter = null; + } + } + return true; + } + }); + + Collections.sort(highlights, BY_START_OFFSET_NODUPS); + final Map ranges2markersCache = new THashMap(10); + final boolean[] changed = {false}; + RangeMarkerTree.sweep(new RangeMarkerTree.Generator(){ + @Override + public boolean generate(Processor processor) { + return ContainerUtil.process(highlights, processor); + } + }, new MarkupModelEx.SweepProcessor() { + @Override + public boolean process(int offset, HighlightInfo info, boolean atStart, Collection overlappingIntervals) { + if (!atStart) { + return true; + } + if (info.isFileLevelAnnotation && psiFile != null && psiFile.getViewProvider().isPhysical()) { + addFileLevelHighlight(project, group, info, psiFile); + changed[0] = true; + return true; + } + if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { + return true; + } + if (info.getStartOffset() < range.getStartOffset() || info.getEndOffset() > range.getEndOffset()) { + createOrReuseHighlighterFor(info, colorsScheme, document, group, psiFile, (MarkupModelEx)markup, infosToRemove, ranges2markersCache, severityRegistrar); + changed[0] = true; + } + return true; + } + }); + for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { + markup.removeHighlighter(highlighter); + changed[0] = true; + } + + if (changed[0]) { + clearWhiteSpaceOptimizationFlag(document); + } + assertMarkupConsistent(markup, project); + } + + static void setHighlightersInRange(@NotNull final Project project, + @NotNull final Document document, + @NotNull final TextRange range, + @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used + @NotNull Collection highlightsCo, + @NotNull final MarkupModelEx markup, + final int group) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final List highlights = new ArrayList(highlightsCo); + + final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); + final HighlightersRecycler infosToRemove = new HighlightersRecycler(); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, range.getStartOffset(), range.getEndOffset(), new Processor() { + @Override + public boolean process(HighlightInfo info) { + if (info.group == group) { + RangeHighlighter highlighter = info.highlighter; + int hiStart = highlighter.getStartOffset(); + int hiEnd = highlighter.getEndOffset(); + boolean willBeRemoved = hiEnd == document.getTextLength() && range.getEndOffset() == document.getTextLength() + || range.intersectsStrict(hiStart, hiEnd) || range.containsRange(hiStart, hiEnd) || hiStart <= range.getStartOffset() && hiEnd >= range.getEndOffset(); + if (willBeRemoved) { + infosToRemove.recycleHighlighter(highlighter); + info.highlighter = null; + } + } + return true; + } + }); + + Collections.sort(highlights, BY_START_OFFSET_NODUPS); + final Map ranges2markersCache = new THashMap(10); + final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + final boolean[] changed = {false}; + RangeMarkerTree.sweep(new RangeMarkerTree.Generator(){ + @Override + public boolean generate(final Processor processor) { + return ContainerUtil.process(highlights, processor); + } + }, new MarkupModelEx.SweepProcessor() { + @Override + public boolean process(int offset, HighlightInfo info, boolean atStart, Collection overlappingIntervals) { + if (!atStart) { + return true; + } + if (info.isFileLevelAnnotation && psiFile != null && psiFile.getViewProvider().isPhysical()) { + addFileLevelHighlight(project, group, info, psiFile); + changed[0] = true; + return true; + } + if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { + return true; + } + if (info.getStartOffset() >= range.getStartOffset() && info.getEndOffset() <= range.getEndOffset()) { + createOrReuseHighlighterFor(info, colorsScheme, document, group, psiFile, markup, infosToRemove, ranges2markersCache, severityRegistrar); + changed[0] = true; + } + return true; + } + }); + for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { + markup.removeHighlighter(highlighter); + changed[0] = true; + } + + if (changed[0]) { + clearWhiteSpaceOptimizationFlag(document); + } + assertMarkupConsistent(markup, project); + } + + private static boolean isWarningCoveredByError(HighlightInfo info, + Collection overlappingIntervals, + SeverityRegistrar severityRegistrar) { + if (!isError(info, severityRegistrar)) { + for (HighlightInfo overlapping : overlappingIntervals) { + boolean overlapIsError = isError(overlapping, severityRegistrar); + if (overlapIsError && DaemonCodeAnalyzerImpl.isCoveredBy(info, overlapping)) { + return true; + } + } + } + return false; + } + + private static boolean isError(HighlightInfo info, SeverityRegistrar severityRegistrar) { + return severityRegistrar.compare(HighlightSeverity.ERROR, info.getSeverity()) <= 0; + } + + // return true if changed + private static void createOrReuseHighlighterFor(@NotNull final HighlightInfo info, + @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used + @NotNull final Document document, + final int group, + @NotNull final PsiFile psiFile, + @NotNull MarkupModelEx markup, + @Nullable HighlightersRecycler infosToRemove, + @Nullable final Map ranges2markersCache, + SeverityRegistrar severityRegistrar) { + final int infoStartOffset = info.startOffset; + int infoEndOffset = info.endOffset; + + if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine) { + infoEndOffset++; //show something in case of empty highlightinfo + } + final int docLength = document.getTextLength(); + if (infoEndOffset > docLength) { + infoEndOffset = docLength; + } + + info.text = document.getCharsSequence().subSequence(infoStartOffset, infoEndOffset).toString(); + info.group = group; + + int layer = getLayer(info, severityRegistrar); + RangeHighlighterEx highlighter = infosToRemove == null ? null : (RangeHighlighterEx)infosToRemove.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, layer); + + final int finalInfoEndOffset = infoEndOffset; + Consumer changeAttributes = new Consumer() { + @Override + public void consume(RangeHighlighterEx finalHighlighter) { + finalHighlighter.setTextAttributes(info.getTextAttributes(psiFile, colorsScheme)); + + info.highlighter = finalHighlighter; + finalHighlighter.setAfterEndOfLine(info.isAfterEndOfLine); + + Color color = info.getErrorStripeMarkColor(psiFile, colorsScheme); + finalHighlighter.setErrorStripeMarkColor(color); + if (info != finalHighlighter.getErrorStripeTooltip()) { + finalHighlighter.setErrorStripeTooltip(info); + } + GutterIconRenderer renderer = info.getGutterIconRenderer(); + finalHighlighter.setGutterIconRenderer(renderer); + + if (ranges2markersCache != null) ranges2markersCache.put(new TextRange(infoStartOffset, finalInfoEndOffset), info.highlighter); + if (info.quickFixActionRanges != null) { + List> list = + new ArrayList>(info.quickFixActionRanges.size()); + for (Pair pair : info.quickFixActionRanges) { + TextRange textRange = pair.second; + RangeMarker marker = getOrCreate(document, ranges2markersCache, textRange); + list.add(Pair.create(pair.first, marker)); + } + info.quickFixActionMarkers = new CopyOnWriteArrayList>(list); + } + info.fixMarker = getOrCreate(document, ranges2markersCache, new TextRange(info.fixStartOffset, info.fixEndOffset)); + } + }; + + if (highlighter == null) { + highlighter = markup.addRangeHighlighterAndChangeAttributes(infoStartOffset, infoEndOffset, layer, null, + HighlighterTargetArea.EXACT_RANGE, false, changeAttributes); + } + else { + markup.changeAttributesInBatch(highlighter, changeAttributes); + } + + assert Comparing.equal(info.getTextAttributes(psiFile, colorsScheme), highlighter.getTextAttributes()) : "Info: " + + info.getTextAttributes(psiFile, colorsScheme) + + "; colorsSheme: " + (colorsScheme == null ? "[global]" : colorsScheme.getName()) + + "; highlighter:" + + highlighter.getTextAttributes(); + } + + private static int getLayer(HighlightInfo info, SeverityRegistrar severityRegistrar) { + final HighlightSeverity severity = info.getSeverity(); + int layer; + if (severity == HighlightSeverity.WARNING) { + layer = HighlighterLayer.WARNING; + } + else if (severityRegistrar.compare(severity, HighlightSeverity.ERROR) >= 0) { + layer = HighlighterLayer.ERROR; + } + else { + layer = HighlighterLayer.ADDITIONAL_SYNTAX; + } + return layer; + } + + private static RangeMarker getOrCreate(@NotNull Document document, @Nullable Map ranges2markersCache, @NotNull TextRange textRange) { + RangeMarker marker = ranges2markersCache == null ? null : ranges2markersCache.get(textRange); + if (marker == null) { + marker = document.createRangeMarker(textRange); + if (ranges2markersCache != null) { + ranges2markersCache.put(textRange, marker); + } + } + return marker; + } + + private static void addFileLevelHighlight(final Project project, final int group, final HighlightInfo info, final PsiFile psiFile) { + VirtualFile vFile = psiFile.getViewProvider().getVirtualFile(); + final FileEditorManager manager = FileEditorManager.getInstance(project); + for (FileEditor fileEditor : manager.getEditors(vFile)) { + if (fileEditor instanceof TextEditor) { + FileLevelIntentionComponent component = new FileLevelIntentionComponent(info.description, info.severity, info.quickFixActionRanges, + project, psiFile, ((TextEditor)fileEditor).getEditor()); + manager.addTopComponent(fileEditor, component); + List fileLevelInfos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS); + if (fileLevelInfos == null) { + fileLevelInfos = new ArrayList(); + fileEditor.putUserData(FILE_LEVEL_HIGHLIGHTS, fileLevelInfos); + } + info.fileLevelComponent = component; + info.group = group; + fileLevelInfos.add(info); + } + } + } + + static void setLineMarkersToEditor(@NotNull Project project, + @NotNull Document document, + int startOffset, + int endOffset, + @NotNull Collection markers, + int group) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + List oldMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(document, project); + List array = new ArrayList(oldMarkers == null ? markers.size() : oldMarkers.size()); + MarkupModel markupModel = document.getMarkupModel(project); + HighlightersRecycler toReuse = new HighlightersRecycler(); + if (oldMarkers != null) { + for (LineMarkerInfo info : oldMarkers) { + RangeHighlighter highlighter = info.highlighter; + boolean toRemove = !highlighter.isValid() || + info.updatePass == group && + startOffset <= highlighter.getStartOffset() && + (highlighter.getEndOffset() < endOffset || highlighter.getEndOffset() == document.getTextLength()); + + if (toRemove) { + toReuse.recycleHighlighter(highlighter); + } + else { + array.add(info); + } + } + } + + for (LineMarkerInfo info : markers) { + PsiElement element = info.getElement(); + if (element == null) { + continue; + } + + TextRange textRange = element.getTextRange(); + if (textRange == null) continue; + TextRange elementRange = InjectedLanguageManager.getInstance(project).injectedToHost(element, textRange); + if (startOffset > elementRange.getStartOffset() || elementRange.getEndOffset() > endOffset) { + continue; + } + RangeHighlighter marker = toReuse.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX); + if (marker == null) { + marker = markupModel.addRangeHighlighter(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX, null, HighlighterTargetArea.EXACT_RANGE); + } + LineMarkerInfo.LineMarkerGutterIconRenderer renderer = (LineMarkerInfo.LineMarkerGutterIconRenderer)info.createGutterRenderer(); + LineMarkerInfo.LineMarkerGutterIconRenderer oldRenderer = marker.getGutterIconRenderer() instanceof LineMarkerInfo.LineMarkerGutterIconRenderer ? (LineMarkerInfo.LineMarkerGutterIconRenderer)marker.getGutterIconRenderer() : null; + if (oldRenderer == null || renderer == null || !renderer.equals(oldRenderer)) { + marker.setGutterIconRenderer(renderer); + } + if (!Comparing.equal(marker.getLineSeparatorColor(), info.separatorColor)) { + marker.setLineSeparatorColor(info.separatorColor); + } + if (!Comparing.equal(marker.getLineSeparatorPlacement(), info.separatorPlacement)) { + marker.setLineSeparatorPlacement(info.separatorPlacement); + } + info.highlighter = marker; + array.add(info); + } + + for (RangeHighlighter highlighter : toReuse.forAllInGarbageBin()) { + markupModel.removeHighlighter(highlighter); + } + + DaemonCodeAnalyzerImpl.setLineMarkers(document, array, project); + } + + private static final Key TYPING_INSIDE_HIGHLIGHTER_OCCURRED = Key.create("TYPING_INSIDE_HIGHLIGHTER_OCCURRED"); + static boolean isWhitespaceOptimizationAllowed(final Document document) { + return document.getUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED) == null; + } + private static void disableWhiteSpaceOptimization(Document document) { + document.putUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED, Boolean.TRUE); + } + private static void clearWhiteSpaceOptimizationFlag(final Document document) { + document.putUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED, null); + } + + static void updateHighlightersByTyping(@NotNull Project project, @NotNull DocumentEvent e) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + final Document document = e.getDocument(); + if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; + + final MarkupModel markup = document.getMarkupModel(project); + assertMarkupConsistent(markup, project); + + int offset = e.getOffset(); + Editor[] editors = EditorFactory.getInstance().getEditors(document, project); + if (editors.length == 0) return; + Editor editor = editors[0]; // use any editor - just to fetch SelectInEditorManager + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(Math.max(0, offset - 1)); + if (iterator.atEnd()) return; + final int start = iterator.getStart(); + while (iterator.getEnd() < e.getOffset() + Math.max(e.getOldLength(), e.getNewLength())) { + iterator.advance(); + if (iterator.atEnd()) return; + } + final int end = iterator.getEnd(); + + final boolean[] highlightersChanged = {false}; + final List removed = new ArrayList(); + final boolean[] documentChangedInsideHighlighter = {false}; + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, start, end, new Processor() { + public boolean process(HighlightInfo info) { + RangeHighlighter highlighter = info.highlighter; + boolean toRemove = false; + if (info.needUpdateOnTyping()) { + int highlighterStart = highlighter.getStartOffset(); + int highlighterEnd = highlighter.getEndOffset(); + if (info.isAfterEndOfLine) { + if (highlighterStart < document.getTextLength()) { + highlighterStart += 1; + } + if (highlighterEnd < document.getTextLength()) { + highlighterEnd += 1; + } + } + + if (!highlighter.isValid()) { + toRemove = true; + } + else if (start < highlighterEnd && highlighterStart <= end) { + documentChangedInsideHighlighter[0] = true; + toRemove = true; + } + } + + if (toRemove) { + //if (info.type.equals(HighlightInfoType.WRONG_REF)) { + /* + markup.removeHighlighter(highlighter); + */ + //} + highlightersChanged[0] = true; + removed.add(info); + } + + return true; + } + }); + + for (HighlightInfo info : removed) { + if (!info.highlighter.isValid() || info.type.equals(HighlightInfoType.WRONG_REF)) { + markup.removeHighlighter(info.highlighter); + } + } + + assertMarkupConsistent(markup, project); + + if (highlightersChanged[0] || documentChangedInsideHighlighter[0]) { + disableWhiteSpaceOptimization(document); + } + } + + @NotNull + @TestOnly + static List getFileLeveleHighlights(Project project, PsiFile file) { + VirtualFile vFile = file.getViewProvider().getVirtualFile(); + final FileEditorManager manager = FileEditorManager.getInstance(project); + List result = new ArrayList(); + for (FileEditor fileEditor : manager.getEditors(vFile)) { + final List infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS); + if (infos == null) continue; + for (HighlightInfo info : infos) { + result.add(info); + } + } + return result; + } + + private static void assertMarkupConsistent(final MarkupModel markup, Project project) { + if (!RedBlackTree.VERIFY) { + return; + } + Document document = markup.getDocument(); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, 0, document.getTextLength(), new Processor() { + public boolean process(HighlightInfo info) { + assert ((MarkupModelEx)markup).containsHighlighter(info.highlighter); + return true; + } + }); + RangeHighlighter[] allHighlighters = markup.getAllHighlighters(); + for (RangeHighlighter highlighter : allHighlighters) { + if (!highlighter.isValid()) continue; + Object tooltip = highlighter.getErrorStripeTooltip(); + if (!(tooltip instanceof HighlightInfo)) { + continue; + } + final HighlightInfo info = (HighlightInfo)tooltip; + boolean contains = !DaemonCodeAnalyzerImpl.processHighlights(document, project, null, info.getActualStartOffset(), info.getActualEndOffset(), new Processor() { + public boolean process(HighlightInfo highlightInfo) { + return UpdateHighlightersUtil.BY_START_OFFSET_NODUPS.compare(highlightInfo, info) != 0; + } + }); + assert contains: info; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/actions/ShowJavaDocInfoAction.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/actions/ShowJavaDocInfoAction.java index 90ef8f0cce84..dfa056a02260 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/actions/ShowJavaDocInfoAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/actions/ShowJavaDocInfoAction.java @@ -1,144 +1,144 @@ -/* - * 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.codeInsight.documentation.actions; - -import com.intellij.codeInsight.CodeInsightActionHandler; -import com.intellij.codeInsight.actions.BaseCodeInsightAction; -import com.intellij.codeInsight.documentation.DocumentationManager; -import com.intellij.codeInsight.hint.HintManagerImpl; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.DumbService; -import com.intellij.psi.*; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -public class ShowJavaDocInfoAction extends BaseCodeInsightAction implements HintManagerImpl.ActionToIgnore, DumbAware { - @NonNls public static final String CODEASSISTS_QUICKJAVADOC_LOOKUP_FEATURE = "codeassists.quickjavadoc.lookup"; - @NonNls public static final String CODEASSISTS_QUICKJAVADOC_FEATURE = "codeassists.quickjavadoc"; - - public ShowJavaDocInfoAction() { - setEnabledInModalContext(true); - setInjectedContext(true); - } - - protected CodeInsightActionHandler getHandler() { - return new CodeInsightActionHandler() { - public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { - DocumentationManager.getInstance(project).showJavaDocInfo(editor, file, true); - } - - public boolean startInWriteAction() { - return false; - } - }; - } - - - protected boolean isValidForLookup() { - return true; - } - - public void update(AnActionEvent event) { - Presentation presentation = event.getPresentation(); - DataContext dataContext = event.getDataContext(); - - Project project = PlatformDataKeys.PROJECT.getData(dataContext); - if (project == null) { - presentation.setEnabled(false); - return; - } - - Editor editor = PlatformDataKeys.EDITOR.getData(dataContext); - PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext); - if (editor == null && element == null) { - presentation.setEnabled(false); - return; - } - - if (LookupManager.getInstance(project).getActiveLookup() != null) { - if (!isValidForLookup()) { - presentation.setEnabled(false); - } - else { - presentation.setEnabled(true); - } - } - else { - if (editor != null) { - PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); - if (file == null) { - presentation.setEnabled(false); - } - - if (element == null && file != null) { - try { - final PsiReference ref = file.findReferenceAt(editor.getCaretModel().getOffset()); - if (ref instanceof PsiPolyVariantReference) { - element = ref.getElement(); - } - } - catch (IndexNotReadyException e) { - element = null; - } - } - } - - if (element != null) { - presentation.setEnabled(true); - } - } - } - - public void actionPerformed(AnActionEvent e) { - DataContext dataContext = e.getDataContext(); - final Project project = PlatformDataKeys.PROJECT.getData(dataContext); - final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext); - final PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext); - - try { - if (project != null && editor != null) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CODEASSISTS_QUICKJAVADOC_FEATURE); - final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).getActiveLookup(); - if (lookup != null) { - //dumpLookupElementWeights(lookup); - FeatureUsageTracker.getInstance().triggerFeatureUsed(CODEASSISTS_QUICKJAVADOC_LOOKUP_FEATURE); - } - actionPerformedImpl(project, editor); - } - else if (project != null) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.quickjavadoc.ctrln"); - CommandProcessor.getInstance().executeCommand(project, new Runnable() { - public void run() { - DocumentationManager.getInstance(project).showJavaDocInfo(element, null); - } - }, getCommandName(), null); - } - } - catch (IndexNotReadyException e1) { - DumbService.getInstance(project).showDumbModeNotification("Documentation is not available until indices are built"); - } - } - -} +/* + * 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.codeInsight.documentation.actions; + +import com.intellij.codeInsight.CodeInsightActionHandler; +import com.intellij.codeInsight.actions.BaseCodeInsightAction; +import com.intellij.codeInsight.documentation.DocumentationManager; +import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.DumbService; +import com.intellij.psi.*; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class ShowJavaDocInfoAction extends BaseCodeInsightAction implements HintManagerImpl.ActionToIgnore, DumbAware { + @NonNls public static final String CODEASSISTS_QUICKJAVADOC_LOOKUP_FEATURE = "codeassists.quickjavadoc.lookup"; + @NonNls public static final String CODEASSISTS_QUICKJAVADOC_FEATURE = "codeassists.quickjavadoc"; + + public ShowJavaDocInfoAction() { + setEnabledInModalContext(true); + setInjectedContext(true); + } + + protected CodeInsightActionHandler getHandler() { + return new CodeInsightActionHandler() { + public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + DocumentationManager.getInstance(project).showJavaDocInfo(editor, file, true); + } + + public boolean startInWriteAction() { + return false; + } + }; + } + + + protected boolean isValidForLookup() { + return true; + } + + public void update(AnActionEvent event) { + Presentation presentation = event.getPresentation(); + DataContext dataContext = event.getDataContext(); + + Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project == null) { + presentation.setEnabled(false); + return; + } + + Editor editor = PlatformDataKeys.EDITOR.getData(dataContext); + PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext); + if (editor == null && element == null) { + presentation.setEnabled(false); + return; + } + + if (LookupManager.getInstance(project).getActiveLookup() != null) { + if (!isValidForLookup()) { + presentation.setEnabled(false); + } + else { + presentation.setEnabled(true); + } + } + else { + if (editor != null) { + PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + if (file == null) { + presentation.setEnabled(false); + } + + if (element == null && file != null) { + try { + final PsiReference ref = file.findReferenceAt(editor.getCaretModel().getOffset()); + if (ref instanceof PsiPolyVariantReference) { + element = ref.getElement(); + } + } + catch (IndexNotReadyException e) { + element = null; + } + } + } + + if (element != null) { + presentation.setEnabled(true); + } + } + } + + public void actionPerformed(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext); + final PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext); + + try { + if (project != null && editor != null) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(CODEASSISTS_QUICKJAVADOC_FEATURE); + final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).getActiveLookup(); + if (lookup != null) { + //dumpLookupElementWeights(lookup); + FeatureUsageTracker.getInstance().triggerFeatureUsed(CODEASSISTS_QUICKJAVADOC_LOOKUP_FEATURE); + } + actionPerformedImpl(project, editor); + } + else if (project != null) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.quickjavadoc.ctrln"); + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + public void run() { + DocumentationManager.getInstance(project).showJavaDocInfo(element, null); + } + }, getCommandName(), null); + } + } + catch (IndexNotReadyException e1) { + DumbService.getInstance(project).showDumbModeNotification("Documentation is not available until indices are built"); + } + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java index 0de430a1f47f..a84084c87322 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CompletionAutoPopupHandler.java @@ -1,234 +1,234 @@ -/* - * 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.codeInsight.editorActions; - -import com.intellij.codeInsight.AutoPopupController; -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; -import com.intellij.codeInsight.completion.CompletionProgressIndicator; -import com.intellij.codeInsight.completion.CompletionType; -import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; -import com.intellij.codeInsight.lookup.Lookup; -import com.intellij.codeInsight.lookup.LookupAdapter; -import com.intellij.codeInsight.lookup.LookupEvent; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorModificationUtil; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.psi.PsiFile; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.annotations.Nullable; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -/** - * @author peter - */ -public class CompletionAutoPopupHandler extends TypedHandlerDelegate { - private static final Key STATE_KEY = Key.create("AutopopupSTATE_KEY"); - public static boolean ourTestingAutopopup = false; - - @Override - public Result beforeCharTyped(char c, - Project project, - Editor editor, - PsiFile file, - FileType fileType) { - final AutoPopupState state = getAutoPopupState(editor); - if (state != null && LookupManager.getActiveLookup(editor) == null) { - state.changeGuard = true; - try { - EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, String.valueOf(c), true); - } - finally { - state.changeGuard = false; - } - return Result.STOP; - } - - return Result.CONTINUE; - } - - @Override - public Result checkAutoPopup(char charTyped, final Project project, final Editor editor, final PsiFile file) { - if (!CodeInsightSettings.getInstance().AUTO_POPUP_COMPLETION_LOOKUP) return Result.CONTINUE; - - if (!Character.isLetter(charTyped) && charTyped != '_') { - finishAutopopupCompletion(editor, false); - return Result.CONTINUE; - } - - if (getAutoPopupState(editor) != null || LookupManager.getActiveLookup(editor) != null) { - return Result.CONTINUE; - } - - final CharSequence text = editor.getDocument().getCharsSequence(); - final int offset = editor.getCaretModel().getOffset(); - if (text.length() > offset && Character.isUnicodeIdentifierPart(text.charAt(offset))) { - return Result.CONTINUE; - } - - final boolean isMainEditor = FileEditorManager.getInstance(project).getSelectedTextEditor() == editor; - - final Runnable request = new Runnable() { - @Override - public void run() { - if (project.isDisposed() || !file.isValid()) return; - if (editor.isDisposed() || isMainEditor && FileEditorManager.getInstance(project).getSelectedTextEditor() != editor) return; - if (ApplicationManager.getApplication().isWriteAccessAllowed()) return; //it will fail anyway - if (DumbService.getInstance(project).isDumb()) return; - - new CodeCompletionHandlerBase(CompletionType.BASIC, false, true).invoke(project, editor); - - final AutoPopupState state = new AutoPopupState(project, editor); - editor.putUserData(STATE_KEY, state); - - final Lookup lookup = LookupManager.getActiveLookup(editor); - if (lookup != null) { - lookup.addLookupListener(new LookupAdapter() { - @Override - public void itemSelected(LookupEvent event) { - final AutoPopupState state = getAutoPopupState(editor); - if (state != null) { - state.stopAutoPopup(); - } - } - - @Override - public void lookupCanceled(LookupEvent event) { - final AutoPopupState state = getAutoPopupState(editor); - if (event.isCanceledExplicitly() && state != null) { - state.stopAutoPopup(); - } - } - }); - } - } - }; - if (ourTestingAutopopup) { - ApplicationManager.getApplication().invokeLater(request); - } else { - AutoPopupController.getInstance(project).invokeAutoPopupRunnable(request, CodeInsightSettings.getInstance().AUTO_LOOKUP_DELAY); - } - return Result.STOP; - } - - @Nullable - private static AutoPopupState getAutoPopupState(Editor editor) { - return editor.getUserData(STATE_KEY); - } - - private static void finishAutopopupCompletion(Editor editor, boolean neglectLookup) { - final AutoPopupState state = getAutoPopupState(editor); - if (state == null || state.changeGuard) { - return; - } - - if (!neglectLookup && LookupManager.getActiveLookup(editor) != null) { //the events during visible lookup period are handled separately - return; - } - - final CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); - if (currentCompletion != null) { - currentCompletion.closeAndFinish(true); - } - state.stopAutoPopup(); - } - - - private static class AutoPopupState { - final Editor editor; - final Project project; - final MessageBusConnection connection; - final EditorMouseAdapter mouseListener; - final CaretListener caretListener; - final DocumentAdapter documentListener; - final PropertyChangeListener lookupListener; - boolean changeGuard = false; - - private AutoPopupState(final Project project, final Editor editor) { - this.editor = editor; - this.project = project; - connection = project.getMessageBus().connect(); - connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { - @Override - public void selectionChanged(FileEditorManagerEvent event) { - finishAutopopupCompletion(editor, false); - } - }); - - mouseListener = new EditorMouseAdapter() { - @Override - public void mouseClicked(EditorMouseEvent e) { - finishAutopopupCompletion(editor, false); - } - }; - - caretListener = new CaretListener() { - @Override - public void caretPositionChanged(CaretEvent e) { - finishAutopopupCompletion(editor, false); - } - }; - editor.getSelectionModel().addSelectionListener(new SelectionListener() { - @Override - public void selectionChanged(SelectionEvent e) { - finishAutopopupCompletion(editor, false); - } - }); - documentListener = new DocumentAdapter() { - @Override - public void documentChanged(DocumentEvent e) { - finishAutopopupCompletion(editor, false); - } - }; - lookupListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getNewValue() != null) { - finishAutopopupCompletion(editor, true); - } - } - }; - - editor.addEditorMouseListener(mouseListener); - editor.getCaretModel().addCaretListener(caretListener); - editor.getDocument().addDocumentListener(documentListener); - LookupManager.getInstance(project).addPropertyChangeListener(lookupListener); - } - - void stopAutoPopup() { - connection.disconnect(); - editor.removeEditorMouseListener(mouseListener); - editor.getCaretModel().removeCaretListener(caretListener); - editor.getDocument().removeDocumentListener(documentListener); - LookupManager.getInstance(project).removePropertyChangeListener(lookupListener); - - editor.putUserData(STATE_KEY, null); - } - - } -} +/* + * 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.codeInsight.editorActions; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; +import com.intellij.codeInsight.completion.CompletionProgressIndicator; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; +import com.intellij.codeInsight.lookup.Lookup; +import com.intellij.codeInsight.lookup.LookupAdapter; +import com.intellij.codeInsight.lookup.LookupEvent; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiFile; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.Nullable; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * @author peter + */ +public class CompletionAutoPopupHandler extends TypedHandlerDelegate { + private static final Key STATE_KEY = Key.create("AutopopupSTATE_KEY"); + public static boolean ourTestingAutopopup = false; + + @Override + public Result beforeCharTyped(char c, + Project project, + Editor editor, + PsiFile file, + FileType fileType) { + final AutoPopupState state = getAutoPopupState(editor); + if (state != null && LookupManager.getActiveLookup(editor) == null) { + state.changeGuard = true; + try { + EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, String.valueOf(c), true); + } + finally { + state.changeGuard = false; + } + return Result.STOP; + } + + return Result.CONTINUE; + } + + @Override + public Result checkAutoPopup(char charTyped, final Project project, final Editor editor, final PsiFile file) { + if (!CodeInsightSettings.getInstance().AUTO_POPUP_COMPLETION_LOOKUP) return Result.CONTINUE; + + if (!Character.isLetter(charTyped) && charTyped != '_') { + finishAutopopupCompletion(editor, false); + return Result.CONTINUE; + } + + if (getAutoPopupState(editor) != null || LookupManager.getActiveLookup(editor) != null) { + return Result.CONTINUE; + } + + final CharSequence text = editor.getDocument().getCharsSequence(); + final int offset = editor.getCaretModel().getOffset(); + if (text.length() > offset && Character.isUnicodeIdentifierPart(text.charAt(offset))) { + return Result.CONTINUE; + } + + final boolean isMainEditor = FileEditorManager.getInstance(project).getSelectedTextEditor() == editor; + + final Runnable request = new Runnable() { + @Override + public void run() { + if (project.isDisposed() || !file.isValid()) return; + if (editor.isDisposed() || isMainEditor && FileEditorManager.getInstance(project).getSelectedTextEditor() != editor) return; + if (ApplicationManager.getApplication().isWriteAccessAllowed()) return; //it will fail anyway + if (DumbService.getInstance(project).isDumb()) return; + + new CodeCompletionHandlerBase(CompletionType.BASIC, false, true).invoke(project, editor); + + final AutoPopupState state = new AutoPopupState(project, editor); + editor.putUserData(STATE_KEY, state); + + final Lookup lookup = LookupManager.getActiveLookup(editor); + if (lookup != null) { + lookup.addLookupListener(new LookupAdapter() { + @Override + public void itemSelected(LookupEvent event) { + final AutoPopupState state = getAutoPopupState(editor); + if (state != null) { + state.stopAutoPopup(); + } + } + + @Override + public void lookupCanceled(LookupEvent event) { + final AutoPopupState state = getAutoPopupState(editor); + if (event.isCanceledExplicitly() && state != null) { + state.stopAutoPopup(); + } + } + }); + } + } + }; + if (ourTestingAutopopup) { + ApplicationManager.getApplication().invokeLater(request); + } else { + AutoPopupController.getInstance(project).invokeAutoPopupRunnable(request, CodeInsightSettings.getInstance().AUTO_LOOKUP_DELAY); + } + return Result.STOP; + } + + @Nullable + private static AutoPopupState getAutoPopupState(Editor editor) { + return editor.getUserData(STATE_KEY); + } + + private static void finishAutopopupCompletion(Editor editor, boolean neglectLookup) { + final AutoPopupState state = getAutoPopupState(editor); + if (state == null || state.changeGuard) { + return; + } + + if (!neglectLookup && LookupManager.getActiveLookup(editor) != null) { //the events during visible lookup period are handled separately + return; + } + + final CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion(); + if (currentCompletion != null) { + currentCompletion.closeAndFinish(true); + } + state.stopAutoPopup(); + } + + + private static class AutoPopupState { + final Editor editor; + final Project project; + final MessageBusConnection connection; + final EditorMouseAdapter mouseListener; + final CaretListener caretListener; + final DocumentAdapter documentListener; + final PropertyChangeListener lookupListener; + boolean changeGuard = false; + + private AutoPopupState(final Project project, final Editor editor) { + this.editor = editor; + this.project = project; + connection = project.getMessageBus().connect(); + connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { + @Override + public void selectionChanged(FileEditorManagerEvent event) { + finishAutopopupCompletion(editor, false); + } + }); + + mouseListener = new EditorMouseAdapter() { + @Override + public void mouseClicked(EditorMouseEvent e) { + finishAutopopupCompletion(editor, false); + } + }; + + caretListener = new CaretListener() { + @Override + public void caretPositionChanged(CaretEvent e) { + finishAutopopupCompletion(editor, false); + } + }; + editor.getSelectionModel().addSelectionListener(new SelectionListener() { + @Override + public void selectionChanged(SelectionEvent e) { + finishAutopopupCompletion(editor, false); + } + }); + documentListener = new DocumentAdapter() { + @Override + public void documentChanged(DocumentEvent e) { + finishAutopopupCompletion(editor, false); + } + }; + lookupListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getNewValue() != null) { + finishAutopopupCompletion(editor, true); + } + } + }; + + editor.addEditorMouseListener(mouseListener); + editor.getCaretModel().addCaretListener(caretListener); + editor.getDocument().addDocumentListener(documentListener); + LookupManager.getInstance(project).addPropertyChangeListener(lookupListener); + } + + void stopAutoPopup() { + connection.disconnect(); + editor.removeEditorMouseListener(mouseListener); + editor.getCaretModel().removeCaretListener(caretListener); + editor.getDocument().removeDocumentListener(documentListener); + LookupManager.getInstance(project).removePropertyChangeListener(lookupListener); + + editor.putUserData(STATE_KEY, null); + } + + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/EnterHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/EnterHandler.java index be5c1dcbb714..b5f0794f5e72 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/EnterHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/EnterHandler.java @@ -1,587 +1,587 @@ -/* - * 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.codeInsight.editorActions; - -import com.intellij.codeInsight.CodeInsightBundle; -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate; -import com.intellij.ide.DataManager; -import com.intellij.lang.*; -import com.intellij.lang.documentation.CodeDocumentationProvider; -import com.intellij.lang.documentation.CompositeDocumentationProvider; -import com.intellij.lang.documentation.DocumentationProvider; -import com.intellij.lexer.Lexer; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.Nullable; - -public class EnterHandler extends BaseEnterHandler { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.EnterHandler"); - - private final EditorActionHandler myOriginalHandler; - - public EnterHandler(EditorActionHandler originalHandler) { - myOriginalHandler = originalHandler; - } - - public boolean isEnabled(Editor editor, DataContext dataContext) { - return myOriginalHandler.isEnabled(editor, dataContext); - } - - public void executeWriteAction(final Editor editor, final DataContext dataContext) { - final Project project = PlatformDataKeys.PROJECT.getData(dataContext); - if (project != null && !project.isDefault()) { - PostprocessReformattingAspect.getInstance(project).disablePostprocessFormattingInside(new Runnable() { - public void run() { - executeWriteActionInner(editor, dataContext, project); - } - }); - } - else { - executeWriteActionInner(editor, dataContext, project); - } - } - - private void executeWriteActionInner(Editor editor, DataContext dataContext, Project project) { - CodeInsightSettings settings = CodeInsightSettings.getInstance(); - if (project == null) { - myOriginalHandler.execute(editor, dataContext); - return; - } - final Document document = editor.getDocument(); - final PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project); - - if (file == null) { - myOriginalHandler.execute(editor, dataContext); - return; - } - - CommandProcessor.getInstance().setCurrentCommandName(CodeInsightBundle.message("command.name.typing")); - - EditorModificationUtil.deleteSelectedText(editor); - - int caretOffset = editor.getCaretModel().getOffset(); - CharSequence text = document.getCharsSequence(); - int length = document.getTextLength(); - if (caretOffset < length && text.charAt(caretOffset) != '\n') { - int offset1 = CharArrayUtil.shiftBackward(text, caretOffset, " \t"); - if (offset1 < 0 || text.charAt(offset1) == '\n') { - int offset2 = CharArrayUtil.shiftForward(text, offset1 + 1, " \t"); - boolean isEmptyLine = offset2 >= length || text.charAt(offset2) == '\n'; - if (!isEmptyLine) { // we are in leading spaces of a non-empty line - myOriginalHandler.execute(editor, dataContext); - return; - } - } - } - - PsiDocumentManager.getInstance(project).commitDocument(document); - - boolean forceIndent = false; - Ref caretOffsetRef = new Ref(caretOffset); - Ref caretAdvanceRef = new Ref(0); - - for(EnterHandlerDelegate delegate: Extensions.getExtensions(EnterHandlerDelegate.EP_NAME)) { - EnterHandlerDelegate.Result result = delegate.preprocessEnter(file, editor, caretOffsetRef, caretAdvanceRef, dataContext, myOriginalHandler); - if (caretOffsetRef.get() > document.getTextLength()) { - throw new AssertionError("Wrong caret offset change by " + delegate); - } - - if (result == EnterHandlerDelegate.Result.Stop) return; - if (result != EnterHandlerDelegate.Result.Continue) { - text = document.getCharsSequence(); - if (result == EnterHandlerDelegate.Result.DefaultForceIndent) { - forceIndent = true; - } - break; - } - } - - caretOffset = caretOffsetRef.get().intValue(); - boolean isFirstColumn = caretOffset == 0 || text.charAt(caretOffset - 1) == '\n'; - final boolean insertSpace = - !isFirstColumn && !(caretOffset >= text.length() || text.charAt(caretOffset) == ' ' || text.charAt(caretOffset) == '\t'); - editor.getCaretModel().moveToOffset(caretOffset); - myOriginalHandler.execute(editor, dataContext); - - if (settings.SMART_INDENT_ON_ENTER || forceIndent) { - caretOffset += 1; - caretOffset = CharArrayUtil.shiftForward(editor.getDocument().getCharsSequence(), caretOffset, " \t"); - } - else { - caretOffset = editor.getCaretModel().getOffset(); - } - - PsiDocumentManager.getInstance(project).commitAllDocuments(); - final DoEnterAction action = new DoEnterAction(file, editor, document, dataContext, caretOffset, !insertSpace, caretAdvanceRef.get()); - action.setForceIndent(forceIndent); - action.run(); - } - - private static boolean isCommentComplete(PsiComment comment, CodeDocumentationAwareCommenter commenter) { - String commentText = comment.getText(); - final boolean docComment = isDocComment(comment, commenter); - final String expectedCommentEnd = docComment ? commenter.getDocumentationCommentSuffix():commenter.getBlockCommentSuffix(); - if (!commentText.endsWith(expectedCommentEnd)) return false; - - final PsiFile containingFile = comment.getContainingFile(); - final Language language = comment.getParent().getLanguage(); - Lexer lexer = LanguageParserDefinitions.INSTANCE.forLanguage(language).createLexer(containingFile.getProject()); - final String commentPrefix = docComment? commenter.getDocumentationCommentPrefix() : commenter.getBlockCommentPrefix(); - lexer.start(commentText, commentPrefix == null? 0 : commentPrefix.length(), commentText.length()); - QuoteHandler fileTypeHandler = TypedHandler.getQuoteHandler(containingFile); - JavaLikeQuoteHandler javaLikeQuoteHandler = fileTypeHandler instanceof JavaLikeQuoteHandler ? - (JavaLikeQuoteHandler)fileTypeHandler:null; - - while (true) { - IElementType tokenType = lexer.getTokenType(); - if (tokenType == null) { - return false; - } - - if (javaLikeQuoteHandler != null && - javaLikeQuoteHandler.getStringTokenTypes() != null && - javaLikeQuoteHandler.getStringTokenTypes().contains(tokenType)) { - String text = commentText.substring(lexer.getTokenStart(), lexer.getTokenEnd()); - int endOffset = comment.getTextRange().getEndOffset(); - - if (text.endsWith(expectedCommentEnd) && - endOffset < containingFile.getTextLength() && - containingFile.getText().charAt(endOffset) == '\n') { - return true; - } - } - if (lexer.getTokenEnd() == commentText.length()) { - if (lexer.getTokenType() == commenter.getLineCommentTokenType()) { - lexer.start(commentText, lexer.getTokenStart() + commenter.getLineCommentPrefix().length(), commentText.length()); - lexer.advance(); - continue; - } - return lexer.getTokenEnd() - lexer.getTokenStart() == 1; - } - if (tokenType == commenter.getDocumentationCommentTokenType() || tokenType == commenter.getBlockCommentTokenType()) { - return false; - } - lexer.advance(); - } - } - - private static boolean isDocComment(final PsiElement element, final CodeDocumentationAwareCommenter commenter) { - if (!(element instanceof PsiComment)) return false; - PsiComment comment = (PsiComment) element; - return commenter.isDocumentationComment(comment); - } - - private static class DoEnterAction implements Runnable { - - private final DataContext myDataContext; - private final PsiFile myFile; - private int myOffset; - private final Document myDocument; - private final boolean myInsertSpace; - private final Editor myEditor; - private int myCaretAdvance; - - private boolean myForceIndent = false; - private static final String LINE_SEPARATOR = "\n"; - - public DoEnterAction(PsiFile file, Editor view, Document document, DataContext dataContext, int offset, boolean insertSpace, - int caretAdvance) - { - myEditor = view; - myFile = file; - myDataContext = dataContext; - myOffset = offset; - myDocument = document; - myInsertSpace = insertSpace; - myCaretAdvance = caretAdvance; - } - - public void setForceIndent(boolean forceIndent) { - myForceIndent = forceIndent; - } - - public void run() { - try { - final CharSequence chars = myDocument.getCharsSequence(); - - int offset = CharArrayUtil.shiftBackwardUntil(chars, myOffset - 1, LINE_SEPARATOR) - 1; - offset = CharArrayUtil.shiftBackwardUntil(chars, offset, LINE_SEPARATOR) + 1; - if (offset < 0) offset = 0; - int lineStart = CharArrayUtil.shiftForward(chars, offset, " \t"); - - final Commenter langCommenter = LanguageCommenters.INSTANCE.forLanguage(PsiUtilBase.getLanguageAtOffset(myFile, offset)); - final boolean isInsideJavalikeCode = langCommenter instanceof CodeDocumentationAwareCommenter; - final CodeDocumentationAwareCommenter commenter = isInsideJavalikeCode ? (CodeDocumentationAwareCommenter)langCommenter:null; - - boolean docStart = isInsideJavalikeCode && - commenter.getDocumentationCommentPrefix() != null && - CharArrayUtil.regionMatches(chars, lineStart, commenter.getDocumentationCommentPrefix()); - boolean cStyleStart = isInsideJavalikeCode && - commenter.getBlockCommentPrefix() != null && - CharArrayUtil.regionMatches(chars, lineStart, commenter.getBlockCommentPrefix()); - boolean docAsterisk = isInsideJavalikeCode && - commenter.getDocumentationCommentLinePrefix() != null && - CharArrayUtil.regionMatches(chars, lineStart, commenter.getDocumentationCommentLinePrefix()); - final int firstNonSpaceInLine = CharArrayUtil.shiftForward(chars, myOffset, " \t"); - boolean slashSlash = isInsideJavalikeCode && - commenter.getLineCommentPrefix() != null && - CharArrayUtil.regionMatches(chars, lineStart, commenter.getLineCommentPrefix()) && - firstNonSpaceInLine < chars.length() && chars.charAt(firstNonSpaceInLine) != '\n'; - - if (docStart) { - PsiElement element = myFile.findElementAt(lineStart); - final String text = element.getText(); - final PsiElement parent = element.getParent(); - - if (text.equals(commenter.getDocumentationCommentPrefix()) && isDocComment(parent, commenter) || - text.startsWith(commenter.getDocumentationCommentPrefix()) && element instanceof PsiComment - ) { - PsiComment comment = isDocComment(parent, commenter) ? (PsiComment)parent:(PsiComment)element; - int commentEnd = comment.getTextRange().getEndOffset(); - - if (myOffset >= commentEnd) { - docStart = false; - } - else { - if (isCommentComplete(comment, commenter)) { - if (myOffset >= commentEnd) { - docAsterisk = false; - docStart = false; - } - else { - docAsterisk = true; - docStart = false; - } - } - else { - generateJavadoc(commenter); - } - } - } - else { - docStart = false; - } - } - else if (cStyleStart) { - PsiElement element = myFile.findElementAt(lineStart); - if (element instanceof PsiComment && commenter.getBlockCommentTokenType() == ((PsiComment)element).getTokenType()) { - final PsiComment comment = (PsiComment)element; - int commentEnd = comment.getTextRange().getEndOffset(); - if (myOffset >= commentEnd) { - docStart = false; - } - else { - if (isCommentComplete(comment, commenter)) { - if (myOffset >= commentEnd) { - docAsterisk = false; - docStart = false; - } - else { - docAsterisk = true; - docStart = false; - } - } - else { - final int currentEndOfLine = CharArrayUtil.shiftForwardUntil(chars, myOffset, "\n"); - myDocument.insertString(currentEndOfLine, " " + commenter.getBlockCommentSuffix()); - int lstart = CharArrayUtil.shiftBackwardUntil(chars, myOffset, "\n"); - myDocument.insertString(currentEndOfLine, chars.subSequence(lstart, myOffset)); - } - } - } - else { - docStart = false; - } - } - - if (docAsterisk) { - docAsterisk = insertDocAsterisk(lineStart, docAsterisk, commenter); - } - - if (CodeInsightSettings.getInstance().SMART_INDENT_ON_ENTER || myForceIndent || docStart || docAsterisk || - slashSlash) { - final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject()); - myOffset = codeStyleManager.adjustLineIndent(myFile, myOffset); - } - - if (docAsterisk || docStart || slashSlash) { - if (myInsertSpace) { - if (myOffset == myDocument.getTextLength()) { - myDocument.insertString(myOffset, " "); - } - myDocument.insertString(myOffset + 1, " "); - } - - final char c = myDocument.getCharsSequence().charAt(myOffset); - if (c != '\n') { - myOffset += 1; - } - } - - if ((docAsterisk || slashSlash) && !docStart) { - myCaretAdvance = slashSlash ? commenter.getLineCommentPrefix().length() : 1; - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - - myEditor.getCaretModel().moveToOffset(myOffset); - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - myEditor.getSelectionModel().removeSelection(); - if (myCaretAdvance != 0) { - LogicalPosition caretPosition = myEditor.getCaretModel().getLogicalPosition(); - LogicalPosition pos = new LogicalPosition(caretPosition.line, caretPosition.column + myCaretAdvance); - myEditor.getCaretModel().moveToLogicalPosition(pos); - } - } - - private void generateJavadoc(CodeDocumentationAwareCommenter commenter) throws IncorrectOperationException { - CodeInsightSettings settings = CodeInsightSettings.getInstance(); - StringBuilder buffer = new StringBuilder(); - final String docCommentLinePrefix = commenter.getDocumentationCommentLinePrefix(); - if(docCommentLinePrefix==null){ - return; - } - - // There are at least two approaches for completing javadoc in case there is a text between current caret position and line end: - // 1. Move that tail text below the javadoc. Use-case: - // Before: - // /**public void foo() {} - // After: - // /** - // */ - // public void foo() {} - // 2. Move the tail text inside the javadoc. Use-case: - // Before: - // /**This is javadoc description - // After: - // /** This is - // * javadoc description - // */ - // The later is most relevant when we have 'auto wrap when typing reaches right margin' option set, i.e. user starts javadoc - // and types until right margin is reached. We want the wrapped text tail to be located inside javadoc and continue typing - // inside it. So, we have a control flow branch below that does the trick. - buffer.append(docCommentLinePrefix); - if (DataManager.getInstance().loadFromDataContext(myDataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) == Boolean.TRUE) { - myDocument.insertString(myOffset, buffer); - - // We create new buffer here because the one referenced by current 'buffer' variable value may be already referenced at another - // place (e.g. 'undo' processing stuff). - buffer = new StringBuilder(LINE_SEPARATOR).append(commenter.getDocumentationCommentSuffix()); - int line = myDocument.getLineNumber(myOffset); - myOffset = myDocument.getLineEndOffset(line); - } - else { - buffer.append(LINE_SEPARATOR); - buffer.append(commenter.getDocumentationCommentSuffix()); - } - - PsiComment comment = createComment(buffer, settings); - if(comment == null){ - return; - } - - myOffset = comment.getTextRange().getStartOffset(); - CharSequence text = myDocument.getCharsSequence(); - myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, LINE_SEPARATOR); - myOffset = CharArrayUtil.shiftForward(text, myOffset, LINE_SEPARATOR); - myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, docCommentLinePrefix) + 1; - removeTrailingSpaces(myDocument, myOffset); - - if (!CodeStyleSettingsManager.getSettings(getProject()).JD_LEADING_ASTERISKS_ARE_ENABLED) { - LOG.assertTrue(CharArrayUtil.regionMatches(myDocument.getCharsSequence(),myOffset - docCommentLinePrefix.length(), docCommentLinePrefix)); - myDocument.deleteString(myOffset - docCommentLinePrefix.length(), myOffset); - myOffset--; - } else { - myDocument.insertString(myOffset, " "); - myOffset++; - } - - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - } - - @Nullable - private PsiComment createComment(final CharSequence buffer, final CodeInsightSettings settings) - throws IncorrectOperationException { - myDocument.insertString(myOffset, buffer); - - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - CodeStyleManager.getInstance(getProject()).adjustLineIndent(myFile, myOffset + buffer.length() - 2); - - PsiComment comment = PsiTreeUtil.getNonStrictParentOfType(myFile.findElementAt(myOffset), PsiComment.class); - - comment = createJavaDocStub(settings, comment, getProject()); - - CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject()); - CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(getProject()); - boolean old = codeStyleSettings.ENABLE_JAVADOC_FORMATTING; - codeStyleSettings.ENABLE_JAVADOC_FORMATTING = false; - - try { - comment = (PsiComment)codeStyleManager.reformat(comment); - } - finally { - codeStyleSettings.ENABLE_JAVADOC_FORMATTING = old; - } - PsiElement next = comment.getNextSibling(); - if (next == null && comment.getParent().getClass() == comment.getClass()) { - next = comment.getParent().getNextSibling(); // expanding chameleon comment produces comment under comment - } - if (!(next instanceof PsiWhiteSpace) || !next.getText().contains(LINE_SEPARATOR)) { - int lineBreakOffset = comment.getTextRange().getEndOffset(); - myDocument.insertString(lineBreakOffset, LINE_SEPARATOR); - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - codeStyleManager.adjustLineIndent(myFile, lineBreakOffset + 1); - comment = PsiTreeUtil.getNonStrictParentOfType(myFile.findElementAt(myOffset), PsiComment.class); - } - return comment; - } - - private PsiComment createJavaDocStub(final CodeInsightSettings settings, - final PsiComment comment, - final Project project) { - if (settings.JAVADOC_STUB_ON_ENTER) { - final DocumentationProvider langDocumentationProvider = - LanguageDocumentation.INSTANCE.forLanguage(comment.getParent().getLanguage()); - - @Nullable final CodeDocumentationProvider docProvider; - if (langDocumentationProvider instanceof CompositeDocumentationProvider) { - docProvider = ((CompositeDocumentationProvider)langDocumentationProvider).getFirstCodeDocumentationProvider(); - } else { - docProvider = langDocumentationProvider instanceof CodeDocumentationProvider ? - (CodeDocumentationProvider)langDocumentationProvider : null; - } - - if (docProvider != null) { - if (docProvider.findExistingDocComment(comment) != comment) return comment; - String docStub = docProvider.generateDocumentationContentStub(comment); - - if (docStub != null && docStub.length() != 0) { - myOffset = CharArrayUtil.shiftForwardUntil(myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR); - myOffset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR); - myDocument.insertString(myOffset, docStub); - } - } - - PsiDocumentManager.getInstance(project).commitAllDocuments(); - return PsiTreeUtil.getNonStrictParentOfType(myFile.findElementAt(myOffset), PsiComment.class); - } - return comment; - } - - private Project getProject() { - return myFile.getProject(); - } - - private static void removeTrailingSpaces(final Document document, final int startOffset) { - int endOffset = startOffset; - - final CharSequence charsSequence = document.getCharsSequence(); - - for (int i = startOffset; i < charsSequence.length(); i++) { - final char c = charsSequence.charAt(i); - endOffset = i; - if (c == '\n') { - break; - } - if (c != ' ' && c != '\t') { - return; - } - } - - document.deleteString(startOffset, endOffset); - } - - private boolean insertDocAsterisk(int lineStart, boolean docAsterisk, CodeDocumentationAwareCommenter commenter) { - PsiElement atLineStart = myFile.findElementAt(lineStart); - if (atLineStart == null) return false; - - final String documentationCommentLinePrefix = commenter.getDocumentationCommentLinePrefix(); - final String docommentPrefix = commenter.getDocumentationCommentPrefix(); - final String text = atLineStart.getText(); - final TextRange textRange = atLineStart.getTextRange(); - - if (text.equals(documentationCommentLinePrefix) || - text.equals(docommentPrefix) || - text.regionMatches(lineStart - textRange.getStartOffset(), docommentPrefix, 0, docommentPrefix.length()) || - text.regionMatches(lineStart - textRange.getStartOffset(), documentationCommentLinePrefix, 0 , documentationCommentLinePrefix.length()) - ) { - PsiElement element = myFile.findElementAt(myOffset); - if (element == null) return false; - - PsiComment comment = element instanceof PsiComment ? (PsiComment)element : PsiTreeUtil.getParentOfType(element, PsiComment.class, false); - if (comment != null) { - int commentEnd = comment.getTextRange().getEndOffset(); - if (myOffset >= commentEnd) { - docAsterisk = false; - } - else { - removeTrailingSpaces(myDocument, myOffset); - myDocument.insertString(myOffset, CodeDocumentationUtil.createDocCommentLine("", getProject(),commenter)); - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - } - } - else { - docAsterisk = false; - } - } - else if (atLineStart instanceof PsiComment && ((PsiComment)atLineStart).getTokenType() == commenter.getBlockCommentTokenType()) { - // Check if C-Style comment already uses asterisks. - boolean usesAstersk = false; - int commentLine = myDocument.getLineNumber(textRange.getStartOffset()); - if (commentLine < myDocument.getLineCount() - 1 && textRange.getEndOffset() >= myOffset) { - int nextLineOffset = myDocument.getLineStartOffset(commentLine + 1); - if (nextLineOffset < textRange.getEndOffset()) { - final CharSequence chars = myDocument.getCharsSequence(); - nextLineOffset = CharArrayUtil.shiftForward(chars, nextLineOffset, " \t"); - usesAstersk = CharArrayUtil.regionMatches(chars, nextLineOffset, documentationCommentLinePrefix); - } - } - if (usesAstersk) { - removeTrailingSpaces(myDocument, myOffset); - myDocument.insertString(myOffset, documentationCommentLinePrefix + " "); - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - } - docAsterisk = usesAstersk; - } - else { - docAsterisk = false; - } - return docAsterisk; - } - } -} +/* + * 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.codeInsight.editorActions; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate; +import com.intellij.ide.DataManager; +import com.intellij.lang.*; +import com.intellij.lang.documentation.CodeDocumentationProvider; +import com.intellij.lang.documentation.CompositeDocumentationProvider; +import com.intellij.lang.documentation.DocumentationProvider; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.Nullable; + +public class EnterHandler extends BaseEnterHandler { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.EnterHandler"); + + private final EditorActionHandler myOriginalHandler; + + public EnterHandler(EditorActionHandler originalHandler) { + myOriginalHandler = originalHandler; + } + + public boolean isEnabled(Editor editor, DataContext dataContext) { + return myOriginalHandler.isEnabled(editor, dataContext); + } + + public void executeWriteAction(final Editor editor, final DataContext dataContext) { + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project != null && !project.isDefault()) { + PostprocessReformattingAspect.getInstance(project).disablePostprocessFormattingInside(new Runnable() { + public void run() { + executeWriteActionInner(editor, dataContext, project); + } + }); + } + else { + executeWriteActionInner(editor, dataContext, project); + } + } + + private void executeWriteActionInner(Editor editor, DataContext dataContext, Project project) { + CodeInsightSettings settings = CodeInsightSettings.getInstance(); + if (project == null) { + myOriginalHandler.execute(editor, dataContext); + return; + } + final Document document = editor.getDocument(); + final PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project); + + if (file == null) { + myOriginalHandler.execute(editor, dataContext); + return; + } + + CommandProcessor.getInstance().setCurrentCommandName(CodeInsightBundle.message("command.name.typing")); + + EditorModificationUtil.deleteSelectedText(editor); + + int caretOffset = editor.getCaretModel().getOffset(); + CharSequence text = document.getCharsSequence(); + int length = document.getTextLength(); + if (caretOffset < length && text.charAt(caretOffset) != '\n') { + int offset1 = CharArrayUtil.shiftBackward(text, caretOffset, " \t"); + if (offset1 < 0 || text.charAt(offset1) == '\n') { + int offset2 = CharArrayUtil.shiftForward(text, offset1 + 1, " \t"); + boolean isEmptyLine = offset2 >= length || text.charAt(offset2) == '\n'; + if (!isEmptyLine) { // we are in leading spaces of a non-empty line + myOriginalHandler.execute(editor, dataContext); + return; + } + } + } + + PsiDocumentManager.getInstance(project).commitDocument(document); + + boolean forceIndent = false; + Ref caretOffsetRef = new Ref(caretOffset); + Ref caretAdvanceRef = new Ref(0); + + for(EnterHandlerDelegate delegate: Extensions.getExtensions(EnterHandlerDelegate.EP_NAME)) { + EnterHandlerDelegate.Result result = delegate.preprocessEnter(file, editor, caretOffsetRef, caretAdvanceRef, dataContext, myOriginalHandler); + if (caretOffsetRef.get() > document.getTextLength()) { + throw new AssertionError("Wrong caret offset change by " + delegate); + } + + if (result == EnterHandlerDelegate.Result.Stop) return; + if (result != EnterHandlerDelegate.Result.Continue) { + text = document.getCharsSequence(); + if (result == EnterHandlerDelegate.Result.DefaultForceIndent) { + forceIndent = true; + } + break; + } + } + + caretOffset = caretOffsetRef.get().intValue(); + boolean isFirstColumn = caretOffset == 0 || text.charAt(caretOffset - 1) == '\n'; + final boolean insertSpace = + !isFirstColumn && !(caretOffset >= text.length() || text.charAt(caretOffset) == ' ' || text.charAt(caretOffset) == '\t'); + editor.getCaretModel().moveToOffset(caretOffset); + myOriginalHandler.execute(editor, dataContext); + + if (settings.SMART_INDENT_ON_ENTER || forceIndent) { + caretOffset += 1; + caretOffset = CharArrayUtil.shiftForward(editor.getDocument().getCharsSequence(), caretOffset, " \t"); + } + else { + caretOffset = editor.getCaretModel().getOffset(); + } + + PsiDocumentManager.getInstance(project).commitAllDocuments(); + final DoEnterAction action = new DoEnterAction(file, editor, document, dataContext, caretOffset, !insertSpace, caretAdvanceRef.get()); + action.setForceIndent(forceIndent); + action.run(); + } + + private static boolean isCommentComplete(PsiComment comment, CodeDocumentationAwareCommenter commenter) { + String commentText = comment.getText(); + final boolean docComment = isDocComment(comment, commenter); + final String expectedCommentEnd = docComment ? commenter.getDocumentationCommentSuffix():commenter.getBlockCommentSuffix(); + if (!commentText.endsWith(expectedCommentEnd)) return false; + + final PsiFile containingFile = comment.getContainingFile(); + final Language language = comment.getParent().getLanguage(); + Lexer lexer = LanguageParserDefinitions.INSTANCE.forLanguage(language).createLexer(containingFile.getProject()); + final String commentPrefix = docComment? commenter.getDocumentationCommentPrefix() : commenter.getBlockCommentPrefix(); + lexer.start(commentText, commentPrefix == null? 0 : commentPrefix.length(), commentText.length()); + QuoteHandler fileTypeHandler = TypedHandler.getQuoteHandler(containingFile); + JavaLikeQuoteHandler javaLikeQuoteHandler = fileTypeHandler instanceof JavaLikeQuoteHandler ? + (JavaLikeQuoteHandler)fileTypeHandler:null; + + while (true) { + IElementType tokenType = lexer.getTokenType(); + if (tokenType == null) { + return false; + } + + if (javaLikeQuoteHandler != null && + javaLikeQuoteHandler.getStringTokenTypes() != null && + javaLikeQuoteHandler.getStringTokenTypes().contains(tokenType)) { + String text = commentText.substring(lexer.getTokenStart(), lexer.getTokenEnd()); + int endOffset = comment.getTextRange().getEndOffset(); + + if (text.endsWith(expectedCommentEnd) && + endOffset < containingFile.getTextLength() && + containingFile.getText().charAt(endOffset) == '\n') { + return true; + } + } + if (lexer.getTokenEnd() == commentText.length()) { + if (lexer.getTokenType() == commenter.getLineCommentTokenType()) { + lexer.start(commentText, lexer.getTokenStart() + commenter.getLineCommentPrefix().length(), commentText.length()); + lexer.advance(); + continue; + } + return lexer.getTokenEnd() - lexer.getTokenStart() == 1; + } + if (tokenType == commenter.getDocumentationCommentTokenType() || tokenType == commenter.getBlockCommentTokenType()) { + return false; + } + lexer.advance(); + } + } + + private static boolean isDocComment(final PsiElement element, final CodeDocumentationAwareCommenter commenter) { + if (!(element instanceof PsiComment)) return false; + PsiComment comment = (PsiComment) element; + return commenter.isDocumentationComment(comment); + } + + private static class DoEnterAction implements Runnable { + + private final DataContext myDataContext; + private final PsiFile myFile; + private int myOffset; + private final Document myDocument; + private final boolean myInsertSpace; + private final Editor myEditor; + private int myCaretAdvance; + + private boolean myForceIndent = false; + private static final String LINE_SEPARATOR = "\n"; + + public DoEnterAction(PsiFile file, Editor view, Document document, DataContext dataContext, int offset, boolean insertSpace, + int caretAdvance) + { + myEditor = view; + myFile = file; + myDataContext = dataContext; + myOffset = offset; + myDocument = document; + myInsertSpace = insertSpace; + myCaretAdvance = caretAdvance; + } + + public void setForceIndent(boolean forceIndent) { + myForceIndent = forceIndent; + } + + public void run() { + try { + final CharSequence chars = myDocument.getCharsSequence(); + + int offset = CharArrayUtil.shiftBackwardUntil(chars, myOffset - 1, LINE_SEPARATOR) - 1; + offset = CharArrayUtil.shiftBackwardUntil(chars, offset, LINE_SEPARATOR) + 1; + if (offset < 0) offset = 0; + int lineStart = CharArrayUtil.shiftForward(chars, offset, " \t"); + + final Commenter langCommenter = LanguageCommenters.INSTANCE.forLanguage(PsiUtilBase.getLanguageAtOffset(myFile, offset)); + final boolean isInsideJavalikeCode = langCommenter instanceof CodeDocumentationAwareCommenter; + final CodeDocumentationAwareCommenter commenter = isInsideJavalikeCode ? (CodeDocumentationAwareCommenter)langCommenter:null; + + boolean docStart = isInsideJavalikeCode && + commenter.getDocumentationCommentPrefix() != null && + CharArrayUtil.regionMatches(chars, lineStart, commenter.getDocumentationCommentPrefix()); + boolean cStyleStart = isInsideJavalikeCode && + commenter.getBlockCommentPrefix() != null && + CharArrayUtil.regionMatches(chars, lineStart, commenter.getBlockCommentPrefix()); + boolean docAsterisk = isInsideJavalikeCode && + commenter.getDocumentationCommentLinePrefix() != null && + CharArrayUtil.regionMatches(chars, lineStart, commenter.getDocumentationCommentLinePrefix()); + final int firstNonSpaceInLine = CharArrayUtil.shiftForward(chars, myOffset, " \t"); + boolean slashSlash = isInsideJavalikeCode && + commenter.getLineCommentPrefix() != null && + CharArrayUtil.regionMatches(chars, lineStart, commenter.getLineCommentPrefix()) && + firstNonSpaceInLine < chars.length() && chars.charAt(firstNonSpaceInLine) != '\n'; + + if (docStart) { + PsiElement element = myFile.findElementAt(lineStart); + final String text = element.getText(); + final PsiElement parent = element.getParent(); + + if (text.equals(commenter.getDocumentationCommentPrefix()) && isDocComment(parent, commenter) || + text.startsWith(commenter.getDocumentationCommentPrefix()) && element instanceof PsiComment + ) { + PsiComment comment = isDocComment(parent, commenter) ? (PsiComment)parent:(PsiComment)element; + int commentEnd = comment.getTextRange().getEndOffset(); + + if (myOffset >= commentEnd) { + docStart = false; + } + else { + if (isCommentComplete(comment, commenter)) { + if (myOffset >= commentEnd) { + docAsterisk = false; + docStart = false; + } + else { + docAsterisk = true; + docStart = false; + } + } + else { + generateJavadoc(commenter); + } + } + } + else { + docStart = false; + } + } + else if (cStyleStart) { + PsiElement element = myFile.findElementAt(lineStart); + if (element instanceof PsiComment && commenter.getBlockCommentTokenType() == ((PsiComment)element).getTokenType()) { + final PsiComment comment = (PsiComment)element; + int commentEnd = comment.getTextRange().getEndOffset(); + if (myOffset >= commentEnd) { + docStart = false; + } + else { + if (isCommentComplete(comment, commenter)) { + if (myOffset >= commentEnd) { + docAsterisk = false; + docStart = false; + } + else { + docAsterisk = true; + docStart = false; + } + } + else { + final int currentEndOfLine = CharArrayUtil.shiftForwardUntil(chars, myOffset, "\n"); + myDocument.insertString(currentEndOfLine, " " + commenter.getBlockCommentSuffix()); + int lstart = CharArrayUtil.shiftBackwardUntil(chars, myOffset, "\n"); + myDocument.insertString(currentEndOfLine, chars.subSequence(lstart, myOffset)); + } + } + } + else { + docStart = false; + } + } + + if (docAsterisk) { + docAsterisk = insertDocAsterisk(lineStart, docAsterisk, commenter); + } + + if (CodeInsightSettings.getInstance().SMART_INDENT_ON_ENTER || myForceIndent || docStart || docAsterisk || + slashSlash) { + final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject()); + myOffset = codeStyleManager.adjustLineIndent(myFile, myOffset); + } + + if (docAsterisk || docStart || slashSlash) { + if (myInsertSpace) { + if (myOffset == myDocument.getTextLength()) { + myDocument.insertString(myOffset, " "); + } + myDocument.insertString(myOffset + 1, " "); + } + + final char c = myDocument.getCharsSequence().charAt(myOffset); + if (c != '\n') { + myOffset += 1; + } + } + + if ((docAsterisk || slashSlash) && !docStart) { + myCaretAdvance = slashSlash ? commenter.getLineCommentPrefix().length() : 1; + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + + myEditor.getCaretModel().moveToOffset(myOffset); + myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + myEditor.getSelectionModel().removeSelection(); + if (myCaretAdvance != 0) { + LogicalPosition caretPosition = myEditor.getCaretModel().getLogicalPosition(); + LogicalPosition pos = new LogicalPosition(caretPosition.line, caretPosition.column + myCaretAdvance); + myEditor.getCaretModel().moveToLogicalPosition(pos); + } + } + + private void generateJavadoc(CodeDocumentationAwareCommenter commenter) throws IncorrectOperationException { + CodeInsightSettings settings = CodeInsightSettings.getInstance(); + StringBuilder buffer = new StringBuilder(); + final String docCommentLinePrefix = commenter.getDocumentationCommentLinePrefix(); + if(docCommentLinePrefix==null){ + return; + } + + // There are at least two approaches for completing javadoc in case there is a text between current caret position and line end: + // 1. Move that tail text below the javadoc. Use-case: + // Before: + // /**public void foo() {} + // After: + // /** + // */ + // public void foo() {} + // 2. Move the tail text inside the javadoc. Use-case: + // Before: + // /**This is javadoc description + // After: + // /** This is + // * javadoc description + // */ + // The later is most relevant when we have 'auto wrap when typing reaches right margin' option set, i.e. user starts javadoc + // and types until right margin is reached. We want the wrapped text tail to be located inside javadoc and continue typing + // inside it. So, we have a control flow branch below that does the trick. + buffer.append(docCommentLinePrefix); + if (DataManager.getInstance().loadFromDataContext(myDataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) == Boolean.TRUE) { + myDocument.insertString(myOffset, buffer); + + // We create new buffer here because the one referenced by current 'buffer' variable value may be already referenced at another + // place (e.g. 'undo' processing stuff). + buffer = new StringBuilder(LINE_SEPARATOR).append(commenter.getDocumentationCommentSuffix()); + int line = myDocument.getLineNumber(myOffset); + myOffset = myDocument.getLineEndOffset(line); + } + else { + buffer.append(LINE_SEPARATOR); + buffer.append(commenter.getDocumentationCommentSuffix()); + } + + PsiComment comment = createComment(buffer, settings); + if(comment == null){ + return; + } + + myOffset = comment.getTextRange().getStartOffset(); + CharSequence text = myDocument.getCharsSequence(); + myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, LINE_SEPARATOR); + myOffset = CharArrayUtil.shiftForward(text, myOffset, LINE_SEPARATOR); + myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, docCommentLinePrefix) + 1; + removeTrailingSpaces(myDocument, myOffset); + + if (!CodeStyleSettingsManager.getSettings(getProject()).JD_LEADING_ASTERISKS_ARE_ENABLED) { + LOG.assertTrue(CharArrayUtil.regionMatches(myDocument.getCharsSequence(),myOffset - docCommentLinePrefix.length(), docCommentLinePrefix)); + myDocument.deleteString(myOffset - docCommentLinePrefix.length(), myOffset); + myOffset--; + } else { + myDocument.insertString(myOffset, " "); + myOffset++; + } + + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + } + + @Nullable + private PsiComment createComment(final CharSequence buffer, final CodeInsightSettings settings) + throws IncorrectOperationException { + myDocument.insertString(myOffset, buffer); + + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + CodeStyleManager.getInstance(getProject()).adjustLineIndent(myFile, myOffset + buffer.length() - 2); + + PsiComment comment = PsiTreeUtil.getNonStrictParentOfType(myFile.findElementAt(myOffset), PsiComment.class); + + comment = createJavaDocStub(settings, comment, getProject()); + + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject()); + CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(getProject()); + boolean old = codeStyleSettings.ENABLE_JAVADOC_FORMATTING; + codeStyleSettings.ENABLE_JAVADOC_FORMATTING = false; + + try { + comment = (PsiComment)codeStyleManager.reformat(comment); + } + finally { + codeStyleSettings.ENABLE_JAVADOC_FORMATTING = old; + } + PsiElement next = comment.getNextSibling(); + if (next == null && comment.getParent().getClass() == comment.getClass()) { + next = comment.getParent().getNextSibling(); // expanding chameleon comment produces comment under comment + } + if (!(next instanceof PsiWhiteSpace) || !next.getText().contains(LINE_SEPARATOR)) { + int lineBreakOffset = comment.getTextRange().getEndOffset(); + myDocument.insertString(lineBreakOffset, LINE_SEPARATOR); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + codeStyleManager.adjustLineIndent(myFile, lineBreakOffset + 1); + comment = PsiTreeUtil.getNonStrictParentOfType(myFile.findElementAt(myOffset), PsiComment.class); + } + return comment; + } + + private PsiComment createJavaDocStub(final CodeInsightSettings settings, + final PsiComment comment, + final Project project) { + if (settings.JAVADOC_STUB_ON_ENTER) { + final DocumentationProvider langDocumentationProvider = + LanguageDocumentation.INSTANCE.forLanguage(comment.getParent().getLanguage()); + + @Nullable final CodeDocumentationProvider docProvider; + if (langDocumentationProvider instanceof CompositeDocumentationProvider) { + docProvider = ((CompositeDocumentationProvider)langDocumentationProvider).getFirstCodeDocumentationProvider(); + } else { + docProvider = langDocumentationProvider instanceof CodeDocumentationProvider ? + (CodeDocumentationProvider)langDocumentationProvider : null; + } + + if (docProvider != null) { + if (docProvider.findExistingDocComment(comment) != comment) return comment; + String docStub = docProvider.generateDocumentationContentStub(comment); + + if (docStub != null && docStub.length() != 0) { + myOffset = CharArrayUtil.shiftForwardUntil(myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR); + myOffset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR); + myDocument.insertString(myOffset, docStub); + } + } + + PsiDocumentManager.getInstance(project).commitAllDocuments(); + return PsiTreeUtil.getNonStrictParentOfType(myFile.findElementAt(myOffset), PsiComment.class); + } + return comment; + } + + private Project getProject() { + return myFile.getProject(); + } + + private static void removeTrailingSpaces(final Document document, final int startOffset) { + int endOffset = startOffset; + + final CharSequence charsSequence = document.getCharsSequence(); + + for (int i = startOffset; i < charsSequence.length(); i++) { + final char c = charsSequence.charAt(i); + endOffset = i; + if (c == '\n') { + break; + } + if (c != ' ' && c != '\t') { + return; + } + } + + document.deleteString(startOffset, endOffset); + } + + private boolean insertDocAsterisk(int lineStart, boolean docAsterisk, CodeDocumentationAwareCommenter commenter) { + PsiElement atLineStart = myFile.findElementAt(lineStart); + if (atLineStart == null) return false; + + final String documentationCommentLinePrefix = commenter.getDocumentationCommentLinePrefix(); + final String docommentPrefix = commenter.getDocumentationCommentPrefix(); + final String text = atLineStart.getText(); + final TextRange textRange = atLineStart.getTextRange(); + + if (text.equals(documentationCommentLinePrefix) || + text.equals(docommentPrefix) || + text.regionMatches(lineStart - textRange.getStartOffset(), docommentPrefix, 0, docommentPrefix.length()) || + text.regionMatches(lineStart - textRange.getStartOffset(), documentationCommentLinePrefix, 0 , documentationCommentLinePrefix.length()) + ) { + PsiElement element = myFile.findElementAt(myOffset); + if (element == null) return false; + + PsiComment comment = element instanceof PsiComment ? (PsiComment)element : PsiTreeUtil.getParentOfType(element, PsiComment.class, false); + if (comment != null) { + int commentEnd = comment.getTextRange().getEndOffset(); + if (myOffset >= commentEnd) { + docAsterisk = false; + } + else { + removeTrailingSpaces(myDocument, myOffset); + myDocument.insertString(myOffset, CodeDocumentationUtil.createDocCommentLine("", getProject(),commenter)); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + } + } + else { + docAsterisk = false; + } + } + else if (atLineStart instanceof PsiComment && ((PsiComment)atLineStart).getTokenType() == commenter.getBlockCommentTokenType()) { + // Check if C-Style comment already uses asterisks. + boolean usesAstersk = false; + int commentLine = myDocument.getLineNumber(textRange.getStartOffset()); + if (commentLine < myDocument.getLineCount() - 1 && textRange.getEndOffset() >= myOffset) { + int nextLineOffset = myDocument.getLineStartOffset(commentLine + 1); + if (nextLineOffset < textRange.getEndOffset()) { + final CharSequence chars = myDocument.getCharsSequence(); + nextLineOffset = CharArrayUtil.shiftForward(chars, nextLineOffset, " \t"); + usesAstersk = CharArrayUtil.regionMatches(chars, nextLineOffset, documentationCommentLinePrefix); + } + } + if (usesAstersk) { + removeTrailingSpaces(myDocument, myOffset); + myDocument.insertString(myOffset, documentationCommentLinePrefix + " "); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + } + docAsterisk = usesAstersk; + } + else { + docAsterisk = false; + } + return docAsterisk; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java index 5faaef36a092..8064fecf5760 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java @@ -1,547 +1,547 @@ -/* - * 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.codeInsight.editorActions; - -import com.intellij.codeInsight.AutoPopupController; -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.highlighting.BraceMatcher; -import com.intellij.codeInsight.highlighting.BraceMatchingUtil; -import com.intellij.codeInsight.highlighting.NontrivialBraceMatcher; -import com.intellij.injected.editor.DocumentWindow; -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageParserDefinitions; -import com.intellij.lang.ParserDefinition; -import com.intellij.lexer.Lexer; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -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.editor.EditorModificationUtil; -import com.intellij.openapi.editor.ScrollType; -import com.intellij.openapi.editor.actionSystem.TypedActionHandler; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.highlighter.HighlighterIterator; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.templateLanguages.TemplateLanguage; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.TokenSet; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -public class TypedHandler implements TypedActionHandler { - - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.TypedHandler"); - - private final TypedActionHandler myOriginalHandler; - - private static final Map quoteHandlers = new HashMap(); - - private static final Map, QuoteHandler> ourBaseLanguageQuoteHandlers = new HashMap, QuoteHandler>(); - - public TypedHandler(TypedActionHandler originalHandler){ - myOriginalHandler = originalHandler; - } - - @Nullable - public static QuoteHandler getQuoteHandler(@NotNull PsiFile file) { - QuoteHandler quoteHandler = getQuoteHandlerForType(file.getFileType()); - if (quoteHandler == null) { - final Language baseLanguage = file.getViewProvider().getBaseLanguage(); - for (Map.Entry, QuoteHandler> entry : ourBaseLanguageQuoteHandlers.entrySet()) { - if (entry.getKey().isInstance(baseLanguage)) { - return entry.getValue(); - } - } - } - return quoteHandler; - } - - public static void registerBaseLanguageQuoteHandler(Class languageClass, QuoteHandler quoteHandler) { - ourBaseLanguageQuoteHandlers.put(languageClass, quoteHandler); - } - - public static QuoteHandler getQuoteHandlerForType(final FileType fileType) { - if (!quoteHandlers.containsKey(fileType)) { - QuoteHandler handler = null; - final QuoteHandlerEP[] handlerEPs = Extensions.getExtensions(QuoteHandlerEP.EP_NAME); - for(QuoteHandlerEP ep: handlerEPs) { - if (ep.fileType.equals(fileType.getName())) { - handler = ep.getHandler(); - break; - } - } - quoteHandlers.put(fileType, handler); - } - return quoteHandlers.get(fileType); - } - - public static void registerQuoteHandler(FileType fileType, QuoteHandler quoteHandler) { - quoteHandlers.put(fileType, quoteHandler); - } - - public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) { - Project project = PlatformDataKeys.PROJECT.getData(dataContext); - if (project == null || editor.isColumnMode()){ - if (myOriginalHandler != null){ - myOriginalHandler.execute(editor, charTyped, dataContext); - } - return; - } - - PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project); - - if (file == null){ - if (myOriginalHandler != null){ - myOriginalHandler.execute(editor, charTyped, dataContext); - } - return; - } - - if (editor.isViewer()) return; - - if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) { - return; - } - - Editor injectedEditor = injectedEditorIfCharTypedIsSignificant(charTyped, editor, file); - if (injectedEditor != editor) { - file = PsiDocumentManager.getInstance(project).getPsiFile(injectedEditor.getDocument()); - editor = injectedEditor; - } - - final TypedHandlerDelegate[] delegates = Extensions.getExtensions(TypedHandlerDelegate.EP_NAME); - AutoPopupController autoPopupController = AutoPopupController.getInstance(project); - - boolean handled = false; - for(TypedHandlerDelegate delegate: delegates) { - final TypedHandlerDelegate.Result result = delegate.checkAutoPopup(charTyped, project, editor, file); - handled = result == TypedHandlerDelegate.Result.STOP; - if (result != TypedHandlerDelegate.Result.CONTINUE) break; - } - - if (!handled) { - if (charTyped == '.') { - autoPopupController.autoPopupMemberLookup(editor, null); - } - - if (charTyped == '(' && !isInsideStringLiteral(editor, file)) { - autoPopupController.autoPopupParameterInfo(editor, null); - } - } - - if (!editor.isInsertMode()){ - myOriginalHandler.execute(editor, charTyped, dataContext); - return; - } - - if (editor.getSelectionModel().hasSelection()){ - EditorModificationUtil.deleteSelectedText(editor); - } - - FileType fileType = file.getFileType(); - - for(TypedHandlerDelegate delegate: delegates) { - final TypedHandlerDelegate.Result result = delegate.beforeCharTyped(charTyped, project, editor, file, fileType); - if (result == TypedHandlerDelegate.Result.STOP) { - return; - } - if (result == TypedHandlerDelegate.Result.DEFAULT) { - break; - } - } - - if (!editor.getSelectionModel().hasBlockSelection()) { - if (')' == charTyped || ']' == charTyped || '}' == charTyped) { - if (StdFileTypes.PLAIN_TEXT != fileType) { - if (handleRParen(editor, fileType, charTyped)) return; - } - } - else if ('"' == charTyped || '\'' == charTyped || '`' == charTyped) { - if (handleQuote(editor, charTyped, dataContext, file)) return; - } - } - - long modificationStampBeforeTyping = editor.getDocument().getModificationStamp(); - myOriginalHandler.execute(editor, charTyped, dataContext); - AutoHardWrapHandler.getInstance().wrapLineIfNecessary(editor, dataContext, modificationStampBeforeTyping); - - if (('(' == charTyped || '[' == charTyped || '{' == charTyped) && - CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && - !editor.getSelectionModel().hasBlockSelection() && fileType != StdFileTypes.PLAIN_TEXT) { - handleAfterLParen(editor, fileType, charTyped); - } - else if ('}' == charTyped) { - indentClosingBrace(project, editor); - } - - for(TypedHandlerDelegate delegate: delegates) { - final TypedHandlerDelegate.Result result = delegate.charTyped(charTyped, project, editor, file); - if (result == TypedHandlerDelegate.Result.STOP) { - return; - } - if (result == TypedHandlerDelegate.Result.DEFAULT) { - break; - } - } - if ('{' == charTyped) { - indentOpenedBrace(project, editor); - } - } - - private static boolean isInsideStringLiteral(final Editor editor, final PsiFile file) { - int offset = editor.getCaretModel().getOffset(); - PsiElement element = file.findElementAt(offset); - if (element == null) return false; - final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage()); - if (definition != null) { - final TokenSet stringLiteralElements = definition.getStringLiteralElements(); - final ASTNode node = element.getNode(); - if (node == null) return false; - final IElementType elementType = node.getElementType(); - if (stringLiteralElements.contains(elementType)) { - return true; - } - PsiElement parent = element.getParent(); - if (parent != null) { - ASTNode parentNode = parent.getNode(); - if (parentNode != null && stringLiteralElements.contains(parentNode.getElementType())) { - return true; - } - } - } - return false; - } - - static Editor injectedEditorIfCharTypedIsSignificant(final char charTyped, Editor editor, PsiFile oldFile) { - boolean significant = charTyped == '"' || - charTyped == '\'' || - charTyped == '[' || - charTyped == '(' || - charTyped == ']' || - charTyped == ')' || - charTyped == '{' || - charTyped == '}' || - charTyped == '.'; - if (!significant) { - return editor; - } - - int offset = editor.getCaretModel().getOffset(); - // even for uncommitted document try to retrieve injected fragment that has been there recently - // we are assuming here that when user is (even furiously) typing, injected language would not change - // and thus we can use its lexer to insert closing braces etc - for (DocumentWindow documentWindow : InjectedLanguageUtil.getCachedInjectedDocuments(oldFile)) { - if (documentWindow.isValid() && documentWindow.containsRange(offset, offset)) { - PsiFile injectedFile = PsiDocumentManager.getInstance(oldFile.getProject()).getPsiFile(documentWindow); - final Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile); - // IDEA-52375 fix: last quote sign should be handled by outer language quote handler - if (injectedEditor.getCaretModel().getOffset() == injectedEditor.getDocument().getTextLength() && - charTyped == editor.getDocument().getCharsSequence().charAt(offset)) { - return editor; - } - return injectedEditor; - } - } - - return editor; - } - - private static void handleAfterLParen(Editor editor, FileType fileType, char lparenChar){ - int offset = editor.getCaretModel().getOffset(); - HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); - boolean atEndOfDocument = offset == editor.getDocument().getTextLength(); - - if (!atEndOfDocument) iterator.retreat(); - if (iterator.atEnd()) return; - BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); - if (iterator.atEnd()) return; - IElementType braceTokenType = iterator.getTokenType(); - final CharSequence fileText = editor.getDocument().getCharsSequence(); - if (!braceMatcher.isLBraceToken(iterator, fileText, fileType)) return; - - if (!iterator.atEnd()) { - iterator.advance(); - - if (!iterator.atEnd()) { - if (!BraceMatchingUtil.isPairedBracesAllowedBeforeTypeInFileType(braceTokenType, iterator.getTokenType(), fileType)) { - return; - } - if (BraceMatchingUtil.isLBraceToken(iterator, fileText, fileType)) { - return; - } - } - - iterator.retreat(); - } - - int lparenOffset = BraceMatchingUtil.findLeftmostLParen(iterator, braceTokenType, fileText,fileType); - if (lparenOffset < 0) lparenOffset = 0; - - iterator = ((EditorEx)editor).getHighlighter().createIterator(lparenOffset); - boolean matched = BraceMatchingUtil.matchBrace(fileText, fileType, iterator, true); - - if (!matched) { - String text; - if (lparenChar == '(') { - text = ")"; - } - else if (lparenChar == '[') { - text = "]"; - } - else if (lparenChar == '<') { - text = ">"; - } - else if (lparenChar == '{') { - text = "}"; - } - else { - throw new AssertionError("Unknown char "+lparenChar); - } - editor.getDocument().insertString(offset, text); - } - } - - public static boolean handleRParen(Editor editor, FileType fileType, char charTyped) { - if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return false; - - int offset = editor.getCaretModel().getOffset(); - - if (offset == editor.getDocument().getTextLength()) return false; - - HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); - if (iterator.atEnd()) return false; - - Language language = iterator.getTokenType().getLanguage(); - final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language); - if (definition != null && !(language instanceof TemplateLanguage)) { - final Lexer lexer = definition.createLexer(editor.getProject()); - lexer.start(Character.toString(charTyped)); - final IElementType tokenType = lexer.getTokenType(); - if (tokenType != iterator.getTokenType()) { - return false; - } - } - - BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); - CharSequence text = editor.getDocument().getCharsSequence(); - if (!braceMatcher.isRBraceToken(iterator, text, fileType)) { - return false; - } - - IElementType tokenType = iterator.getTokenType(); - - iterator.retreat(); - - IElementType lparenTokenType = braceMatcher.getOppositeBraceTokenType(tokenType); - int lparenthOffset = BraceMatchingUtil.findLeftmostLParen( - iterator, - lparenTokenType, - text, - fileType - ); - - if (lparenthOffset < 0) { - if (braceMatcher instanceof NontrivialBraceMatcher) { - for(IElementType t:((NontrivialBraceMatcher)braceMatcher).getOppositeBraceTokenTypes(tokenType)) { - if (t == lparenTokenType) continue; - lparenthOffset = BraceMatchingUtil.findLeftmostLParen( - iterator, - t, text, - fileType - ); - if (lparenthOffset >= 0) break; - } - } - if (lparenthOffset < 0) return false; - } - - iterator = ((EditorEx) editor).getHighlighter().createIterator(lparenthOffset); - boolean matched = BraceMatchingUtil.matchBrace(text, fileType, iterator, true); - - if (!matched) return false; - - editor.getCaretModel().moveToOffset(offset + 1); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - return true; - } - - private boolean handleQuote(Editor editor, char quote, DataContext dataContext, PsiFile file) { - if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE) return false; - final QuoteHandler quoteHandler = getQuoteHandler(file); - if (quoteHandler == null) return false; - - int offset = editor.getCaretModel().getOffset(); - - final Document document = editor.getDocument(); - CharSequence chars = document.getCharsSequence(); - int length = document.getTextLength(); - if (isTypingEscapeQuote(editor, quoteHandler, offset)) return false; - - if (offset < length && chars.charAt(offset) == quote){ - if (isClosingQuote(editor, quoteHandler, offset)){ - editor.getCaretModel().moveToOffset(offset + 1); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - return true; - } - } - - HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); - - if (!iterator.atEnd()){ - IElementType tokenType = iterator.getTokenType(); - if (quoteHandler instanceof JavaLikeQuoteHandler) { - try { - if (!((JavaLikeQuoteHandler)quoteHandler).isAppropriateElementTypeForLiteral(tokenType)) return false; - } - catch (AbstractMethodError incompatiblePluginErrorThatDoesNotInterestUs) { - // ignore - } - } - } - - myOriginalHandler.execute(editor, quote, dataContext); - offset = editor.getCaretModel().getOffset(); - - if (isOpeningQuote(editor, quoteHandler, offset - 1) && - hasNonClosedLiterals(editor, quoteHandler, offset - 1)) { - if (offset == document.getTextLength() || - !Character.isUnicodeIdentifierPart(document.getCharsSequence().charAt(offset))) { //any better heuristic or an API? - document.insertString(offset, String.valueOf(quote)); - } - } - - return true; - } - - private static boolean isClosingQuote(Editor editor, QuoteHandler quoteHandler, int offset) { - HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); - if (iterator.atEnd()){ - LOG.assertTrue(false); - return false; - } - - return quoteHandler.isClosingQuote(iterator,offset); - } - - private static boolean isOpeningQuote(Editor editor, QuoteHandler quoteHandler, int offset) { - HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); - if (iterator.atEnd()){ - LOG.assertTrue(false); - return false; - } - - return quoteHandler.isOpeningQuote(iterator, offset); - } - - private static boolean hasNonClosedLiterals(Editor editor, QuoteHandler quoteHandler, int offset) { - HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); - if (iterator.atEnd()) { - LOG.assertTrue(false); - return false; - } - - return quoteHandler.hasNonClosedLiteral(editor, iterator, offset); - } - - private static boolean isTypingEscapeQuote(Editor editor, QuoteHandler quoteHandler, int offset){ - if (offset == 0) return false; - CharSequence chars = editor.getDocument().getCharsSequence(); - int offset1 = CharArrayUtil.shiftBackward(chars, offset - 1, "\\"); - int slashCount = offset - 1 - offset1; - return slashCount % 2 != 0 && isInsideLiteral(editor, quoteHandler, offset); - } - - private static boolean isInsideLiteral(Editor editor, QuoteHandler quoteHandler, int offset){ - if (offset == 0) return false; - - HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset - 1); - if (iterator.atEnd()){ - LOG.assertTrue(false); - return false; - } - - return quoteHandler.isInsideLiteral(iterator); - } - - private static void indentClosingBrace(@NotNull Project project, @NotNull Editor editor){ - indentBrace(project, editor, '}'); - } - - static void indentOpenedBrace(@NotNull Project project, @NotNull Editor editor){ - indentBrace(project, editor, '{'); - } - - private static void indentBrace(@NotNull final Project project, @NotNull final Editor editor, final char braceChar) { - final int offset = editor.getCaretModel().getOffset() - 1; - final Document document = editor.getDocument(); - CharSequence chars = document.getCharsSequence(); - if (offset < 0 || chars.charAt(offset) != braceChar) return; - - int spaceStart = CharArrayUtil.shiftBackward(chars, offset - 1, " \t"); - if (spaceStart < 0 || chars.charAt(spaceStart) == '\n' || chars.charAt(spaceStart) == '\r'){ - PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); - documentManager.commitDocument(document); - - final PsiFile file = documentManager.getPsiFile(document); - if (file == null || !file.isWritable()) return; - PsiElement element = file.findElementAt(offset); - if (element == null) return; - - EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); - HighlighterIterator iterator = highlighter.createIterator(offset); - - BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(file.getFileType(), iterator); - if (element.getNode() != null && braceMatcher.isStructuralBrace(iterator, chars, file.getFileType())) { - final Runnable action = new Runnable() { - public void run(){ - try{ - int newOffset = CodeStyleManager.getInstance(project).adjustLineIndent(file, offset); - editor.getCaretModel().moveToOffset(newOffset + 1); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().removeSelection(); - } - catch(IncorrectOperationException e){ - LOG.error(e); - } - } - }; - ApplicationManager.getApplication().runWriteAction(action); - } - } - } - -} - +/* + * 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.codeInsight.editorActions; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.highlighting.BraceMatcher; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.codeInsight.highlighting.NontrivialBraceMatcher; +import com.intellij.injected.editor.DocumentWindow; +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageParserDefinitions; +import com.intellij.lang.ParserDefinition; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +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.editor.EditorModificationUtil; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.actionSystem.TypedActionHandler; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.templateLanguages.TemplateLanguage; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class TypedHandler implements TypedActionHandler { + + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.TypedHandler"); + + private final TypedActionHandler myOriginalHandler; + + private static final Map quoteHandlers = new HashMap(); + + private static final Map, QuoteHandler> ourBaseLanguageQuoteHandlers = new HashMap, QuoteHandler>(); + + public TypedHandler(TypedActionHandler originalHandler){ + myOriginalHandler = originalHandler; + } + + @Nullable + public static QuoteHandler getQuoteHandler(@NotNull PsiFile file) { + QuoteHandler quoteHandler = getQuoteHandlerForType(file.getFileType()); + if (quoteHandler == null) { + final Language baseLanguage = file.getViewProvider().getBaseLanguage(); + for (Map.Entry, QuoteHandler> entry : ourBaseLanguageQuoteHandlers.entrySet()) { + if (entry.getKey().isInstance(baseLanguage)) { + return entry.getValue(); + } + } + } + return quoteHandler; + } + + public static void registerBaseLanguageQuoteHandler(Class languageClass, QuoteHandler quoteHandler) { + ourBaseLanguageQuoteHandlers.put(languageClass, quoteHandler); + } + + public static QuoteHandler getQuoteHandlerForType(final FileType fileType) { + if (!quoteHandlers.containsKey(fileType)) { + QuoteHandler handler = null; + final QuoteHandlerEP[] handlerEPs = Extensions.getExtensions(QuoteHandlerEP.EP_NAME); + for(QuoteHandlerEP ep: handlerEPs) { + if (ep.fileType.equals(fileType.getName())) { + handler = ep.getHandler(); + break; + } + } + quoteHandlers.put(fileType, handler); + } + return quoteHandlers.get(fileType); + } + + public static void registerQuoteHandler(FileType fileType, QuoteHandler quoteHandler) { + quoteHandlers.put(fileType, quoteHandler); + } + + public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) { + Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project == null || editor.isColumnMode()){ + if (myOriginalHandler != null){ + myOriginalHandler.execute(editor, charTyped, dataContext); + } + return; + } + + PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project); + + if (file == null){ + if (myOriginalHandler != null){ + myOriginalHandler.execute(editor, charTyped, dataContext); + } + return; + } + + if (editor.isViewer()) return; + + if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) { + return; + } + + Editor injectedEditor = injectedEditorIfCharTypedIsSignificant(charTyped, editor, file); + if (injectedEditor != editor) { + file = PsiDocumentManager.getInstance(project).getPsiFile(injectedEditor.getDocument()); + editor = injectedEditor; + } + + final TypedHandlerDelegate[] delegates = Extensions.getExtensions(TypedHandlerDelegate.EP_NAME); + AutoPopupController autoPopupController = AutoPopupController.getInstance(project); + + boolean handled = false; + for(TypedHandlerDelegate delegate: delegates) { + final TypedHandlerDelegate.Result result = delegate.checkAutoPopup(charTyped, project, editor, file); + handled = result == TypedHandlerDelegate.Result.STOP; + if (result != TypedHandlerDelegate.Result.CONTINUE) break; + } + + if (!handled) { + if (charTyped == '.') { + autoPopupController.autoPopupMemberLookup(editor, null); + } + + if (charTyped == '(' && !isInsideStringLiteral(editor, file)) { + autoPopupController.autoPopupParameterInfo(editor, null); + } + } + + if (!editor.isInsertMode()){ + myOriginalHandler.execute(editor, charTyped, dataContext); + return; + } + + if (editor.getSelectionModel().hasSelection()){ + EditorModificationUtil.deleteSelectedText(editor); + } + + FileType fileType = file.getFileType(); + + for(TypedHandlerDelegate delegate: delegates) { + final TypedHandlerDelegate.Result result = delegate.beforeCharTyped(charTyped, project, editor, file, fileType); + if (result == TypedHandlerDelegate.Result.STOP) { + return; + } + if (result == TypedHandlerDelegate.Result.DEFAULT) { + break; + } + } + + if (!editor.getSelectionModel().hasBlockSelection()) { + if (')' == charTyped || ']' == charTyped || '}' == charTyped) { + if (StdFileTypes.PLAIN_TEXT != fileType) { + if (handleRParen(editor, fileType, charTyped)) return; + } + } + else if ('"' == charTyped || '\'' == charTyped || '`' == charTyped) { + if (handleQuote(editor, charTyped, dataContext, file)) return; + } + } + + long modificationStampBeforeTyping = editor.getDocument().getModificationStamp(); + myOriginalHandler.execute(editor, charTyped, dataContext); + AutoHardWrapHandler.getInstance().wrapLineIfNecessary(editor, dataContext, modificationStampBeforeTyping); + + if (('(' == charTyped || '[' == charTyped || '{' == charTyped) && + CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && + !editor.getSelectionModel().hasBlockSelection() && fileType != StdFileTypes.PLAIN_TEXT) { + handleAfterLParen(editor, fileType, charTyped); + } + else if ('}' == charTyped) { + indentClosingBrace(project, editor); + } + + for(TypedHandlerDelegate delegate: delegates) { + final TypedHandlerDelegate.Result result = delegate.charTyped(charTyped, project, editor, file); + if (result == TypedHandlerDelegate.Result.STOP) { + return; + } + if (result == TypedHandlerDelegate.Result.DEFAULT) { + break; + } + } + if ('{' == charTyped) { + indentOpenedBrace(project, editor); + } + } + + private static boolean isInsideStringLiteral(final Editor editor, final PsiFile file) { + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + if (element == null) return false; + final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage()); + if (definition != null) { + final TokenSet stringLiteralElements = definition.getStringLiteralElements(); + final ASTNode node = element.getNode(); + if (node == null) return false; + final IElementType elementType = node.getElementType(); + if (stringLiteralElements.contains(elementType)) { + return true; + } + PsiElement parent = element.getParent(); + if (parent != null) { + ASTNode parentNode = parent.getNode(); + if (parentNode != null && stringLiteralElements.contains(parentNode.getElementType())) { + return true; + } + } + } + return false; + } + + static Editor injectedEditorIfCharTypedIsSignificant(final char charTyped, Editor editor, PsiFile oldFile) { + boolean significant = charTyped == '"' || + charTyped == '\'' || + charTyped == '[' || + charTyped == '(' || + charTyped == ']' || + charTyped == ')' || + charTyped == '{' || + charTyped == '}' || + charTyped == '.'; + if (!significant) { + return editor; + } + + int offset = editor.getCaretModel().getOffset(); + // even for uncommitted document try to retrieve injected fragment that has been there recently + // we are assuming here that when user is (even furiously) typing, injected language would not change + // and thus we can use its lexer to insert closing braces etc + for (DocumentWindow documentWindow : InjectedLanguageUtil.getCachedInjectedDocuments(oldFile)) { + if (documentWindow.isValid() && documentWindow.containsRange(offset, offset)) { + PsiFile injectedFile = PsiDocumentManager.getInstance(oldFile.getProject()).getPsiFile(documentWindow); + final Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile); + // IDEA-52375 fix: last quote sign should be handled by outer language quote handler + if (injectedEditor.getCaretModel().getOffset() == injectedEditor.getDocument().getTextLength() && + charTyped == editor.getDocument().getCharsSequence().charAt(offset)) { + return editor; + } + return injectedEditor; + } + } + + return editor; + } + + private static void handleAfterLParen(Editor editor, FileType fileType, char lparenChar){ + int offset = editor.getCaretModel().getOffset(); + HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); + boolean atEndOfDocument = offset == editor.getDocument().getTextLength(); + + if (!atEndOfDocument) iterator.retreat(); + if (iterator.atEnd()) return; + BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); + if (iterator.atEnd()) return; + IElementType braceTokenType = iterator.getTokenType(); + final CharSequence fileText = editor.getDocument().getCharsSequence(); + if (!braceMatcher.isLBraceToken(iterator, fileText, fileType)) return; + + if (!iterator.atEnd()) { + iterator.advance(); + + if (!iterator.atEnd()) { + if (!BraceMatchingUtil.isPairedBracesAllowedBeforeTypeInFileType(braceTokenType, iterator.getTokenType(), fileType)) { + return; + } + if (BraceMatchingUtil.isLBraceToken(iterator, fileText, fileType)) { + return; + } + } + + iterator.retreat(); + } + + int lparenOffset = BraceMatchingUtil.findLeftmostLParen(iterator, braceTokenType, fileText,fileType); + if (lparenOffset < 0) lparenOffset = 0; + + iterator = ((EditorEx)editor).getHighlighter().createIterator(lparenOffset); + boolean matched = BraceMatchingUtil.matchBrace(fileText, fileType, iterator, true); + + if (!matched) { + String text; + if (lparenChar == '(') { + text = ")"; + } + else if (lparenChar == '[') { + text = "]"; + } + else if (lparenChar == '<') { + text = ">"; + } + else if (lparenChar == '{') { + text = "}"; + } + else { + throw new AssertionError("Unknown char "+lparenChar); + } + editor.getDocument().insertString(offset, text); + } + } + + public static boolean handleRParen(Editor editor, FileType fileType, char charTyped) { + if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return false; + + int offset = editor.getCaretModel().getOffset(); + + if (offset == editor.getDocument().getTextLength()) return false; + + HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); + if (iterator.atEnd()) return false; + + Language language = iterator.getTokenType().getLanguage(); + final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language); + if (definition != null && !(language instanceof TemplateLanguage)) { + final Lexer lexer = definition.createLexer(editor.getProject()); + lexer.start(Character.toString(charTyped)); + final IElementType tokenType = lexer.getTokenType(); + if (tokenType != iterator.getTokenType()) { + return false; + } + } + + BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); + CharSequence text = editor.getDocument().getCharsSequence(); + if (!braceMatcher.isRBraceToken(iterator, text, fileType)) { + return false; + } + + IElementType tokenType = iterator.getTokenType(); + + iterator.retreat(); + + IElementType lparenTokenType = braceMatcher.getOppositeBraceTokenType(tokenType); + int lparenthOffset = BraceMatchingUtil.findLeftmostLParen( + iterator, + lparenTokenType, + text, + fileType + ); + + if (lparenthOffset < 0) { + if (braceMatcher instanceof NontrivialBraceMatcher) { + for(IElementType t:((NontrivialBraceMatcher)braceMatcher).getOppositeBraceTokenTypes(tokenType)) { + if (t == lparenTokenType) continue; + lparenthOffset = BraceMatchingUtil.findLeftmostLParen( + iterator, + t, text, + fileType + ); + if (lparenthOffset >= 0) break; + } + } + if (lparenthOffset < 0) return false; + } + + iterator = ((EditorEx) editor).getHighlighter().createIterator(lparenthOffset); + boolean matched = BraceMatchingUtil.matchBrace(text, fileType, iterator, true); + + if (!matched) return false; + + editor.getCaretModel().moveToOffset(offset + 1); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + return true; + } + + private boolean handleQuote(Editor editor, char quote, DataContext dataContext, PsiFile file) { + if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE) return false; + final QuoteHandler quoteHandler = getQuoteHandler(file); + if (quoteHandler == null) return false; + + int offset = editor.getCaretModel().getOffset(); + + final Document document = editor.getDocument(); + CharSequence chars = document.getCharsSequence(); + int length = document.getTextLength(); + if (isTypingEscapeQuote(editor, quoteHandler, offset)) return false; + + if (offset < length && chars.charAt(offset) == quote){ + if (isClosingQuote(editor, quoteHandler, offset)){ + editor.getCaretModel().moveToOffset(offset + 1); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + return true; + } + } + + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); + + if (!iterator.atEnd()){ + IElementType tokenType = iterator.getTokenType(); + if (quoteHandler instanceof JavaLikeQuoteHandler) { + try { + if (!((JavaLikeQuoteHandler)quoteHandler).isAppropriateElementTypeForLiteral(tokenType)) return false; + } + catch (AbstractMethodError incompatiblePluginErrorThatDoesNotInterestUs) { + // ignore + } + } + } + + myOriginalHandler.execute(editor, quote, dataContext); + offset = editor.getCaretModel().getOffset(); + + if (isOpeningQuote(editor, quoteHandler, offset - 1) && + hasNonClosedLiterals(editor, quoteHandler, offset - 1)) { + if (offset == document.getTextLength() || + !Character.isUnicodeIdentifierPart(document.getCharsSequence().charAt(offset))) { //any better heuristic or an API? + document.insertString(offset, String.valueOf(quote)); + } + } + + return true; + } + + private static boolean isClosingQuote(Editor editor, QuoteHandler quoteHandler, int offset) { + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); + if (iterator.atEnd()){ + LOG.assertTrue(false); + return false; + } + + return quoteHandler.isClosingQuote(iterator,offset); + } + + private static boolean isOpeningQuote(Editor editor, QuoteHandler quoteHandler, int offset) { + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); + if (iterator.atEnd()){ + LOG.assertTrue(false); + return false; + } + + return quoteHandler.isOpeningQuote(iterator, offset); + } + + private static boolean hasNonClosedLiterals(Editor editor, QuoteHandler quoteHandler, int offset) { + HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); + if (iterator.atEnd()) { + LOG.assertTrue(false); + return false; + } + + return quoteHandler.hasNonClosedLiteral(editor, iterator, offset); + } + + private static boolean isTypingEscapeQuote(Editor editor, QuoteHandler quoteHandler, int offset){ + if (offset == 0) return false; + CharSequence chars = editor.getDocument().getCharsSequence(); + int offset1 = CharArrayUtil.shiftBackward(chars, offset - 1, "\\"); + int slashCount = offset - 1 - offset1; + return slashCount % 2 != 0 && isInsideLiteral(editor, quoteHandler, offset); + } + + private static boolean isInsideLiteral(Editor editor, QuoteHandler quoteHandler, int offset){ + if (offset == 0) return false; + + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset - 1); + if (iterator.atEnd()){ + LOG.assertTrue(false); + return false; + } + + return quoteHandler.isInsideLiteral(iterator); + } + + private static void indentClosingBrace(@NotNull Project project, @NotNull Editor editor){ + indentBrace(project, editor, '}'); + } + + static void indentOpenedBrace(@NotNull Project project, @NotNull Editor editor){ + indentBrace(project, editor, '{'); + } + + private static void indentBrace(@NotNull final Project project, @NotNull final Editor editor, final char braceChar) { + final int offset = editor.getCaretModel().getOffset() - 1; + final Document document = editor.getDocument(); + CharSequence chars = document.getCharsSequence(); + if (offset < 0 || chars.charAt(offset) != braceChar) return; + + int spaceStart = CharArrayUtil.shiftBackward(chars, offset - 1, " \t"); + if (spaceStart < 0 || chars.charAt(spaceStart) == '\n' || chars.charAt(spaceStart) == '\r'){ + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitDocument(document); + + final PsiFile file = documentManager.getPsiFile(document); + if (file == null || !file.isWritable()) return; + PsiElement element = file.findElementAt(offset); + if (element == null) return; + + EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); + HighlighterIterator iterator = highlighter.createIterator(offset); + + BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(file.getFileType(), iterator); + if (element.getNode() != null && braceMatcher.isStructuralBrace(iterator, chars, file.getFileType())) { + final Runnable action = new Runnable() { + public void run(){ + try{ + int newOffset = CodeStyleManager.getInstance(project).adjustLineIndent(file, offset); + editor.getCaretModel().moveToOffset(newOffset + 1); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + } + catch(IncorrectOperationException e){ + LOG.error(e); + } + } + }; + ApplicationManager.getApplication().runWriteAction(action); + } + } + } + +} + diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterAfterUnmatchedBraceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterAfterUnmatchedBraceHandler.java index d98c1ee9ed2f..b2fbe79eaa34 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterAfterUnmatchedBraceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterAfterUnmatchedBraceHandler.java @@ -1,187 +1,187 @@ -/* - * 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.codeInsight.editorActions.enter; - -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.highlighting.BraceMatcher; -import com.intellij.codeInsight.highlighting.BraceMatchingUtil; -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.highlighter.HighlighterIterator; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.TokenType; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.text.CharArrayUtil; - -public class EnterAfterUnmatchedBraceHandler implements EnterHandlerDelegate { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler"); - - public Result preprocessEnter(final PsiFile file, final Editor editor, final Ref caretOffsetRef, final Ref caretAdvance, - final DataContext dataContext, final EditorActionHandler originalHandler) { - Document document = editor.getDocument(); - CharSequence text = document.getCharsSequence(); - Project project = file.getProject(); - int caretOffset = caretOffsetRef.get().intValue(); - if (!CodeInsightSettings.getInstance().INSERT_BRACE_ON_ENTER || !isAfterUnmatchedLBrace(editor, caretOffset, file.getFileType())) { - return Result.Continue; - } - - int offset = CharArrayUtil.shiftForward(text, caretOffset, " \t"); - if (offset < document.getTextLength()) { - char c = text.charAt(offset); - if (c != ')' && c != ']' && c != ';' && c != ',' && c != '%' && c != '<' && c != '?') { - offset = calculateOffsetToInsertClosingBrace(file, text, offset); - //offset = CharArrayUtil.shiftForwardUntil(text, caretOffset, "\n"); - } - } - offset = Math.min(offset, document.getTextLength()); - - // We need to adjust indents of the text that will be moved, hence, need to insert preliminary line feed. - // Example: - // if (test1()) { - // } else { if (test2()) { - // foo(); - // } - // We insert here '\n}' after 'foo();' and have the following: - // if (test1()) { - // } else { if (test2()) { - // foo(); - // } - // } - // That is formatted incorrectly because line feed between 'else' and 'if' is not inserted yet (whole 'if' block is indent anchor - // to 'if' code block('{}')). So, we insert temporary line feed between 'if' and 'else', correct indent and remove that temporary - // line feed. - document.insertString(offset, "\n}"); - document.insertString(caretOffset, "\n"); - PsiDocumentManager.getInstance(project).commitDocument(document); - try { - CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(caretOffset, offset + 2)); - //CodeStyleManager.getInstance(project).adjustLineIndent(file, offset + 1); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - finally { - document.deleteString(caretOffset, caretOffset + 1); - } - return Result.DefaultForceIndent; - } - - /** - * Current handler inserts closing curly brace (right brace) if necessary. There is a possible case that it should be located - * more than one line forward. - *

- * Example - *

-   *     if (test1()) {
-   *     } else { if (test2()) {
-   *         foo();
-   *     }
-   * 
- *

- * We want to get this after the processing: - *

-   *     if (test1()) {
-   *     } else {
-   *         if (test2()) {
-   *             foo();
-   *         }
-   *     }
-   * 
- * I.e. closing brace should be inserted two lines below current caret line. Hence, we need to calculate correct offset - * to use for brace inserting. This method is responsible for that. - *

- * In essence it inspects PSI structure and finds PSE elements with the max length that starts at caret offset. End offset - * of that element is used as an insertion point. - * - * @param file target PSI file - * @param text text from the given file - * @param offset target offset where line feed will be inserted - * @return offset to use for inserting closing brace - */ - private static int calculateOffsetToInsertClosingBrace(PsiFile file, CharSequence text, final int offset) { - PsiElement element = PsiUtilBase.getElementAtOffset(file, offset); - ASTNode node = element.getNode(); - if (node != null && node.getElementType() == TokenType.WHITE_SPACE) { - return CharArrayUtil.shiftForwardUntil(text, offset, "\n"); - } - for (PsiElement parent = element.getParent(); parent != null; parent = parent.getParent()) { - ASTNode parentNode = parent.getNode(); - if (parentNode == null || parentNode.getStartOffset() != offset) { - break; - } - element = parent; - } - if (element.getTextOffset() != offset) { - return CharArrayUtil.shiftForwardUntil(text, offset, "\n"); - } - else { - return element.getTextRange().getEndOffset(); - } - } - - public static boolean isAfterUnmatchedLBrace(Editor editor, int offset, FileType fileType) { - if (offset == 0) return false; - CharSequence chars = editor.getDocument().getCharsSequence(); - if (chars.charAt(offset - 1) != '{') return false; - - EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); - HighlighterIterator iterator = highlighter.createIterator(offset - 1); - BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); - - if (!braceMatcher.isLBraceToken(iterator, chars, fileType) || - !braceMatcher.isStructuralBrace(iterator, chars, fileType) - ) { - return false; - } - - Language language = iterator.getTokenType().getLanguage(); - - iterator = highlighter.createIterator(0); - int balance = 0; - while (!iterator.atEnd()) { - IElementType tokenType = iterator.getTokenType(); - if (tokenType.getLanguage().equals(language)) { - if (braceMatcher.isStructuralBrace(iterator, chars, fileType)) { - if (braceMatcher.isLBraceToken(iterator, chars, fileType)) { - balance++; - } else if (braceMatcher.isRBraceToken(iterator, chars, fileType)) { - balance--; - } - } - } - iterator.advance(); - } - return balance > 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.codeInsight.editorActions.enter; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.highlighting.BraceMatcher; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.TokenType; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.text.CharArrayUtil; + +public class EnterAfterUnmatchedBraceHandler implements EnterHandlerDelegate { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler"); + + public Result preprocessEnter(final PsiFile file, final Editor editor, final Ref caretOffsetRef, final Ref caretAdvance, + final DataContext dataContext, final EditorActionHandler originalHandler) { + Document document = editor.getDocument(); + CharSequence text = document.getCharsSequence(); + Project project = file.getProject(); + int caretOffset = caretOffsetRef.get().intValue(); + if (!CodeInsightSettings.getInstance().INSERT_BRACE_ON_ENTER || !isAfterUnmatchedLBrace(editor, caretOffset, file.getFileType())) { + return Result.Continue; + } + + int offset = CharArrayUtil.shiftForward(text, caretOffset, " \t"); + if (offset < document.getTextLength()) { + char c = text.charAt(offset); + if (c != ')' && c != ']' && c != ';' && c != ',' && c != '%' && c != '<' && c != '?') { + offset = calculateOffsetToInsertClosingBrace(file, text, offset); + //offset = CharArrayUtil.shiftForwardUntil(text, caretOffset, "\n"); + } + } + offset = Math.min(offset, document.getTextLength()); + + // We need to adjust indents of the text that will be moved, hence, need to insert preliminary line feed. + // Example: + // if (test1()) { + // } else { if (test2()) { + // foo(); + // } + // We insert here '\n}' after 'foo();' and have the following: + // if (test1()) { + // } else { if (test2()) { + // foo(); + // } + // } + // That is formatted incorrectly because line feed between 'else' and 'if' is not inserted yet (whole 'if' block is indent anchor + // to 'if' code block('{}')). So, we insert temporary line feed between 'if' and 'else', correct indent and remove that temporary + // line feed. + document.insertString(offset, "\n}"); + document.insertString(caretOffset, "\n"); + PsiDocumentManager.getInstance(project).commitDocument(document); + try { + CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(caretOffset, offset + 2)); + //CodeStyleManager.getInstance(project).adjustLineIndent(file, offset + 1); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + finally { + document.deleteString(caretOffset, caretOffset + 1); + } + return Result.DefaultForceIndent; + } + + /** + * Current handler inserts closing curly brace (right brace) if necessary. There is a possible case that it should be located + * more than one line forward. + *

+ * Example + *

+   *     if (test1()) {
+   *     } else { if (test2()) {
+   *         foo();
+   *     }
+   * 
+ *

+ * We want to get this after the processing: + *

+   *     if (test1()) {
+   *     } else {
+   *         if (test2()) {
+   *             foo();
+   *         }
+   *     }
+   * 
+ * I.e. closing brace should be inserted two lines below current caret line. Hence, we need to calculate correct offset + * to use for brace inserting. This method is responsible for that. + *

+ * In essence it inspects PSI structure and finds PSE elements with the max length that starts at caret offset. End offset + * of that element is used as an insertion point. + * + * @param file target PSI file + * @param text text from the given file + * @param offset target offset where line feed will be inserted + * @return offset to use for inserting closing brace + */ + private static int calculateOffsetToInsertClosingBrace(PsiFile file, CharSequence text, final int offset) { + PsiElement element = PsiUtilBase.getElementAtOffset(file, offset); + ASTNode node = element.getNode(); + if (node != null && node.getElementType() == TokenType.WHITE_SPACE) { + return CharArrayUtil.shiftForwardUntil(text, offset, "\n"); + } + for (PsiElement parent = element.getParent(); parent != null; parent = parent.getParent()) { + ASTNode parentNode = parent.getNode(); + if (parentNode == null || parentNode.getStartOffset() != offset) { + break; + } + element = parent; + } + if (element.getTextOffset() != offset) { + return CharArrayUtil.shiftForwardUntil(text, offset, "\n"); + } + else { + return element.getTextRange().getEndOffset(); + } + } + + public static boolean isAfterUnmatchedLBrace(Editor editor, int offset, FileType fileType) { + if (offset == 0) return false; + CharSequence chars = editor.getDocument().getCharsSequence(); + if (chars.charAt(offset - 1) != '{') return false; + + EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); + HighlighterIterator iterator = highlighter.createIterator(offset - 1); + BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); + + if (!braceMatcher.isLBraceToken(iterator, chars, fileType) || + !braceMatcher.isStructuralBrace(iterator, chars, fileType) + ) { + return false; + } + + Language language = iterator.getTokenType().getLanguage(); + + iterator = highlighter.createIterator(0); + int balance = 0; + while (!iterator.atEnd()) { + IElementType tokenType = iterator.getTokenType(); + if (tokenType.getLanguage().equals(language)) { + if (braceMatcher.isStructuralBrace(iterator, chars, fileType)) { + if (braceMatcher.isLBraceToken(iterator, chars, fileType)) { + balance++; + } else if (braceMatcher.isRBraceToken(iterator, chars, fileType)) { + balance--; + } + } + } + iterator.advance(); + } + return balance > 0; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/TailTypeDecorator.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/TailTypeDecorator.java index 3be05abcc7e9..264afef7d421 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/TailTypeDecorator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/TailTypeDecorator.java @@ -1,60 +1,60 @@ -/* - * 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.codeInsight.lookup; - -import com.intellij.codeInsight.TailType; -import com.intellij.codeInsight.completion.InsertionContext; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import org.jetbrains.annotations.Nullable; - -/** - * Consider using {@link InsertHandler} instead - * @author peter - */ -public abstract class TailTypeDecorator extends LookupElementDecorator { - public TailTypeDecorator(T delegate) { - super(delegate); - } - - public static TailTypeDecorator withTail(T element, final TailType type) { - return new TailTypeDecorator(element) { - @Override - protected TailType computeTailType(InsertionContext context) { - return type; - } - }; - } - - @Nullable - protected abstract TailType computeTailType(InsertionContext context); - - @Override - public void handleInsert(InsertionContext context) { - final LookupElement delegate = getDelegate(); - final TailType tailType = computeTailType(context); - - final LookupItem lookupItem = delegate.as(LookupItem.class); - if (lookupItem != null && tailType != null) { - lookupItem.setTailType(TailType.UNKNOWN); - } - delegate.handleInsert(context); - if (tailType != null && tailType.isApplicable(context)) { - PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); - tailType.processTail(context.getEditor(), context.getTailOffset()); - } - } - -} +/* + * 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.codeInsight.lookup; + +import com.intellij.codeInsight.TailType; +import com.intellij.codeInsight.completion.InsertionContext; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import org.jetbrains.annotations.Nullable; + +/** + * Consider using {@link InsertHandler} instead + * @author peter + */ +public abstract class TailTypeDecorator extends LookupElementDecorator { + public TailTypeDecorator(T delegate) { + super(delegate); + } + + public static TailTypeDecorator withTail(T element, final TailType type) { + return new TailTypeDecorator(element) { + @Override + protected TailType computeTailType(InsertionContext context) { + return type; + } + }; + } + + @Nullable + protected abstract TailType computeTailType(InsertionContext context); + + @Override + public void handleInsert(InsertionContext context) { + final LookupElement delegate = getDelegate(); + final TailType tailType = computeTailType(context); + + final LookupItem lookupItem = delegate.as(LookupItem.class); + if (lookupItem != null && tailType != null) { + lookupItem.setTailType(TailType.UNKNOWN); + } + delegate.handleInsert(context); + if (tailType != null && tailType.isApplicable(context)) { + PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); + tailType.processTail(context.getEditor(), context.getTailOffset()); + } + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 0a35737b229a..fab60030a8e6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -1,996 +1,996 @@ -/* - * 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.codeInsight.lookup.impl; - -import com.intellij.codeInsight.completion.CompletionLookupArranger; -import com.intellij.codeInsight.completion.PrefixMatcher; -import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; -import com.intellij.codeInsight.hint.HintManagerImpl; -import com.intellij.codeInsight.hint.HintUtil; -import com.intellij.codeInsight.lookup.*; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.lang.LangBundle; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.impl.DebugUtil; -import com.intellij.ui.LightweightHint; -import com.intellij.ui.ListScrollingUtil; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.components.JBList; -import com.intellij.ui.plaf.beg.BegPopupMenuBorder; -import com.intellij.ui.popup.PopupIcons; -import com.intellij.util.CollectConsumer; -import com.intellij.util.SmartList; -import com.intellij.util.containers.SortedList; -import com.intellij.util.ui.AsyncProcessIcon; -import gnu.trove.THashSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.*; -import java.util.List; - -public class LookupImpl extends LightweightHint implements Lookup, Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl"); - private static final int MAX_PREFERRED_COUNT = 5; - - private static final LookupItem EMPTY_LOOKUP_ITEM = LookupItem.fromString("preselect"); - - private final Project myProject; - private final Editor myEditor; - - private int myMinPrefixLength; - private int myPreferredItemsCount; - private String myInitialPrefix; - private LookupArranger myArranger; - - private RangeMarker myLookupStartMarker; - private final JList myList = new JBList(new DefaultListModel()); - private final LookupCellRenderer myCellRenderer; - private Boolean myPositionedAbove = null; - - private final ArrayList myListeners = new ArrayList(); - - private boolean myShown = false; - private boolean myDisposed = false; - private boolean myHidden = false; - private LookupElement myPreselectedItem = EMPTY_LOOKUP_ITEM; - private String mySelectionInvariant = null; - private boolean mySelectionTouched; - private boolean myFocused = true; - private String myAdditionalPrefix = ""; - private final AsyncProcessIcon myProcessIcon = new AsyncProcessIcon("Completion progress"); - private final JPanel myIconPanel = new JPanel(new BorderLayout()); - private volatile boolean myCalculating; - private final JLabel myAdComponent; - private volatile String myAdText; - private volatile int myLookupTextWidth = 50; - private static final int LOOKUP_HEIGHT = Integer.getInteger("idea.lookup.height", 11).intValue(); - private boolean myReused; - private boolean myChangeGuard; - private LookupModel myModel = new LookupModel(); - - public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger){ - super(new JPanel(new BorderLayout())); - setForceShowAsPopup(true); - myProject = project; - myEditor = editor; - - myIconPanel.setVisible(false); - myCellRenderer = new LookupCellRenderer(this); - myList.setCellRenderer(myCellRenderer); - - myList.setFocusable(false); - - myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR); - - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList); - scrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0)); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - getComponent().add(scrollPane, BorderLayout.NORTH); - scrollPane.setBorder(null); - - myAdComponent = HintUtil.createAdComponent(null); - getComponent().add(myAdComponent, BorderLayout.SOUTH); - getComponent().setBorder(new BegPopupMenuBorder()); - - myIconPanel.setBackground(Color.LIGHT_GRAY); - myIconPanel.add(myProcessIcon); - - final ListModel model = myList.getModel(); - addEmptyItem((DefaultListModel)model); - updateListHeight(model); - - setArranger(arranger); - - addListeners(); - } - - public void setArranger(LookupArranger arranger) { - myArranger = arranger; - myModel.setArranger(arranger); - } - - @Override - public boolean isFocused() { - return myFocused; - } - - public void setFocused(boolean focused) { - myFocused = focused; - } - - public boolean isCalculating() { - return myCalculating; - } - - public void setCalculating(final boolean calculating) { - myCalculating = calculating; - myIconPanel.setVisible(calculating); - if (calculating) { - myProcessIcon.resume(); - } else { - myProcessIcon.suspend(); - } - } - - public int getPreferredItemsCount() { - return myPreferredItemsCount; - } - - public void markSelectionTouched() { - if (!ApplicationManager.getApplication().isUnitTestMode()) { - ApplicationManager.getApplication().assertIsDispatchThread(); - } - mySelectionTouched = true; - myPreselectedItem = null; - } - - @TestOnly - public void resort() { - mySelectionTouched = false; - myPreselectedItem = EMPTY_LOOKUP_ITEM; - final List items = myModel.getItems(); - myModel.clearItems(); - for (final LookupElement item : items) { - addItem(item); - } - updateList(); - } - - public void addItem(LookupElement item) { - myModel.addItem(item); - - updateLookupWidth(item); - updateItemActions(item); - } - - public void updateLookupWidth(LookupElement item) { - final LookupElementPresentation presentation = renderItemApproximately(item); - int maxWidth = myCellRenderer.updateMaximumWidth(presentation); - myLookupTextWidth = Math.max(maxWidth, myLookupTextWidth); - - myModel.setItemPresentation(item, presentation); - - } - - public void updateItemActions(LookupElement item) { - final CollectConsumer consumer = new CollectConsumer(); - for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) { - provider.fillActions(item, this, consumer); - } - myModel.setItemActions(item, consumer.getResult()); - } - - public Collection getActionsFor(LookupElement element) { - return myModel.getActionsFor(element); - } - - public int getMinPrefixLength() { - return myMinPrefixLength; - } - - public JList getList() { - return myList; - } - - public List getItems() { - final ArrayList result = new ArrayList(); - final Object[] objects; - synchronized (myList) { - objects = ((DefaultListModel)myList.getModel()).toArray(); - } - for (final Object object : objects) { - if (!(object instanceof EmptyLookupItem)) { - result.add((LookupElement) object); - } - } - return result; - } - - public void setAdvertisementText(@Nullable String text) { - myAdText = text; - } - - public String getAdvertisementText() { - return myAdText; - } - - - public String getAdditionalPrefix() { - return myAdditionalPrefix; - } - - public void setAdditionalPrefix(final String additionalPrefix) { - myAdditionalPrefix = additionalPrefix; - myInitialPrefix = null; - refreshUi(); - } - - private void updateList() { - if (!ApplicationManager.getApplication().isUnitTestMode()) { - ApplicationManager.getApplication().assertIsDispatchThread(); - } - - if (myReused) { - myModel.collectGarbage(); - myReused = false; - } - - final Pair,List>> snapshot = myModel.getModelSnapshot(); - final LinkedHashSet items = snapshot.first; - checkMinPrefixLengthChanges(items); - - LookupElement oldSelected = mySelectionTouched ? (LookupElement)myList.getSelectedValue() : null; - String oldInvariant = mySelectionInvariant; - boolean hasExactPrefixes; - final boolean hasPreselectedItem; - final boolean hasItems; - DefaultListModel model = (DefaultListModel)myList.getModel(); - final LookupElement preselectedItem = myPreselectedItem; - synchronized (myList) { - model.clear(); - - Set firstItems = new THashSet(); - - hasExactPrefixes = addExactPrefixItems(model, firstItems, items); - addMostRelevantItems(model, firstItems, snapshot.second); - hasPreselectedItem = items.contains(preselectedItem) && addPreselectedItem(model, firstItems, preselectedItem); - myPreferredItemsCount = firstItems.size(); - - addRemainingItemsLexicographically(model, firstItems, items); - - hasItems = model.getSize() != 0; - if (!hasItems) { - addEmptyItem(model); - } - } - - updateListHeight(model); - - myAdComponent.setText(myAdText); - - if (hasItems) { - myList.setFixedCellWidth(Math.max(myLookupTextWidth + myCellRenderer.getIconIndent(), myAdComponent.getPreferredSize().width)); - - if (isFocused() && (!hasExactPrefixes || mySelectionTouched)) { - restoreSelection(oldSelected, hasPreselectedItem, oldInvariant); - } - else { - ListScrollingUtil.selectItem(myList, 0); - } - } - } - - private void checkMinPrefixLengthChanges(Collection items) { - int minPrefixLength = items.isEmpty() ? 0 : Integer.MAX_VALUE; - for (final LookupElement item : items) { - minPrefixLength = Math.min(item.getPrefixMatcher().getPrefix().length(), minPrefixLength); - } - - if (myMinPrefixLength != minPrefixLength) { - myLookupStartMarker = null; - } - myMinPrefixLength = minPrefixLength; - } - - private void restoreSelection(@Nullable LookupElement oldSelected, boolean choosePreselectedItem, @Nullable String oldInvariant) { - if (oldSelected != null) { - if (oldSelected.isValid() && ListScrollingUtil.selectItem(myList, oldSelected)) { - return; - } - - if (oldInvariant != null) { - for (LookupElement element : getItems()) { - if (oldInvariant.equals(myModel.getItemPresentationInvariant(element)) && ListScrollingUtil.selectItem(myList, element)) { - return; - } - } - } - } - - if (choosePreselectedItem) { - ListScrollingUtil.selectItem(myList, myPreselectedItem); - } else { - selectMostPreferableItem(); - } - - if (myPreselectedItem != null) { - myPreselectedItem = getCurrentItem(); - } - } - - private void updateListHeight(ListModel model) { - myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, model.getElementAt(0), 0, false, false).getPreferredSize().height); - - myList.setVisibleRowCount(Math.min(model.getSize(), LOOKUP_HEIGHT)); - } - - private void addEmptyItem(DefaultListModel model) { - LookupItem item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions")); - item.setPrefixMatcher(new CamelHumpMatcher("")); - if (!myCalculating) { - myList.setFixedCellWidth(Math.max(myCellRenderer.updateMaximumWidth(renderItemApproximately(item)), myLookupTextWidth)); - } - - model.addElement(item); - } - - private static LookupElementPresentation renderItemApproximately(LookupElement item) { - final LookupElementPresentation p = new LookupElementPresentation(); - item.renderElement(p); - return p; - } - - private void addRemainingItemsLexicographically(DefaultListModel model, Set firstItems, Collection allItems) { - for (LookupElement item : allItems) { - if (!firstItems.contains(item) && prefixMatches(item)) { - model.addElement(item); - } - } - } - - private boolean addPreselectedItem(DefaultListModel model, Set firstItems, @Nullable final LookupElement preselectedItem) { - final boolean hasPreselectedItem = !mySelectionTouched && preselectedItem != EMPTY_LOOKUP_ITEM && preselectedItem != null && prefixMatches(preselectedItem); - if (hasPreselectedItem && !firstItems.contains(preselectedItem)) { - firstItems.add(preselectedItem); - model.addElement(preselectedItem); - } - return hasPreselectedItem; - } - - private void addMostRelevantItems(DefaultListModel model, Set firstItems, final Collection> sortedItems) { - for (final List elements : sortedItems) { - final List suitable = new SmartList(); - for (final LookupElement item : elements) { - if (!firstItems.contains(item) && prefixMatches(item)) { - suitable.add(item); - } - } - - if (firstItems.size() + suitable.size() > MAX_PREFERRED_COUNT) break; - for (final LookupElement item : suitable) { - firstItems.add(item); - model.addElement(item); - } - } - } - - private boolean addExactPrefixItems(DefaultListModel model, Set firstItems, final Collection elements) { - List sorted = new SortedList(new Comparator() { - public int compare(LookupElement o1, LookupElement o2) { - //noinspection unchecked - return myArranger.getRelevance(o1).compareTo(myArranger.getRelevance(o2)); - } - }); - for (final LookupElement item : elements) { - if (isExactPrefixItem(item)) { - sorted.add(item); - - } - } - for (final LookupElement item : sorted) { - model.addElement(item); - firstItems.add(item); - } - - return !firstItems.isEmpty(); - } - - private boolean isExactPrefixItem(LookupElement item) { - return item.getAllLookupStrings().contains(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix); - } - - private boolean prefixMatches(final LookupElement item) { - if (myAdditionalPrefix.length() == 0) return item.isPrefixMatched(); - - return item.getPrefixMatcher().cloneWithPrefix(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix).prefixMatches(item); - } - - /** - * @return point in layered pane coordinate system. - */ - public Point calculatePosition(){ - Dimension dim = getComponent().getPreferredSize(); - int lookupStart = getLookupStart(); - if (lookupStart < 0) { - LOG.error(lookupStart + "; minprefix=" + myMinPrefixLength + "; offset=" + myEditor.getCaretModel().getOffset() + "; element=" + - getPsiElement()); - } - - LogicalPosition pos = myEditor.offsetToLogicalPosition(lookupStart); - Point location = myEditor.logicalPositionToXY(pos); - location.y += myEditor.getLineHeight(); - JComponent editorComponent = myEditor.getComponent(); - JComponent internalComponent = myEditor.getContentComponent(); - final JRootPane rootPane = editorComponent.getRootPane(); - if (rootPane == null) { - LOG.error(myArranger); - } - JLayeredPane layeredPane = rootPane.getLayeredPane(); - Point layeredPanePoint=SwingUtilities.convertPoint(internalComponent,location, layeredPane); - layeredPanePoint.x -= myCellRenderer.getIconIndent(); - layeredPanePoint.x -= getComponent().getInsets().left; - if (dim.width > layeredPane.getWidth()){ - dim.width = layeredPane.getWidth(); - } - int wshift = layeredPane.getWidth() - (layeredPanePoint.x + dim.width); - if (wshift < 0){ - layeredPanePoint.x += wshift; - } - - int shiftLow = layeredPane.getHeight() - (layeredPanePoint.y + dim.height); - int shiftHigh = layeredPanePoint.y - dim.height; - if (!isPositionedAboveCaret()) { - myPositionedAbove = shiftLow < 0 && shiftLow < shiftHigh ? Boolean.TRUE : Boolean.FALSE; - } - if (isPositionedAboveCaret()) { - layeredPanePoint.y -= dim.height + myEditor.getLineHeight(); - if (pos.line == 0) { - layeredPanePoint.y += 1; - //otherwise the lookup won't intersect with the editor and every editor's resize (e.g. after typing in console) will close the lookup - } - } - return layeredPanePoint; - } - - public void finishLookup(final char completionChar) { - final LookupElement item = (LookupElement)myList.getSelectedValue(); - doHide(false, true); - if (item == null || - item instanceof EmptyLookupItem || - item.getObject() instanceof DeferredUserLookupValue && - item.as(LookupItem.class) != null && - !((DeferredUserLookupValue)item.getObject()).handleUserSelection(item.as(LookupItem.class), myProject)) { - fireItemSelected(null, completionChar); - return; - } - - final PsiFile file = getPsiFile(); - if (file != null && !WriteCommandAction.ensureFilesWritable(myProject, Arrays.asList(file))) { - return; - } - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - EditorModificationUtil.deleteSelectedText(myEditor); - final int caretOffset = myEditor.getCaretModel().getOffset(); - final String prefix = item.getPrefixMatcher().getPrefix(); - int lookupStart = caretOffset - prefix.length() - myAdditionalPrefix.length(); - - final String lookupString = item.getLookupString(); - if (!StringUtil.startsWithConcatenationOf(lookupString, prefix, myAdditionalPrefix)) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.camelHumps"); - } - - myEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString); - - int offset = lookupStart + lookupString.length(); - myEditor.getCaretModel().moveToOffset(offset); - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - myEditor.getSelectionModel().removeSelection(); - } - }); - - fireItemSelected(item, completionChar); - } - - public int getLookupStart() { - if (myLookupStartMarker == null) { - final int start = calcLookupStart(); - myLookupStartMarker = myEditor.getDocument().createRangeMarker(start, start); - myLookupStartMarker.setGreedyToLeft(true); - } - - return myLookupStartMarker.getStartOffset(); - } - - public void performGuardedChange(Runnable change) { - assert !myChangeGuard; - myChangeGuard = true; - try { - change.run(); - } - finally { - myChangeGuard = false; - } - } - - public boolean isShown() { - return myShown; - } - - public void show(){ - ApplicationManager.getApplication().assertIsDispatchThread(); - LOG.assertTrue(!myDisposed, disposeTrace); - LOG.assertTrue(!myShown); - myShown = true; - - if (ApplicationManager.getApplication().isUnitTestMode()) return; - - getComponent().setBorder(null); - - Point p = calculatePosition(); - HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl(); - hintManager.showEditorHint(this, myEditor, p, HintManagerImpl.HIDE_BY_ESCAPE | HintManagerImpl.UPDATE_BY_SCROLLING, 0, false); - - getComponent().getRootPane().getLayeredPane().add(myIconPanel, 42, 0); - } - - private void addListeners() { - myEditor.getDocument().addDocumentListener(new DocumentAdapter() { - public void documentChanged(DocumentEvent e) { - if (!myChangeGuard) { - hide(); - } - } - }, this); - - final CaretListener caretListener = new CaretListener() { - public void caretPositionChanged(CaretEvent e){ - if (!myChangeGuard) { - hide(); - } - } - }; - final SelectionListener selectionListener = new SelectionListener() { - public void selectionChanged(final SelectionEvent e) { - if (!myChangeGuard) { - hide(); - } - } - }; - final EditorMouseListener mouseListener = new EditorMouseAdapter() { - public void mouseClicked(EditorMouseEvent e){ - e.consume(); - hide(); - } - }; - - myEditor.getCaretModel().addCaretListener(caretListener); - myEditor.getSelectionModel().addSelectionListener(selectionListener); - myEditor.addEditorMouseListener(mouseListener); - Disposer.register(this, new Disposable() { - @Override - public void dispose() { - myEditor.getCaretModel().removeCaretListener(caretListener); - myEditor.getSelectionModel().removeSelectionListener(selectionListener); - myEditor.removeEditorMouseListener(mouseListener); - } - }); - - myList.addListSelectionListener(new ListSelectionListener() { - private LookupElement oldItem = null; - - public void valueChanged(ListSelectionEvent e){ - LookupElement item = getCurrentItem(); - if (oldItem != item) { - mySelectionInvariant = item == null ? null : myModel.getItemPresentationInvariant(item); - fireCurrentItemChanged(item); - } - oldItem = item; - } - }); - - myList.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e){ - setFocused(true); - - final Point point = e.getPoint(); - final int i = myList.locationToIndex(point); - if (i >= 0) { - final LookupElement selected = (LookupElement)myList.getModel().getElementAt(i); - if (selected != null && - e.getClickCount() == 1 && - point.x >= myList.getCellBounds(i, i).width - PopupIcons.EMPTY_ICON.getIconWidth() && - ShowLookupActionsHandler.showItemActions(LookupImpl.this, selected)) { - return; - } - } - - if (e.getClickCount() == 2){ - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - finishLookup(NORMAL_SELECT_CHAR); - } - }, "", null); - } - } - }); - } - - private int calcLookupStart() { - int offset = myEditor.getSelectionModel().hasSelection() - ? myEditor.getSelectionModel().getSelectionStart() - : myEditor.getCaretModel().getOffset(); - return Math.max(offset - myMinPrefixLength - myAdditionalPrefix.length(), 0); - } - - private void selectMostPreferableItem() { - final List sortedItems = getItems(); - final int index = doSelectMostPreferableItem(sortedItems); - myList.setSelectedIndex(index); - - if (index >= 0 && index < myList.getModel().getSize()){ - ListScrollingUtil.selectItem(myList, index); - } - else if (!sortedItems.isEmpty()) { - ListScrollingUtil.selectItem(myList, 0); - } - } - - @Nullable - public LookupElement getCurrentItem(){ - LookupElement item = (LookupElement)myList.getSelectedValue(); - return item instanceof EmptyLookupItem ? null : item; - } - - public void setCurrentItem(LookupElement item){ - ListScrollingUtil.selectItem(myList, item); - } - - public void addLookupListener(LookupListener listener){ - myListeners.add(listener); - } - - public void removeLookupListener(LookupListener listener){ - myListeners.remove(listener); - } - - public Rectangle getCurrentItemBounds(){ - int index = myList.getSelectedIndex(); - Rectangle itmBounds = myList.getCellBounds(index, index); - if (itmBounds == null){ - LOG.error("No bounds for " + index + "; size=" + myList.getModel().getSize()); - return null; - } - Point layeredPanePoint=SwingUtilities.convertPoint(myList,itmBounds.x,itmBounds.y,getComponent()); - itmBounds.x = layeredPanePoint.x; - itmBounds.y = layeredPanePoint.y; - return itmBounds; - } - - public void fireItemSelected(final LookupElement item, char completionChar){ - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - - if (item != null) { - myArranger.itemSelected(item, this); - } - - if (!myListeners.isEmpty()){ - LookupEvent event = new LookupEvent(this, item, completionChar); - LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]); - for (LookupListener listener : listeners) { - try { - listener.itemSelected(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - } - - private void fireLookupCanceled(final boolean explicitly) { - if (!myListeners.isEmpty()){ - LookupEvent event = new LookupEvent(this, explicitly); - LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]); - for (LookupListener listener : listeners) { - try { - listener.lookupCanceled(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - } - - private void fireCurrentItemChanged(LookupElement item){ - if (!myListeners.isEmpty()){ - LookupEvent event = new LookupEvent(this, item, (char)0); - LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]); - for (LookupListener listener : listeners) { - listener.currentItemChanged(event); - } - } - } - - private static int divideString(String lookupString, PrefixMatcher matcher) { - for (int i = matcher.getPrefix().length(); i <= lookupString.length(); i++) { - if (matcher.prefixMatches(lookupString.substring(0, i))) { - return i; - } - } - return -1; - } - - public boolean fillInCommonPrefix(boolean explicitlyInvoked) { - if (explicitlyInvoked) { - setFocused(true); - } - - if (explicitlyInvoked && myCalculating) return false; - if (!explicitlyInvoked && mySelectionTouched) return false; - - ListModel listModel = myList.getModel(); - if (listModel.getSize() <= 1) return false; - - if (listModel.getSize() == 0) return false; - - final LookupElement firstItem = (LookupElement)listModel.getElementAt(0); - if (listModel.getSize() == 1 && firstItem instanceof EmptyLookupItem) return false; - - final PrefixMatcher firstItemMatcher = firstItem.getPrefixMatcher(); - final String oldPrefix = firstItemMatcher.getPrefix(); - final String presentPrefix = oldPrefix + myAdditionalPrefix; - final PrefixMatcher matcher = firstItemMatcher.cloneWithPrefix(presentPrefix); - String lookupString = firstItem.getLookupString(); - int div = divideString(lookupString, matcher); - if (div < 0) return false; - - String beforeCaret = lookupString.substring(0, div); - String afterCaret = lookupString.substring(div); - - - for (int i = 1; i < listModel.getSize(); i++) { - LookupElement item = (LookupElement)listModel.getElementAt(i); - if (!oldPrefix.equals(item.getPrefixMatcher().getPrefix())) return false; - - lookupString = item.getLookupString(); - div = divideString(lookupString, item.getPrefixMatcher().cloneWithPrefix(presentPrefix)); - if (div < 0) return false; - - String _afterCaret = lookupString.substring(div); - if (beforeCaret != null) { - if (div != beforeCaret.length() || !lookupString.startsWith(beforeCaret)) { - beforeCaret = null; - } - } - - while (afterCaret.length() > 0) { - if (_afterCaret.startsWith(afterCaret)) { - break; - } - afterCaret = afterCaret.substring(0, afterCaret.length() - 1); - } - if (afterCaret.length() == 0) return false; - } - - if (myAdditionalPrefix.length() == 0 && myInitialPrefix == null && !explicitlyInvoked) { - myInitialPrefix = presentPrefix; - } - else { - myInitialPrefix = null; - } - - final String finalBeforeCaret = beforeCaret; - final String finalAfterCaret = afterCaret; - Runnable runnable = new Runnable() { - public void run() { - doInsertCommonPrefix(presentPrefix, finalBeforeCaret, finalAfterCaret); - } - }; - performGuardedChange(runnable); - return true; - } - - private void doInsertCommonPrefix(String presentPrefix, String beforeCaret, String afterCaret) { - EditorModificationUtil.deleteSelectedText(myEditor); - int offset = myEditor.getCaretModel().getOffset(); - if (beforeCaret != null) { // correct case, expand camel-humps - final int start = offset - presentPrefix.length(); - myAdditionalPrefix = ""; - myEditor.getDocument().replaceString(start, offset, beforeCaret); - presentPrefix = beforeCaret; - } - - offset = myEditor.getCaretModel().getOffset(); - myEditor.getDocument().insertString(offset, afterCaret); - - final String newPrefix = presentPrefix + afterCaret; - myModel.retainMatchingItems(newPrefix); - myAdditionalPrefix = ""; - - offset += afterCaret.length(); - myEditor.getCaretModel().moveToOffset(offset); - refreshUi(); - } - - @Nullable - public PsiFile getPsiFile() { - return PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); - } - - public boolean isCompletion() { - return myArranger instanceof CompletionLookupArranger; - } - - public PsiElement getPsiElement() { - PsiFile file = getPsiFile(); - if (file == null) return null; - - int offset = getLookupStart(); - if (offset > 0) return file.findElementAt(offset - 1); - - return file.findElementAt(0); - } - - public Editor getEditor() { - return myEditor; - } - - public boolean isPositionedAboveCaret(){ - return myPositionedAbove != null && myPositionedAbove.booleanValue(); - } - - public void hide(){ - hideLookup(true); - } - - public void hideLookup(boolean explicitly) { - ApplicationManager.getApplication().assertIsDispatchThread(); - - if (myDisposed) return; - - doHide(true, explicitly); - } - - private void doHide(final boolean fireCanceled, final boolean explicitly) { - assert !myDisposed : disposeTrace; - myHidden = true; - - try { - super.hide(); - - Disposer.dispose(this); - } - catch (Throwable e) { - LOG.error(e); - } - - assert myDisposed; - - if (fireCanceled) { - fireLookupCanceled(explicitly); - } - } - - public void restorePrefix() { - if (myInitialPrefix != null) { - myEditor.getDocument().replaceString(getLookupStart(), myEditor.getCaretModel().getOffset(), myInitialPrefix); - } - } - - String disposeTrace = null; - - public void dispose() { - assert ApplicationManager.getApplication().isDispatchThread(); - assert myHidden; - assert !myDisposed : disposeTrace; - - Disposer.dispose(myProcessIcon); - - myDisposed = true; - disposeTrace = DebugUtil.currentStackTrace(); - } - - private int doSelectMostPreferableItem(List items) { - if (items.isEmpty()) { - return -1; - } - - if (items.size() == 1) { - return 0; - } - - for (int i = 0; i < items.size(); i++) { - LookupElement item = items.get(i); - if (isExactPrefixItem(item)) { - return i; - } - } - - final int index = myArranger.suggestPreselectedItem(items); - assert index >= 0 && index < items.size(); - return index; - } - - public void refreshUi() { - updateList(); - - if (isVisible() && !ApplicationManager.getApplication().isUnitTestMode()) { - if (myEditor.getComponent().getRootPane() == null) { - LOG.error("Null root pane"); - } - - Point point = calculatePosition(); - updateBounds(point.x,point.y); - - final Dimension size = myProcessIcon.getPreferredSize(); - myIconPanel.setBounds(getComponent().getRootPane().getLayeredPane().getWidth() - size.width, 0, size.width, size.height); - - HintManagerImpl.adjustEditorHintPosition(this, myEditor, point); - } - } - - public LookupArranger getArranger() { - return myArranger; - } - - public void markReused() { - myReused = true; - myModel.clearItems(); - setAdvertisementText(null); - myAdditionalPrefix = ""; - myPreselectedItem = null; - } - - boolean isDisposed() { - return myDisposed; - } - - @Override - public void showItemPopup(JBPopup hint) { - final Rectangle bounds = getCurrentItemBounds(); - hint.show(new RelativePoint(getComponent(), new Point(bounds.x + bounds.width, - bounds.y))); - } -} +/* + * 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.codeInsight.lookup.impl; + +import com.intellij.codeInsight.completion.CompletionLookupArranger; +import com.intellij.codeInsight.completion.PrefixMatcher; +import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; +import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.codeInsight.hint.HintUtil; +import com.intellij.codeInsight.lookup.*; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.lang.LangBundle; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.ui.LightweightHint; +import com.intellij.ui.ListScrollingUtil; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBList; +import com.intellij.ui.plaf.beg.BegPopupMenuBorder; +import com.intellij.ui.popup.PopupIcons; +import com.intellij.util.CollectConsumer; +import com.intellij.util.SmartList; +import com.intellij.util.containers.SortedList; +import com.intellij.util.ui.AsyncProcessIcon; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; + +public class LookupImpl extends LightweightHint implements Lookup, Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl"); + private static final int MAX_PREFERRED_COUNT = 5; + + private static final LookupItem EMPTY_LOOKUP_ITEM = LookupItem.fromString("preselect"); + + private final Project myProject; + private final Editor myEditor; + + private int myMinPrefixLength; + private int myPreferredItemsCount; + private String myInitialPrefix; + private LookupArranger myArranger; + + private RangeMarker myLookupStartMarker; + private final JList myList = new JBList(new DefaultListModel()); + private final LookupCellRenderer myCellRenderer; + private Boolean myPositionedAbove = null; + + private final ArrayList myListeners = new ArrayList(); + + private boolean myShown = false; + private boolean myDisposed = false; + private boolean myHidden = false; + private LookupElement myPreselectedItem = EMPTY_LOOKUP_ITEM; + private String mySelectionInvariant = null; + private boolean mySelectionTouched; + private boolean myFocused = true; + private String myAdditionalPrefix = ""; + private final AsyncProcessIcon myProcessIcon = new AsyncProcessIcon("Completion progress"); + private final JPanel myIconPanel = new JPanel(new BorderLayout()); + private volatile boolean myCalculating; + private final JLabel myAdComponent; + private volatile String myAdText; + private volatile int myLookupTextWidth = 50; + private static final int LOOKUP_HEIGHT = Integer.getInteger("idea.lookup.height", 11).intValue(); + private boolean myReused; + private boolean myChangeGuard; + private LookupModel myModel = new LookupModel(); + + public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger){ + super(new JPanel(new BorderLayout())); + setForceShowAsPopup(true); + myProject = project; + myEditor = editor; + + myIconPanel.setVisible(false); + myCellRenderer = new LookupCellRenderer(this); + myList.setCellRenderer(myCellRenderer); + + myList.setFocusable(false); + + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR); + + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList); + scrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0)); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + getComponent().add(scrollPane, BorderLayout.NORTH); + scrollPane.setBorder(null); + + myAdComponent = HintUtil.createAdComponent(null); + getComponent().add(myAdComponent, BorderLayout.SOUTH); + getComponent().setBorder(new BegPopupMenuBorder()); + + myIconPanel.setBackground(Color.LIGHT_GRAY); + myIconPanel.add(myProcessIcon); + + final ListModel model = myList.getModel(); + addEmptyItem((DefaultListModel)model); + updateListHeight(model); + + setArranger(arranger); + + addListeners(); + } + + public void setArranger(LookupArranger arranger) { + myArranger = arranger; + myModel.setArranger(arranger); + } + + @Override + public boolean isFocused() { + return myFocused; + } + + public void setFocused(boolean focused) { + myFocused = focused; + } + + public boolean isCalculating() { + return myCalculating; + } + + public void setCalculating(final boolean calculating) { + myCalculating = calculating; + myIconPanel.setVisible(calculating); + if (calculating) { + myProcessIcon.resume(); + } else { + myProcessIcon.suspend(); + } + } + + public int getPreferredItemsCount() { + return myPreferredItemsCount; + } + + public void markSelectionTouched() { + if (!ApplicationManager.getApplication().isUnitTestMode()) { + ApplicationManager.getApplication().assertIsDispatchThread(); + } + mySelectionTouched = true; + myPreselectedItem = null; + } + + @TestOnly + public void resort() { + mySelectionTouched = false; + myPreselectedItem = EMPTY_LOOKUP_ITEM; + final List items = myModel.getItems(); + myModel.clearItems(); + for (final LookupElement item : items) { + addItem(item); + } + updateList(); + } + + public void addItem(LookupElement item) { + myModel.addItem(item); + + updateLookupWidth(item); + updateItemActions(item); + } + + public void updateLookupWidth(LookupElement item) { + final LookupElementPresentation presentation = renderItemApproximately(item); + int maxWidth = myCellRenderer.updateMaximumWidth(presentation); + myLookupTextWidth = Math.max(maxWidth, myLookupTextWidth); + + myModel.setItemPresentation(item, presentation); + + } + + public void updateItemActions(LookupElement item) { + final CollectConsumer consumer = new CollectConsumer(); + for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) { + provider.fillActions(item, this, consumer); + } + myModel.setItemActions(item, consumer.getResult()); + } + + public Collection getActionsFor(LookupElement element) { + return myModel.getActionsFor(element); + } + + public int getMinPrefixLength() { + return myMinPrefixLength; + } + + public JList getList() { + return myList; + } + + public List getItems() { + final ArrayList result = new ArrayList(); + final Object[] objects; + synchronized (myList) { + objects = ((DefaultListModel)myList.getModel()).toArray(); + } + for (final Object object : objects) { + if (!(object instanceof EmptyLookupItem)) { + result.add((LookupElement) object); + } + } + return result; + } + + public void setAdvertisementText(@Nullable String text) { + myAdText = text; + } + + public String getAdvertisementText() { + return myAdText; + } + + + public String getAdditionalPrefix() { + return myAdditionalPrefix; + } + + public void setAdditionalPrefix(final String additionalPrefix) { + myAdditionalPrefix = additionalPrefix; + myInitialPrefix = null; + refreshUi(); + } + + private void updateList() { + if (!ApplicationManager.getApplication().isUnitTestMode()) { + ApplicationManager.getApplication().assertIsDispatchThread(); + } + + if (myReused) { + myModel.collectGarbage(); + myReused = false; + } + + final Pair,List>> snapshot = myModel.getModelSnapshot(); + final LinkedHashSet items = snapshot.first; + checkMinPrefixLengthChanges(items); + + LookupElement oldSelected = mySelectionTouched ? (LookupElement)myList.getSelectedValue() : null; + String oldInvariant = mySelectionInvariant; + boolean hasExactPrefixes; + final boolean hasPreselectedItem; + final boolean hasItems; + DefaultListModel model = (DefaultListModel)myList.getModel(); + final LookupElement preselectedItem = myPreselectedItem; + synchronized (myList) { + model.clear(); + + Set firstItems = new THashSet(); + + hasExactPrefixes = addExactPrefixItems(model, firstItems, items); + addMostRelevantItems(model, firstItems, snapshot.second); + hasPreselectedItem = items.contains(preselectedItem) && addPreselectedItem(model, firstItems, preselectedItem); + myPreferredItemsCount = firstItems.size(); + + addRemainingItemsLexicographically(model, firstItems, items); + + hasItems = model.getSize() != 0; + if (!hasItems) { + addEmptyItem(model); + } + } + + updateListHeight(model); + + myAdComponent.setText(myAdText); + + if (hasItems) { + myList.setFixedCellWidth(Math.max(myLookupTextWidth + myCellRenderer.getIconIndent(), myAdComponent.getPreferredSize().width)); + + if (isFocused() && (!hasExactPrefixes || mySelectionTouched)) { + restoreSelection(oldSelected, hasPreselectedItem, oldInvariant); + } + else { + ListScrollingUtil.selectItem(myList, 0); + } + } + } + + private void checkMinPrefixLengthChanges(Collection items) { + int minPrefixLength = items.isEmpty() ? 0 : Integer.MAX_VALUE; + for (final LookupElement item : items) { + minPrefixLength = Math.min(item.getPrefixMatcher().getPrefix().length(), minPrefixLength); + } + + if (myMinPrefixLength != minPrefixLength) { + myLookupStartMarker = null; + } + myMinPrefixLength = minPrefixLength; + } + + private void restoreSelection(@Nullable LookupElement oldSelected, boolean choosePreselectedItem, @Nullable String oldInvariant) { + if (oldSelected != null) { + if (oldSelected.isValid() && ListScrollingUtil.selectItem(myList, oldSelected)) { + return; + } + + if (oldInvariant != null) { + for (LookupElement element : getItems()) { + if (oldInvariant.equals(myModel.getItemPresentationInvariant(element)) && ListScrollingUtil.selectItem(myList, element)) { + return; + } + } + } + } + + if (choosePreselectedItem) { + ListScrollingUtil.selectItem(myList, myPreselectedItem); + } else { + selectMostPreferableItem(); + } + + if (myPreselectedItem != null) { + myPreselectedItem = getCurrentItem(); + } + } + + private void updateListHeight(ListModel model) { + myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, model.getElementAt(0), 0, false, false).getPreferredSize().height); + + myList.setVisibleRowCount(Math.min(model.getSize(), LOOKUP_HEIGHT)); + } + + private void addEmptyItem(DefaultListModel model) { + LookupItem item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions")); + item.setPrefixMatcher(new CamelHumpMatcher("")); + if (!myCalculating) { + myList.setFixedCellWidth(Math.max(myCellRenderer.updateMaximumWidth(renderItemApproximately(item)), myLookupTextWidth)); + } + + model.addElement(item); + } + + private static LookupElementPresentation renderItemApproximately(LookupElement item) { + final LookupElementPresentation p = new LookupElementPresentation(); + item.renderElement(p); + return p; + } + + private void addRemainingItemsLexicographically(DefaultListModel model, Set firstItems, Collection allItems) { + for (LookupElement item : allItems) { + if (!firstItems.contains(item) && prefixMatches(item)) { + model.addElement(item); + } + } + } + + private boolean addPreselectedItem(DefaultListModel model, Set firstItems, @Nullable final LookupElement preselectedItem) { + final boolean hasPreselectedItem = !mySelectionTouched && preselectedItem != EMPTY_LOOKUP_ITEM && preselectedItem != null && prefixMatches(preselectedItem); + if (hasPreselectedItem && !firstItems.contains(preselectedItem)) { + firstItems.add(preselectedItem); + model.addElement(preselectedItem); + } + return hasPreselectedItem; + } + + private void addMostRelevantItems(DefaultListModel model, Set firstItems, final Collection> sortedItems) { + for (final List elements : sortedItems) { + final List suitable = new SmartList(); + for (final LookupElement item : elements) { + if (!firstItems.contains(item) && prefixMatches(item)) { + suitable.add(item); + } + } + + if (firstItems.size() + suitable.size() > MAX_PREFERRED_COUNT) break; + for (final LookupElement item : suitable) { + firstItems.add(item); + model.addElement(item); + } + } + } + + private boolean addExactPrefixItems(DefaultListModel model, Set firstItems, final Collection elements) { + List sorted = new SortedList(new Comparator() { + public int compare(LookupElement o1, LookupElement o2) { + //noinspection unchecked + return myArranger.getRelevance(o1).compareTo(myArranger.getRelevance(o2)); + } + }); + for (final LookupElement item : elements) { + if (isExactPrefixItem(item)) { + sorted.add(item); + + } + } + for (final LookupElement item : sorted) { + model.addElement(item); + firstItems.add(item); + } + + return !firstItems.isEmpty(); + } + + private boolean isExactPrefixItem(LookupElement item) { + return item.getAllLookupStrings().contains(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix); + } + + private boolean prefixMatches(final LookupElement item) { + if (myAdditionalPrefix.length() == 0) return item.isPrefixMatched(); + + return item.getPrefixMatcher().cloneWithPrefix(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix).prefixMatches(item); + } + + /** + * @return point in layered pane coordinate system. + */ + public Point calculatePosition(){ + Dimension dim = getComponent().getPreferredSize(); + int lookupStart = getLookupStart(); + if (lookupStart < 0) { + LOG.error(lookupStart + "; minprefix=" + myMinPrefixLength + "; offset=" + myEditor.getCaretModel().getOffset() + "; element=" + + getPsiElement()); + } + + LogicalPosition pos = myEditor.offsetToLogicalPosition(lookupStart); + Point location = myEditor.logicalPositionToXY(pos); + location.y += myEditor.getLineHeight(); + JComponent editorComponent = myEditor.getComponent(); + JComponent internalComponent = myEditor.getContentComponent(); + final JRootPane rootPane = editorComponent.getRootPane(); + if (rootPane == null) { + LOG.error(myArranger); + } + JLayeredPane layeredPane = rootPane.getLayeredPane(); + Point layeredPanePoint=SwingUtilities.convertPoint(internalComponent,location, layeredPane); + layeredPanePoint.x -= myCellRenderer.getIconIndent(); + layeredPanePoint.x -= getComponent().getInsets().left; + if (dim.width > layeredPane.getWidth()){ + dim.width = layeredPane.getWidth(); + } + int wshift = layeredPane.getWidth() - (layeredPanePoint.x + dim.width); + if (wshift < 0){ + layeredPanePoint.x += wshift; + } + + int shiftLow = layeredPane.getHeight() - (layeredPanePoint.y + dim.height); + int shiftHigh = layeredPanePoint.y - dim.height; + if (!isPositionedAboveCaret()) { + myPositionedAbove = shiftLow < 0 && shiftLow < shiftHigh ? Boolean.TRUE : Boolean.FALSE; + } + if (isPositionedAboveCaret()) { + layeredPanePoint.y -= dim.height + myEditor.getLineHeight(); + if (pos.line == 0) { + layeredPanePoint.y += 1; + //otherwise the lookup won't intersect with the editor and every editor's resize (e.g. after typing in console) will close the lookup + } + } + return layeredPanePoint; + } + + public void finishLookup(final char completionChar) { + final LookupElement item = (LookupElement)myList.getSelectedValue(); + doHide(false, true); + if (item == null || + item instanceof EmptyLookupItem || + item.getObject() instanceof DeferredUserLookupValue && + item.as(LookupItem.class) != null && + !((DeferredUserLookupValue)item.getObject()).handleUserSelection(item.as(LookupItem.class), myProject)) { + fireItemSelected(null, completionChar); + return; + } + + final PsiFile file = getPsiFile(); + if (file != null && !WriteCommandAction.ensureFilesWritable(myProject, Arrays.asList(file))) { + return; + } + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + EditorModificationUtil.deleteSelectedText(myEditor); + final int caretOffset = myEditor.getCaretModel().getOffset(); + final String prefix = item.getPrefixMatcher().getPrefix(); + int lookupStart = caretOffset - prefix.length() - myAdditionalPrefix.length(); + + final String lookupString = item.getLookupString(); + if (!StringUtil.startsWithConcatenationOf(lookupString, prefix, myAdditionalPrefix)) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.camelHumps"); + } + + myEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString); + + int offset = lookupStart + lookupString.length(); + myEditor.getCaretModel().moveToOffset(offset); + myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + myEditor.getSelectionModel().removeSelection(); + } + }); + + fireItemSelected(item, completionChar); + } + + public int getLookupStart() { + if (myLookupStartMarker == null) { + final int start = calcLookupStart(); + myLookupStartMarker = myEditor.getDocument().createRangeMarker(start, start); + myLookupStartMarker.setGreedyToLeft(true); + } + + return myLookupStartMarker.getStartOffset(); + } + + public void performGuardedChange(Runnable change) { + assert !myChangeGuard; + myChangeGuard = true; + try { + change.run(); + } + finally { + myChangeGuard = false; + } + } + + public boolean isShown() { + return myShown; + } + + public void show(){ + ApplicationManager.getApplication().assertIsDispatchThread(); + LOG.assertTrue(!myDisposed, disposeTrace); + LOG.assertTrue(!myShown); + myShown = true; + + if (ApplicationManager.getApplication().isUnitTestMode()) return; + + getComponent().setBorder(null); + + Point p = calculatePosition(); + HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl(); + hintManager.showEditorHint(this, myEditor, p, HintManagerImpl.HIDE_BY_ESCAPE | HintManagerImpl.UPDATE_BY_SCROLLING, 0, false); + + getComponent().getRootPane().getLayeredPane().add(myIconPanel, 42, 0); + } + + private void addListeners() { + myEditor.getDocument().addDocumentListener(new DocumentAdapter() { + public void documentChanged(DocumentEvent e) { + if (!myChangeGuard) { + hide(); + } + } + }, this); + + final CaretListener caretListener = new CaretListener() { + public void caretPositionChanged(CaretEvent e){ + if (!myChangeGuard) { + hide(); + } + } + }; + final SelectionListener selectionListener = new SelectionListener() { + public void selectionChanged(final SelectionEvent e) { + if (!myChangeGuard) { + hide(); + } + } + }; + final EditorMouseListener mouseListener = new EditorMouseAdapter() { + public void mouseClicked(EditorMouseEvent e){ + e.consume(); + hide(); + } + }; + + myEditor.getCaretModel().addCaretListener(caretListener); + myEditor.getSelectionModel().addSelectionListener(selectionListener); + myEditor.addEditorMouseListener(mouseListener); + Disposer.register(this, new Disposable() { + @Override + public void dispose() { + myEditor.getCaretModel().removeCaretListener(caretListener); + myEditor.getSelectionModel().removeSelectionListener(selectionListener); + myEditor.removeEditorMouseListener(mouseListener); + } + }); + + myList.addListSelectionListener(new ListSelectionListener() { + private LookupElement oldItem = null; + + public void valueChanged(ListSelectionEvent e){ + LookupElement item = getCurrentItem(); + if (oldItem != item) { + mySelectionInvariant = item == null ? null : myModel.getItemPresentationInvariant(item); + fireCurrentItemChanged(item); + } + oldItem = item; + } + }); + + myList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e){ + setFocused(true); + + final Point point = e.getPoint(); + final int i = myList.locationToIndex(point); + if (i >= 0) { + final LookupElement selected = (LookupElement)myList.getModel().getElementAt(i); + if (selected != null && + e.getClickCount() == 1 && + point.x >= myList.getCellBounds(i, i).width - PopupIcons.EMPTY_ICON.getIconWidth() && + ShowLookupActionsHandler.showItemActions(LookupImpl.this, selected)) { + return; + } + } + + if (e.getClickCount() == 2){ + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + finishLookup(NORMAL_SELECT_CHAR); + } + }, "", null); + } + } + }); + } + + private int calcLookupStart() { + int offset = myEditor.getSelectionModel().hasSelection() + ? myEditor.getSelectionModel().getSelectionStart() + : myEditor.getCaretModel().getOffset(); + return Math.max(offset - myMinPrefixLength - myAdditionalPrefix.length(), 0); + } + + private void selectMostPreferableItem() { + final List sortedItems = getItems(); + final int index = doSelectMostPreferableItem(sortedItems); + myList.setSelectedIndex(index); + + if (index >= 0 && index < myList.getModel().getSize()){ + ListScrollingUtil.selectItem(myList, index); + } + else if (!sortedItems.isEmpty()) { + ListScrollingUtil.selectItem(myList, 0); + } + } + + @Nullable + public LookupElement getCurrentItem(){ + LookupElement item = (LookupElement)myList.getSelectedValue(); + return item instanceof EmptyLookupItem ? null : item; + } + + public void setCurrentItem(LookupElement item){ + ListScrollingUtil.selectItem(myList, item); + } + + public void addLookupListener(LookupListener listener){ + myListeners.add(listener); + } + + public void removeLookupListener(LookupListener listener){ + myListeners.remove(listener); + } + + public Rectangle getCurrentItemBounds(){ + int index = myList.getSelectedIndex(); + Rectangle itmBounds = myList.getCellBounds(index, index); + if (itmBounds == null){ + LOG.error("No bounds for " + index + "; size=" + myList.getModel().getSize()); + return null; + } + Point layeredPanePoint=SwingUtilities.convertPoint(myList,itmBounds.x,itmBounds.y,getComponent()); + itmBounds.x = layeredPanePoint.x; + itmBounds.y = layeredPanePoint.y; + return itmBounds; + } + + public void fireItemSelected(final LookupElement item, char completionChar){ + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + if (item != null) { + myArranger.itemSelected(item, this); + } + + if (!myListeners.isEmpty()){ + LookupEvent event = new LookupEvent(this, item, completionChar); + LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]); + for (LookupListener listener : listeners) { + try { + listener.itemSelected(event); + } + catch (Throwable e) { + LOG.error(e); + } + } + } + } + + private void fireLookupCanceled(final boolean explicitly) { + if (!myListeners.isEmpty()){ + LookupEvent event = new LookupEvent(this, explicitly); + LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]); + for (LookupListener listener : listeners) { + try { + listener.lookupCanceled(event); + } + catch (Throwable e) { + LOG.error(e); + } + } + } + } + + private void fireCurrentItemChanged(LookupElement item){ + if (!myListeners.isEmpty()){ + LookupEvent event = new LookupEvent(this, item, (char)0); + LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]); + for (LookupListener listener : listeners) { + listener.currentItemChanged(event); + } + } + } + + private static int divideString(String lookupString, PrefixMatcher matcher) { + for (int i = matcher.getPrefix().length(); i <= lookupString.length(); i++) { + if (matcher.prefixMatches(lookupString.substring(0, i))) { + return i; + } + } + return -1; + } + + public boolean fillInCommonPrefix(boolean explicitlyInvoked) { + if (explicitlyInvoked) { + setFocused(true); + } + + if (explicitlyInvoked && myCalculating) return false; + if (!explicitlyInvoked && mySelectionTouched) return false; + + ListModel listModel = myList.getModel(); + if (listModel.getSize() <= 1) return false; + + if (listModel.getSize() == 0) return false; + + final LookupElement firstItem = (LookupElement)listModel.getElementAt(0); + if (listModel.getSize() == 1 && firstItem instanceof EmptyLookupItem) return false; + + final PrefixMatcher firstItemMatcher = firstItem.getPrefixMatcher(); + final String oldPrefix = firstItemMatcher.getPrefix(); + final String presentPrefix = oldPrefix + myAdditionalPrefix; + final PrefixMatcher matcher = firstItemMatcher.cloneWithPrefix(presentPrefix); + String lookupString = firstItem.getLookupString(); + int div = divideString(lookupString, matcher); + if (div < 0) return false; + + String beforeCaret = lookupString.substring(0, div); + String afterCaret = lookupString.substring(div); + + + for (int i = 1; i < listModel.getSize(); i++) { + LookupElement item = (LookupElement)listModel.getElementAt(i); + if (!oldPrefix.equals(item.getPrefixMatcher().getPrefix())) return false; + + lookupString = item.getLookupString(); + div = divideString(lookupString, item.getPrefixMatcher().cloneWithPrefix(presentPrefix)); + if (div < 0) return false; + + String _afterCaret = lookupString.substring(div); + if (beforeCaret != null) { + if (div != beforeCaret.length() || !lookupString.startsWith(beforeCaret)) { + beforeCaret = null; + } + } + + while (afterCaret.length() > 0) { + if (_afterCaret.startsWith(afterCaret)) { + break; + } + afterCaret = afterCaret.substring(0, afterCaret.length() - 1); + } + if (afterCaret.length() == 0) return false; + } + + if (myAdditionalPrefix.length() == 0 && myInitialPrefix == null && !explicitlyInvoked) { + myInitialPrefix = presentPrefix; + } + else { + myInitialPrefix = null; + } + + final String finalBeforeCaret = beforeCaret; + final String finalAfterCaret = afterCaret; + Runnable runnable = new Runnable() { + public void run() { + doInsertCommonPrefix(presentPrefix, finalBeforeCaret, finalAfterCaret); + } + }; + performGuardedChange(runnable); + return true; + } + + private void doInsertCommonPrefix(String presentPrefix, String beforeCaret, String afterCaret) { + EditorModificationUtil.deleteSelectedText(myEditor); + int offset = myEditor.getCaretModel().getOffset(); + if (beforeCaret != null) { // correct case, expand camel-humps + final int start = offset - presentPrefix.length(); + myAdditionalPrefix = ""; + myEditor.getDocument().replaceString(start, offset, beforeCaret); + presentPrefix = beforeCaret; + } + + offset = myEditor.getCaretModel().getOffset(); + myEditor.getDocument().insertString(offset, afterCaret); + + final String newPrefix = presentPrefix + afterCaret; + myModel.retainMatchingItems(newPrefix); + myAdditionalPrefix = ""; + + offset += afterCaret.length(); + myEditor.getCaretModel().moveToOffset(offset); + refreshUi(); + } + + @Nullable + public PsiFile getPsiFile() { + return PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); + } + + public boolean isCompletion() { + return myArranger instanceof CompletionLookupArranger; + } + + public PsiElement getPsiElement() { + PsiFile file = getPsiFile(); + if (file == null) return null; + + int offset = getLookupStart(); + if (offset > 0) return file.findElementAt(offset - 1); + + return file.findElementAt(0); + } + + public Editor getEditor() { + return myEditor; + } + + public boolean isPositionedAboveCaret(){ + return myPositionedAbove != null && myPositionedAbove.booleanValue(); + } + + public void hide(){ + hideLookup(true); + } + + public void hideLookup(boolean explicitly) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + if (myDisposed) return; + + doHide(true, explicitly); + } + + private void doHide(final boolean fireCanceled, final boolean explicitly) { + assert !myDisposed : disposeTrace; + myHidden = true; + + try { + super.hide(); + + Disposer.dispose(this); + } + catch (Throwable e) { + LOG.error(e); + } + + assert myDisposed; + + if (fireCanceled) { + fireLookupCanceled(explicitly); + } + } + + public void restorePrefix() { + if (myInitialPrefix != null) { + myEditor.getDocument().replaceString(getLookupStart(), myEditor.getCaretModel().getOffset(), myInitialPrefix); + } + } + + String disposeTrace = null; + + public void dispose() { + assert ApplicationManager.getApplication().isDispatchThread(); + assert myHidden; + assert !myDisposed : disposeTrace; + + Disposer.dispose(myProcessIcon); + + myDisposed = true; + disposeTrace = DebugUtil.currentStackTrace(); + } + + private int doSelectMostPreferableItem(List items) { + if (items.isEmpty()) { + return -1; + } + + if (items.size() == 1) { + return 0; + } + + for (int i = 0; i < items.size(); i++) { + LookupElement item = items.get(i); + if (isExactPrefixItem(item)) { + return i; + } + } + + final int index = myArranger.suggestPreselectedItem(items); + assert index >= 0 && index < items.size(); + return index; + } + + public void refreshUi() { + updateList(); + + if (isVisible() && !ApplicationManager.getApplication().isUnitTestMode()) { + if (myEditor.getComponent().getRootPane() == null) { + LOG.error("Null root pane"); + } + + Point point = calculatePosition(); + updateBounds(point.x,point.y); + + final Dimension size = myProcessIcon.getPreferredSize(); + myIconPanel.setBounds(getComponent().getRootPane().getLayeredPane().getWidth() - size.width, 0, size.width, size.height); + + HintManagerImpl.adjustEditorHintPosition(this, myEditor, point); + } + } + + public LookupArranger getArranger() { + return myArranger; + } + + public void markReused() { + myReused = true; + myModel.clearItems(); + setAdvertisementText(null); + myAdditionalPrefix = ""; + myPreselectedItem = null; + } + + boolean isDisposed() { + return myDisposed; + } + + @Override + public void showItemPopup(JBPopup hint) { + final Rectangle bounds = getCurrentItemBounds(); + hint.show(new RelativePoint(getComponent(), new Point(bounds.x + bounds.width, + bounds.y))); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupManagerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupManagerImpl.java index 5e81f4c7c87e..bd18aa1f081f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupManagerImpl.java @@ -1,224 +1,224 @@ -/* - * 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.codeInsight.lookup.impl; - -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.completion.CompletionProcess; -import com.intellij.codeInsight.completion.CompletionService; -import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; -import com.intellij.codeInsight.documentation.DocumentationManager; -import com.intellij.codeInsight.hint.EditorHintListener; -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.codeInsight.lookup.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.event.EditorFactoryAdapter; -import com.intellij.openapi.editor.event.EditorFactoryEvent; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.ui.LightweightHint; -import com.intellij.util.Alarm; -import com.intellij.util.messages.MessageBus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; - -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; - -public class LookupManagerImpl extends LookupManager { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupManagerImpl"); - private final Project myProject; - private LookupImpl myActiveLookup = null; - private Editor myActiveLookupEditor = null; - private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this); - - public LookupManagerImpl(Project project, MessageBus bus) { - myProject = project; - - bus.connect().subscribe(EditorHintListener.TOPIC, new EditorHintListener() { - public void hintShown(final Project project, final LightweightHint hint, final int flags) { - if (project == myProject) { - Lookup lookup = getActiveLookup(); - if (lookup != null && (flags & HintManager.HIDE_BY_LOOKUP_ITEM_CHANGE) != 0) { - lookup.addLookupListener(new LookupAdapter() { - public void currentItemChanged(LookupEvent event) { - hint.hide(); - } - - public void itemSelected(LookupEvent event) { - hint.hide(); - } - - public void lookupCanceled(LookupEvent event) { - hint.hide(); - } - }); - } - } - } - }); - - - final EditorFactoryAdapter myEditorFactoryListener = new EditorFactoryAdapter() { - public void editorReleased(EditorFactoryEvent event) { - if (event.getEditor() == myActiveLookupEditor) { - hideActiveLookup(); - } - } - }; - EditorFactory.getInstance().addEditorFactoryListener(myEditorFactoryListener, myProject); - } - - public Lookup showLookup(final Editor editor, - @NotNull LookupElement[] items, - final String prefix, - @NotNull final LookupArranger arranger) { - final LookupImpl lookup = createLookup(editor, items, prefix, arranger); - lookup.show(); - return lookup; - } - - public LookupImpl createLookup(final Editor editor, - @NotNull LookupElement[] items, - final String prefix, - @NotNull final LookupArranger arranger) { - hideActiveLookup(); - - final CodeInsightSettings settings = CodeInsightSettings.getInstance(); - - final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument()); - - final DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); - final boolean previousUpdate; - if (daemonCodeAnalyzer != null) { - previousUpdate = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).isUpdateByTimerEnabled(); - daemonCodeAnalyzer.setUpdateByTimerEnabled(false); - } - else { - previousUpdate = false; - } - final LookupImpl lookup = new LookupImpl(myProject, editor, arranger); - - final Alarm alarm = new Alarm(); - final Runnable request = new Runnable() { - public void run() { - if (myActiveLookup == lookup) { - final CompletionProcess completion = CompletionService.getCompletionService().getCurrentCompletion(); - if (completion == null || !completion.isAutopopupCompletion()) { - DocumentationManager.getInstance(myProject).showJavaDocInfo(editor, psiFile, false); - } - } - } - }; - if (settings.AUTO_POPUP_JAVADOC_INFO) { - alarm.addRequest(request, settings.JAVADOC_INFO_DELAY); - } - - myActiveLookup = lookup; - myActiveLookupEditor = editor; - myActiveLookup.addLookupListener(new LookupAdapter() { - public void itemSelected(LookupEvent event) { - lookupClosed(); - } - - public void lookupCanceled(LookupEvent event) { - lookupClosed(); - } - - public void currentItemChanged(LookupEvent event) { - alarm.cancelAllRequests(); - if (settings.AUTO_POPUP_JAVADOC_INFO) { - alarm.addRequest(request, settings.JAVADOC_INFO_DELAY); - } - } - - private void lookupClosed() { - alarm.cancelAllRequests(); - if (daemonCodeAnalyzer != null) { - daemonCodeAnalyzer.setUpdateByTimerEnabled(previousUpdate); - } - if (myActiveLookup == null) return; - LOG.assertTrue(myActiveLookup.isDisposed()); - myActiveLookup.removeLookupListener(this); - Lookup lookup = myActiveLookup; - myActiveLookup = null; - myActiveLookupEditor = null; - myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, lookup, null); - } - }); - - if (items.length > 0) { - for (final LookupElement item : items) { - if (prefix != null) { - item.setPrefixMatcher(new CamelHumpMatcher(prefix)); - } - myActiveLookup.addItem(item); - } - myActiveLookup.refreshUi(); - } else { - alarm.cancelAllRequests(); // no items -> no doc - } - - myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, null, myActiveLookup); - return myActiveLookup; - } - - public void hideActiveLookup() { - if (myActiveLookup != null) { - myActiveLookup.hide(); - } - } - - public Lookup getActiveLookup() { - return myActiveLookup; - } - - public void addPropertyChangeListener(PropertyChangeListener listener) { - myPropertyChangeSupport.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(PropertyChangeListener listener) { - myPropertyChangeSupport.removePropertyChangeListener(listener); - } - - - @TestOnly - public void forceSelection(char completion, int index){ - if(myActiveLookup == null) throw new RuntimeException("There are no items in this lookup"); - final LookupElement lookupItem = myActiveLookup.getItems().get(index); - myActiveLookup.setCurrentItem(lookupItem); - myActiveLookup.finishLookup(completion); - } - - @TestOnly - public void forceSelection(char completion, LookupElement item){ - myActiveLookup.setCurrentItem(item); - myActiveLookup.finishLookup(completion); - } - - @TestOnly - public void clearLookup() { - if (myActiveLookup != null) { - myActiveLookup.hide(); - myActiveLookup = null; - } - } -} +/* + * 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.codeInsight.lookup.impl; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.completion.CompletionProcess; +import com.intellij.codeInsight.completion.CompletionService; +import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.codeInsight.documentation.DocumentationManager; +import com.intellij.codeInsight.hint.EditorHintListener; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.lookup.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.event.EditorFactoryAdapter; +import com.intellij.openapi.editor.event.EditorFactoryEvent; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.ui.LightweightHint; +import com.intellij.util.Alarm; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +public class LookupManagerImpl extends LookupManager { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupManagerImpl"); + private final Project myProject; + private LookupImpl myActiveLookup = null; + private Editor myActiveLookupEditor = null; + private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this); + + public LookupManagerImpl(Project project, MessageBus bus) { + myProject = project; + + bus.connect().subscribe(EditorHintListener.TOPIC, new EditorHintListener() { + public void hintShown(final Project project, final LightweightHint hint, final int flags) { + if (project == myProject) { + Lookup lookup = getActiveLookup(); + if (lookup != null && (flags & HintManager.HIDE_BY_LOOKUP_ITEM_CHANGE) != 0) { + lookup.addLookupListener(new LookupAdapter() { + public void currentItemChanged(LookupEvent event) { + hint.hide(); + } + + public void itemSelected(LookupEvent event) { + hint.hide(); + } + + public void lookupCanceled(LookupEvent event) { + hint.hide(); + } + }); + } + } + } + }); + + + final EditorFactoryAdapter myEditorFactoryListener = new EditorFactoryAdapter() { + public void editorReleased(EditorFactoryEvent event) { + if (event.getEditor() == myActiveLookupEditor) { + hideActiveLookup(); + } + } + }; + EditorFactory.getInstance().addEditorFactoryListener(myEditorFactoryListener, myProject); + } + + public Lookup showLookup(final Editor editor, + @NotNull LookupElement[] items, + final String prefix, + @NotNull final LookupArranger arranger) { + final LookupImpl lookup = createLookup(editor, items, prefix, arranger); + lookup.show(); + return lookup; + } + + public LookupImpl createLookup(final Editor editor, + @NotNull LookupElement[] items, + final String prefix, + @NotNull final LookupArranger arranger) { + hideActiveLookup(); + + final CodeInsightSettings settings = CodeInsightSettings.getInstance(); + + final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument()); + + final DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); + final boolean previousUpdate; + if (daemonCodeAnalyzer != null) { + previousUpdate = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).isUpdateByTimerEnabled(); + daemonCodeAnalyzer.setUpdateByTimerEnabled(false); + } + else { + previousUpdate = false; + } + final LookupImpl lookup = new LookupImpl(myProject, editor, arranger); + + final Alarm alarm = new Alarm(); + final Runnable request = new Runnable() { + public void run() { + if (myActiveLookup == lookup) { + final CompletionProcess completion = CompletionService.getCompletionService().getCurrentCompletion(); + if (completion == null || !completion.isAutopopupCompletion()) { + DocumentationManager.getInstance(myProject).showJavaDocInfo(editor, psiFile, false); + } + } + } + }; + if (settings.AUTO_POPUP_JAVADOC_INFO) { + alarm.addRequest(request, settings.JAVADOC_INFO_DELAY); + } + + myActiveLookup = lookup; + myActiveLookupEditor = editor; + myActiveLookup.addLookupListener(new LookupAdapter() { + public void itemSelected(LookupEvent event) { + lookupClosed(); + } + + public void lookupCanceled(LookupEvent event) { + lookupClosed(); + } + + public void currentItemChanged(LookupEvent event) { + alarm.cancelAllRequests(); + if (settings.AUTO_POPUP_JAVADOC_INFO) { + alarm.addRequest(request, settings.JAVADOC_INFO_DELAY); + } + } + + private void lookupClosed() { + alarm.cancelAllRequests(); + if (daemonCodeAnalyzer != null) { + daemonCodeAnalyzer.setUpdateByTimerEnabled(previousUpdate); + } + if (myActiveLookup == null) return; + LOG.assertTrue(myActiveLookup.isDisposed()); + myActiveLookup.removeLookupListener(this); + Lookup lookup = myActiveLookup; + myActiveLookup = null; + myActiveLookupEditor = null; + myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, lookup, null); + } + }); + + if (items.length > 0) { + for (final LookupElement item : items) { + if (prefix != null) { + item.setPrefixMatcher(new CamelHumpMatcher(prefix)); + } + myActiveLookup.addItem(item); + } + myActiveLookup.refreshUi(); + } else { + alarm.cancelAllRequests(); // no items -> no doc + } + + myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, null, myActiveLookup); + return myActiveLookup; + } + + public void hideActiveLookup() { + if (myActiveLookup != null) { + myActiveLookup.hide(); + } + } + + public Lookup getActiveLookup() { + return myActiveLookup; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + myPropertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + myPropertyChangeSupport.removePropertyChangeListener(listener); + } + + + @TestOnly + public void forceSelection(char completion, int index){ + if(myActiveLookup == null) throw new RuntimeException("There are no items in this lookup"); + final LookupElement lookupItem = myActiveLookup.getItems().get(index); + myActiveLookup.setCurrentItem(lookupItem); + myActiveLookup.finishLookup(completion); + } + + @TestOnly + public void forceSelection(char completion, LookupElement item){ + myActiveLookup.setCurrentItem(item); + myActiveLookup.finishLookup(completion); + } + + @TestOnly + public void clearLookup() { + if (myActiveLookup != null) { + myActiveLookup.hide(); + myActiveLookup = null; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java index 22e4c7366b7f..ecaf3f02a204 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java @@ -1,52 +1,52 @@ -/* - * 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.codeInsight.lookup.impl.actions; - -import com.intellij.codeInsight.lookup.Lookup; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.codeInsight.template.impl.TemplateSettings; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorActionHandler; - -public class ChooseItemAction extends EditorAction { - public ChooseItemAction(){ - super(new Handler()); - } - - private static class Handler extends EditorActionHandler { - public void execute(final Editor editor, final DataContext dataContext) { - LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); - assert lookup != null; - lookup.finishLookup(Lookup.NORMAL_SELECT_CHAR); - } - } - - public void update(Editor editor, Presentation presentation, DataContext dataContext){ - LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); - if (lookup != null) { - lookup.refreshUi(); // to bring the list model up to date - presentation.setEnabled(lookup.isFocused() && - !ChooseItemReplaceAction.hasTemplatePrefix(lookup, TemplateSettings.ENTER_CHAR)); - } else { - presentation.setEnabled(false); - } - } -} +/* + * 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.codeInsight.lookup.impl.actions; + +import com.intellij.codeInsight.lookup.Lookup; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.codeInsight.template.impl.TemplateSettings; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; + +public class ChooseItemAction extends EditorAction { + public ChooseItemAction(){ + super(new Handler()); + } + + private static class Handler extends EditorActionHandler { + public void execute(final Editor editor, final DataContext dataContext) { + LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); + assert lookup != null; + lookup.finishLookup(Lookup.NORMAL_SELECT_CHAR); + } + } + + public void update(Editor editor, Presentation presentation, DataContext dataContext){ + LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); + if (lookup != null) { + lookup.refreshUi(); // to bring the list model up to date + presentation.setEnabled(lookup.isFocused() && + !ChooseItemReplaceAction.hasTemplatePrefix(lookup, TemplateSettings.ENTER_CHAR)); + } else { + presentation.setEnabled(false); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemReplaceAction.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemReplaceAction.java index f1825663ed38..08052e24bd8a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemReplaceAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemReplaceAction.java @@ -1,71 +1,71 @@ -/* - * 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.codeInsight.lookup.impl.actions; - -import com.intellij.codeInsight.completion.CodeCompletionFeatures; -import com.intellij.codeInsight.completion.CompletionUtil; -import com.intellij.codeInsight.lookup.Lookup; -import com.intellij.codeInsight.lookup.LookupManager; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor; -import com.intellij.codeInsight.template.impl.TemplateImpl; -import com.intellij.codeInsight.template.impl.TemplateSettings; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; - -public class ChooseItemReplaceAction extends EditorAction { - public ChooseItemReplaceAction(){ - super(new Handler()); - } - - private static class Handler extends EditorWriteActionHandler { - public void executeWriteAction(Editor editor, DataContext dataContext) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_REPLACE); - LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); - assert lookup != null; - lookup.finishLookup(Lookup.REPLACE_SELECT_CHAR); - } - } - - static boolean hasTemplatePrefix(LookupImpl lookup, char shortcutChar) { - final PsiFile file = lookup.getPsiFile(); - final Editor editor = lookup.getEditor(); - PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()); - - final int offset = editor.getCaretModel().getOffset(); - final String prefix = CompletionUtil.findJavaIdentifierPrefix(file, offset); - final TemplateImpl template = LiveTemplateCompletionContributor.findApplicableTemplate(file, offset, prefix); - return template != null && shortcutChar == TemplateSettings.getInstance().getShortcutChar(template); - } - - public void update(Editor editor, Presentation presentation, DataContext dataContext){ - LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); - if (lookup != null) { - lookup.refreshUi(); // to bring the list model up to date - presentation.setEnabled((lookup.isFocused() || lookup.isCompletion() && !lookup.getItems().isEmpty()) && - !hasTemplatePrefix(lookup, TemplateSettings.TAB_CHAR)); - } else { - presentation.setEnabled(false); - } - } -} +/* + * 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.codeInsight.lookup.impl.actions; + +import com.intellij.codeInsight.completion.CodeCompletionFeatures; +import com.intellij.codeInsight.completion.CompletionUtil; +import com.intellij.codeInsight.lookup.Lookup; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor; +import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.codeInsight.template.impl.TemplateSettings; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; + +public class ChooseItemReplaceAction extends EditorAction { + public ChooseItemReplaceAction(){ + super(new Handler()); + } + + private static class Handler extends EditorWriteActionHandler { + public void executeWriteAction(Editor editor, DataContext dataContext) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_REPLACE); + LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); + assert lookup != null; + lookup.finishLookup(Lookup.REPLACE_SELECT_CHAR); + } + } + + static boolean hasTemplatePrefix(LookupImpl lookup, char shortcutChar) { + final PsiFile file = lookup.getPsiFile(); + final Editor editor = lookup.getEditor(); + PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()); + + final int offset = editor.getCaretModel().getOffset(); + final String prefix = CompletionUtil.findJavaIdentifierPrefix(file, offset); + final TemplateImpl template = LiveTemplateCompletionContributor.findApplicableTemplate(file, offset, prefix); + return template != null && shortcutChar == TemplateSettings.getInstance().getShortcutChar(template); + } + + public void update(Editor editor, Presentation presentation, DataContext dataContext){ + LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor); + if (lookup != null) { + lookup.refreshUi(); // to bring the list model up to date + presentation.setEnabled((lookup.isFocused() || lookup.isCompletion() && !lookup.getItems().isEmpty()) && + !hasTemplatePrefix(lookup, TemplateSettings.TAB_CHAR)); + } else { + presentation.setEnabled(false); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java index ab549dd1774e..bb3b5915a753 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java @@ -1,88 +1,88 @@ -/* - * 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.codeInsight.template.impl; - -import com.intellij.codeInsight.completion.*; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.codeInsight.template.TemplateManager; -import com.intellij.openapi.util.Condition; -import com.intellij.patterns.PlatformPatterns; -import com.intellij.psi.PsiFile; -import com.intellij.util.ProcessingContext; -import com.intellij.util.containers.CollectionFactory; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author peter - */ -public class LiveTemplateCompletionContributor extends CompletionContributor { - public LiveTemplateCompletionContributor() { - extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new CompletionProvider() { - @Override - protected void addCompletions(@NotNull CompletionParameters parameters, - ProcessingContext context, - @NotNull CompletionResultSet result) { - if (parameters.getInvocationCount() > 0) return; //only in autopopups for now - - final PsiFile file = parameters.getOriginalFile(); - final int offset = parameters.getOffset(); - final String prefix = result.getPrefixMatcher().getPrefix(); - final TemplateImpl template = findApplicableTemplate(file, offset, prefix); - if (template != null) { - result.addElement(LookupElementBuilder.create(prefix).setTypeText(template.getDescription()).setInsertHandler(new InsertHandler() { - @Override - public void handleInsert(InsertionContext context, LookupElement item) { - context.getDocument().deleteString(context.getStartOffset(), context.getTailOffset()); - context.setAddCompletionChar(false); - TemplateManager.getInstance(context.getProject()).startTemplate(context.getEditor(), template); - } - })); - } else { - for (final TemplateImpl possible : listApplicableTemplates(file, offset)) { - result.restartCompletionOnPrefixChange(possible.getKey()); - } - } - - } - }); - } - - private static List listApplicableTemplates(PsiFile file, int offset) { - final ArrayList result = CollectionFactory.arrayList(); - for (final TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { - if (!template.isDeactivated() && !template.isSelectionTemplate() && TemplateManagerImpl.isApplicable(file, offset, template)) { - result.add(template); - } - } - return result; - } - - @Nullable - public static TemplateImpl findApplicableTemplate(PsiFile file, int offset, final String key) { - return ContainerUtil.find(listApplicableTemplates(file, offset), new Condition() { - @Override - public boolean value(TemplateImpl template) { - return key.equals(template.getKey()); - } - }); - } -} +/* + * 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.codeInsight.template.impl; + +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.openapi.util.Condition; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiFile; +import com.intellij.util.ProcessingContext; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author peter + */ +public class LiveTemplateCompletionContributor extends CompletionContributor { + public LiveTemplateCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + ProcessingContext context, + @NotNull CompletionResultSet result) { + if (parameters.getInvocationCount() > 0) return; //only in autopopups for now + + final PsiFile file = parameters.getOriginalFile(); + final int offset = parameters.getOffset(); + final String prefix = result.getPrefixMatcher().getPrefix(); + final TemplateImpl template = findApplicableTemplate(file, offset, prefix); + if (template != null) { + result.addElement(LookupElementBuilder.create(prefix).setTypeText(template.getDescription()).setInsertHandler(new InsertHandler() { + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + context.getDocument().deleteString(context.getStartOffset(), context.getTailOffset()); + context.setAddCompletionChar(false); + TemplateManager.getInstance(context.getProject()).startTemplate(context.getEditor(), template); + } + })); + } else { + for (final TemplateImpl possible : listApplicableTemplates(file, offset)) { + result.restartCompletionOnPrefixChange(possible.getKey()); + } + } + + } + }); + } + + private static List listApplicableTemplates(PsiFile file, int offset) { + final ArrayList result = CollectionFactory.arrayList(); + for (final TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { + if (!template.isDeactivated() && !template.isSelectionTemplate() && TemplateManagerImpl.isApplicable(file, offset, template)) { + result.add(template); + } + } + return result; + } + + @Nullable + public static TemplateImpl findApplicableTemplate(PsiFile file, int offset, final String key) { + return ContainerUtil.find(listApplicableTemplates(file, offset), new Condition() { + @Override + public boolean value(TemplateImpl template) { + return key.equals(template.getKey()); + } + }); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSegments.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSegments.java index 37c1a9821b5a..abb26c704eba 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSegments.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSegments.java @@ -1,132 +1,132 @@ -/* - * 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.codeInsight.template.impl; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.ex.RangeMarkerEx; - -import java.util.ArrayList; - -public class TemplateSegments { - private final ArrayList mySegments = new ArrayList(); - private final Editor myEditor; - - public TemplateSegments(Editor editor) { - myEditor = editor; - } - - public int getSegmentStart(int i) { - RangeMarker rangeMarker = mySegments.get(i); - return rangeMarker.getStartOffset(); - } - - public int getSegmentEnd(int i) { - RangeMarker rangeMarker = mySegments.get(i); - return rangeMarker.getEndOffset(); - } - - public boolean isValid(int i) { - return mySegments.get(i).isValid(); - } - - public void removeAll() { - mySegments.clear(); - } - - public void addSegment(int start, int end) { - RangeMarker rangeMarker = myEditor.getDocument().createRangeMarker(start, end); - mySegments.add(rangeMarker); - } - - public void setSegmentsGreedy(boolean greedy) { - for (final RangeMarker segment : mySegments) { - segment.setGreedyToRight(greedy); - segment.setGreedyToLeft(greedy); - } - } - - public boolean isInvalid() { - for (RangeMarker marker : mySegments) { - if (!marker.isValid()) { - return true; - } - } - return false; - } - - public void replaceSegmentAt(int index, int start, int end) { - RangeMarker rangeMarker = mySegments.get(index); - rangeMarker.dispose(); - Document doc = myEditor.getDocument(); - rangeMarker = doc.createRangeMarker(start, end); - rangeMarker.setGreedyToLeft(true); - rangeMarker.setGreedyToRight(true); - mySegments.set(index, rangeMarker); - } - - public void setNeighboursGreedy(final int segmentNumber, final boolean greedy) { - if (segmentNumber > 0) { - final RangeMarker left = mySegments.get(segmentNumber - 1); - left.setGreedyToLeft(greedy); - left.setGreedyToRight(greedy); - } - if (segmentNumber + 1 < mySegments.size()) { - final RangeMarker right = mySegments.get(segmentNumber + 1); - right.setGreedyToLeft(greedy); - right.setGreedyToRight(greedy); - } - } - - /** - * IDEADEV-13618 - * - * prevent two different segments to grow simultaneously if they both starts at the same offset. - */ - public void lockSegmentAtTheSameOffsetIfAny(final int number) { - if (number == -1) { - return; - } - - final RangeMarker current = mySegments.get(number); - int offset = current.getStartOffset(); - - for (int i = 0; i < mySegments.size(); i++) { - if (i != number) { - final RangeMarker segment = mySegments.get(i); - final int startOffset2 = segment.getStartOffset(); - if (offset == startOffset2) { - segment.setGreedyToLeft(false); - } - } - } - } - - public int getSegmentWithTheSameStart(int segmentNumber, int start) { - for (int i = segmentNumber + 1; i < mySegments.size(); i++) { - final RangeMarker segment = mySegments.get(i); - final int startOffset2 = segment.getStartOffset(); - if (start == startOffset2) { - return i; - } - } - - return -1; - } +/* + * 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.codeInsight.template.impl; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.ex.RangeMarkerEx; + +import java.util.ArrayList; + +public class TemplateSegments { + private final ArrayList mySegments = new ArrayList(); + private final Editor myEditor; + + public TemplateSegments(Editor editor) { + myEditor = editor; + } + + public int getSegmentStart(int i) { + RangeMarker rangeMarker = mySegments.get(i); + return rangeMarker.getStartOffset(); + } + + public int getSegmentEnd(int i) { + RangeMarker rangeMarker = mySegments.get(i); + return rangeMarker.getEndOffset(); + } + + public boolean isValid(int i) { + return mySegments.get(i).isValid(); + } + + public void removeAll() { + mySegments.clear(); + } + + public void addSegment(int start, int end) { + RangeMarker rangeMarker = myEditor.getDocument().createRangeMarker(start, end); + mySegments.add(rangeMarker); + } + + public void setSegmentsGreedy(boolean greedy) { + for (final RangeMarker segment : mySegments) { + segment.setGreedyToRight(greedy); + segment.setGreedyToLeft(greedy); + } + } + + public boolean isInvalid() { + for (RangeMarker marker : mySegments) { + if (!marker.isValid()) { + return true; + } + } + return false; + } + + public void replaceSegmentAt(int index, int start, int end) { + RangeMarker rangeMarker = mySegments.get(index); + rangeMarker.dispose(); + Document doc = myEditor.getDocument(); + rangeMarker = doc.createRangeMarker(start, end); + rangeMarker.setGreedyToLeft(true); + rangeMarker.setGreedyToRight(true); + mySegments.set(index, rangeMarker); + } + + public void setNeighboursGreedy(final int segmentNumber, final boolean greedy) { + if (segmentNumber > 0) { + final RangeMarker left = mySegments.get(segmentNumber - 1); + left.setGreedyToLeft(greedy); + left.setGreedyToRight(greedy); + } + if (segmentNumber + 1 < mySegments.size()) { + final RangeMarker right = mySegments.get(segmentNumber + 1); + right.setGreedyToLeft(greedy); + right.setGreedyToRight(greedy); + } + } + + /** + * IDEADEV-13618 + * + * prevent two different segments to grow simultaneously if they both starts at the same offset. + */ + public void lockSegmentAtTheSameOffsetIfAny(final int number) { + if (number == -1) { + return; + } + + final RangeMarker current = mySegments.get(number); + int offset = current.getStartOffset(); + + for (int i = 0; i < mySegments.size(); i++) { + if (i != number) { + final RangeMarker segment = mySegments.get(i); + final int startOffset2 = segment.getStartOffset(); + if (offset == startOffset2) { + segment.setGreedyToLeft(false); + } + } + } + } + + public int getSegmentWithTheSameStart(int segmentNumber, int start) { + for (int i = segmentNumber + 1; i < mySegments.size(); i++) { + final RangeMarker segment = mySegments.get(i); + final int startOffset2 = segment.getStartOffset(); + if (start == startOffset2) { + return i; + } + } + + return -1; + } } \ No newline at end of file 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 9d989a4cad31..6eb72386deba 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 @@ -1,702 +1,702 @@ -/* - * 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.codeInsight.template.impl; - -import com.intellij.codeInsight.CodeInsightBundle; -import com.intellij.codeInsight.template.Template; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.application.ex.DecodeDefaultsUtil; -import com.intellij.openapi.components.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.options.BaseSchemeProcessor; -import com.intellij.openapi.options.SchemeProcessor; -import com.intellij.openapi.options.SchemesManager; -import com.intellij.openapi.options.SchemesManagerFactory; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.JDOMUtil; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.util.containers.MultiMap; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - - -@State( - name="TemplateSettings", - storages= { - @Storage( - id="other", - file = "$APP_CONFIG$/other.xml" - )} -) -public class TemplateSettings implements PersistentStateComponent, ExportableComponent { - - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateSettings"); - - public @NonNls static final String USER_GROUP_NAME = "user"; - private @NonNls static final String TEMPLATE_SET = "templateSet"; - private @NonNls static final String GROUP = "group"; - private @NonNls static final String TEMPLATE = "template"; - - private @NonNls static final String DELETED_TEMPLATES = "deleted_templates"; - private final List myDeletedTemplates = new ArrayList(); - - public static final char SPACE_CHAR = ' '; - public static final char TAB_CHAR = '\t'; - public static final char ENTER_CHAR = '\n'; - public static final char DEFAULT_CHAR = 'D'; - - private static final @NonNls String SPACE = "SPACE"; - private static final @NonNls String TAB = "TAB"; - private static final @NonNls String ENTER = "ENTER"; - - private static final @NonNls String NAME = "name"; - private static final @NonNls String VALUE = "value"; - private static final @NonNls String DESCRIPTION = "description"; - private static final @NonNls String SHORTCUT = "shortcut"; - - private static final @NonNls String VARIABLE = "variable"; - private static final @NonNls String EXPRESSION = "expression"; - private static final @NonNls String DEFAULT_VALUE = "defaultValue"; - private static final @NonNls String ALWAYS_STOP_AT = "alwaysStopAt"; - - private static final @NonNls String CONTEXT = "context"; - private static final @NonNls String TO_REFORMAT = "toReformat"; - private static final @NonNls String TO_SHORTEN_FQ_NAMES = "toShortenFQNames"; - - private static final @NonNls String DEFAULT_SHORTCUT = "defaultShortcut"; - private static final @NonNls String DEACTIVATED = "deactivated"; - - @NonNls private static final String RESOURCE_BUNDLE = "resource-bundle"; - @NonNls private static final String KEY = "key"; - @NonNls private static final String ID = "id"; - - private static final @NonNls String TEMPLATES_CONFIG_FOLDER = "templates"; - - private final MultiMap myTemplates = new MultiMap() { - @Override - protected Map> createMap() { - return new LinkedHashMap>(); - } - }; - - private final Map myTemplatesById = new LinkedHashMap(); - private final Map myDefaultTemplates = new LinkedHashMap(); - - private int myMaxKeyLength = 0; - private char myDefaultShortcutChar = TAB_CHAR; - private String myLastSelectedTemplateKey; - private final SchemesManager mySchemesManager; - private static final String FILE_SPEC = "$ROOT_CONFIG$/templates"; - - private static class TemplateKey { - final String groupName; - final String key; - - private TemplateKey(String groupName, String key) { - this.groupName = groupName; - this.key = key; - } - - public static TemplateKey keyOf(TemplateImpl template) { - return new TemplateKey(template.getGroupName(), template.getKey()); - } - - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - TemplateKey that = (TemplateKey)o; - - if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false; - if (key != null ? !key.equals(that.key) : that.key != null) return false; - - return true; - } - - public int hashCode() { - int result = groupName != null ? groupName.hashCode() : 0; - result = 31 * result + (key != null ? key.hashCode() : 0); - return result; - } - } - - public TemplateSettings(SchemesManagerFactory schemesManagerFactory) { - - SchemeProcessor processor = new BaseSchemeProcessor() { - @Nullable - public TemplateGroup readScheme(final Document schemeContent) - throws InvalidDataException, IOException, JDOMException { - return readTemplateFile(schemeContent, schemeContent.getRootElement().getAttributeValue("group"), false, false, - getClass().getClassLoader()); - } - - - public boolean shouldBeSaved(final TemplateGroup template) { - for (TemplateImpl t : template.getElements()) { - if (differsFromDefault(t)) { - return true; - } - } - return false; - } - - public Document writeScheme(final TemplateGroup template) throws WriteExternalException { - Element templateSetElement = new Element(TEMPLATE_SET); - templateSetElement.setAttribute(GROUP, template.getName()); - - for (TemplateImpl t : template.getElements()) { - if (differsFromDefault(t)) { - saveTemplate(t, templateSetElement); - } - } - - return new Document(templateSetElement); - } - - public void initScheme(final TemplateGroup scheme) { - Collection templates = scheme.getElements(); - - for (TemplateImpl template : templates) { - addTemplateImpl(template); - } - } - - public void onSchemeAdded(final TemplateGroup scheme) { - for (TemplateImpl template : scheme.getElements()) { - addTemplateImpl(template); - } - } - - public void onSchemeDeleted(final TemplateGroup scheme) { - for (TemplateImpl template : scheme.getElements()) { - removeTemplate(template); - } - } - }; - - mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, processor, RoamingType.PER_USER); - - loadTemplates(); - } - - private boolean differsFromDefault(TemplateImpl t) { - TemplateImpl def = myDefaultTemplates.get(TemplateKey.keyOf(t)); - if (def == null) return true; - return !t.equals(def) || !t.contextsEqual(def); - } - - @NotNull - public File[] getExportFiles() { - return new File[]{getTemplateDirectory(true),PathManager.getDefaultOptionsFile()}; - } - - @NotNull - public String getPresentableName() { - return CodeInsightBundle.message("templates.export.display.name"); - } - - public static TemplateSettings getInstance() { - return ServiceManager.getService(TemplateSettings.class); - } - - public void loadState(Element parentNode) { - Element element = parentNode.getChild(DEFAULT_SHORTCUT); - if (element != null) { - String shortcut = element.getAttributeValue(SHORTCUT); - if (TAB.equals(shortcut)) { - myDefaultShortcutChar = TAB_CHAR; - } else if (ENTER.equals(shortcut)) { - myDefaultShortcutChar = ENTER_CHAR; - } else { - myDefaultShortcutChar = SPACE_CHAR; - } - } - - Element deleted = parentNode.getChild(DELETED_TEMPLATES); - if (deleted != null) { - List children = deleted.getChildren(); - for (final Object aChildren : children) { - Element child = (Element)aChildren; - myDeletedTemplates.add(new TemplateKey(child.getAttributeValue(GROUP), child.getAttributeValue(NAME))); - } - } - - for (TemplateKey templateKey : myDeletedTemplates) { - if (templateKey.groupName == null) { - final Collection templates = myTemplates.get(templateKey.key); - for (TemplateImpl template : templates) { - removeTemplate(template); - } - } - else { - final TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName); - if (toDelete != null) { - removeTemplate(toDelete); - } - } - } - - //TODO lesya reload schemes - } - - public Element getState() { - Element parentNode = new Element("TemplateSettings"); - Element element = new Element(DEFAULT_SHORTCUT); - if (myDefaultShortcutChar == TAB_CHAR) { - element.setAttribute(SHORTCUT, TAB); - } else if (myDefaultShortcutChar == ENTER_CHAR) { - element.setAttribute(SHORTCUT, ENTER); - } else { - element.setAttribute(SHORTCUT, SPACE); - } - parentNode.addContent(element); - - if (myDeletedTemplates.size() > 0) { - Element deleted = new Element(DELETED_TEMPLATES); - for (final TemplateKey deletedTemplate : myDeletedTemplates) { - if (deletedTemplate.key != null) { - Element template = new Element(TEMPLATE); - template.setAttribute(NAME, deletedTemplate.key); - if (deletedTemplate.groupName != null) { - template.setAttribute(GROUP, deletedTemplate.groupName); - } - deleted.addContent(template); - } - } - parentNode.addContent(deleted); - } - return parentNode; - } - - public String getLastSelectedTemplateKey() { - return myLastSelectedTemplateKey; - } - - public void setLastSelectedTemplateKey(String key) { - myLastSelectedTemplateKey = key; - } - - public TemplateImpl[] getTemplates() { - final Collection all = myTemplates.values(); - return all.toArray(new TemplateImpl[all.size()]); - } - - public char getDefaultShortcutChar() { - return myDefaultShortcutChar; - } - - public void setDefaultShortcutChar(char defaultShortcutChar) { - myDefaultShortcutChar = defaultShortcutChar; - } - - public Collection getTemplates(@NonNls String key) { - return myTemplates.get(key); - } - - @Nullable - public TemplateImpl getTemplate(@NonNls String key, String group) { - final Collection templates = myTemplates.get(key); - for (TemplateImpl template : templates) { - if (template.getGroupName().equals(group)) { - return template; - } - } - return null; - } - - public Template getTemplateById(@NonNls String id) { - return myTemplatesById.get(id); - } - - public int getMaxKeyLength() { - return myMaxKeyLength; - } - - public void addTemplate(Template template) { - clearPreviouslyRegistered(template); - addTemplateImpl(template); - - TemplateImpl templateImpl = (TemplateImpl)template; - String groupName = templateImpl.getGroupName(); - TemplateGroup group = mySchemesManager.findSchemeByName(groupName); - if (group == null) { - group = new TemplateGroup(groupName); - mySchemesManager.addNewScheme(group, true); - } - group.addElement(templateImpl); - } - - private void clearPreviouslyRegistered(final Template template) { - TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName()); - if (existing != null) { - LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered"); - TemplateGroup group = mySchemesManager.findSchemeByName(existing.getGroupName()); - if (group != null) { - group.removeElement(existing); - if (group.isEmpty()) { - mySchemesManager.removeScheme(group); - } - } - myTemplates.removeValue(template.getKey(), existing); - } - } - - private void addTemplateImpl(Template template) { - final TemplateImpl templateImpl = (TemplateImpl)template; - if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) { - myTemplates.putValue(template.getKey(), templateImpl); - } - - myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length()); - myDeletedTemplates.remove(TemplateKey.keyOf((TemplateImpl)template)); - - } - - private void addTemplateById(Template template) { - if (!myTemplatesById.containsKey(template.getId())) { - final String id = template.getId(); - if (id != null) { - myTemplatesById.put(id, template); - } - } - } - - public void removeTemplate(Template template) { - myTemplates.removeValue(template.getKey(), (TemplateImpl )template); - - TemplateImpl templateImpl = (TemplateImpl)template; - String groupName = templateImpl.getGroupName(); - TemplateGroup group = mySchemesManager.findSchemeByName(groupName); - - if (group != null) { - group.removeElement((TemplateImpl)template); - if (group.isEmpty()) { - mySchemesManager.removeScheme(group); - } - - } - } - - private TemplateImpl addTemplate(String key, String string, String group, String description, String shortcut, boolean isDefault, - final String id) { - TemplateImpl template = new TemplateImpl(key, string, group); - template.setId(id); - template.setDescription(description); - if (TAB.equals(shortcut)) { - template.setShortcutChar(TAB_CHAR); - } else if (ENTER.equals(shortcut)) { - template.setShortcutChar(ENTER_CHAR); - } else if (SPACE.equals(shortcut)) { - template.setShortcutChar(SPACE_CHAR); - } else { - template.setShortcutChar(DEFAULT_CHAR); - } - if (isDefault) { - myDefaultTemplates.put(TemplateKey.keyOf(template), template); - } - return template; - } - - @Nullable - private static File getTemplateDirectory(boolean toCreate) { - String directoryPath = PathManager.getConfigPath() + File.separator + TEMPLATES_CONFIG_FOLDER; - File directory = new File(directoryPath); - if (!directory.exists()) { - if (!toCreate) { - return null; - } - if (!directory.mkdir()) { - if (LOG.isDebugEnabled()) { - LOG.debug("cannot create directory: " + directory.getAbsolutePath()); - } - return null; - } - } - return directory; - } - - private void loadTemplates() { - Collection loaded = mySchemesManager.loadSchemes(); - for (TemplateGroup group : loaded) { - Collection templates = group.getElements(); - - for (TemplateImpl template : templates) { - addTemplateImpl(template); - } - } - - loadDefaultLiveTemplates(); - } - - private void loadDefaultLiveTemplates() { - try { - for(DefaultLiveTemplatesProvider provider: Extensions.getExtensions(DefaultLiveTemplatesProvider.EP_NAME)) { - for (String defTemplate : provider.getDefaultLiveTemplateFiles()) { - readDefTemplate(provider, defTemplate, true); - } - try { - String[] hidden = provider.getHiddenLiveTemplateFiles(); - if (hidden != null) { - for (String s : hidden) { - readDefTemplate(provider, s, false); - } - } - } - catch (AbstractMethodError ignore) { - } - } - } catch (Exception e) { - LOG.error(e); - } - } - - private void readDefTemplate(DefaultLiveTemplatesProvider provider, String defTemplate, boolean registerTemplate) - throws JDOMException, InvalidDataException, IOException { - String templateName = getDefaultTemplateName(defTemplate); - InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate); - if (inputStream != null) { - readDefTemplateFile(inputStream, templateName, provider.getClass().getClassLoader(), registerTemplate); - } - } - - public static String getDefaultTemplateName(String defTemplate) { - return defTemplate.substring(defTemplate.lastIndexOf("/") + 1); - } - - public void readDefTemplateFile(InputStream inputStream, String defGroupName, ClassLoader classLoader, boolean registerTemplate) throws JDOMException, InvalidDataException, IOException { - readTemplateFile(JDOMUtil.loadDocument(inputStream), defGroupName, true, registerTemplate, classLoader); - } - - @Nullable - public TemplateGroup readTemplateFile(Document document, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, ClassLoader classLoader) throws InvalidDataException { - if (document == null) { - throw new InvalidDataException(); - } - Element root = document.getRootElement(); - if (root == null || !TEMPLATE_SET.equals(root.getName())) { - throw new InvalidDataException(); - } - - String groupName = root.getAttributeValue(GROUP); - if (groupName == null || groupName.length() == 0) groupName = defGroupName; - - TemplateGroup result = new TemplateGroup(groupName); - - Map created = new LinkedHashMap(); - - for (final Object o1 : root.getChildren(TEMPLATE)) { - Element element = (Element)o1; - - TemplateImpl template = readTemplateFromElement(isDefault, groupName, element, classLoader); - boolean defaultTemplateModified = isDefault && (myDeletedTemplates.contains(TemplateKey.keyOf(template)) || - myTemplatesById.containsKey(template.getId()) || - getTemplate(template.getKey(), template.getGroupName()) != null); - - if(!defaultTemplateModified) { - created.put(template.getKey(), template); - } - } - - if (registerTemplate) { - TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName()); - if (existingScheme != null) { - result = existingScheme; - } - } - - for (TemplateImpl template : created.values()) { - if (registerTemplate) { - clearPreviouslyRegistered(template); - addTemplateImpl(template); - } - addTemplateById(template); - - result.addElement(template); - } - - if (registerTemplate) { - TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName()); - if (existingScheme == null && !result.isEmpty()) { - mySchemesManager.addNewScheme(result, false); - } - } - - return result.isEmpty() ? null : result; - - } - - /** Use {@link com.intellij.codeInsight.template.impl.DefaultLiveTemplatesProvider} instead */ - @Deprecated() - public void readHiddenTemplateFile(Document document) throws InvalidDataException { - readTemplateFile(document, null, false, false, getClass().getClassLoader()); - } - - private TemplateImpl readTemplateFromElement(final boolean isDefault, - final String groupName, - final Element element, - ClassLoader classLoader) throws InvalidDataException { - String name = element.getAttributeValue(NAME); - String value = element.getAttributeValue(VALUE); - String description; - String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE); - String key = element.getAttributeValue(KEY); - String id = element.getAttributeValue(ID); - if (resourceBundle != null && key != null) { - if (classLoader == null) { - classLoader = getClass().getClassLoader(); - } - ResourceBundle bundle = ResourceBundle.getBundle(resourceBundle, Locale.getDefault(), classLoader); - description = bundle.getString(key); - } - else { - description = element.getAttributeValue(DESCRIPTION); - } - String shortcut = element.getAttributeValue(SHORTCUT); - TemplateImpl template = addTemplate(name, value, groupName, description, shortcut, isDefault, id); - - template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT))); - template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES))); - template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED))); - - - for (final Object o : element.getChildren(VARIABLE)) { - Element e = (Element)o; - String variableName = e.getAttributeValue(NAME); - String expression = e.getAttributeValue(EXPRESSION); - String defaultValue = e.getAttributeValue(DEFAULT_VALUE); - boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT)); - template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt); - } - - Element context = element.getChild(CONTEXT); - if (context != null) { - template.getTemplateContext().readExternal(context); - } - - return template; - } - - private static void saveTemplate(TemplateImpl template, Element templateSetElement) { - Element element = new Element(TEMPLATE); - final String id = template.getId(); - if (id != null) { - element.setAttribute(ID, id); - } - element.setAttribute(NAME, template.getKey()); - element.setAttribute(VALUE, template.getString()); - if (template.getShortcutChar() == TAB_CHAR) { - element.setAttribute(SHORTCUT, TAB); - } else if (template.getShortcutChar() == ENTER_CHAR) { - element.setAttribute(SHORTCUT, ENTER); - } else if (template.getShortcutChar() == SPACE_CHAR) { - element.setAttribute(SHORTCUT, SPACE); - } - if (template.getDescription() != null) { - element.setAttribute(DESCRIPTION, template.getDescription()); - } - element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat())); - element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames())); - if (template.isDeactivated()) { - element.setAttribute(DEACTIVATED, Boolean.toString(true)); - } - - for (int i = 0; i < template.getVariableCount(); i++) { - Element variableElement = new Element(VARIABLE); - variableElement.setAttribute(NAME, template.getVariableNameAt(i)); - variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i)); - variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i)); - variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i))); - element.addContent(variableElement); - } - - try { - Element contextElement = new Element(CONTEXT); - template.getTemplateContext().writeExternal(contextElement); - element.addContent(contextElement); - } catch (WriteExternalException ignore) { - } - templateSetElement.addContent(element); - } - - public void setTemplates(List newGroups) { - myTemplates.clear(); - myDeletedTemplates.clear(); - for (TemplateImpl template : myDefaultTemplates.values()) { - myDeletedTemplates.add(TemplateKey.keyOf(template)); - } - mySchemesManager.clearAllSchemes(); - myMaxKeyLength = 0; - for (TemplateGroup group : newGroups) { - if (!group.isEmpty()) { - mySchemesManager.addNewScheme(group, true); - for (TemplateImpl template : group.getElements()) { - clearPreviouslyRegistered(template); - addTemplateImpl(template); - } - } - } - } - - public SchemesManager getSchemesManager() { - return mySchemesManager; - } - - public List getTemplateGroups() { - return mySchemesManager.getAllSchemes(); - } - - public List collectMatchingCandidates(String key, Character shortcutChar, boolean hasArgument) { - final Collection templates = getTemplates(key); - List candidates = new ArrayList(); - for (TemplateImpl template : templates) { - if (template.isDeactivated()) { - continue; - } - if (shortcutChar != null && getShortcutChar(template) != shortcutChar) { - continue; - } - if (template.isSelectionTemplate()) { - continue; - } - if (hasArgument && !template.hasArgument()) { - continue; - } - candidates.add(template); - } - return candidates; - } - - public char getShortcutChar(TemplateImpl template) { - char c = template.getShortcutChar(); - if (c == DEFAULT_CHAR) { - return getDefaultShortcutChar(); - } - else { - return c; - } - } -} +/* + * 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.codeInsight.template.impl; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.template.Template; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.ex.DecodeDefaultsUtil; +import com.intellij.openapi.components.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.options.BaseSchemeProcessor; +import com.intellij.openapi.options.SchemeProcessor; +import com.intellij.openapi.options.SchemesManager; +import com.intellij.openapi.options.SchemesManagerFactory; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.util.containers.MultiMap; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + + +@State( + name="TemplateSettings", + storages= { + @Storage( + id="other", + file = "$APP_CONFIG$/other.xml" + )} +) +public class TemplateSettings implements PersistentStateComponent, ExportableComponent { + + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateSettings"); + + public @NonNls static final String USER_GROUP_NAME = "user"; + private @NonNls static final String TEMPLATE_SET = "templateSet"; + private @NonNls static final String GROUP = "group"; + private @NonNls static final String TEMPLATE = "template"; + + private @NonNls static final String DELETED_TEMPLATES = "deleted_templates"; + private final List myDeletedTemplates = new ArrayList(); + + public static final char SPACE_CHAR = ' '; + public static final char TAB_CHAR = '\t'; + public static final char ENTER_CHAR = '\n'; + public static final char DEFAULT_CHAR = 'D'; + + private static final @NonNls String SPACE = "SPACE"; + private static final @NonNls String TAB = "TAB"; + private static final @NonNls String ENTER = "ENTER"; + + private static final @NonNls String NAME = "name"; + private static final @NonNls String VALUE = "value"; + private static final @NonNls String DESCRIPTION = "description"; + private static final @NonNls String SHORTCUT = "shortcut"; + + private static final @NonNls String VARIABLE = "variable"; + private static final @NonNls String EXPRESSION = "expression"; + private static final @NonNls String DEFAULT_VALUE = "defaultValue"; + private static final @NonNls String ALWAYS_STOP_AT = "alwaysStopAt"; + + private static final @NonNls String CONTEXT = "context"; + private static final @NonNls String TO_REFORMAT = "toReformat"; + private static final @NonNls String TO_SHORTEN_FQ_NAMES = "toShortenFQNames"; + + private static final @NonNls String DEFAULT_SHORTCUT = "defaultShortcut"; + private static final @NonNls String DEACTIVATED = "deactivated"; + + @NonNls private static final String RESOURCE_BUNDLE = "resource-bundle"; + @NonNls private static final String KEY = "key"; + @NonNls private static final String ID = "id"; + + private static final @NonNls String TEMPLATES_CONFIG_FOLDER = "templates"; + + private final MultiMap myTemplates = new MultiMap() { + @Override + protected Map> createMap() { + return new LinkedHashMap>(); + } + }; + + private final Map myTemplatesById = new LinkedHashMap(); + private final Map myDefaultTemplates = new LinkedHashMap(); + + private int myMaxKeyLength = 0; + private char myDefaultShortcutChar = TAB_CHAR; + private String myLastSelectedTemplateKey; + private final SchemesManager mySchemesManager; + private static final String FILE_SPEC = "$ROOT_CONFIG$/templates"; + + private static class TemplateKey { + final String groupName; + final String key; + + private TemplateKey(String groupName, String key) { + this.groupName = groupName; + this.key = key; + } + + public static TemplateKey keyOf(TemplateImpl template) { + return new TemplateKey(template.getGroupName(), template.getKey()); + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TemplateKey that = (TemplateKey)o; + + if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false; + if (key != null ? !key.equals(that.key) : that.key != null) return false; + + return true; + } + + public int hashCode() { + int result = groupName != null ? groupName.hashCode() : 0; + result = 31 * result + (key != null ? key.hashCode() : 0); + return result; + } + } + + public TemplateSettings(SchemesManagerFactory schemesManagerFactory) { + + SchemeProcessor processor = new BaseSchemeProcessor() { + @Nullable + public TemplateGroup readScheme(final Document schemeContent) + throws InvalidDataException, IOException, JDOMException { + return readTemplateFile(schemeContent, schemeContent.getRootElement().getAttributeValue("group"), false, false, + getClass().getClassLoader()); + } + + + public boolean shouldBeSaved(final TemplateGroup template) { + for (TemplateImpl t : template.getElements()) { + if (differsFromDefault(t)) { + return true; + } + } + return false; + } + + public Document writeScheme(final TemplateGroup template) throws WriteExternalException { + Element templateSetElement = new Element(TEMPLATE_SET); + templateSetElement.setAttribute(GROUP, template.getName()); + + for (TemplateImpl t : template.getElements()) { + if (differsFromDefault(t)) { + saveTemplate(t, templateSetElement); + } + } + + return new Document(templateSetElement); + } + + public void initScheme(final TemplateGroup scheme) { + Collection templates = scheme.getElements(); + + for (TemplateImpl template : templates) { + addTemplateImpl(template); + } + } + + public void onSchemeAdded(final TemplateGroup scheme) { + for (TemplateImpl template : scheme.getElements()) { + addTemplateImpl(template); + } + } + + public void onSchemeDeleted(final TemplateGroup scheme) { + for (TemplateImpl template : scheme.getElements()) { + removeTemplate(template); + } + } + }; + + mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, processor, RoamingType.PER_USER); + + loadTemplates(); + } + + private boolean differsFromDefault(TemplateImpl t) { + TemplateImpl def = myDefaultTemplates.get(TemplateKey.keyOf(t)); + if (def == null) return true; + return !t.equals(def) || !t.contextsEqual(def); + } + + @NotNull + public File[] getExportFiles() { + return new File[]{getTemplateDirectory(true),PathManager.getDefaultOptionsFile()}; + } + + @NotNull + public String getPresentableName() { + return CodeInsightBundle.message("templates.export.display.name"); + } + + public static TemplateSettings getInstance() { + return ServiceManager.getService(TemplateSettings.class); + } + + public void loadState(Element parentNode) { + Element element = parentNode.getChild(DEFAULT_SHORTCUT); + if (element != null) { + String shortcut = element.getAttributeValue(SHORTCUT); + if (TAB.equals(shortcut)) { + myDefaultShortcutChar = TAB_CHAR; + } else if (ENTER.equals(shortcut)) { + myDefaultShortcutChar = ENTER_CHAR; + } else { + myDefaultShortcutChar = SPACE_CHAR; + } + } + + Element deleted = parentNode.getChild(DELETED_TEMPLATES); + if (deleted != null) { + List children = deleted.getChildren(); + for (final Object aChildren : children) { + Element child = (Element)aChildren; + myDeletedTemplates.add(new TemplateKey(child.getAttributeValue(GROUP), child.getAttributeValue(NAME))); + } + } + + for (TemplateKey templateKey : myDeletedTemplates) { + if (templateKey.groupName == null) { + final Collection templates = myTemplates.get(templateKey.key); + for (TemplateImpl template : templates) { + removeTemplate(template); + } + } + else { + final TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName); + if (toDelete != null) { + removeTemplate(toDelete); + } + } + } + + //TODO lesya reload schemes + } + + public Element getState() { + Element parentNode = new Element("TemplateSettings"); + Element element = new Element(DEFAULT_SHORTCUT); + if (myDefaultShortcutChar == TAB_CHAR) { + element.setAttribute(SHORTCUT, TAB); + } else if (myDefaultShortcutChar == ENTER_CHAR) { + element.setAttribute(SHORTCUT, ENTER); + } else { + element.setAttribute(SHORTCUT, SPACE); + } + parentNode.addContent(element); + + if (myDeletedTemplates.size() > 0) { + Element deleted = new Element(DELETED_TEMPLATES); + for (final TemplateKey deletedTemplate : myDeletedTemplates) { + if (deletedTemplate.key != null) { + Element template = new Element(TEMPLATE); + template.setAttribute(NAME, deletedTemplate.key); + if (deletedTemplate.groupName != null) { + template.setAttribute(GROUP, deletedTemplate.groupName); + } + deleted.addContent(template); + } + } + parentNode.addContent(deleted); + } + return parentNode; + } + + public String getLastSelectedTemplateKey() { + return myLastSelectedTemplateKey; + } + + public void setLastSelectedTemplateKey(String key) { + myLastSelectedTemplateKey = key; + } + + public TemplateImpl[] getTemplates() { + final Collection all = myTemplates.values(); + return all.toArray(new TemplateImpl[all.size()]); + } + + public char getDefaultShortcutChar() { + return myDefaultShortcutChar; + } + + public void setDefaultShortcutChar(char defaultShortcutChar) { + myDefaultShortcutChar = defaultShortcutChar; + } + + public Collection getTemplates(@NonNls String key) { + return myTemplates.get(key); + } + + @Nullable + public TemplateImpl getTemplate(@NonNls String key, String group) { + final Collection templates = myTemplates.get(key); + for (TemplateImpl template : templates) { + if (template.getGroupName().equals(group)) { + return template; + } + } + return null; + } + + public Template getTemplateById(@NonNls String id) { + return myTemplatesById.get(id); + } + + public int getMaxKeyLength() { + return myMaxKeyLength; + } + + public void addTemplate(Template template) { + clearPreviouslyRegistered(template); + addTemplateImpl(template); + + TemplateImpl templateImpl = (TemplateImpl)template; + String groupName = templateImpl.getGroupName(); + TemplateGroup group = mySchemesManager.findSchemeByName(groupName); + if (group == null) { + group = new TemplateGroup(groupName); + mySchemesManager.addNewScheme(group, true); + } + group.addElement(templateImpl); + } + + private void clearPreviouslyRegistered(final Template template) { + TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName()); + if (existing != null) { + LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered"); + TemplateGroup group = mySchemesManager.findSchemeByName(existing.getGroupName()); + if (group != null) { + group.removeElement(existing); + if (group.isEmpty()) { + mySchemesManager.removeScheme(group); + } + } + myTemplates.removeValue(template.getKey(), existing); + } + } + + private void addTemplateImpl(Template template) { + final TemplateImpl templateImpl = (TemplateImpl)template; + if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) { + myTemplates.putValue(template.getKey(), templateImpl); + } + + myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length()); + myDeletedTemplates.remove(TemplateKey.keyOf((TemplateImpl)template)); + + } + + private void addTemplateById(Template template) { + if (!myTemplatesById.containsKey(template.getId())) { + final String id = template.getId(); + if (id != null) { + myTemplatesById.put(id, template); + } + } + } + + public void removeTemplate(Template template) { + myTemplates.removeValue(template.getKey(), (TemplateImpl )template); + + TemplateImpl templateImpl = (TemplateImpl)template; + String groupName = templateImpl.getGroupName(); + TemplateGroup group = mySchemesManager.findSchemeByName(groupName); + + if (group != null) { + group.removeElement((TemplateImpl)template); + if (group.isEmpty()) { + mySchemesManager.removeScheme(group); + } + + } + } + + private TemplateImpl addTemplate(String key, String string, String group, String description, String shortcut, boolean isDefault, + final String id) { + TemplateImpl template = new TemplateImpl(key, string, group); + template.setId(id); + template.setDescription(description); + if (TAB.equals(shortcut)) { + template.setShortcutChar(TAB_CHAR); + } else if (ENTER.equals(shortcut)) { + template.setShortcutChar(ENTER_CHAR); + } else if (SPACE.equals(shortcut)) { + template.setShortcutChar(SPACE_CHAR); + } else { + template.setShortcutChar(DEFAULT_CHAR); + } + if (isDefault) { + myDefaultTemplates.put(TemplateKey.keyOf(template), template); + } + return template; + } + + @Nullable + private static File getTemplateDirectory(boolean toCreate) { + String directoryPath = PathManager.getConfigPath() + File.separator + TEMPLATES_CONFIG_FOLDER; + File directory = new File(directoryPath); + if (!directory.exists()) { + if (!toCreate) { + return null; + } + if (!directory.mkdir()) { + if (LOG.isDebugEnabled()) { + LOG.debug("cannot create directory: " + directory.getAbsolutePath()); + } + return null; + } + } + return directory; + } + + private void loadTemplates() { + Collection loaded = mySchemesManager.loadSchemes(); + for (TemplateGroup group : loaded) { + Collection templates = group.getElements(); + + for (TemplateImpl template : templates) { + addTemplateImpl(template); + } + } + + loadDefaultLiveTemplates(); + } + + private void loadDefaultLiveTemplates() { + try { + for(DefaultLiveTemplatesProvider provider: Extensions.getExtensions(DefaultLiveTemplatesProvider.EP_NAME)) { + for (String defTemplate : provider.getDefaultLiveTemplateFiles()) { + readDefTemplate(provider, defTemplate, true); + } + try { + String[] hidden = provider.getHiddenLiveTemplateFiles(); + if (hidden != null) { + for (String s : hidden) { + readDefTemplate(provider, s, false); + } + } + } + catch (AbstractMethodError ignore) { + } + } + } catch (Exception e) { + LOG.error(e); + } + } + + private void readDefTemplate(DefaultLiveTemplatesProvider provider, String defTemplate, boolean registerTemplate) + throws JDOMException, InvalidDataException, IOException { + String templateName = getDefaultTemplateName(defTemplate); + InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate); + if (inputStream != null) { + readDefTemplateFile(inputStream, templateName, provider.getClass().getClassLoader(), registerTemplate); + } + } + + public static String getDefaultTemplateName(String defTemplate) { + return defTemplate.substring(defTemplate.lastIndexOf("/") + 1); + } + + public void readDefTemplateFile(InputStream inputStream, String defGroupName, ClassLoader classLoader, boolean registerTemplate) throws JDOMException, InvalidDataException, IOException { + readTemplateFile(JDOMUtil.loadDocument(inputStream), defGroupName, true, registerTemplate, classLoader); + } + + @Nullable + public TemplateGroup readTemplateFile(Document document, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, ClassLoader classLoader) throws InvalidDataException { + if (document == null) { + throw new InvalidDataException(); + } + Element root = document.getRootElement(); + if (root == null || !TEMPLATE_SET.equals(root.getName())) { + throw new InvalidDataException(); + } + + String groupName = root.getAttributeValue(GROUP); + if (groupName == null || groupName.length() == 0) groupName = defGroupName; + + TemplateGroup result = new TemplateGroup(groupName); + + Map created = new LinkedHashMap(); + + for (final Object o1 : root.getChildren(TEMPLATE)) { + Element element = (Element)o1; + + TemplateImpl template = readTemplateFromElement(isDefault, groupName, element, classLoader); + boolean defaultTemplateModified = isDefault && (myDeletedTemplates.contains(TemplateKey.keyOf(template)) || + myTemplatesById.containsKey(template.getId()) || + getTemplate(template.getKey(), template.getGroupName()) != null); + + if(!defaultTemplateModified) { + created.put(template.getKey(), template); + } + } + + if (registerTemplate) { + TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName()); + if (existingScheme != null) { + result = existingScheme; + } + } + + for (TemplateImpl template : created.values()) { + if (registerTemplate) { + clearPreviouslyRegistered(template); + addTemplateImpl(template); + } + addTemplateById(template); + + result.addElement(template); + } + + if (registerTemplate) { + TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName()); + if (existingScheme == null && !result.isEmpty()) { + mySchemesManager.addNewScheme(result, false); + } + } + + return result.isEmpty() ? null : result; + + } + + /** Use {@link com.intellij.codeInsight.template.impl.DefaultLiveTemplatesProvider} instead */ + @Deprecated() + public void readHiddenTemplateFile(Document document) throws InvalidDataException { + readTemplateFile(document, null, false, false, getClass().getClassLoader()); + } + + private TemplateImpl readTemplateFromElement(final boolean isDefault, + final String groupName, + final Element element, + ClassLoader classLoader) throws InvalidDataException { + String name = element.getAttributeValue(NAME); + String value = element.getAttributeValue(VALUE); + String description; + String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE); + String key = element.getAttributeValue(KEY); + String id = element.getAttributeValue(ID); + if (resourceBundle != null && key != null) { + if (classLoader == null) { + classLoader = getClass().getClassLoader(); + } + ResourceBundle bundle = ResourceBundle.getBundle(resourceBundle, Locale.getDefault(), classLoader); + description = bundle.getString(key); + } + else { + description = element.getAttributeValue(DESCRIPTION); + } + String shortcut = element.getAttributeValue(SHORTCUT); + TemplateImpl template = addTemplate(name, value, groupName, description, shortcut, isDefault, id); + + template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT))); + template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES))); + template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED))); + + + for (final Object o : element.getChildren(VARIABLE)) { + Element e = (Element)o; + String variableName = e.getAttributeValue(NAME); + String expression = e.getAttributeValue(EXPRESSION); + String defaultValue = e.getAttributeValue(DEFAULT_VALUE); + boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT)); + template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt); + } + + Element context = element.getChild(CONTEXT); + if (context != null) { + template.getTemplateContext().readExternal(context); + } + + return template; + } + + private static void saveTemplate(TemplateImpl template, Element templateSetElement) { + Element element = new Element(TEMPLATE); + final String id = template.getId(); + if (id != null) { + element.setAttribute(ID, id); + } + element.setAttribute(NAME, template.getKey()); + element.setAttribute(VALUE, template.getString()); + if (template.getShortcutChar() == TAB_CHAR) { + element.setAttribute(SHORTCUT, TAB); + } else if (template.getShortcutChar() == ENTER_CHAR) { + element.setAttribute(SHORTCUT, ENTER); + } else if (template.getShortcutChar() == SPACE_CHAR) { + element.setAttribute(SHORTCUT, SPACE); + } + if (template.getDescription() != null) { + element.setAttribute(DESCRIPTION, template.getDescription()); + } + element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat())); + element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames())); + if (template.isDeactivated()) { + element.setAttribute(DEACTIVATED, Boolean.toString(true)); + } + + for (int i = 0; i < template.getVariableCount(); i++) { + Element variableElement = new Element(VARIABLE); + variableElement.setAttribute(NAME, template.getVariableNameAt(i)); + variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i)); + variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i)); + variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i))); + element.addContent(variableElement); + } + + try { + Element contextElement = new Element(CONTEXT); + template.getTemplateContext().writeExternal(contextElement); + element.addContent(contextElement); + } catch (WriteExternalException ignore) { + } + templateSetElement.addContent(element); + } + + public void setTemplates(List newGroups) { + myTemplates.clear(); + myDeletedTemplates.clear(); + for (TemplateImpl template : myDefaultTemplates.values()) { + myDeletedTemplates.add(TemplateKey.keyOf(template)); + } + mySchemesManager.clearAllSchemes(); + myMaxKeyLength = 0; + for (TemplateGroup group : newGroups) { + if (!group.isEmpty()) { + mySchemesManager.addNewScheme(group, true); + for (TemplateImpl template : group.getElements()) { + clearPreviouslyRegistered(template); + addTemplateImpl(template); + } + } + } + } + + public SchemesManager getSchemesManager() { + return mySchemesManager; + } + + public List getTemplateGroups() { + return mySchemesManager.getAllSchemes(); + } + + public List collectMatchingCandidates(String key, Character shortcutChar, boolean hasArgument) { + final Collection templates = getTemplates(key); + List candidates = new ArrayList(); + for (TemplateImpl template : templates) { + if (template.isDeactivated()) { + continue; + } + if (shortcutChar != null && getShortcutChar(template) != shortcutChar) { + continue; + } + if (template.isSelectionTemplate()) { + continue; + } + if (hasArgument && !template.hasArgument()) { + continue; + } + candidates.add(template); + } + return candidates; + } + + public char getShortcutChar(TemplateImpl template) { + char c = template.getShortcutChar(); + if (c == DEFAULT_CHAR) { + return getDefaultShortcutChar(); + } + else { + return c; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/ProgramRunnerUtil.java b/platform/lang-impl/src/com/intellij/execution/ProgramRunnerUtil.java index abf317051335..61e535774ca6 100644 --- a/platform/lang-impl/src/com/intellij/execution/ProgramRunnerUtil.java +++ b/platform/lang-impl/src/com/intellij/execution/ProgramRunnerUtil.java @@ -1,118 +1,118 @@ -/* - * 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.execution; - -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.execution.impl.RunDialog; -import com.intellij.execution.impl.RunManagerImpl; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.ExecutionUtil; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.IconLoader; -import com.intellij.ui.LayeredIcon; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * @author spleaner - */ -public class ProgramRunnerUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.execution.ProgramRunnerUtil"); - private static final Icon INVALID_CONFIGURATION = IconLoader.getIcon("/runConfigurations/invalidConfigurationLayer.png"); - - private ProgramRunnerUtil() { - } - - @Nullable - public static ProgramRunner getRunner(final String executorId, final RunnerAndConfigurationSettings configuration) { - return RunnerRegistry.getInstance().getRunner(executorId, configuration.getConfiguration()); - } - - public static void executeConfiguration(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings configuration, - @NotNull final Executor executor, final boolean showSettings) { - ProgramRunner runner = getRunner(executor.getId(), configuration); - if (runner == null) { - LOG.error("Runner MUST not be null! Cannot find runner for " + executor.getId() + " and " + configuration.getConfiguration().getFactory().getName()); - return; - } - if (ExecutorRegistry.getInstance().isStarting(project, executor.getId(), runner.getRunnerId())){ - return; - } - - if (!RunManagerImpl.canRunConfiguration(configuration, executor) || (showSettings && RunManagerImpl.isEditBeforeRun(configuration))) { - final boolean result = RunDialog.editConfiguration(project, configuration, "Edit configuration", executor.getActionName(), executor.getIcon()); - if (!result) { - return; - } - - while (!RunManagerImpl.canRunConfiguration(configuration, executor)) { - if (0 == Messages.showYesNoDialog(project, "Configuration is still wrong. Do you want to edit it again?", "Change configuration settings", Messages.getErrorIcon())) { - final boolean result2 = RunDialog.editConfiguration(project, configuration, "Edit configuration", executor.getActionName(), executor.getIcon()); - if (!result2) { - return; - } - } else { - break; - } - } - } - - try { - runner.execute(executor, new ExecutionEnvironment(runner, configuration, project)); - } - catch (ExecutionException e) { - ExecutionUtil.handleExecutionError(project, executor.getToolWindowId(), configuration.getConfiguration(), e); - } - } - - public static void executeConfiguration(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings configuration, - @NotNull final Executor executor) { - executeConfiguration(project, configuration, executor, true); - } - - public static Icon getConfigurationIcon(final Project project, final RunnerAndConfigurationSettings settings, final boolean invalid) { - final RunManager runManager = RunManager.getInstance(project); - return getConfigurationIcon(settings, invalid, runManager.isTemporary(settings.getConfiguration())); - } - - public static Icon getConfigurationIcon(final RunnerAndConfigurationSettings settings, - final boolean invalid, - boolean isTemporary) { - RunConfiguration configuration = settings.getConfiguration(); - final Icon icon = settings.getFactory().getIcon(configuration); - LOG.assertTrue(icon != null, "Icon should not be null!"); - - final Icon configurationIcon = isTemporary ? IconLoader.getTransparentIcon(icon, 0.3f) : icon; - if (invalid) { - return LayeredIcon.create(configurationIcon, INVALID_CONFIGURATION); - } - - return configurationIcon; - } - - public static String shortenName(final String name, final int toBeAdded) { - if (name == null) return ""; - final int symbols = Math.max(10, 20 - toBeAdded); - if (name.length() < symbols) return name; - else return name.substring(0, symbols) + "..."; - } -} +/* + * 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.execution; + +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.impl.RunDialog; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ExecutionUtil; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.IconLoader; +import com.intellij.ui.LayeredIcon; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * @author spleaner + */ +public class ProgramRunnerUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.execution.ProgramRunnerUtil"); + private static final Icon INVALID_CONFIGURATION = IconLoader.getIcon("/runConfigurations/invalidConfigurationLayer.png"); + + private ProgramRunnerUtil() { + } + + @Nullable + public static ProgramRunner getRunner(final String executorId, final RunnerAndConfigurationSettings configuration) { + return RunnerRegistry.getInstance().getRunner(executorId, configuration.getConfiguration()); + } + + public static void executeConfiguration(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings configuration, + @NotNull final Executor executor, final boolean showSettings) { + ProgramRunner runner = getRunner(executor.getId(), configuration); + if (runner == null) { + LOG.error("Runner MUST not be null! Cannot find runner for " + executor.getId() + " and " + configuration.getConfiguration().getFactory().getName()); + return; + } + if (ExecutorRegistry.getInstance().isStarting(project, executor.getId(), runner.getRunnerId())){ + return; + } + + if (!RunManagerImpl.canRunConfiguration(configuration, executor) || (showSettings && RunManagerImpl.isEditBeforeRun(configuration))) { + final boolean result = RunDialog.editConfiguration(project, configuration, "Edit configuration", executor.getActionName(), executor.getIcon()); + if (!result) { + return; + } + + while (!RunManagerImpl.canRunConfiguration(configuration, executor)) { + if (0 == Messages.showYesNoDialog(project, "Configuration is still wrong. Do you want to edit it again?", "Change configuration settings", Messages.getErrorIcon())) { + final boolean result2 = RunDialog.editConfiguration(project, configuration, "Edit configuration", executor.getActionName(), executor.getIcon()); + if (!result2) { + return; + } + } else { + break; + } + } + } + + try { + runner.execute(executor, new ExecutionEnvironment(runner, configuration, project)); + } + catch (ExecutionException e) { + ExecutionUtil.handleExecutionError(project, executor.getToolWindowId(), configuration.getConfiguration(), e); + } + } + + public static void executeConfiguration(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings configuration, + @NotNull final Executor executor) { + executeConfiguration(project, configuration, executor, true); + } + + public static Icon getConfigurationIcon(final Project project, final RunnerAndConfigurationSettings settings, final boolean invalid) { + final RunManager runManager = RunManager.getInstance(project); + return getConfigurationIcon(settings, invalid, runManager.isTemporary(settings.getConfiguration())); + } + + public static Icon getConfigurationIcon(final RunnerAndConfigurationSettings settings, + final boolean invalid, + boolean isTemporary) { + RunConfiguration configuration = settings.getConfiguration(); + final Icon icon = settings.getFactory().getIcon(configuration); + LOG.assertTrue(icon != null, "Icon should not be null!"); + + final Icon configurationIcon = isTemporary ? IconLoader.getTransparentIcon(icon, 0.3f) : icon; + if (invalid) { + return LayeredIcon.create(configurationIcon, INVALID_CONFIGURATION); + } + + return configurationIcon; + } + + public static String shortenName(final String name, final int toBeAdded) { + if (name == null) return ""; + final int symbols = Math.max(10, 20 - toBeAdded); + if (name.length() < symbols) return name; + else return name.substring(0, symbols) + "..."; + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java b/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java index 5feffd68b143..c2a9d032ae6d 100644 --- a/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java +++ b/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java @@ -1,84 +1,84 @@ -/* - * 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.execution; - -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.Collection; -import java.util.Map; - -/** - * Manages {@link RunConfiguration}s. - * - * @see RunnerRegistry - * @see ExecutionManager - */ -public abstract class RunManagerEx extends RunManager { - public static RunManagerEx getInstanceEx(final Project project) { - return (RunManagerEx)project.getComponent(RunManager.class); - } - - @Nullable - public abstract RunnerAndConfigurationSettings getSelectedConfiguration(); - - public abstract boolean isTemporary(RunnerAndConfigurationSettings configuration); - - public abstract void setActiveConfiguration(RunnerAndConfigurationSettings configuration); - - public abstract void setSelectedConfiguration(RunnerAndConfigurationSettings configuration); - - public abstract void setTemporaryConfiguration(RunnerAndConfigurationSettings tempConfiguration); - - public abstract void setEditBeforeRun(RunConfiguration settings, boolean edit); - - public abstract RunManagerConfig getConfig(); - - @NotNull - public abstract RunnerAndConfigurationSettings createConfiguration(String name, ConfigurationFactory type); - - public abstract void addConfiguration(RunnerAndConfigurationSettings settings, boolean isShared, Map, BeforeRunTask> tasks); - - public abstract void addConfiguration(final RunnerAndConfigurationSettings settings, final boolean isShared); - - public abstract boolean isConfigurationShared(RunnerAndConfigurationSettings settings); - - @NotNull - public abstract Map, BeforeRunTask> getBeforeRunTasks(RunConfiguration settings); - - @Nullable - public abstract T getBeforeRunTask(RunConfiguration settings, Key taskProviderID); - - @NotNull - public abstract Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks); - - public abstract RunnerAndConfigurationSettings findConfigurationByName(@NotNull final String name); - - public abstract Icon getConfigurationIcon(@NotNull RunnerAndConfigurationSettings settings); - public abstract void invalidateConfigurationIcon(@NotNull RunnerAndConfigurationSettings settings); - - public abstract Collection getSortedConfigurations(); - - public abstract void removeConfiguration(RunnerAndConfigurationSettings settings); - - public abstract void addRunManagerListener(RunManagerListener listener); - public abstract void removeRunManagerListener(RunManagerListener listener); +/* + * 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.execution; + +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Collection; +import java.util.Map; + +/** + * Manages {@link RunConfiguration}s. + * + * @see RunnerRegistry + * @see ExecutionManager + */ +public abstract class RunManagerEx extends RunManager { + public static RunManagerEx getInstanceEx(final Project project) { + return (RunManagerEx)project.getComponent(RunManager.class); + } + + @Nullable + public abstract RunnerAndConfigurationSettings getSelectedConfiguration(); + + public abstract boolean isTemporary(RunnerAndConfigurationSettings configuration); + + public abstract void setActiveConfiguration(RunnerAndConfigurationSettings configuration); + + public abstract void setSelectedConfiguration(RunnerAndConfigurationSettings configuration); + + public abstract void setTemporaryConfiguration(RunnerAndConfigurationSettings tempConfiguration); + + public abstract void setEditBeforeRun(RunConfiguration settings, boolean edit); + + public abstract RunManagerConfig getConfig(); + + @NotNull + public abstract RunnerAndConfigurationSettings createConfiguration(String name, ConfigurationFactory type); + + public abstract void addConfiguration(RunnerAndConfigurationSettings settings, boolean isShared, Map, BeforeRunTask> tasks); + + public abstract void addConfiguration(final RunnerAndConfigurationSettings settings, final boolean isShared); + + public abstract boolean isConfigurationShared(RunnerAndConfigurationSettings settings); + + @NotNull + public abstract Map, BeforeRunTask> getBeforeRunTasks(RunConfiguration settings); + + @Nullable + public abstract T getBeforeRunTask(RunConfiguration settings, Key taskProviderID); + + @NotNull + public abstract Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks); + + public abstract RunnerAndConfigurationSettings findConfigurationByName(@NotNull final String name); + + public abstract Icon getConfigurationIcon(@NotNull RunnerAndConfigurationSettings settings); + public abstract void invalidateConfigurationIcon(@NotNull RunnerAndConfigurationSettings settings); + + public abstract Collection getSortedConfigurations(); + + public abstract void removeConfiguration(RunnerAndConfigurationSettings settings); + + public abstract void addRunManagerListener(RunManagerListener listener); + public abstract void removeRunManagerListener(RunManagerListener listener); } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationAction.java b/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationAction.java index 053b74d257d8..95485e768ff4 100644 --- a/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationAction.java +++ b/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationAction.java @@ -1,893 +1,893 @@ -/* - * 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.execution.actions; - -import com.intellij.execution.*; -import com.intellij.execution.configurations.ConfigurationType; -import com.intellij.execution.configurations.UnknownConfigurationType; -import com.intellij.execution.executors.DefaultRunExecutor; -import com.intellij.execution.impl.EditConfigurationsDialog; -import com.intellij.execution.impl.RunDialog; -import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl; -import com.intellij.execution.junit.RuntimeConfigurationProducer; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.ide.DataManager; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.ListPopupStep; -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.IconLoader; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.ui.popup.list.ListPopupImpl; -import com.intellij.ui.popup.list.PopupListElementRenderer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; -import java.awt.event.MouseEvent; -import java.util.*; -import java.util.List; - -/** - * @author spleaner - */ -public class ChooseRunConfigurationAction extends AnAction { - private static final Icon EDIT_ICON = IconLoader.getIcon("/actions/editSource.png"); - private static final Icon SAVE_ICON = IconLoader.getIcon("/runConfigurations/saveTempConfig.png"); - - private Executor myCurrentExecutor; - private boolean myEditConfiguration; - - @Override - public void actionPerformed(AnActionEvent e) { - final Project project = e.getData(PlatformDataKeys.PROJECT); - assert project != null; - - final Executor executor = getDefaultExecutor(); - assert executor != null; - - final RunListPopup popup = new RunListPopup(project, new ConfigurationListPopupStep(this, project, String.format("%s", executor.getActionName()))) { - @Override - protected void handleShiftClick(final boolean handleFinalChoices, final InputEvent inputEvent, final RunListPopup popup) { - try { - myCurrentExecutor = getAlternateExecutor(); - popup._handleSelect(handleFinalChoices, inputEvent); - } finally { - myCurrentExecutor = null; - } - } - }; - registerActions(popup); - - final String adText = getAdText(getAlternateExecutor()); - if (adText != null) { - popup.setAdText(adText); - } - - popup.showCenteredInCurrentWindow(project); - } - - protected String getAdKey() { - return "run.configuration.alternate.action.ad"; - } - - protected static boolean canRun(@NotNull final Executor executor, final RunnerAndConfigurationSettings settings) { - return ProgramRunnerUtil.getRunner(executor.getId(), settings) != null; - } - - @Nullable - protected String getAdText(final Executor alternateExecutor) { - final PropertiesComponent properties = PropertiesComponent.getInstance(); - if (alternateExecutor != null && !properties.isTrueValue(getAdKey())) { - return String - .format("Hold %s to %s", KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("SHIFT")), alternateExecutor.getActionName()); - } - - if (!properties.isTrueValue("run.configuration.edit.ad")) { - return String.format("Press %s to Edit", KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("F4"))); - } - - if (!properties.isTrueValue("run.configuration.delete.ad")) { - return String.format("Press %s to Delete configuration", KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("DELETE"))); - } - - return null; - } - - private void registerActions(final RunListPopup popup) { - popup.registerAction("alternateExecutor", KeyStroke.getKeyStroke("shift pressed SHIFT"), new AbstractAction() { - public void actionPerformed(ActionEvent e) { - myCurrentExecutor = getAlternateExecutor(); - updatePresentation(popup); - } - }); - - popup.registerAction("restoreDefaultExecutor", KeyStroke.getKeyStroke("released SHIFT"), new AbstractAction() { - public void actionPerformed(ActionEvent e) { - myCurrentExecutor = getDefaultExecutor(); - updatePresentation(popup); - } - }); - - - popup.registerAction("invokeAction", KeyStroke.getKeyStroke("shift ENTER"), new AbstractAction() { - public void actionPerformed(ActionEvent e) { - try { - popup.handleSelect(true); - } - finally { - myCurrentExecutor = null; - } - } - }); - - popup.registerAction("editConfiguration", KeyStroke.getKeyStroke("F4"), new AbstractAction() { - public void actionPerformed(ActionEvent e) { - myEditConfiguration = true; - try { - popup.handleSelect(true); - } - finally { - myEditConfiguration = false; - } - } - }); - - - popup.registerAction("deleteConfiguration", KeyStroke.getKeyStroke("DELETE"), new AbstractAction() { - public void actionPerformed(ActionEvent e) { - popup.removeSelected(); - } - }); - - final Action action0 = createNumberAction(0, popup, getDefaultExecutor()); - final Action action0_ = createNumberAction(0, popup, getAlternateExecutor()); - popup.registerAction("0Action", KeyStroke.getKeyStroke("0"), action0); - popup.registerAction("0Action_", KeyStroke.getKeyStroke("shift pressed 0"), action0_); - popup.registerAction("0Action1", KeyStroke.getKeyStroke("NUMPAD0"), action0); - popup.registerAction("0Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD0"), action0_); - - final Action action1 = createNumberAction(1, popup, getDefaultExecutor()); - final Action action1_ = createNumberAction(1, popup, getAlternateExecutor()); - popup.registerAction("1Action", KeyStroke.getKeyStroke("1"), action1); - popup.registerAction("1Action_", KeyStroke.getKeyStroke("shift pressed 1"), action1_); - popup.registerAction("1Action1", KeyStroke.getKeyStroke("NUMPAD1"), action1); - popup.registerAction("1Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD1"), action1_); - - final Action action2 = createNumberAction(2, popup, getDefaultExecutor()); - final Action action2_ = createNumberAction(2, popup, getAlternateExecutor()); - popup.registerAction("2Action", KeyStroke.getKeyStroke("2"), action2); - popup.registerAction("2Action_", KeyStroke.getKeyStroke("shift pressed 2"), action2_); - popup.registerAction("2Action1", KeyStroke.getKeyStroke("NUMPAD2"), action2); - popup.registerAction("2Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD2"), action2_); - - final Action action3 = createNumberAction(3, popup, getDefaultExecutor()); - final Action action3_ = createNumberAction(3, popup, getAlternateExecutor()); - popup.registerAction("3Action", KeyStroke.getKeyStroke("3"), action3); - popup.registerAction("3Action_", KeyStroke.getKeyStroke("shift pressed 3"), action3_); - popup.registerAction("3Action1", KeyStroke.getKeyStroke("NUMPAD3"), action3); - popup.registerAction("3Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD3"), action3_); - } - - private void updatePresentation(ListPopupImpl listPopup) { - final Executor executor = getCurrentExecutor(); - if (executor != null) { - listPopup.setCaption(executor.getActionName()); - } - } - - static void execute(final ItemWrapper itemWrapper, final Executor executor) { - if (executor == null) { - return; - } - - final DataContext dataContext = DataManager.getInstance().getDataContext(); - final Project project = PlatformDataKeys.PROJECT.getData(dataContext); - if (project != null) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - itemWrapper.perform(project, executor, dataContext); - } - }); - } - } - - void editConfiguration(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings configuration) { - final Executor executor = getCurrentExecutor(); - assert executor != null; - - PropertiesComponent.getInstance().setValue("run.configuration.edit.ad", Boolean.toString(true)); - if (RunDialog.editConfiguration(project, configuration, "Edit configuration settings", executor.getActionName(), executor.getIcon())) { - RunManagerEx.getInstanceEx(project).setSelectedConfiguration(configuration); - ProgramRunnerUtil.executeConfiguration(project, configuration, executor, false); - } - } - - private static void deleteConfiguration(final Project project, @NotNull final RunnerAndConfigurationSettings configurationSettings) { - final RunManagerEx manager = RunManagerEx.getInstanceEx(project); - manager.removeConfiguration(configurationSettings); - } - - @Nullable - protected Executor getDefaultExecutor() { - return DefaultRunExecutor.getRunExecutorInstance(); - } - - @Nullable - protected Executor getAlternateExecutor() { - return ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG); - } - - @Nullable - protected Executor getCurrentExecutor() { - return myCurrentExecutor == null ? getDefaultExecutor() : myCurrentExecutor; - } - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - final Project project = e.getData(PlatformDataKeys.PROJECT); - - presentation.setEnabled(true); - if (project == null || project.isDisposed()) { - presentation.setEnabled(false); - presentation.setVisible(false); - return; - } - - if (null == getDefaultExecutor()) { - presentation.setEnabled(false); - presentation.setVisible(false); - return; - } - - presentation.setEnabled(true); - presentation.setVisible(true); - } - - private static Action createNumberAction(final int number, final ListPopupImpl listPopup, final Executor executor) { - return new AbstractAction() { - public void actionPerformed(ActionEvent e) { - for (final Object item : listPopup.getListStep().getValues()) { - if (item instanceof ItemWrapper && ((ItemWrapper)item).getMnemonic() == number) { - listPopup.setFinalRunnable(new Runnable() { - public void run() { - execute((ItemWrapper)item, executor); - } - }); - listPopup.closeOk(null); - } - } - } - }; - } - - private abstract static class ItemWrapper { - private final T myValue; - private boolean myDynamic = false; - private int myMnemonic = -1; - - protected ItemWrapper(@Nullable final T value) { - myValue = value; - } - - public int getMnemonic() { - return myMnemonic; - } - - public void setMnemonic(int mnemonic) { - myMnemonic = mnemonic; - } - - public T getValue() { - return myValue; - } - - public boolean isDynamic() { - return myDynamic; - } - - public void setDynamic(final boolean b) { - myDynamic = b; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ItemWrapper)) return false; - - ItemWrapper that = (ItemWrapper)o; - - if (myValue != null ? !myValue.equals(that.myValue) : that.myValue != null) return false; - - return true; - } - - @Override - public int hashCode() { - return myValue != null ? myValue.hashCode() : 0; - } - - public abstract Icon getIcon(); - - public abstract String getText(); - - public abstract void perform(@NotNull final Project project, @NotNull final Executor executor, @NotNull final DataContext context); - - @Nullable - public ConfigurationType getType() { - return null; - } - - public boolean available(Executor executor) { - return false; - } - - public boolean hasActions() { - return false; - } - - public PopupStep getNextStep(Project project, ChooseRunConfigurationAction action) { - return PopupStep.FINAL_CHOICE; - } - - public static ItemWrapper wrap(@NotNull final Project project, - @NotNull final RunnerAndConfigurationSettings settings, - final boolean dynamic) { - final ItemWrapper result = wrap(project, settings); - result.setDynamic(dynamic); - return result; - } - - public static ItemWrapper wrap(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings settings) { - return new ItemWrapper(settings) { - @Override - public void perform(@NotNull Project project, @NotNull Executor executor, @NotNull DataContext context) { - RunManagerEx.getInstanceEx(project).setSelectedConfiguration(getValue()); - ProgramRunnerUtil.executeConfiguration(project, getValue(), executor); - } - - @Override - public ConfigurationType getType() { - return getValue().getType(); - } - - @Override - public Icon getIcon() { - return RunManagerEx.getInstanceEx(project).getConfigurationIcon(getValue()); - } - - @Override - public String getText() { - if (getMnemonic() != -1) { - return String.format("%s", getValue().getName()); - } - - return getValue().getName(); - } - - @Override - public boolean hasActions() { - return true; - } - - @Override - public boolean available(Executor executor) { - return null != ProgramRunnerUtil.getRunner(executor.getId(), getValue()); - } - - @Override - public PopupStep getNextStep(@NotNull final Project project, @NotNull final ChooseRunConfigurationAction action) { - return new ConfigurationActionsStep(project, action, getValue(), isDynamic()); - } - }; - } - - public boolean canBeDeleted() { - return !isDynamic() && getValue() != null; - } - } - - private static final class ConfigurationListPopupStep extends BaseListPopupStep { - private final Project myProject; - private final ChooseRunConfigurationAction myAction; - private int myDefaultConfiguration = -1; - - private ConfigurationListPopupStep(@NotNull final ChooseRunConfigurationAction action, - @NotNull final Project project, - @NotNull final String title) { - super(title, createSettingsList(project)); - myProject = project; - myAction = action; - - if (-1 == getDefaultOptionIndex()) { - myDefaultConfiguration = getDynamicIndex(); - } - } - - private int getDynamicIndex() { - int i = 0; - for (final ItemWrapper wrapper : getValues()) { - if (wrapper.isDynamic()) { - return i; - } - i++; - } - - return -1; - } - - @Override - public boolean isAutoSelectionEnabled() { - return false; - } - - private static ItemWrapper[] createSettingsList(@NotNull final Project project) { - final RunManagerEx manager = RunManagerEx.getInstanceEx(project); - - final List result = new ArrayList(); - - final RunnerAndConfigurationSettings selectedConfiguration = manager.getSelectedConfiguration(); - - final Map wrappedExisting = new LinkedHashMap(); - final ConfigurationType[] factories = manager.getConfigurationFactories(); - for (final ConfigurationType factory : factories) { - if (!(factory instanceof UnknownConfigurationType)) { - final RunnerAndConfigurationSettings[] configurations = manager.getConfigurationSettings(factory); - for (final RunnerAndConfigurationSettings configuration : configurations) { - final ItemWrapper wrapped = ItemWrapper.wrap(project, configuration); - if (configuration == selectedConfiguration) { - wrapped.setMnemonic(1); - } - - wrappedExisting.put(configuration, wrapped); - } - } - } - - populateWithDynamicRunners(result, wrappedExisting, project, manager, selectedConfiguration); - result.addAll(wrappedExisting.values()); - - //noinspection unchecked - final ItemWrapper edit = new ItemWrapper(null) { - @Override - public Icon getIcon() { - return EDIT_ICON; - } - - @Override - public String getText() { - return "Edit configurations..."; - } - - @Override - public void perform(@NotNull final Project project, @NotNull final Executor executor, @NotNull DataContext context) { - final EditConfigurationsDialog dialog = new EditConfigurationsDialog(project) { - @Override - protected void init() { - setOKButtonText(executor.getStartActionText()); - setOKButtonIcon(executor.getIcon()); - - super.init(); - } - }; - - dialog.show(); - if (dialog.isOK()) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - final RunnerAndConfigurationSettings configuration = RunManager.getInstance(project).getSelectedConfiguration(); - if (configuration instanceof RunnerAndConfigurationSettingsImpl) { - if (canRun(executor, configuration)) { - ProgramRunnerUtil.executeConfiguration(project, configuration, executor); - } - } - } - }); - } - } - - @Override - public boolean available(Executor executor) { - return true; - } - }; - - edit.setMnemonic(0); - result.add(0, edit); - - return result.toArray(new ItemWrapper[result.size()]); - } - - @NotNull - private static List populateWithDynamicRunners(final List result, - Map existing, - final Project project, final RunManagerEx manager, - final RunnerAndConfigurationSettings selectedConfiguration) { - - final ArrayList contextConfigurations = new ArrayList(); - final DataContext dataContext = DataManager.getInstance().getDataContext(); - final ConfigurationContext context = ConfigurationContext.getFromContext(dataContext); - - final List producers = PreferedProducerFind.findPreferredProducers(context.getLocation(), context, false); - if (producers == null) return Collections.emptyList(); - - Collections.sort(producers, new Comparator() { - public int compare(final RuntimeConfigurationProducer p1, final RuntimeConfigurationProducer p2) { - return p1.getConfigurationType().getDisplayName().compareTo(p2.getConfigurationType().getDisplayName()); - } - }); - - final RunnerAndConfigurationSettings[] preferred = {null}; - - int i = 2; // selectedConfiguration == null ? 1 : 2; - for (final RuntimeConfigurationProducer producer : producers) { - final RunnerAndConfigurationSettings configuration = producer.getConfiguration(); - if (configuration != null) { - if (existing.keySet().contains(configuration)) { - final ItemWrapper wrapper = existing.get(configuration); - if (wrapper.getMnemonic() != 1) { - wrapper.setMnemonic(i); - i++; - } - } else { - if (selectedConfiguration != null && configuration.equals(selectedConfiguration)) continue; - contextConfigurations.add(configuration); - - if (preferred[0] == null) { - preferred[0] = configuration; - } - - //noinspection unchecked - final ItemWrapper wrapper = new ItemWrapper(configuration) { - @Override - public Icon getIcon() { - return RunManagerEx.getInstanceEx(project).getConfigurationIcon(configuration); - } - - @Override - public String getText() { - return String.format("%s", configuration.getName()); - } - - @Override - public boolean available(Executor executor) { - return canRun(executor, configuration); - } - - @Override - public void perform(@NotNull Project project, @NotNull Executor executor, @NotNull DataContext context) { - manager.setTemporaryConfiguration(configuration); - RunManagerEx.getInstanceEx(project).setSelectedConfiguration(configuration); - ProgramRunnerUtil.executeConfiguration(project, configuration, executor); - } - - @Override - public PopupStep getNextStep(@NotNull final Project project, @NotNull final ChooseRunConfigurationAction action) { - return new ConfigurationActionsStep(project, action, configuration, isDynamic()); - } - - @Override - public boolean hasActions() { - return true; - } - }; - - wrapper.setDynamic(true); - wrapper.setMnemonic(i); - result.add(wrapper); - i++; - } - } - } - - return contextConfigurations; - } - - @Override - public ListSeparator getSeparatorAbove(ItemWrapper value) { - final List configurations = getValues(); - final int index = configurations.indexOf(value); - if (index > 0 && index <= configurations.size() - 1) { - final ItemWrapper aboveConfiguration = index == 0 ? null : configurations.get(index - 1); - - if (aboveConfiguration != null && aboveConfiguration.isDynamic() != value.isDynamic()) { - return new ListSeparator(); - } - - final ConfigurationType currentType = value.getType(); - final ConfigurationType aboveType = aboveConfiguration == null ? null : aboveConfiguration.getType(); - if (aboveType != currentType && currentType != null) { - return new ListSeparator(); // new ListSeparator(currentType.getDisplayName()); - } - } - - return null; - } - - @Override - public boolean isSpeedSearchEnabled() { - return true; - } - - @Override - public int getDefaultOptionIndex() { - final RunnerAndConfigurationSettings currentConfiguration = RunManager.getInstance(myProject).getSelectedConfiguration(); - if (currentConfiguration == null && myDefaultConfiguration != -1) { - return myDefaultConfiguration; - } - - return currentConfiguration instanceof RunnerAndConfigurationSettingsImpl ? getValues() - .indexOf(ItemWrapper.wrap(myProject, currentConfiguration)) : -1; - } - - @Override - public PopupStep onChosen(final ItemWrapper wrapper, boolean finalChoice) { - if (myAction.myEditConfiguration) { - final Object o = wrapper.getValue(); - if (o instanceof RunnerAndConfigurationSettingsImpl) { - return doFinalStep(new Runnable() { - public void run() { - myAction.editConfiguration(myProject, (RunnerAndConfigurationSettings)o); - } - }); - } - } - - final Executor executor = myAction.getCurrentExecutor(); - assert executor != null; - - if (finalChoice && wrapper.available(executor)) { - return doFinalStep(new Runnable() { - public void run() { - if (executor == myAction.getAlternateExecutor()) { - PropertiesComponent.getInstance().setValue(myAction.getAdKey(), Boolean.toString(true)); - } - - wrapper.perform(myProject, executor, DataManager.getInstance().getDataContext()); - } - }); - } - else { - return wrapper.getNextStep(myProject, myAction); - } - } - - @Override - public boolean hasSubstep(ItemWrapper selectedValue) { - return selectedValue.hasActions(); - } - - @NotNull - @Override - public String getTextFor(ItemWrapper value) { - return value.getText(); - } - - @Override - public Icon getIconFor(ItemWrapper value) { - return value.getIcon(); - } - } - - private static final class ConfigurationActionsStep extends BaseListPopupStep { - private ConfigurationActionsStep(@NotNull final Project project, - ChooseRunConfigurationAction action, - @NotNull final RunnerAndConfigurationSettings settings, final boolean dynamic) { - super("Actions", buildActions(project, action, settings, dynamic)); - } - - private static ActionWrapper[] buildActions(@NotNull final Project project, - final ChooseRunConfigurationAction action, - @NotNull final RunnerAndConfigurationSettings settings, - final boolean dynamic) { - final List result = new ArrayList(); - final Executor[] executors = ExecutorRegistry.getInstance().getRegisteredExecutors(); - for (final Executor executor : executors) { - final ProgramRunner runner = RunnerRegistry.getInstance().getRunner(executor.getId(), settings.getConfiguration()); - if (runner != null) { - result.add(new ActionWrapper(executor.getActionName(), executor.getIcon()) { - @Override - public void perform() { - final RunManagerEx manager = RunManagerEx.getInstanceEx(project); - if (dynamic) manager.setTemporaryConfiguration(settings); - manager.setSelectedConfiguration(settings); - ProgramRunnerUtil.executeConfiguration(project, settings, executor); - } - }); - } - } - - result.add(new ActionWrapper("Edit...", EDIT_ICON) { - @Override - public void perform() { - final RunManagerEx manager = RunManagerEx.getInstanceEx(project); - if (dynamic) manager.setTemporaryConfiguration(settings); - action.editConfiguration(project, settings); - } - }); - - if (RunManager.getInstance(project).isTemporary(settings.getConfiguration()) || dynamic) { - result.add(new ActionWrapper("Save temp configuration", SAVE_ICON) { - @Override - public void perform() { - final RunManagerEx manager = RunManagerEx.getInstanceEx(project); - if (dynamic) manager.setTemporaryConfiguration(settings); - manager.makeStable(settings.getConfiguration()); - } - }); - } - - return result.toArray(new ActionWrapper[result.size()]); - } - - @Override - public PopupStep onChosen(final ActionWrapper selectedValue, boolean finalChoice) { - return doFinalStep(new Runnable() { - public void run() { - selectedValue.perform(); - } - }); - } - - @Override - public Icon getIconFor(ActionWrapper aValue) { - return aValue.getIcon(); - } - - @NotNull - @Override - public String getTextFor(ActionWrapper value) { - return value.getName(); - } - } - - private abstract static class ActionWrapper { - private final String myName; - private final Icon myIcon; - - private ActionWrapper(String name, Icon icon) { - myName = name; - myIcon = icon; - } - - public abstract void perform(); - - public String getName() { - return myName; - } - - public Icon getIcon() { - return myIcon; - } - } - - private static class RunListElementRenderer extends PopupListElementRenderer { - private JLabel myLabel; - private final ListPopupImpl myPopup1; - - private RunListElementRenderer(ListPopupImpl popup) { - super(popup); - - myPopup1 = popup; - } - - @Override - protected JComponent createItemComponent() { - if (myLabel == null) { - myLabel = new JLabel(); - myLabel.setPreferredSize(new JLabel("8.").getPreferredSize()); - } - - final JComponent result = super.createItemComponent(); - result.add(myLabel, BorderLayout.WEST); - return result; - } - - @Override - protected void customizeComponent(JList list, Object value, boolean isSelected) { - super.customizeComponent(list, value, isSelected); - - ListPopupStep step = myPopup1.getListStep(); - boolean isSelectable = step.isSelectable(value); - myLabel.setEnabled(isSelectable); - - if (isSelected) { - setSelected(myLabel); - } else { - setDeselected(myLabel); - } - - if (value instanceof ItemWrapper) { - final int mnemonic = ((ItemWrapper)value).getMnemonic(); - if (mnemonic != -1) { - myLabel.setText(mnemonic + "."); - myLabel.setDisplayedMnemonicIndex(0); - } else { - myLabel.setText(""); - } - } - } - } - - private static abstract class RunListPopup extends ListPopupImpl { - private final Project myProject_; - - public RunListPopup(final Project project, ListPopupStep step) { - super(step); - myProject_ = project; - } - - @Override - public void handleSelect(boolean handleFinalChoices, InputEvent e) { - if (e instanceof MouseEvent && e.isShiftDown()) { - handleShiftClick(handleFinalChoices, e, this); - return; - } - - _handleSelect(handleFinalChoices, e); - } - - protected void _handleSelect(boolean handleFinalChoices, InputEvent e) { - super.handleSelect(handleFinalChoices, e); - } - - protected abstract void handleShiftClick(boolean handleFinalChoices, final InputEvent inputEvent, final RunListPopup popup); - - @Override - protected boolean isActionClick(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON2 || e.isPopupTrigger() || e.getID() != MouseEvent.MOUSE_PRESSED) return false; - return e.getButton() == MouseEvent.BUTTON1; - } - - @Override - protected ListCellRenderer getListElementRenderer() { - return new RunListElementRenderer(this); - } - - public void removeSelected() { - final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(); - if (!propertiesComponent.isTrueValue("run.configuration.delete.ad")) propertiesComponent.setValue("run.configuration.delete.ad", Boolean.toString(true)); - - final int index = getSelectedIndex(); - if (index == -1) { - return; - } - - final Object o = getListModel().get(index); - if (o != null && o instanceof ItemWrapper && ((ItemWrapper)o).canBeDeleted()) { - deleteConfiguration(myProject_, (RunnerAndConfigurationSettings) ((ItemWrapper)o).getValue()); - getListModel().deleteItem(o); - final List values = getListStep().getValues(); - values.remove(o); - - if (index < values.size()){ - onChildSelectedFor(values.get(index)); - } else if (index - 1 >= 0) { - onChildSelectedFor(values.get(index - 1)); - } - } - } - } -} +/* + * 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.execution.actions; + +import com.intellij.execution.*; +import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.UnknownConfigurationType; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.impl.EditConfigurationsDialog; +import com.intellij.execution.impl.RunDialog; +import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl; +import com.intellij.execution.junit.RuntimeConfigurationProducer; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.ide.DataManager; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.ListPopupStep; +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.IconLoader; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.ui.popup.list.PopupListElementRenderer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; + +/** + * @author spleaner + */ +public class ChooseRunConfigurationAction extends AnAction { + private static final Icon EDIT_ICON = IconLoader.getIcon("/actions/editSource.png"); + private static final Icon SAVE_ICON = IconLoader.getIcon("/runConfigurations/saveTempConfig.png"); + + private Executor myCurrentExecutor; + private boolean myEditConfiguration; + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + assert project != null; + + final Executor executor = getDefaultExecutor(); + assert executor != null; + + final RunListPopup popup = new RunListPopup(project, new ConfigurationListPopupStep(this, project, String.format("%s", executor.getActionName()))) { + @Override + protected void handleShiftClick(final boolean handleFinalChoices, final InputEvent inputEvent, final RunListPopup popup) { + try { + myCurrentExecutor = getAlternateExecutor(); + popup._handleSelect(handleFinalChoices, inputEvent); + } finally { + myCurrentExecutor = null; + } + } + }; + registerActions(popup); + + final String adText = getAdText(getAlternateExecutor()); + if (adText != null) { + popup.setAdText(adText); + } + + popup.showCenteredInCurrentWindow(project); + } + + protected String getAdKey() { + return "run.configuration.alternate.action.ad"; + } + + protected static boolean canRun(@NotNull final Executor executor, final RunnerAndConfigurationSettings settings) { + return ProgramRunnerUtil.getRunner(executor.getId(), settings) != null; + } + + @Nullable + protected String getAdText(final Executor alternateExecutor) { + final PropertiesComponent properties = PropertiesComponent.getInstance(); + if (alternateExecutor != null && !properties.isTrueValue(getAdKey())) { + return String + .format("Hold %s to %s", KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("SHIFT")), alternateExecutor.getActionName()); + } + + if (!properties.isTrueValue("run.configuration.edit.ad")) { + return String.format("Press %s to Edit", KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("F4"))); + } + + if (!properties.isTrueValue("run.configuration.delete.ad")) { + return String.format("Press %s to Delete configuration", KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("DELETE"))); + } + + return null; + } + + private void registerActions(final RunListPopup popup) { + popup.registerAction("alternateExecutor", KeyStroke.getKeyStroke("shift pressed SHIFT"), new AbstractAction() { + public void actionPerformed(ActionEvent e) { + myCurrentExecutor = getAlternateExecutor(); + updatePresentation(popup); + } + }); + + popup.registerAction("restoreDefaultExecutor", KeyStroke.getKeyStroke("released SHIFT"), new AbstractAction() { + public void actionPerformed(ActionEvent e) { + myCurrentExecutor = getDefaultExecutor(); + updatePresentation(popup); + } + }); + + + popup.registerAction("invokeAction", KeyStroke.getKeyStroke("shift ENTER"), new AbstractAction() { + public void actionPerformed(ActionEvent e) { + try { + popup.handleSelect(true); + } + finally { + myCurrentExecutor = null; + } + } + }); + + popup.registerAction("editConfiguration", KeyStroke.getKeyStroke("F4"), new AbstractAction() { + public void actionPerformed(ActionEvent e) { + myEditConfiguration = true; + try { + popup.handleSelect(true); + } + finally { + myEditConfiguration = false; + } + } + }); + + + popup.registerAction("deleteConfiguration", KeyStroke.getKeyStroke("DELETE"), new AbstractAction() { + public void actionPerformed(ActionEvent e) { + popup.removeSelected(); + } + }); + + final Action action0 = createNumberAction(0, popup, getDefaultExecutor()); + final Action action0_ = createNumberAction(0, popup, getAlternateExecutor()); + popup.registerAction("0Action", KeyStroke.getKeyStroke("0"), action0); + popup.registerAction("0Action_", KeyStroke.getKeyStroke("shift pressed 0"), action0_); + popup.registerAction("0Action1", KeyStroke.getKeyStroke("NUMPAD0"), action0); + popup.registerAction("0Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD0"), action0_); + + final Action action1 = createNumberAction(1, popup, getDefaultExecutor()); + final Action action1_ = createNumberAction(1, popup, getAlternateExecutor()); + popup.registerAction("1Action", KeyStroke.getKeyStroke("1"), action1); + popup.registerAction("1Action_", KeyStroke.getKeyStroke("shift pressed 1"), action1_); + popup.registerAction("1Action1", KeyStroke.getKeyStroke("NUMPAD1"), action1); + popup.registerAction("1Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD1"), action1_); + + final Action action2 = createNumberAction(2, popup, getDefaultExecutor()); + final Action action2_ = createNumberAction(2, popup, getAlternateExecutor()); + popup.registerAction("2Action", KeyStroke.getKeyStroke("2"), action2); + popup.registerAction("2Action_", KeyStroke.getKeyStroke("shift pressed 2"), action2_); + popup.registerAction("2Action1", KeyStroke.getKeyStroke("NUMPAD2"), action2); + popup.registerAction("2Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD2"), action2_); + + final Action action3 = createNumberAction(3, popup, getDefaultExecutor()); + final Action action3_ = createNumberAction(3, popup, getAlternateExecutor()); + popup.registerAction("3Action", KeyStroke.getKeyStroke("3"), action3); + popup.registerAction("3Action_", KeyStroke.getKeyStroke("shift pressed 3"), action3_); + popup.registerAction("3Action1", KeyStroke.getKeyStroke("NUMPAD3"), action3); + popup.registerAction("3Action_1", KeyStroke.getKeyStroke("shift pressed NUMPAD3"), action3_); + } + + private void updatePresentation(ListPopupImpl listPopup) { + final Executor executor = getCurrentExecutor(); + if (executor != null) { + listPopup.setCaption(executor.getActionName()); + } + } + + static void execute(final ItemWrapper itemWrapper, final Executor executor) { + if (executor == null) { + return; + } + + final DataContext dataContext = DataManager.getInstance().getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project != null) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + itemWrapper.perform(project, executor, dataContext); + } + }); + } + } + + void editConfiguration(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings configuration) { + final Executor executor = getCurrentExecutor(); + assert executor != null; + + PropertiesComponent.getInstance().setValue("run.configuration.edit.ad", Boolean.toString(true)); + if (RunDialog.editConfiguration(project, configuration, "Edit configuration settings", executor.getActionName(), executor.getIcon())) { + RunManagerEx.getInstanceEx(project).setSelectedConfiguration(configuration); + ProgramRunnerUtil.executeConfiguration(project, configuration, executor, false); + } + } + + private static void deleteConfiguration(final Project project, @NotNull final RunnerAndConfigurationSettings configurationSettings) { + final RunManagerEx manager = RunManagerEx.getInstanceEx(project); + manager.removeConfiguration(configurationSettings); + } + + @Nullable + protected Executor getDefaultExecutor() { + return DefaultRunExecutor.getRunExecutorInstance(); + } + + @Nullable + protected Executor getAlternateExecutor() { + return ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG); + } + + @Nullable + protected Executor getCurrentExecutor() { + return myCurrentExecutor == null ? getDefaultExecutor() : myCurrentExecutor; + } + + @Override + public void update(AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + final Project project = e.getData(PlatformDataKeys.PROJECT); + + presentation.setEnabled(true); + if (project == null || project.isDisposed()) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + + if (null == getDefaultExecutor()) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + + presentation.setEnabled(true); + presentation.setVisible(true); + } + + private static Action createNumberAction(final int number, final ListPopupImpl listPopup, final Executor executor) { + return new AbstractAction() { + public void actionPerformed(ActionEvent e) { + for (final Object item : listPopup.getListStep().getValues()) { + if (item instanceof ItemWrapper && ((ItemWrapper)item).getMnemonic() == number) { + listPopup.setFinalRunnable(new Runnable() { + public void run() { + execute((ItemWrapper)item, executor); + } + }); + listPopup.closeOk(null); + } + } + } + }; + } + + private abstract static class ItemWrapper { + private final T myValue; + private boolean myDynamic = false; + private int myMnemonic = -1; + + protected ItemWrapper(@Nullable final T value) { + myValue = value; + } + + public int getMnemonic() { + return myMnemonic; + } + + public void setMnemonic(int mnemonic) { + myMnemonic = mnemonic; + } + + public T getValue() { + return myValue; + } + + public boolean isDynamic() { + return myDynamic; + } + + public void setDynamic(final boolean b) { + myDynamic = b; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ItemWrapper)) return false; + + ItemWrapper that = (ItemWrapper)o; + + if (myValue != null ? !myValue.equals(that.myValue) : that.myValue != null) return false; + + return true; + } + + @Override + public int hashCode() { + return myValue != null ? myValue.hashCode() : 0; + } + + public abstract Icon getIcon(); + + public abstract String getText(); + + public abstract void perform(@NotNull final Project project, @NotNull final Executor executor, @NotNull final DataContext context); + + @Nullable + public ConfigurationType getType() { + return null; + } + + public boolean available(Executor executor) { + return false; + } + + public boolean hasActions() { + return false; + } + + public PopupStep getNextStep(Project project, ChooseRunConfigurationAction action) { + return PopupStep.FINAL_CHOICE; + } + + public static ItemWrapper wrap(@NotNull final Project project, + @NotNull final RunnerAndConfigurationSettings settings, + final boolean dynamic) { + final ItemWrapper result = wrap(project, settings); + result.setDynamic(dynamic); + return result; + } + + public static ItemWrapper wrap(@NotNull final Project project, @NotNull final RunnerAndConfigurationSettings settings) { + return new ItemWrapper(settings) { + @Override + public void perform(@NotNull Project project, @NotNull Executor executor, @NotNull DataContext context) { + RunManagerEx.getInstanceEx(project).setSelectedConfiguration(getValue()); + ProgramRunnerUtil.executeConfiguration(project, getValue(), executor); + } + + @Override + public ConfigurationType getType() { + return getValue().getType(); + } + + @Override + public Icon getIcon() { + return RunManagerEx.getInstanceEx(project).getConfigurationIcon(getValue()); + } + + @Override + public String getText() { + if (getMnemonic() != -1) { + return String.format("%s", getValue().getName()); + } + + return getValue().getName(); + } + + @Override + public boolean hasActions() { + return true; + } + + @Override + public boolean available(Executor executor) { + return null != ProgramRunnerUtil.getRunner(executor.getId(), getValue()); + } + + @Override + public PopupStep getNextStep(@NotNull final Project project, @NotNull final ChooseRunConfigurationAction action) { + return new ConfigurationActionsStep(project, action, getValue(), isDynamic()); + } + }; + } + + public boolean canBeDeleted() { + return !isDynamic() && getValue() != null; + } + } + + private static final class ConfigurationListPopupStep extends BaseListPopupStep { + private final Project myProject; + private final ChooseRunConfigurationAction myAction; + private int myDefaultConfiguration = -1; + + private ConfigurationListPopupStep(@NotNull final ChooseRunConfigurationAction action, + @NotNull final Project project, + @NotNull final String title) { + super(title, createSettingsList(project)); + myProject = project; + myAction = action; + + if (-1 == getDefaultOptionIndex()) { + myDefaultConfiguration = getDynamicIndex(); + } + } + + private int getDynamicIndex() { + int i = 0; + for (final ItemWrapper wrapper : getValues()) { + if (wrapper.isDynamic()) { + return i; + } + i++; + } + + return -1; + } + + @Override + public boolean isAutoSelectionEnabled() { + return false; + } + + private static ItemWrapper[] createSettingsList(@NotNull final Project project) { + final RunManagerEx manager = RunManagerEx.getInstanceEx(project); + + final List result = new ArrayList(); + + final RunnerAndConfigurationSettings selectedConfiguration = manager.getSelectedConfiguration(); + + final Map wrappedExisting = new LinkedHashMap(); + final ConfigurationType[] factories = manager.getConfigurationFactories(); + for (final ConfigurationType factory : factories) { + if (!(factory instanceof UnknownConfigurationType)) { + final RunnerAndConfigurationSettings[] configurations = manager.getConfigurationSettings(factory); + for (final RunnerAndConfigurationSettings configuration : configurations) { + final ItemWrapper wrapped = ItemWrapper.wrap(project, configuration); + if (configuration == selectedConfiguration) { + wrapped.setMnemonic(1); + } + + wrappedExisting.put(configuration, wrapped); + } + } + } + + populateWithDynamicRunners(result, wrappedExisting, project, manager, selectedConfiguration); + result.addAll(wrappedExisting.values()); + + //noinspection unchecked + final ItemWrapper edit = new ItemWrapper(null) { + @Override + public Icon getIcon() { + return EDIT_ICON; + } + + @Override + public String getText() { + return "Edit configurations..."; + } + + @Override + public void perform(@NotNull final Project project, @NotNull final Executor executor, @NotNull DataContext context) { + final EditConfigurationsDialog dialog = new EditConfigurationsDialog(project) { + @Override + protected void init() { + setOKButtonText(executor.getStartActionText()); + setOKButtonIcon(executor.getIcon()); + + super.init(); + } + }; + + dialog.show(); + if (dialog.isOK()) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final RunnerAndConfigurationSettings configuration = RunManager.getInstance(project).getSelectedConfiguration(); + if (configuration instanceof RunnerAndConfigurationSettingsImpl) { + if (canRun(executor, configuration)) { + ProgramRunnerUtil.executeConfiguration(project, configuration, executor); + } + } + } + }); + } + } + + @Override + public boolean available(Executor executor) { + return true; + } + }; + + edit.setMnemonic(0); + result.add(0, edit); + + return result.toArray(new ItemWrapper[result.size()]); + } + + @NotNull + private static List populateWithDynamicRunners(final List result, + Map existing, + final Project project, final RunManagerEx manager, + final RunnerAndConfigurationSettings selectedConfiguration) { + + final ArrayList contextConfigurations = new ArrayList(); + final DataContext dataContext = DataManager.getInstance().getDataContext(); + final ConfigurationContext context = ConfigurationContext.getFromContext(dataContext); + + final List producers = PreferedProducerFind.findPreferredProducers(context.getLocation(), context, false); + if (producers == null) return Collections.emptyList(); + + Collections.sort(producers, new Comparator() { + public int compare(final RuntimeConfigurationProducer p1, final RuntimeConfigurationProducer p2) { + return p1.getConfigurationType().getDisplayName().compareTo(p2.getConfigurationType().getDisplayName()); + } + }); + + final RunnerAndConfigurationSettings[] preferred = {null}; + + int i = 2; // selectedConfiguration == null ? 1 : 2; + for (final RuntimeConfigurationProducer producer : producers) { + final RunnerAndConfigurationSettings configuration = producer.getConfiguration(); + if (configuration != null) { + if (existing.keySet().contains(configuration)) { + final ItemWrapper wrapper = existing.get(configuration); + if (wrapper.getMnemonic() != 1) { + wrapper.setMnemonic(i); + i++; + } + } else { + if (selectedConfiguration != null && configuration.equals(selectedConfiguration)) continue; + contextConfigurations.add(configuration); + + if (preferred[0] == null) { + preferred[0] = configuration; + } + + //noinspection unchecked + final ItemWrapper wrapper = new ItemWrapper(configuration) { + @Override + public Icon getIcon() { + return RunManagerEx.getInstanceEx(project).getConfigurationIcon(configuration); + } + + @Override + public String getText() { + return String.format("%s", configuration.getName()); + } + + @Override + public boolean available(Executor executor) { + return canRun(executor, configuration); + } + + @Override + public void perform(@NotNull Project project, @NotNull Executor executor, @NotNull DataContext context) { + manager.setTemporaryConfiguration(configuration); + RunManagerEx.getInstanceEx(project).setSelectedConfiguration(configuration); + ProgramRunnerUtil.executeConfiguration(project, configuration, executor); + } + + @Override + public PopupStep getNextStep(@NotNull final Project project, @NotNull final ChooseRunConfigurationAction action) { + return new ConfigurationActionsStep(project, action, configuration, isDynamic()); + } + + @Override + public boolean hasActions() { + return true; + } + }; + + wrapper.setDynamic(true); + wrapper.setMnemonic(i); + result.add(wrapper); + i++; + } + } + } + + return contextConfigurations; + } + + @Override + public ListSeparator getSeparatorAbove(ItemWrapper value) { + final List configurations = getValues(); + final int index = configurations.indexOf(value); + if (index > 0 && index <= configurations.size() - 1) { + final ItemWrapper aboveConfiguration = index == 0 ? null : configurations.get(index - 1); + + if (aboveConfiguration != null && aboveConfiguration.isDynamic() != value.isDynamic()) { + return new ListSeparator(); + } + + final ConfigurationType currentType = value.getType(); + final ConfigurationType aboveType = aboveConfiguration == null ? null : aboveConfiguration.getType(); + if (aboveType != currentType && currentType != null) { + return new ListSeparator(); // new ListSeparator(currentType.getDisplayName()); + } + } + + return null; + } + + @Override + public boolean isSpeedSearchEnabled() { + return true; + } + + @Override + public int getDefaultOptionIndex() { + final RunnerAndConfigurationSettings currentConfiguration = RunManager.getInstance(myProject).getSelectedConfiguration(); + if (currentConfiguration == null && myDefaultConfiguration != -1) { + return myDefaultConfiguration; + } + + return currentConfiguration instanceof RunnerAndConfigurationSettingsImpl ? getValues() + .indexOf(ItemWrapper.wrap(myProject, currentConfiguration)) : -1; + } + + @Override + public PopupStep onChosen(final ItemWrapper wrapper, boolean finalChoice) { + if (myAction.myEditConfiguration) { + final Object o = wrapper.getValue(); + if (o instanceof RunnerAndConfigurationSettingsImpl) { + return doFinalStep(new Runnable() { + public void run() { + myAction.editConfiguration(myProject, (RunnerAndConfigurationSettings)o); + } + }); + } + } + + final Executor executor = myAction.getCurrentExecutor(); + assert executor != null; + + if (finalChoice && wrapper.available(executor)) { + return doFinalStep(new Runnable() { + public void run() { + if (executor == myAction.getAlternateExecutor()) { + PropertiesComponent.getInstance().setValue(myAction.getAdKey(), Boolean.toString(true)); + } + + wrapper.perform(myProject, executor, DataManager.getInstance().getDataContext()); + } + }); + } + else { + return wrapper.getNextStep(myProject, myAction); + } + } + + @Override + public boolean hasSubstep(ItemWrapper selectedValue) { + return selectedValue.hasActions(); + } + + @NotNull + @Override + public String getTextFor(ItemWrapper value) { + return value.getText(); + } + + @Override + public Icon getIconFor(ItemWrapper value) { + return value.getIcon(); + } + } + + private static final class ConfigurationActionsStep extends BaseListPopupStep { + private ConfigurationActionsStep(@NotNull final Project project, + ChooseRunConfigurationAction action, + @NotNull final RunnerAndConfigurationSettings settings, final boolean dynamic) { + super("Actions", buildActions(project, action, settings, dynamic)); + } + + private static ActionWrapper[] buildActions(@NotNull final Project project, + final ChooseRunConfigurationAction action, + @NotNull final RunnerAndConfigurationSettings settings, + final boolean dynamic) { + final List result = new ArrayList(); + final Executor[] executors = ExecutorRegistry.getInstance().getRegisteredExecutors(); + for (final Executor executor : executors) { + final ProgramRunner runner = RunnerRegistry.getInstance().getRunner(executor.getId(), settings.getConfiguration()); + if (runner != null) { + result.add(new ActionWrapper(executor.getActionName(), executor.getIcon()) { + @Override + public void perform() { + final RunManagerEx manager = RunManagerEx.getInstanceEx(project); + if (dynamic) manager.setTemporaryConfiguration(settings); + manager.setSelectedConfiguration(settings); + ProgramRunnerUtil.executeConfiguration(project, settings, executor); + } + }); + } + } + + result.add(new ActionWrapper("Edit...", EDIT_ICON) { + @Override + public void perform() { + final RunManagerEx manager = RunManagerEx.getInstanceEx(project); + if (dynamic) manager.setTemporaryConfiguration(settings); + action.editConfiguration(project, settings); + } + }); + + if (RunManager.getInstance(project).isTemporary(settings.getConfiguration()) || dynamic) { + result.add(new ActionWrapper("Save temp configuration", SAVE_ICON) { + @Override + public void perform() { + final RunManagerEx manager = RunManagerEx.getInstanceEx(project); + if (dynamic) manager.setTemporaryConfiguration(settings); + manager.makeStable(settings.getConfiguration()); + } + }); + } + + return result.toArray(new ActionWrapper[result.size()]); + } + + @Override + public PopupStep onChosen(final ActionWrapper selectedValue, boolean finalChoice) { + return doFinalStep(new Runnable() { + public void run() { + selectedValue.perform(); + } + }); + } + + @Override + public Icon getIconFor(ActionWrapper aValue) { + return aValue.getIcon(); + } + + @NotNull + @Override + public String getTextFor(ActionWrapper value) { + return value.getName(); + } + } + + private abstract static class ActionWrapper { + private final String myName; + private final Icon myIcon; + + private ActionWrapper(String name, Icon icon) { + myName = name; + myIcon = icon; + } + + public abstract void perform(); + + public String getName() { + return myName; + } + + public Icon getIcon() { + return myIcon; + } + } + + private static class RunListElementRenderer extends PopupListElementRenderer { + private JLabel myLabel; + private final ListPopupImpl myPopup1; + + private RunListElementRenderer(ListPopupImpl popup) { + super(popup); + + myPopup1 = popup; + } + + @Override + protected JComponent createItemComponent() { + if (myLabel == null) { + myLabel = new JLabel(); + myLabel.setPreferredSize(new JLabel("8.").getPreferredSize()); + } + + final JComponent result = super.createItemComponent(); + result.add(myLabel, BorderLayout.WEST); + return result; + } + + @Override + protected void customizeComponent(JList list, Object value, boolean isSelected) { + super.customizeComponent(list, value, isSelected); + + ListPopupStep step = myPopup1.getListStep(); + boolean isSelectable = step.isSelectable(value); + myLabel.setEnabled(isSelectable); + + if (isSelected) { + setSelected(myLabel); + } else { + setDeselected(myLabel); + } + + if (value instanceof ItemWrapper) { + final int mnemonic = ((ItemWrapper)value).getMnemonic(); + if (mnemonic != -1) { + myLabel.setText(mnemonic + "."); + myLabel.setDisplayedMnemonicIndex(0); + } else { + myLabel.setText(""); + } + } + } + } + + private static abstract class RunListPopup extends ListPopupImpl { + private final Project myProject_; + + public RunListPopup(final Project project, ListPopupStep step) { + super(step); + myProject_ = project; + } + + @Override + public void handleSelect(boolean handleFinalChoices, InputEvent e) { + if (e instanceof MouseEvent && e.isShiftDown()) { + handleShiftClick(handleFinalChoices, e, this); + return; + } + + _handleSelect(handleFinalChoices, e); + } + + protected void _handleSelect(boolean handleFinalChoices, InputEvent e) { + super.handleSelect(handleFinalChoices, e); + } + + protected abstract void handleShiftClick(boolean handleFinalChoices, final InputEvent inputEvent, final RunListPopup popup); + + @Override + protected boolean isActionClick(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON2 || e.isPopupTrigger() || e.getID() != MouseEvent.MOUSE_PRESSED) return false; + return e.getButton() == MouseEvent.BUTTON1; + } + + @Override + protected ListCellRenderer getListElementRenderer() { + return new RunListElementRenderer(this); + } + + public void removeSelected() { + final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(); + if (!propertiesComponent.isTrueValue("run.configuration.delete.ad")) propertiesComponent.setValue("run.configuration.delete.ad", Boolean.toString(true)); + + final int index = getSelectedIndex(); + if (index == -1) { + return; + } + + final Object o = getListModel().get(index); + if (o != null && o instanceof ItemWrapper && ((ItemWrapper)o).canBeDeleted()) { + deleteConfiguration(myProject_, (RunnerAndConfigurationSettings) ((ItemWrapper)o).getValue()); + getListModel().deleteItem(o); + final List values = getListStep().getValues(); + values.remove(o); + + if (index < values.size()){ + onChildSelectedFor(values.get(index)); + } else if (index - 1 >= 0) { + onChildSelectedFor(values.get(index - 1)); + } + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java index 7c94d748e322..efcbd9ef4ea0 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java @@ -1,238 +1,238 @@ -/* - * 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.execution.impl; - -import com.intellij.execution.BeforeRunTask; -import com.intellij.execution.BeforeRunTaskProvider; -import com.intellij.execution.ExecutionBundle; -import com.intellij.execution.RunnerAndConfigurationSettings; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.execution.configurations.UnknownRunConfiguration; -import com.intellij.ide.DataManager; -import com.intellij.ide.impl.TypeSafeDataProviderAdapter; -import com.intellij.openapi.actionSystem.DataKey; -import com.intellij.openapi.actionSystem.DataSink; -import com.intellij.openapi.actionSystem.TypeSafeDataProvider; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.SettingsEditor; -import com.intellij.openapi.ui.FixedSizeButton; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Key; -import gnu.trove.THashMap; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * User: anna - * Date: 27-Mar-2006 - */ -public class ConfigurationSettingsEditorWrapper extends SettingsEditor { - public static DataKey CONFIGURATION_EDITOR_KEY = DataKey.create("ConfigurationSettingsEditor"); - private JPanel myComponentPlace; - private JCheckBox myCbStoreProjectConfiguration; - private JPanel myWholePanel; - - private JPanel myStepsPanel; - private JCheckBox myShowSettingsBeforeRunCheckBox; - private Map, BeforeRunTask> myStepsBeforeLaunch; - private final Map, StepBeforeLaunchRow> myStepBeforeLaunchRows = new THashMap, StepBeforeLaunchRow>(); - - private boolean myStoreProjectConfiguration; - private boolean myEditBeforeRun; - - private final ConfigurationSettingsEditor myEditor; - - public ConfigurationSettingsEditorWrapper(final RunnerAndConfigurationSettings settings) { - myEditor = new ConfigurationSettingsEditor(settings); - Disposer.register(this, myEditor); - doReset(settings); - } - - private void doReset(RunnerAndConfigurationSettings settings) { - final RunConfiguration runConfiguration = settings.getConfiguration(); - final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(runConfiguration.getProject()); - - myStepsBeforeLaunch = runManager.getBeforeRunTasks(runConfiguration); - myStepBeforeLaunchRows.clear(); - - final BeforeRunTaskProvider[] providers = Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, - runConfiguration.getProject()); - myStepsPanel.removeAll(); - if (runConfiguration instanceof UnknownRunConfiguration) { - myStepsPanel.setVisible(false); - } - else { - List stepsRows = new ArrayList(); - for (BeforeRunTaskProvider provider : providers) { - final BeforeRunTask task = myStepsBeforeLaunch.get(provider.getId()); - if (task != null) { - final StepBeforeLaunchRow stepRow = new StepBeforeLaunchRow(runConfiguration, provider, task); - myStepBeforeLaunchRows.put(provider.getId(), stepRow); - stepsRows.add(stepRow); - } - } - - myStepsPanel.setLayout(new GridLayout(stepsRows.size(), 1)); - for (StepBeforeLaunchRow stepRow : stepsRows) { - myStepsPanel.add(stepRow); - } - } - - myEditBeforeRun = settings.isEditBeforeRun(); - myShowSettingsBeforeRunCheckBox = new JCheckBox(ExecutionBundle.message("configuration.edit.before.run")); - myShowSettingsBeforeRunCheckBox.setEnabled(!(runConfiguration instanceof UnknownRunConfiguration)); - myShowSettingsBeforeRunCheckBox.setSelected(myEditBeforeRun); - myShowSettingsBeforeRunCheckBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - myEditBeforeRun = myShowSettingsBeforeRunCheckBox.isSelected(); - } - }); - myShowSettingsBeforeRunCheckBox.setVisible(!settings.isTemplate()); - myStepsPanel.add(myShowSettingsBeforeRunCheckBox); - - myStoreProjectConfiguration = runManager.isConfigurationShared(settings); - myCbStoreProjectConfiguration.setEnabled(!(runConfiguration instanceof UnknownRunConfiguration)); - myCbStoreProjectConfiguration.setSelected(myStoreProjectConfiguration); - myCbStoreProjectConfiguration.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - myStoreProjectConfiguration = myCbStoreProjectConfiguration.isSelected(); - } - }); - - myCbStoreProjectConfiguration.setVisible(!settings.isTemplate()); - } - - @NotNull - protected JComponent createEditor() { - myComponentPlace.setLayout(new GridBagLayout()); - myComponentPlace.add(myEditor.getComponent(), new GridBagConstraints(0,0,1,1,1.0,1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0)); - myComponentPlace.doLayout(); - myWholePanel.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, new TypeSafeDataProviderAdapter(new MyDataProvider())); - return myWholePanel; - } - - public void updateBeforeRunTaskPanel(@NotNull Key key) { - myStepBeforeLaunchRows.get(key).update(myStepsBeforeLaunch.get(key)); - } - - protected void disposeEditor() { - } - - public void resetEditorFrom(final RunnerAndConfigurationSettings settings) { - myEditor.resetEditorFrom(settings); - doReset(settings); - } - - public void applyEditorTo(final RunnerAndConfigurationSettings settings) throws ConfigurationException { - myEditor.applyEditorTo(settings); - doApply(settings); - } - - public RunnerAndConfigurationSettings getSnapshot() throws ConfigurationException { - RunnerAndConfigurationSettings result = myEditor.getSnapshot(); - doApply(result); - return result; - } - - private void doApply(final RunnerAndConfigurationSettings settings) { - final RunConfiguration runConfiguration = settings.getConfiguration(); - final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(runConfiguration.getProject()); - runManager.setBeforeRunTasks(runConfiguration, myStepsBeforeLaunch); - runManager.shareConfiguration(runConfiguration, myStoreProjectConfiguration); - runManager.setEditBeforeRun(runConfiguration, myEditBeforeRun); - } - - public Map, BeforeRunTask> getStepsBeforeLaunch() { - return Collections.unmodifiableMap(myStepsBeforeLaunch); - } - - public boolean isStoreProjectConfiguration() { - return myStoreProjectConfiguration; - } - - private class StepBeforeLaunchRow extends JPanel { - private final JCheckBox myCheckBox; - private FixedSizeButton myButton; - private final RunConfiguration myRunConfiguration; - private final BeforeRunTaskProvider myProvider; - - public StepBeforeLaunchRow(final RunConfiguration runConfiguration, final BeforeRunTaskProvider provider, - final BeforeRunTask beforeRunTask) { - super(new GridBagLayout()); - myRunConfiguration = runConfiguration; - myProvider = provider; - final boolean isChecked = beforeRunTask.isEnabled(); - myCheckBox = new JCheckBox(provider.getDescription(runConfiguration, beforeRunTask), isChecked); - GridBagConstraints gc = new GridBagConstraints(GridBagConstraints.RELATIVE, 0 , 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,0,0), 0, 0); - add(myCheckBox, gc); - gc.weightx = 1; - if (provider.hasConfigurationButton()) { - myButton = new FixedSizeButton(20); - add(myButton, gc); - - myButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (provider.configureTask(runConfiguration, beforeRunTask)) { - myCheckBox.setText(provider.getDescription(runConfiguration, beforeRunTask)); - fireEditorStateChanged(); - } - } - }); - } - else { - add(Box.createHorizontalBox(), gc); - } - enableSettings(beforeRunTask); - myCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - beforeRunTask.setEnabled(myCheckBox.isSelected()); - enableSettings(beforeRunTask); - } - }); - } - - private void enableSettings(final BeforeRunTask task) { - if (myButton != null) { - myButton.setEnabled(myCheckBox.isSelected()); - } - myCheckBox.setText(myProvider.getDescription(myRunConfiguration, task)); - } - - public void update(BeforeRunTask task) { - myCheckBox.setSelected(task.isEnabled()); - enableSettings(task); - } - } - - private class MyDataProvider implements TypeSafeDataProvider { - public void calcData(DataKey key, DataSink sink) { - if (key.equals(CONFIGURATION_EDITOR_KEY)) { - sink.put(CONFIGURATION_EDITOR_KEY, ConfigurationSettingsEditorWrapper.this); - } - } - } -} +/* + * 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.execution.impl; + +import com.intellij.execution.BeforeRunTask; +import com.intellij.execution.BeforeRunTaskProvider; +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.UnknownRunConfiguration; +import com.intellij.ide.DataManager; +import com.intellij.ide.impl.TypeSafeDataProviderAdapter; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.actionSystem.DataSink; +import com.intellij.openapi.actionSystem.TypeSafeDataProvider; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.ui.FixedSizeButton; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * User: anna + * Date: 27-Mar-2006 + */ +public class ConfigurationSettingsEditorWrapper extends SettingsEditor { + public static DataKey CONFIGURATION_EDITOR_KEY = DataKey.create("ConfigurationSettingsEditor"); + private JPanel myComponentPlace; + private JCheckBox myCbStoreProjectConfiguration; + private JPanel myWholePanel; + + private JPanel myStepsPanel; + private JCheckBox myShowSettingsBeforeRunCheckBox; + private Map, BeforeRunTask> myStepsBeforeLaunch; + private final Map, StepBeforeLaunchRow> myStepBeforeLaunchRows = new THashMap, StepBeforeLaunchRow>(); + + private boolean myStoreProjectConfiguration; + private boolean myEditBeforeRun; + + private final ConfigurationSettingsEditor myEditor; + + public ConfigurationSettingsEditorWrapper(final RunnerAndConfigurationSettings settings) { + myEditor = new ConfigurationSettingsEditor(settings); + Disposer.register(this, myEditor); + doReset(settings); + } + + private void doReset(RunnerAndConfigurationSettings settings) { + final RunConfiguration runConfiguration = settings.getConfiguration(); + final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(runConfiguration.getProject()); + + myStepsBeforeLaunch = runManager.getBeforeRunTasks(runConfiguration); + myStepBeforeLaunchRows.clear(); + + final BeforeRunTaskProvider[] providers = Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, + runConfiguration.getProject()); + myStepsPanel.removeAll(); + if (runConfiguration instanceof UnknownRunConfiguration) { + myStepsPanel.setVisible(false); + } + else { + List stepsRows = new ArrayList(); + for (BeforeRunTaskProvider provider : providers) { + final BeforeRunTask task = myStepsBeforeLaunch.get(provider.getId()); + if (task != null) { + final StepBeforeLaunchRow stepRow = new StepBeforeLaunchRow(runConfiguration, provider, task); + myStepBeforeLaunchRows.put(provider.getId(), stepRow); + stepsRows.add(stepRow); + } + } + + myStepsPanel.setLayout(new GridLayout(stepsRows.size(), 1)); + for (StepBeforeLaunchRow stepRow : stepsRows) { + myStepsPanel.add(stepRow); + } + } + + myEditBeforeRun = settings.isEditBeforeRun(); + myShowSettingsBeforeRunCheckBox = new JCheckBox(ExecutionBundle.message("configuration.edit.before.run")); + myShowSettingsBeforeRunCheckBox.setEnabled(!(runConfiguration instanceof UnknownRunConfiguration)); + myShowSettingsBeforeRunCheckBox.setSelected(myEditBeforeRun); + myShowSettingsBeforeRunCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myEditBeforeRun = myShowSettingsBeforeRunCheckBox.isSelected(); + } + }); + myShowSettingsBeforeRunCheckBox.setVisible(!settings.isTemplate()); + myStepsPanel.add(myShowSettingsBeforeRunCheckBox); + + myStoreProjectConfiguration = runManager.isConfigurationShared(settings); + myCbStoreProjectConfiguration.setEnabled(!(runConfiguration instanceof UnknownRunConfiguration)); + myCbStoreProjectConfiguration.setSelected(myStoreProjectConfiguration); + myCbStoreProjectConfiguration.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + myStoreProjectConfiguration = myCbStoreProjectConfiguration.isSelected(); + } + }); + + myCbStoreProjectConfiguration.setVisible(!settings.isTemplate()); + } + + @NotNull + protected JComponent createEditor() { + myComponentPlace.setLayout(new GridBagLayout()); + myComponentPlace.add(myEditor.getComponent(), new GridBagConstraints(0,0,1,1,1.0,1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0)); + myComponentPlace.doLayout(); + myWholePanel.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, new TypeSafeDataProviderAdapter(new MyDataProvider())); + return myWholePanel; + } + + public void updateBeforeRunTaskPanel(@NotNull Key key) { + myStepBeforeLaunchRows.get(key).update(myStepsBeforeLaunch.get(key)); + } + + protected void disposeEditor() { + } + + public void resetEditorFrom(final RunnerAndConfigurationSettings settings) { + myEditor.resetEditorFrom(settings); + doReset(settings); + } + + public void applyEditorTo(final RunnerAndConfigurationSettings settings) throws ConfigurationException { + myEditor.applyEditorTo(settings); + doApply(settings); + } + + public RunnerAndConfigurationSettings getSnapshot() throws ConfigurationException { + RunnerAndConfigurationSettings result = myEditor.getSnapshot(); + doApply(result); + return result; + } + + private void doApply(final RunnerAndConfigurationSettings settings) { + final RunConfiguration runConfiguration = settings.getConfiguration(); + final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(runConfiguration.getProject()); + runManager.setBeforeRunTasks(runConfiguration, myStepsBeforeLaunch); + runManager.shareConfiguration(runConfiguration, myStoreProjectConfiguration); + runManager.setEditBeforeRun(runConfiguration, myEditBeforeRun); + } + + public Map, BeforeRunTask> getStepsBeforeLaunch() { + return Collections.unmodifiableMap(myStepsBeforeLaunch); + } + + public boolean isStoreProjectConfiguration() { + return myStoreProjectConfiguration; + } + + private class StepBeforeLaunchRow extends JPanel { + private final JCheckBox myCheckBox; + private FixedSizeButton myButton; + private final RunConfiguration myRunConfiguration; + private final BeforeRunTaskProvider myProvider; + + public StepBeforeLaunchRow(final RunConfiguration runConfiguration, final BeforeRunTaskProvider provider, + final BeforeRunTask beforeRunTask) { + super(new GridBagLayout()); + myRunConfiguration = runConfiguration; + myProvider = provider; + final boolean isChecked = beforeRunTask.isEnabled(); + myCheckBox = new JCheckBox(provider.getDescription(runConfiguration, beforeRunTask), isChecked); + GridBagConstraints gc = new GridBagConstraints(GridBagConstraints.RELATIVE, 0 , 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,0,0), 0, 0); + add(myCheckBox, gc); + gc.weightx = 1; + if (provider.hasConfigurationButton()) { + myButton = new FixedSizeButton(20); + add(myButton, gc); + + myButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (provider.configureTask(runConfiguration, beforeRunTask)) { + myCheckBox.setText(provider.getDescription(runConfiguration, beforeRunTask)); + fireEditorStateChanged(); + } + } + }); + } + else { + add(Box.createHorizontalBox(), gc); + } + enableSettings(beforeRunTask); + myCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + beforeRunTask.setEnabled(myCheckBox.isSelected()); + enableSettings(beforeRunTask); + } + }); + } + + private void enableSettings(final BeforeRunTask task) { + if (myButton != null) { + myButton.setEnabled(myCheckBox.isSelected()); + } + myCheckBox.setText(myProvider.getDescription(myRunConfiguration, task)); + } + + public void update(BeforeRunTask task) { + myCheckBox.setSelected(task.isEnabled()); + enableSettings(task); + } + } + + private class MyDataProvider implements TypeSafeDataProvider { + public void calcData(DataKey key, DataSink sink) { + if (key.equals(CONFIGURATION_EDITOR_KEY)) { + sink.put(CONFIGURATION_EDITOR_KEY, ConfigurationSettingsEditorWrapper.this); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java index 67ad5da9646f..7abf57264399 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java @@ -1,909 +1,909 @@ -/* - * 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.execution.impl; - -import com.intellij.execution.*; -import com.intellij.execution.configurations.*; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.ui.IconDeferrer; -import com.intellij.util.EventDispatcher; -import com.intellij.util.Function; -import com.intellij.util.NullableFunction; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.WeakHashMap; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.*; - - -public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, ProjectComponent { - private final Project myProject; - - private final Map myTypesByName = new LinkedHashMap(); - - private final Map myConfigurations = - new LinkedHashMap(); // template configurations are not included here - private final Map mySharedConfigurations = new TreeMap(); - /** - * configurationID -> [BeforeTaskProviderName->BeforeRunTask] - */ - private final Map, BeforeRunTask>> myConfigurationToBeforeTasksMap = - new WeakHashMap, BeforeRunTask>>(); - - private final Map myTemplateConfigurationsMap = - new HashMap(); - private RunnerAndConfigurationSettings mySelectedConfiguration = null; - private String mySelectedConfig = null; - - private Map myIdToIcon = new com.intellij.util.containers.HashMap(); - - @NonNls - protected static final String CONFIGURATION = "configuration"; - private ConfigurationType[] myTypes; - private final RunManagerConfig myConfig; - @NonNls - protected static final String NAME_ATTR = "name"; - @NonNls - protected static final String SELECTED_ATTR = "selected"; - @NonNls private static final String METHOD = "method"; - @NonNls private static final String OPTION = "option"; - @NonNls private static final String VALUE = "value"; - - private List myUnloadedElements = null; - private JDOMExternalizableStringList myOrder = new JDOMExternalizableStringList(); - private boolean myOrdered = true; - - private final EventDispatcher myDispatcher = EventDispatcher.create(RunManagerListener.class); - - public RunManagerImpl(final Project project, - PropertiesComponent propertiesComponent) { - myConfig = new RunManagerConfig(propertiesComponent, this); - myProject = project; - - initConfigurationTypes(); - } - - // separate method needed for tests - public final void initializeConfigurationTypes(@NotNull final ConfigurationType[] factories) { - Arrays.sort(factories, new Comparator() { - public int compare(final ConfigurationType o1, final ConfigurationType o2) { - return o1.getDisplayName().compareTo(o2.getDisplayName()); - } - }); - - final ArrayList types = new ArrayList(Arrays.asList(factories)); - types.add(UnknownConfigurationType.INSTANCE); - myTypes = types.toArray(new ConfigurationType[types.size()]); - - for (final ConfigurationType type : factories) { - myTypesByName.put(type.getId(), type); - } - - final UnknownConfigurationType broken = UnknownConfigurationType.INSTANCE; - myTypesByName.put(broken.getId(), broken); - } - - private void initConfigurationTypes() { - final ConfigurationType[] configurationTypes = Extensions.getExtensions(ConfigurationType.CONFIGURATION_TYPE_EP); - initializeConfigurationTypes(configurationTypes); - } - - public void disposeComponent() { - } - - public void initComponent() { - } - - public void projectOpened() { - } - - @NotNull - public RunnerAndConfigurationSettings createConfiguration(final String name, final ConfigurationFactory factory) { - return createConfiguration(doCreateConfiguration(name, factory), factory); - } - - protected RunConfiguration doCreateConfiguration(String name, ConfigurationFactory factory) { - return factory.createConfiguration(name, getConfigurationTemplate(factory).getConfiguration()); - } - - @NotNull - public RunnerAndConfigurationSettings createConfiguration(final RunConfiguration runConfiguration, - final ConfigurationFactory factory) { - RunnerAndConfigurationSettings template = getConfigurationTemplate(factory); - myConfigurationToBeforeTasksMap.put(runConfiguration, getBeforeRunTasks(template.getConfiguration())); - shareConfiguration(runConfiguration, isConfigurationShared(template)); - RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(this, runConfiguration, false); - settings.importRunnerAndConfigurationSettings((RunnerAndConfigurationSettingsImpl)template); - return settings; - } - - public void projectClosed() { - myTemplateConfigurationsMap.clear(); - } - - public RunManagerConfig getConfig() { - return myConfig; - } - - public ConfigurationType[] getConfigurationFactories() { - return myTypes.clone(); - } - - public ConfigurationType[] getConfigurationFactories(final boolean includeUnknown) { - final ConfigurationType[] configurationTypes = myTypes.clone(); - if (!includeUnknown) { - final List types = new ArrayList(); - for (ConfigurationType configurationType : configurationTypes) { - if (!(configurationType instanceof UnknownConfigurationType)) { - types.add(configurationType); - } - } - - return types.toArray(new ConfigurationType[types.size()]); - } - - return configurationTypes; - } - - /** - * Template configuration is not included - */ - public RunConfiguration[] getConfigurations(@NotNull final ConfigurationType type) { - - final List array = new ArrayList(); - for (RunnerAndConfigurationSettings myConfiguration : getSortedConfigurations()) { - final RunConfiguration configuration = myConfiguration.getConfiguration(); - final ConfigurationType configurationType = configuration.getType(); - if (type.getId().equals(configurationType.getId())) { - array.add(configuration); - } - } - return array.toArray(new RunConfiguration[array.size()]); - } - - public RunConfiguration[] getAllConfigurations() { - RunConfiguration[] result = new RunConfiguration[myConfigurations.size()]; - int i = 0; - for (Iterator iterator = getSortedConfigurations().iterator(); iterator.hasNext(); i++) { - RunnerAndConfigurationSettings settings = iterator.next(); - result[i] = settings.getConfiguration(); - } - - return result; - } - - @Nullable - public RunnerAndConfigurationSettings getSettings(RunConfiguration configuration) { - for (RunnerAndConfigurationSettings settings : getSortedConfigurations()) { - if (settings.getConfiguration() == configuration) return settings; - } - return null; - } - - /** - * Template configuration is not included - */ - public RunnerAndConfigurationSettings[] getConfigurationSettings(@NotNull final ConfigurationType type) { - - final LinkedHashSet array = new LinkedHashSet(); - for (RunnerAndConfigurationSettings configuration : getSortedConfigurations()) { - final ConfigurationType configurationType = configuration.getType(); - if (configurationType != null && type.getId().equals(configurationType.getId())) { - array.add(configuration); - } - } - return array.toArray(new RunnerAndConfigurationSettings[array.size()]); - } - - public RunnerAndConfigurationSettings getConfigurationTemplate(final ConfigurationFactory factory) { - RunnerAndConfigurationSettings template = myTemplateConfigurationsMap.get(factory.getType().getId() + "." + factory.getName()); - if (template == null) { - template = new RunnerAndConfigurationSettingsImpl(this, factory.createTemplateConfiguration(myProject, this), true); - if (template.getConfiguration() instanceof UnknownRunConfiguration) { - ((UnknownRunConfiguration) template.getConfiguration()).setDoNotStore(true); - } - myTemplateConfigurationsMap.put(factory.getType().getId() + "." + factory.getName(), template); - } - return template; - } - - public void addConfiguration(RunnerAndConfigurationSettings settings, boolean shared, Map, BeforeRunTask> tasks) { - final RunConfiguration configuration = settings.getConfiguration(); - final String configName = getUniqueName(configuration); - - myConfigurations.put(configName, settings); - checkRecentsLimit(); - - mySharedConfigurations.put(configuration.getUniqueID(), shared); - setBeforeRunTasks(configuration, tasks); - myDispatcher.getMulticaster().runConfigurationAdded(settings); - } - - void checkRecentsLimit() { - List removed = new ArrayList(); - while (getTempConfigurations().length > getConfig().getRecentsLimit()) { - for (Iterator> it = myConfigurations.entrySet().iterator(); it.hasNext();) { - Map.Entry entry = it.next(); - if (entry.getValue().isTemporary()) { - removed.add(entry.getValue()); - it.remove(); - break; - } - } - } - fireRunConfigurationsRemoved(removed); - } - - public static String getUniqueName(final RunConfiguration settings) { - return settings.getType().getDisplayName() + "." + settings.getName() + (settings instanceof UnknownRunConfiguration ? settings.getUniqueID() : ""); - } - - public void removeConfigurations(@NotNull final ConfigurationType type) { - List removed = new ArrayList(); - for (Iterator it = getSortedConfigurations().iterator(); it.hasNext();) { - final RunnerAndConfigurationSettings configuration = it.next(); - final ConfigurationType configurationType = configuration.getType(); - if (configurationType != null && type.getId().equals(configurationType.getId())) { - removed.add(configuration); - invalidateConfigurationIcon(configuration); - it.remove(); - } - } - fireRunConfigurationsRemoved(removed); - - final RunnerAndConfigurationSettings selectedConfiguration = getSelectedConfiguration(); - if (selectedConfiguration != null && removed.contains(selectedConfiguration)) { - final Collection sortedConfigurations = getSortedConfigurations(); - RunnerAndConfigurationSettings toSelect = null; - if (sortedConfigurations.size() > 0) { - toSelect = sortedConfigurations.iterator().next(); - } - - setSelectedConfiguration(toSelect); - } - } - - @Override - public Collection getSortedConfigurations() { - if (!myOrdered && !myOrder.isEmpty()) { //compatibility - final HashMap settings = - new HashMap(myConfigurations); //sort shared and local configurations - myConfigurations.clear(); - final List order = new ArrayList(settings.keySet()); - Collections.sort(order, new Comparator() { - public int compare(final String o1, final String o2) { - return myOrder.indexOf(o1) - myOrder.indexOf(o2); - } - }); - for (String configName : order) { - myConfigurations.put(configName, settings.get(configName)); - } - myOrdered = true; - } - return myConfigurations.values(); - } - - @Override - public void removeConfiguration(RunnerAndConfigurationSettings settings) { - for (Iterator it = getSortedConfigurations().iterator(); it.hasNext();) { - final RunnerAndConfigurationSettings configuration = it.next(); - if (configuration.equals(settings)) { - if (mySelectedConfiguration != null && settings.equals(mySelectedConfiguration)) { - setSelectedConfiguration(null); - } - - it.remove(); - invalidateConfigurationIcon(configuration); - myDispatcher.getMulticaster().runConfigurationRemoved(configuration); - break; - } - } - } - - public RunnerAndConfigurationSettings getSelectedConfiguration() { - if (mySelectedConfiguration == null && mySelectedConfig != null) { - mySelectedConfiguration = myConfigurations.get(mySelectedConfig); - mySelectedConfig = null; - } - return mySelectedConfiguration; - } - - public void setSelectedConfiguration(final RunnerAndConfigurationSettings configuration) { - mySelectedConfiguration = configuration; - if (configuration != null) invalidateConfigurationIcon(configuration); - fireRunConfigurationSelected(); - } - - public static boolean canRunConfiguration(@NotNull final RunnerAndConfigurationSettings configuration, @NotNull final Executor executor) { - try { - configuration.checkSettings(executor); - } - catch (RuntimeConfigurationError er) { - return false; - } - catch (RuntimeConfigurationException e) { - return true; - } - return true; - } - - public void writeExternal(@NotNull final Element parentNode) throws WriteExternalException { - - writeContext(parentNode); - for (final RunnerAndConfigurationSettings runnerAndConfigurationSettings : myTemplateConfigurationsMap.values()) { - if (runnerAndConfigurationSettings.getConfiguration() instanceof UnknownRunConfiguration) { - if (((UnknownRunConfiguration) runnerAndConfigurationSettings.getConfiguration()).isDoNotStore()) { - continue; - } - } - - addConfigurationElement(parentNode, runnerAndConfigurationSettings); - } - - final Collection configurations = getStableConfigurations().values(); - for (RunnerAndConfigurationSettings configuration : configurations) { - if (!isConfigurationShared(configuration)) { - addConfigurationElement(parentNode, configuration); - } - } - - final JDOMExternalizableStringList order = new JDOMExternalizableStringList(); - - //temp && stable configurations, !unknown - order.addAll(ContainerUtil.findAll(myConfigurations.keySet(), new Condition() { - public boolean value(final String s) { - return !s.startsWith(UnknownConfigurationType.NAME); - } - })); - - order.writeExternal(parentNode); - - if (myUnloadedElements != null) { - for (Element unloadedElement : myUnloadedElements) { - parentNode.addContent((Element)unloadedElement.clone()); - } - } - } - - public void writeContext(Element parentNode) throws WriteExternalException { - for (RunnerAndConfigurationSettings configurationSettings : myConfigurations.values()) { - if (configurationSettings.isTemporary()) { - addConfigurationElement(parentNode, configurationSettings, CONFIGURATION); - } - } - if (mySelectedConfiguration != null) { - parentNode.setAttribute(SELECTED_ATTR, getUniqueName(mySelectedConfiguration.getConfiguration())); - } else if (mySelectedConfig != null) { - parentNode.setAttribute(SELECTED_ATTR, mySelectedConfig); - } - } - - public void addConfigurationElement(final Element parentNode, RunnerAndConfigurationSettings template) throws WriteExternalException { - addConfigurationElement(parentNode, template, CONFIGURATION); - } - - private void addConfigurationElement(final Element parentNode, RunnerAndConfigurationSettings settings, String elementType) - throws WriteExternalException { - final Element configurationElement = new Element(elementType); - parentNode.addContent(configurationElement); - ((RunnerAndConfigurationSettingsImpl)settings).writeExternal(configurationElement); - - if (!(settings.getConfiguration() instanceof UnknownRunConfiguration)) { - final Map, BeforeRunTask> tasks = getBeforeRunTasks(settings.getConfiguration()); - Map, BeforeRunTask> templateTasks; - if (!settings.isTemplate()) { - final RunnerAndConfigurationSettings templateSettings = getConfigurationTemplate(settings.getFactory()); - templateTasks = getBeforeRunTasks(templateSettings.getConfiguration()); - } - else { - templateTasks = null; - } - final List> order = new ArrayList>(tasks.keySet()); - Collections.sort(order, new Comparator>() { - public int compare(Key o1, Key o2) { - return o1.toString().compareToIgnoreCase(o2.toString()); - } - }); - final Element methodsElement = new Element(METHOD); - for (Key providerID : order) { - final BeforeRunTask beforeRunTask = tasks.get(providerID); - - if (templateTasks != null) { - final BeforeRunTask templateTask = templateTasks.get(providerID); - if (beforeRunTask.equals(templateTask)) { - continue; // not neccesary saving if the task is the same as template - } - } - - final Element child = new Element(OPTION); - child.setAttribute(NAME_ATTR, providerID.toString()); - beforeRunTask.writeExternal(child); - methodsElement.addContent(child); - } - configurationElement.addContent(methodsElement); - } - } - - public void readExternal(final Element parentNode) throws InvalidDataException { - clear(); - - final Comparator comparator = new Comparator() { - public int compare(Element a, Element b) { - final boolean aDefault = Boolean.valueOf(a.getAttributeValue("default", "false")); - final boolean bDefault = Boolean.valueOf(b.getAttributeValue("default", "false")); - return aDefault == bDefault ? 0 : aDefault ? -1 : 1; - } - }; - - final List children = parentNode.getChildren(); - final List sortedElements = new ArrayList(); - for (final Object aChildren : children) { - final Element element = (Element)aChildren; - if (Comparing.strEqual(element.getName(), CONFIGURATION)) { - sortedElements.add(element); - } - } - - Collections.sort(sortedElements, comparator); // ensure templates are loaded first! - - for (final Element element : sortedElements) { - if (loadConfiguration(element, false) == null) { - if (myUnloadedElements == null) myUnloadedElements = new ArrayList(2); - myUnloadedElements.add(element); - } - } - - myOrder.readExternal(parentNode); - myOrdered = false; - - mySelectedConfig = parentNode.getAttributeValue(SELECTED_ATTR); - fireBeforeRunTasksUpdated(); - fireRunConfigurationSelected(); - } - - public void readContext(Element parentNode) throws InvalidDataException { - final List children = parentNode.getChildren(); - mySelectedConfig = parentNode.getAttributeValue(SELECTED_ATTR); - for (final Object aChildren : children) { - final Element element = (Element)aChildren; - if (mySelectedConfig == null && Boolean.valueOf(element.getAttributeValue(SELECTED_ATTR)).booleanValue()) { - mySelectedConfig = element.getAttributeValue(RunnerAndConfigurationSettingsImpl.NAME_ATTR); - } - loadConfiguration(element, false); - } - if (mySelectedConfig != null) { - RunnerAndConfigurationSettings configurationSettings = myConfigurations.get(mySelectedConfig); - if (configurationSettings != null) { - mySelectedConfiguration = null; - } - } - fireRunConfigurationSelected(); - } - - // used by MPS, don't delete - public void clearAll(){ - clear(); - initializeConfigurationTypes(new ConfigurationType[0]); - } - - private void clear() { - final List configurations = new ArrayList(myConfigurations.values()); - myConfigurations.clear(); - myUnloadedElements = null; - myConfigurationToBeforeTasksMap.clear(); - mySharedConfigurations.clear(); - myTemplateConfigurationsMap.clear(); - mySelectedConfiguration = null; - myIdToIcon.clear(); - fireRunConfigurationsRemoved(configurations); - } - - @Nullable - public RunnerAndConfigurationSettings loadConfiguration(final Element element, boolean isShared) throws InvalidDataException { - RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(this); - settings.readExternal(element); - ConfigurationFactory factory = settings.getFactory(); - if (factory == null) { - return null; - } - - final Element methodsElement = element.getChild(METHOD); - final Map, BeforeRunTask> map = readStepsBeforeRun(methodsElement, settings); - if (settings.isTemplate()) { - myTemplateConfigurationsMap.put(factory.getType().getId() + "." + factory.getName(), settings); - setBeforeRunTasks(settings.getConfiguration(), map); - } - else { - addConfiguration(settings, isShared, map); - if (Boolean.valueOf(element.getAttributeValue(SELECTED_ATTR)).booleanValue()) { //to support old style - setSelectedConfiguration(settings); - } - } - return settings; - } - - @NotNull - private Map, BeforeRunTask> readStepsBeforeRun(final Element child, RunnerAndConfigurationSettings settings) { - final Map, BeforeRunTask> map = new HashMap, BeforeRunTask>(); - if (child != null) { - for (Object o : child.getChildren(OPTION)) { - final Element methodElement = (Element)o; - final String providerName = methodElement.getAttributeValue(NAME_ATTR); - final Key id = getProviderKey(providerName); - final BeforeRunTaskProvider provider = getProvider(id); - final BeforeRunTask beforeRunTask = provider.createTask(settings.getConfiguration()); - if (beforeRunTask != null) { - beforeRunTask.readExternal(methodElement); - map.put(id, beforeRunTask); - } - } - } - return map; - } - - - @Nullable - public ConfigurationType getConfigurationType(final String typeName) { - return myTypesByName.get(typeName); - } - - @Nullable - public ConfigurationFactory getFactory(final String typeName, String factoryName) { - final ConfigurationType type = myTypesByName.get(typeName); - if (factoryName == null) { - factoryName = type != null ? type.getConfigurationFactories()[0].getName() : null; - } - return findFactoryOfTypeNameByName(typeName, factoryName); - } - - - @Nullable - private ConfigurationFactory findFactoryOfTypeNameByName(final String typeName, final String factoryName) { - ConfigurationType type = myTypesByName.get(typeName); - if (type == null) { - type = myTypesByName.get(UnknownConfigurationType.NAME); - } - - return findFactoryOfTypeByName(type, factoryName); - } - - @Nullable - private static ConfigurationFactory findFactoryOfTypeByName(final ConfigurationType type, final String factoryName) { - if (factoryName == null) return null; - - if (type instanceof UnknownConfigurationType) { - return type.getConfigurationFactories()[0]; - } - - final ConfigurationFactory[] factories = type.getConfigurationFactories(); - for (final ConfigurationFactory factory : factories) { - if (factoryName.equals(factory.getName())) return factory; - } - - return null; - } - - @NotNull - public String getComponentName() { - return "RunManager"; - } - - public void setTemporaryConfiguration(@NotNull final RunnerAndConfigurationSettings tempConfiguration) { - tempConfiguration.setTemporary(true); - invalidateConfigurationIcon(tempConfiguration); - - addConfiguration(tempConfiguration, isConfigurationShared(tempConfiguration), - getBeforeRunTasks(tempConfiguration.getConfiguration())); - setActiveConfiguration(tempConfiguration); - } - - public static boolean isEditBeforeRun(@NotNull final RunnerAndConfigurationSettings configuration) { - return configuration.isEditBeforeRun(); - } - - public void setEditBeforeRun(@NotNull final RunConfiguration configuration, final boolean edit) { - final RunnerAndConfigurationSettings settings = getSettings(configuration); - if (settings != null) settings.setEditBeforeRun(edit); - } - - public void setActiveConfiguration(final RunnerAndConfigurationSettings configuration) { - setSelectedConfiguration(configuration); - } - - public Map getStableConfigurations() { - final Map result = - new LinkedHashMap(myConfigurations); - for (Iterator> it = result.entrySet().iterator(); it.hasNext();) { - Map.Entry entry = it.next(); - if (entry.getValue().isTemporary()) { - it.remove(); - } - } - return result; - } - - public boolean isTemporary(final RunConfiguration configuration) { - return Arrays.asList(getTempConfigurations()).contains(configuration); - } - - public boolean isTemporary(RunnerAndConfigurationSettings settings) { - return settings.isTemporary(); - } - - public RunConfiguration[] getTempConfigurations() { - List configurations = ContainerUtil.mapNotNull(myConfigurations.values(), new NullableFunction() { - public RunConfiguration fun(RunnerAndConfigurationSettings settings) { - return settings.isTemporary() ? settings.getConfiguration() : null; - } - }); - return configurations.toArray(new RunConfiguration[configurations.size()]); - } - - public void makeStable(final RunConfiguration configuration) { - RunnerAndConfigurationSettings settings = getSettings(configuration); - if (settings != null) { - invalidateConfigurationIcon(settings); - settings.setTemporary(false); - } - } - - public RunnerAndConfigurationSettings createRunConfiguration(String name, ConfigurationFactory type) { - return createConfiguration(name, type); - } - - public boolean isConfigurationShared(final RunnerAndConfigurationSettings settings) { - Boolean shared = mySharedConfigurations.get(settings.getConfiguration().getUniqueID()); - if (shared == null) { - final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); - shared = mySharedConfigurations.get(template.getConfiguration().getUniqueID()); - } - return shared != null && shared.booleanValue(); - } - - public Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks) { - final Collection tasks = new ArrayList(); - if (includeOnlyActiveTasks) { - final Set checkedTemplates = new HashSet(); - for (RunnerAndConfigurationSettings settings : myConfigurations.values()) { - final BeforeRunTask runTask = getBeforeRunTask(settings.getConfiguration(), taskProviderID); - if (runTask != null && runTask.isEnabled()) { - tasks.add((T)runTask); - } - else { - final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); - if (!checkedTemplates.contains(template)) { - checkedTemplates.add(template); - final BeforeRunTask templateTask = getBeforeRunTask(template.getConfiguration(), taskProviderID); - if (templateTask != null && templateTask.isEnabled()) { - tasks.add((T)templateTask); - } - } - } - } - } - else { - for (RunnerAndConfigurationSettings settings : myTemplateConfigurationsMap.values()) { - final T task = getBeforeRunTask(settings.getConfiguration(), taskProviderID); - if (task != null) { - tasks.add(task); - } - } - for (RunnerAndConfigurationSettings settings : myConfigurations.values()) { - final T task = getBeforeRunTask(settings.getConfiguration(), taskProviderID); - if (task != null) { - tasks.add(task); - } - } - } - return tasks; - } - - public void invalidateConfigurationIcon(@NotNull final RunnerAndConfigurationSettings settings) { - myIdToIcon.remove(settings.getConfiguration().getUniqueID()); - } - - public Icon getConfigurationIcon(@NotNull final RunnerAndConfigurationSettings settings) { - final int uniqueID = settings.getConfiguration().getUniqueID(); - Icon icon = myIdToIcon.get(uniqueID); - if (icon == null) { - icon = IconDeferrer.getInstance().defer(settings.getConfiguration().getIcon(), Pair.create(myProject, settings), new Function, Icon>() { - @Override - public Icon fun(Pair projectRunnerAndConfigurationSettingsPair) { - Icon icon; - try { - settings.checkSettings(); - icon = ProgramRunnerUtil.getConfigurationIcon(myProject, settings, false); - } - catch (RuntimeConfigurationException e) { - icon = ProgramRunnerUtil.getConfigurationIcon(myProject, settings, true); - } - - return icon; - } - }); - - myIdToIcon.put(uniqueID, icon); - } - - return icon; - } - - @Override - @Nullable - public RunnerAndConfigurationSettings findConfigurationByName(@NotNull String name) { - return null; - } - - @Nullable - public T getBeforeRunTask(RunConfiguration settings, Key taskProviderID) { - Map, BeforeRunTask> tasks = myConfigurationToBeforeTasksMap.get(settings); - if (tasks == null) { - tasks = getBeforeRunTasks(settings); - myConfigurationToBeforeTasksMap.put(settings, tasks); - } - return (T)tasks.get(taskProviderID); - } - - public Map, BeforeRunTask> getBeforeRunTasks(final RunConfiguration settings) { - final Map, BeforeRunTask> tasks = myConfigurationToBeforeTasksMap.get(settings); - if (tasks != null) { - final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); - for (Map.Entry, BeforeRunTask> entry : tasks.entrySet()) { - _tasks.put(entry.getKey(), entry.getValue().clone()); - } - return _tasks; - } - - final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); - final Map, BeforeRunTask> templateTasks = myConfigurationToBeforeTasksMap.get(template.getConfiguration()); - if (templateTasks != null) { - final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); - for (Map.Entry, BeforeRunTask> entry : templateTasks.entrySet()) { - _tasks.put(entry.getKey(), entry.getValue().clone()); - } - return _tasks; - } - - final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); - for (BeforeRunTaskProvider provider : Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { - BeforeRunTask task = provider.createTask(settings); - if (task != null) { - Key providerID = provider.getId(); - _tasks.put(providerID, task); - settings.getFactory().configureBeforeRunTaskDefaults(providerID, task); - } - } - return _tasks; - } - - public void shareConfiguration(final RunConfiguration runConfiguration, final boolean shareConfiguration) { - mySharedConfigurations.put(runConfiguration.getUniqueID(), shareConfiguration); - } - - public final void setBeforeRunTasks(final RunConfiguration runConfiguration, Map, BeforeRunTask> tasks) { - final Map, BeforeRunTask> taskMap = getBeforeRunTasks(runConfiguration); - for (Map.Entry, BeforeRunTask> entry : tasks.entrySet()) { - //if (taskMap.containsKey(entry.getKey())) { - taskMap.put(entry.getKey(), entry.getValue()); - //} - } - myConfigurationToBeforeTasksMap.put(runConfiguration, taskMap); - fireBeforeRunTasksUpdated(); - } - - public final void resetBeforeRunTasks(final RunConfiguration runConfiguration) { - myConfigurationToBeforeTasksMap.remove(runConfiguration); - fireBeforeRunTasksUpdated(); - } - - public void addConfiguration(final RunnerAndConfigurationSettings settings, final boolean isShared) { - addConfiguration(settings, isShared, Collections., BeforeRunTask>emptyMap()); - } - - public static RunManagerImpl getInstanceImpl(final Project project) { - return (RunManagerImpl)RunManager.getInstance(project); - } - - public void removeNotExistingSharedConfigurations(final Set existing) { - List removed = new ArrayList(); - for (Iterator> it = myConfigurations.entrySet().iterator(); it.hasNext();) { - Map.Entry c = it.next(); - final RunnerAndConfigurationSettings o = c.getValue(); - if (!o.isTemplate() && isConfigurationShared(o) && !existing.contains(getUniqueName(o.getConfiguration()))) { - removed.add(o); - invalidateConfigurationIcon(o); - it.remove(); - } - } - fireRunConfigurationsRemoved(removed); - } - - public void fireRunConfigurationChanged(RunnerAndConfigurationSettings settings) { - invalidateConfigurationIcon(settings); - myDispatcher.getMulticaster().runConfigurationChanged(settings); - } - - private void fireRunConfigurationsRemoved(List removed) { - for (RunnerAndConfigurationSettings settings : removed) { - myDispatcher.getMulticaster().runConfigurationRemoved(settings); - } - } - - private void fireRunConfigurationSelected() { - myDispatcher.getMulticaster().runConfigurationSelected(); - } - - @Override - public void addRunManagerListener(RunManagerListener listener) { - myDispatcher.addListener(listener); - } - - @Override - public void removeRunManagerListener(RunManagerListener listener) { - myDispatcher.removeListener(listener); - } - - public void fireBeforeRunTasksUpdated() { - myDispatcher.getMulticaster().beforeRunTasksChanged(); - } - - private Map, BeforeRunTaskProvider> myBeforeStepsMap; - private Map> myProviderKeysMap; - - @NotNull - private synchronized BeforeRunTaskProvider getProvider(Key providerId) { - if (myBeforeStepsMap == null) { - initProviderMaps(); - } - return myBeforeStepsMap.get(providerId); - } - - @NotNull - private synchronized Key getProviderKey(String keyString) { - if (myProviderKeysMap == null) { - initProviderMaps(); - } - Key id = myProviderKeysMap.get(keyString); - if (id == null) { - final UnknownBeforeRunTaskProvider provider = new UnknownBeforeRunTaskProvider(keyString); - id = provider.getId(); - myProviderKeysMap.put(keyString, id); - myBeforeStepsMap.put(id, provider); - } - return id; - } - - private void initProviderMaps() { - myBeforeStepsMap = new HashMap, BeforeRunTaskProvider>(); - myProviderKeysMap = new HashMap>(); - for (BeforeRunTaskProvider provider : Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { - final Key id = provider.getId(); - myBeforeStepsMap.put(id, provider); - myProviderKeysMap.put(id.toString(), id); - } - } - - -} +/* + * 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.execution.impl; + +import com.intellij.execution.*; +import com.intellij.execution.configurations.*; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.*; +import com.intellij.ui.IconDeferrer; +import com.intellij.util.EventDispatcher; +import com.intellij.util.Function; +import com.intellij.util.NullableFunction; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.WeakHashMap; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.*; + + +public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, ProjectComponent { + private final Project myProject; + + private final Map myTypesByName = new LinkedHashMap(); + + private final Map myConfigurations = + new LinkedHashMap(); // template configurations are not included here + private final Map mySharedConfigurations = new TreeMap(); + /** + * configurationID -> [BeforeTaskProviderName->BeforeRunTask] + */ + private final Map, BeforeRunTask>> myConfigurationToBeforeTasksMap = + new WeakHashMap, BeforeRunTask>>(); + + private final Map myTemplateConfigurationsMap = + new HashMap(); + private RunnerAndConfigurationSettings mySelectedConfiguration = null; + private String mySelectedConfig = null; + + private Map myIdToIcon = new com.intellij.util.containers.HashMap(); + + @NonNls + protected static final String CONFIGURATION = "configuration"; + private ConfigurationType[] myTypes; + private final RunManagerConfig myConfig; + @NonNls + protected static final String NAME_ATTR = "name"; + @NonNls + protected static final String SELECTED_ATTR = "selected"; + @NonNls private static final String METHOD = "method"; + @NonNls private static final String OPTION = "option"; + @NonNls private static final String VALUE = "value"; + + private List myUnloadedElements = null; + private JDOMExternalizableStringList myOrder = new JDOMExternalizableStringList(); + private boolean myOrdered = true; + + private final EventDispatcher myDispatcher = EventDispatcher.create(RunManagerListener.class); + + public RunManagerImpl(final Project project, + PropertiesComponent propertiesComponent) { + myConfig = new RunManagerConfig(propertiesComponent, this); + myProject = project; + + initConfigurationTypes(); + } + + // separate method needed for tests + public final void initializeConfigurationTypes(@NotNull final ConfigurationType[] factories) { + Arrays.sort(factories, new Comparator() { + public int compare(final ConfigurationType o1, final ConfigurationType o2) { + return o1.getDisplayName().compareTo(o2.getDisplayName()); + } + }); + + final ArrayList types = new ArrayList(Arrays.asList(factories)); + types.add(UnknownConfigurationType.INSTANCE); + myTypes = types.toArray(new ConfigurationType[types.size()]); + + for (final ConfigurationType type : factories) { + myTypesByName.put(type.getId(), type); + } + + final UnknownConfigurationType broken = UnknownConfigurationType.INSTANCE; + myTypesByName.put(broken.getId(), broken); + } + + private void initConfigurationTypes() { + final ConfigurationType[] configurationTypes = Extensions.getExtensions(ConfigurationType.CONFIGURATION_TYPE_EP); + initializeConfigurationTypes(configurationTypes); + } + + public void disposeComponent() { + } + + public void initComponent() { + } + + public void projectOpened() { + } + + @NotNull + public RunnerAndConfigurationSettings createConfiguration(final String name, final ConfigurationFactory factory) { + return createConfiguration(doCreateConfiguration(name, factory), factory); + } + + protected RunConfiguration doCreateConfiguration(String name, ConfigurationFactory factory) { + return factory.createConfiguration(name, getConfigurationTemplate(factory).getConfiguration()); + } + + @NotNull + public RunnerAndConfigurationSettings createConfiguration(final RunConfiguration runConfiguration, + final ConfigurationFactory factory) { + RunnerAndConfigurationSettings template = getConfigurationTemplate(factory); + myConfigurationToBeforeTasksMap.put(runConfiguration, getBeforeRunTasks(template.getConfiguration())); + shareConfiguration(runConfiguration, isConfigurationShared(template)); + RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(this, runConfiguration, false); + settings.importRunnerAndConfigurationSettings((RunnerAndConfigurationSettingsImpl)template); + return settings; + } + + public void projectClosed() { + myTemplateConfigurationsMap.clear(); + } + + public RunManagerConfig getConfig() { + return myConfig; + } + + public ConfigurationType[] getConfigurationFactories() { + return myTypes.clone(); + } + + public ConfigurationType[] getConfigurationFactories(final boolean includeUnknown) { + final ConfigurationType[] configurationTypes = myTypes.clone(); + if (!includeUnknown) { + final List types = new ArrayList(); + for (ConfigurationType configurationType : configurationTypes) { + if (!(configurationType instanceof UnknownConfigurationType)) { + types.add(configurationType); + } + } + + return types.toArray(new ConfigurationType[types.size()]); + } + + return configurationTypes; + } + + /** + * Template configuration is not included + */ + public RunConfiguration[] getConfigurations(@NotNull final ConfigurationType type) { + + final List array = new ArrayList(); + for (RunnerAndConfigurationSettings myConfiguration : getSortedConfigurations()) { + final RunConfiguration configuration = myConfiguration.getConfiguration(); + final ConfigurationType configurationType = configuration.getType(); + if (type.getId().equals(configurationType.getId())) { + array.add(configuration); + } + } + return array.toArray(new RunConfiguration[array.size()]); + } + + public RunConfiguration[] getAllConfigurations() { + RunConfiguration[] result = new RunConfiguration[myConfigurations.size()]; + int i = 0; + for (Iterator iterator = getSortedConfigurations().iterator(); iterator.hasNext(); i++) { + RunnerAndConfigurationSettings settings = iterator.next(); + result[i] = settings.getConfiguration(); + } + + return result; + } + + @Nullable + public RunnerAndConfigurationSettings getSettings(RunConfiguration configuration) { + for (RunnerAndConfigurationSettings settings : getSortedConfigurations()) { + if (settings.getConfiguration() == configuration) return settings; + } + return null; + } + + /** + * Template configuration is not included + */ + public RunnerAndConfigurationSettings[] getConfigurationSettings(@NotNull final ConfigurationType type) { + + final LinkedHashSet array = new LinkedHashSet(); + for (RunnerAndConfigurationSettings configuration : getSortedConfigurations()) { + final ConfigurationType configurationType = configuration.getType(); + if (configurationType != null && type.getId().equals(configurationType.getId())) { + array.add(configuration); + } + } + return array.toArray(new RunnerAndConfigurationSettings[array.size()]); + } + + public RunnerAndConfigurationSettings getConfigurationTemplate(final ConfigurationFactory factory) { + RunnerAndConfigurationSettings template = myTemplateConfigurationsMap.get(factory.getType().getId() + "." + factory.getName()); + if (template == null) { + template = new RunnerAndConfigurationSettingsImpl(this, factory.createTemplateConfiguration(myProject, this), true); + if (template.getConfiguration() instanceof UnknownRunConfiguration) { + ((UnknownRunConfiguration) template.getConfiguration()).setDoNotStore(true); + } + myTemplateConfigurationsMap.put(factory.getType().getId() + "." + factory.getName(), template); + } + return template; + } + + public void addConfiguration(RunnerAndConfigurationSettings settings, boolean shared, Map, BeforeRunTask> tasks) { + final RunConfiguration configuration = settings.getConfiguration(); + final String configName = getUniqueName(configuration); + + myConfigurations.put(configName, settings); + checkRecentsLimit(); + + mySharedConfigurations.put(configuration.getUniqueID(), shared); + setBeforeRunTasks(configuration, tasks); + myDispatcher.getMulticaster().runConfigurationAdded(settings); + } + + void checkRecentsLimit() { + List removed = new ArrayList(); + while (getTempConfigurations().length > getConfig().getRecentsLimit()) { + for (Iterator> it = myConfigurations.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + if (entry.getValue().isTemporary()) { + removed.add(entry.getValue()); + it.remove(); + break; + } + } + } + fireRunConfigurationsRemoved(removed); + } + + public static String getUniqueName(final RunConfiguration settings) { + return settings.getType().getDisplayName() + "." + settings.getName() + (settings instanceof UnknownRunConfiguration ? settings.getUniqueID() : ""); + } + + public void removeConfigurations(@NotNull final ConfigurationType type) { + List removed = new ArrayList(); + for (Iterator it = getSortedConfigurations().iterator(); it.hasNext();) { + final RunnerAndConfigurationSettings configuration = it.next(); + final ConfigurationType configurationType = configuration.getType(); + if (configurationType != null && type.getId().equals(configurationType.getId())) { + removed.add(configuration); + invalidateConfigurationIcon(configuration); + it.remove(); + } + } + fireRunConfigurationsRemoved(removed); + + final RunnerAndConfigurationSettings selectedConfiguration = getSelectedConfiguration(); + if (selectedConfiguration != null && removed.contains(selectedConfiguration)) { + final Collection sortedConfigurations = getSortedConfigurations(); + RunnerAndConfigurationSettings toSelect = null; + if (sortedConfigurations.size() > 0) { + toSelect = sortedConfigurations.iterator().next(); + } + + setSelectedConfiguration(toSelect); + } + } + + @Override + public Collection getSortedConfigurations() { + if (!myOrdered && !myOrder.isEmpty()) { //compatibility + final HashMap settings = + new HashMap(myConfigurations); //sort shared and local configurations + myConfigurations.clear(); + final List order = new ArrayList(settings.keySet()); + Collections.sort(order, new Comparator() { + public int compare(final String o1, final String o2) { + return myOrder.indexOf(o1) - myOrder.indexOf(o2); + } + }); + for (String configName : order) { + myConfigurations.put(configName, settings.get(configName)); + } + myOrdered = true; + } + return myConfigurations.values(); + } + + @Override + public void removeConfiguration(RunnerAndConfigurationSettings settings) { + for (Iterator it = getSortedConfigurations().iterator(); it.hasNext();) { + final RunnerAndConfigurationSettings configuration = it.next(); + if (configuration.equals(settings)) { + if (mySelectedConfiguration != null && settings.equals(mySelectedConfiguration)) { + setSelectedConfiguration(null); + } + + it.remove(); + invalidateConfigurationIcon(configuration); + myDispatcher.getMulticaster().runConfigurationRemoved(configuration); + break; + } + } + } + + public RunnerAndConfigurationSettings getSelectedConfiguration() { + if (mySelectedConfiguration == null && mySelectedConfig != null) { + mySelectedConfiguration = myConfigurations.get(mySelectedConfig); + mySelectedConfig = null; + } + return mySelectedConfiguration; + } + + public void setSelectedConfiguration(final RunnerAndConfigurationSettings configuration) { + mySelectedConfiguration = configuration; + if (configuration != null) invalidateConfigurationIcon(configuration); + fireRunConfigurationSelected(); + } + + public static boolean canRunConfiguration(@NotNull final RunnerAndConfigurationSettings configuration, @NotNull final Executor executor) { + try { + configuration.checkSettings(executor); + } + catch (RuntimeConfigurationError er) { + return false; + } + catch (RuntimeConfigurationException e) { + return true; + } + return true; + } + + public void writeExternal(@NotNull final Element parentNode) throws WriteExternalException { + + writeContext(parentNode); + for (final RunnerAndConfigurationSettings runnerAndConfigurationSettings : myTemplateConfigurationsMap.values()) { + if (runnerAndConfigurationSettings.getConfiguration() instanceof UnknownRunConfiguration) { + if (((UnknownRunConfiguration) runnerAndConfigurationSettings.getConfiguration()).isDoNotStore()) { + continue; + } + } + + addConfigurationElement(parentNode, runnerAndConfigurationSettings); + } + + final Collection configurations = getStableConfigurations().values(); + for (RunnerAndConfigurationSettings configuration : configurations) { + if (!isConfigurationShared(configuration)) { + addConfigurationElement(parentNode, configuration); + } + } + + final JDOMExternalizableStringList order = new JDOMExternalizableStringList(); + + //temp && stable configurations, !unknown + order.addAll(ContainerUtil.findAll(myConfigurations.keySet(), new Condition() { + public boolean value(final String s) { + return !s.startsWith(UnknownConfigurationType.NAME); + } + })); + + order.writeExternal(parentNode); + + if (myUnloadedElements != null) { + for (Element unloadedElement : myUnloadedElements) { + parentNode.addContent((Element)unloadedElement.clone()); + } + } + } + + public void writeContext(Element parentNode) throws WriteExternalException { + for (RunnerAndConfigurationSettings configurationSettings : myConfigurations.values()) { + if (configurationSettings.isTemporary()) { + addConfigurationElement(parentNode, configurationSettings, CONFIGURATION); + } + } + if (mySelectedConfiguration != null) { + parentNode.setAttribute(SELECTED_ATTR, getUniqueName(mySelectedConfiguration.getConfiguration())); + } else if (mySelectedConfig != null) { + parentNode.setAttribute(SELECTED_ATTR, mySelectedConfig); + } + } + + public void addConfigurationElement(final Element parentNode, RunnerAndConfigurationSettings template) throws WriteExternalException { + addConfigurationElement(parentNode, template, CONFIGURATION); + } + + private void addConfigurationElement(final Element parentNode, RunnerAndConfigurationSettings settings, String elementType) + throws WriteExternalException { + final Element configurationElement = new Element(elementType); + parentNode.addContent(configurationElement); + ((RunnerAndConfigurationSettingsImpl)settings).writeExternal(configurationElement); + + if (!(settings.getConfiguration() instanceof UnknownRunConfiguration)) { + final Map, BeforeRunTask> tasks = getBeforeRunTasks(settings.getConfiguration()); + Map, BeforeRunTask> templateTasks; + if (!settings.isTemplate()) { + final RunnerAndConfigurationSettings templateSettings = getConfigurationTemplate(settings.getFactory()); + templateTasks = getBeforeRunTasks(templateSettings.getConfiguration()); + } + else { + templateTasks = null; + } + final List> order = new ArrayList>(tasks.keySet()); + Collections.sort(order, new Comparator>() { + public int compare(Key o1, Key o2) { + return o1.toString().compareToIgnoreCase(o2.toString()); + } + }); + final Element methodsElement = new Element(METHOD); + for (Key providerID : order) { + final BeforeRunTask beforeRunTask = tasks.get(providerID); + + if (templateTasks != null) { + final BeforeRunTask templateTask = templateTasks.get(providerID); + if (beforeRunTask.equals(templateTask)) { + continue; // not neccesary saving if the task is the same as template + } + } + + final Element child = new Element(OPTION); + child.setAttribute(NAME_ATTR, providerID.toString()); + beforeRunTask.writeExternal(child); + methodsElement.addContent(child); + } + configurationElement.addContent(methodsElement); + } + } + + public void readExternal(final Element parentNode) throws InvalidDataException { + clear(); + + final Comparator comparator = new Comparator() { + public int compare(Element a, Element b) { + final boolean aDefault = Boolean.valueOf(a.getAttributeValue("default", "false")); + final boolean bDefault = Boolean.valueOf(b.getAttributeValue("default", "false")); + return aDefault == bDefault ? 0 : aDefault ? -1 : 1; + } + }; + + final List children = parentNode.getChildren(); + final List sortedElements = new ArrayList(); + for (final Object aChildren : children) { + final Element element = (Element)aChildren; + if (Comparing.strEqual(element.getName(), CONFIGURATION)) { + sortedElements.add(element); + } + } + + Collections.sort(sortedElements, comparator); // ensure templates are loaded first! + + for (final Element element : sortedElements) { + if (loadConfiguration(element, false) == null) { + if (myUnloadedElements == null) myUnloadedElements = new ArrayList(2); + myUnloadedElements.add(element); + } + } + + myOrder.readExternal(parentNode); + myOrdered = false; + + mySelectedConfig = parentNode.getAttributeValue(SELECTED_ATTR); + fireBeforeRunTasksUpdated(); + fireRunConfigurationSelected(); + } + + public void readContext(Element parentNode) throws InvalidDataException { + final List children = parentNode.getChildren(); + mySelectedConfig = parentNode.getAttributeValue(SELECTED_ATTR); + for (final Object aChildren : children) { + final Element element = (Element)aChildren; + if (mySelectedConfig == null && Boolean.valueOf(element.getAttributeValue(SELECTED_ATTR)).booleanValue()) { + mySelectedConfig = element.getAttributeValue(RunnerAndConfigurationSettingsImpl.NAME_ATTR); + } + loadConfiguration(element, false); + } + if (mySelectedConfig != null) { + RunnerAndConfigurationSettings configurationSettings = myConfigurations.get(mySelectedConfig); + if (configurationSettings != null) { + mySelectedConfiguration = null; + } + } + fireRunConfigurationSelected(); + } + + // used by MPS, don't delete + public void clearAll(){ + clear(); + initializeConfigurationTypes(new ConfigurationType[0]); + } + + private void clear() { + final List configurations = new ArrayList(myConfigurations.values()); + myConfigurations.clear(); + myUnloadedElements = null; + myConfigurationToBeforeTasksMap.clear(); + mySharedConfigurations.clear(); + myTemplateConfigurationsMap.clear(); + mySelectedConfiguration = null; + myIdToIcon.clear(); + fireRunConfigurationsRemoved(configurations); + } + + @Nullable + public RunnerAndConfigurationSettings loadConfiguration(final Element element, boolean isShared) throws InvalidDataException { + RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(this); + settings.readExternal(element); + ConfigurationFactory factory = settings.getFactory(); + if (factory == null) { + return null; + } + + final Element methodsElement = element.getChild(METHOD); + final Map, BeforeRunTask> map = readStepsBeforeRun(methodsElement, settings); + if (settings.isTemplate()) { + myTemplateConfigurationsMap.put(factory.getType().getId() + "." + factory.getName(), settings); + setBeforeRunTasks(settings.getConfiguration(), map); + } + else { + addConfiguration(settings, isShared, map); + if (Boolean.valueOf(element.getAttributeValue(SELECTED_ATTR)).booleanValue()) { //to support old style + setSelectedConfiguration(settings); + } + } + return settings; + } + + @NotNull + private Map, BeforeRunTask> readStepsBeforeRun(final Element child, RunnerAndConfigurationSettings settings) { + final Map, BeforeRunTask> map = new HashMap, BeforeRunTask>(); + if (child != null) { + for (Object o : child.getChildren(OPTION)) { + final Element methodElement = (Element)o; + final String providerName = methodElement.getAttributeValue(NAME_ATTR); + final Key id = getProviderKey(providerName); + final BeforeRunTaskProvider provider = getProvider(id); + final BeforeRunTask beforeRunTask = provider.createTask(settings.getConfiguration()); + if (beforeRunTask != null) { + beforeRunTask.readExternal(methodElement); + map.put(id, beforeRunTask); + } + } + } + return map; + } + + + @Nullable + public ConfigurationType getConfigurationType(final String typeName) { + return myTypesByName.get(typeName); + } + + @Nullable + public ConfigurationFactory getFactory(final String typeName, String factoryName) { + final ConfigurationType type = myTypesByName.get(typeName); + if (factoryName == null) { + factoryName = type != null ? type.getConfigurationFactories()[0].getName() : null; + } + return findFactoryOfTypeNameByName(typeName, factoryName); + } + + + @Nullable + private ConfigurationFactory findFactoryOfTypeNameByName(final String typeName, final String factoryName) { + ConfigurationType type = myTypesByName.get(typeName); + if (type == null) { + type = myTypesByName.get(UnknownConfigurationType.NAME); + } + + return findFactoryOfTypeByName(type, factoryName); + } + + @Nullable + private static ConfigurationFactory findFactoryOfTypeByName(final ConfigurationType type, final String factoryName) { + if (factoryName == null) return null; + + if (type instanceof UnknownConfigurationType) { + return type.getConfigurationFactories()[0]; + } + + final ConfigurationFactory[] factories = type.getConfigurationFactories(); + for (final ConfigurationFactory factory : factories) { + if (factoryName.equals(factory.getName())) return factory; + } + + return null; + } + + @NotNull + public String getComponentName() { + return "RunManager"; + } + + public void setTemporaryConfiguration(@NotNull final RunnerAndConfigurationSettings tempConfiguration) { + tempConfiguration.setTemporary(true); + invalidateConfigurationIcon(tempConfiguration); + + addConfiguration(tempConfiguration, isConfigurationShared(tempConfiguration), + getBeforeRunTasks(tempConfiguration.getConfiguration())); + setActiveConfiguration(tempConfiguration); + } + + public static boolean isEditBeforeRun(@NotNull final RunnerAndConfigurationSettings configuration) { + return configuration.isEditBeforeRun(); + } + + public void setEditBeforeRun(@NotNull final RunConfiguration configuration, final boolean edit) { + final RunnerAndConfigurationSettings settings = getSettings(configuration); + if (settings != null) settings.setEditBeforeRun(edit); + } + + public void setActiveConfiguration(final RunnerAndConfigurationSettings configuration) { + setSelectedConfiguration(configuration); + } + + public Map getStableConfigurations() { + final Map result = + new LinkedHashMap(myConfigurations); + for (Iterator> it = result.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + if (entry.getValue().isTemporary()) { + it.remove(); + } + } + return result; + } + + public boolean isTemporary(final RunConfiguration configuration) { + return Arrays.asList(getTempConfigurations()).contains(configuration); + } + + public boolean isTemporary(RunnerAndConfigurationSettings settings) { + return settings.isTemporary(); + } + + public RunConfiguration[] getTempConfigurations() { + List configurations = ContainerUtil.mapNotNull(myConfigurations.values(), new NullableFunction() { + public RunConfiguration fun(RunnerAndConfigurationSettings settings) { + return settings.isTemporary() ? settings.getConfiguration() : null; + } + }); + return configurations.toArray(new RunConfiguration[configurations.size()]); + } + + public void makeStable(final RunConfiguration configuration) { + RunnerAndConfigurationSettings settings = getSettings(configuration); + if (settings != null) { + invalidateConfigurationIcon(settings); + settings.setTemporary(false); + } + } + + public RunnerAndConfigurationSettings createRunConfiguration(String name, ConfigurationFactory type) { + return createConfiguration(name, type); + } + + public boolean isConfigurationShared(final RunnerAndConfigurationSettings settings) { + Boolean shared = mySharedConfigurations.get(settings.getConfiguration().getUniqueID()); + if (shared == null) { + final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); + shared = mySharedConfigurations.get(template.getConfiguration().getUniqueID()); + } + return shared != null && shared.booleanValue(); + } + + public Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks) { + final Collection tasks = new ArrayList(); + if (includeOnlyActiveTasks) { + final Set checkedTemplates = new HashSet(); + for (RunnerAndConfigurationSettings settings : myConfigurations.values()) { + final BeforeRunTask runTask = getBeforeRunTask(settings.getConfiguration(), taskProviderID); + if (runTask != null && runTask.isEnabled()) { + tasks.add((T)runTask); + } + else { + final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); + if (!checkedTemplates.contains(template)) { + checkedTemplates.add(template); + final BeforeRunTask templateTask = getBeforeRunTask(template.getConfiguration(), taskProviderID); + if (templateTask != null && templateTask.isEnabled()) { + tasks.add((T)templateTask); + } + } + } + } + } + else { + for (RunnerAndConfigurationSettings settings : myTemplateConfigurationsMap.values()) { + final T task = getBeforeRunTask(settings.getConfiguration(), taskProviderID); + if (task != null) { + tasks.add(task); + } + } + for (RunnerAndConfigurationSettings settings : myConfigurations.values()) { + final T task = getBeforeRunTask(settings.getConfiguration(), taskProviderID); + if (task != null) { + tasks.add(task); + } + } + } + return tasks; + } + + public void invalidateConfigurationIcon(@NotNull final RunnerAndConfigurationSettings settings) { + myIdToIcon.remove(settings.getConfiguration().getUniqueID()); + } + + public Icon getConfigurationIcon(@NotNull final RunnerAndConfigurationSettings settings) { + final int uniqueID = settings.getConfiguration().getUniqueID(); + Icon icon = myIdToIcon.get(uniqueID); + if (icon == null) { + icon = IconDeferrer.getInstance().defer(settings.getConfiguration().getIcon(), Pair.create(myProject, settings), new Function, Icon>() { + @Override + public Icon fun(Pair projectRunnerAndConfigurationSettingsPair) { + Icon icon; + try { + settings.checkSettings(); + icon = ProgramRunnerUtil.getConfigurationIcon(myProject, settings, false); + } + catch (RuntimeConfigurationException e) { + icon = ProgramRunnerUtil.getConfigurationIcon(myProject, settings, true); + } + + return icon; + } + }); + + myIdToIcon.put(uniqueID, icon); + } + + return icon; + } + + @Override + @Nullable + public RunnerAndConfigurationSettings findConfigurationByName(@NotNull String name) { + return null; + } + + @Nullable + public T getBeforeRunTask(RunConfiguration settings, Key taskProviderID) { + Map, BeforeRunTask> tasks = myConfigurationToBeforeTasksMap.get(settings); + if (tasks == null) { + tasks = getBeforeRunTasks(settings); + myConfigurationToBeforeTasksMap.put(settings, tasks); + } + return (T)tasks.get(taskProviderID); + } + + public Map, BeforeRunTask> getBeforeRunTasks(final RunConfiguration settings) { + final Map, BeforeRunTask> tasks = myConfigurationToBeforeTasksMap.get(settings); + if (tasks != null) { + final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); + for (Map.Entry, BeforeRunTask> entry : tasks.entrySet()) { + _tasks.put(entry.getKey(), entry.getValue().clone()); + } + return _tasks; + } + + final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); + final Map, BeforeRunTask> templateTasks = myConfigurationToBeforeTasksMap.get(template.getConfiguration()); + if (templateTasks != null) { + final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); + for (Map.Entry, BeforeRunTask> entry : templateTasks.entrySet()) { + _tasks.put(entry.getKey(), entry.getValue().clone()); + } + return _tasks; + } + + final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); + for (BeforeRunTaskProvider provider : Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { + BeforeRunTask task = provider.createTask(settings); + if (task != null) { + Key providerID = provider.getId(); + _tasks.put(providerID, task); + settings.getFactory().configureBeforeRunTaskDefaults(providerID, task); + } + } + return _tasks; + } + + public void shareConfiguration(final RunConfiguration runConfiguration, final boolean shareConfiguration) { + mySharedConfigurations.put(runConfiguration.getUniqueID(), shareConfiguration); + } + + public final void setBeforeRunTasks(final RunConfiguration runConfiguration, Map, BeforeRunTask> tasks) { + final Map, BeforeRunTask> taskMap = getBeforeRunTasks(runConfiguration); + for (Map.Entry, BeforeRunTask> entry : tasks.entrySet()) { + //if (taskMap.containsKey(entry.getKey())) { + taskMap.put(entry.getKey(), entry.getValue()); + //} + } + myConfigurationToBeforeTasksMap.put(runConfiguration, taskMap); + fireBeforeRunTasksUpdated(); + } + + public final void resetBeforeRunTasks(final RunConfiguration runConfiguration) { + myConfigurationToBeforeTasksMap.remove(runConfiguration); + fireBeforeRunTasksUpdated(); + } + + public void addConfiguration(final RunnerAndConfigurationSettings settings, final boolean isShared) { + addConfiguration(settings, isShared, Collections., BeforeRunTask>emptyMap()); + } + + public static RunManagerImpl getInstanceImpl(final Project project) { + return (RunManagerImpl)RunManager.getInstance(project); + } + + public void removeNotExistingSharedConfigurations(final Set existing) { + List removed = new ArrayList(); + for (Iterator> it = myConfigurations.entrySet().iterator(); it.hasNext();) { + Map.Entry c = it.next(); + final RunnerAndConfigurationSettings o = c.getValue(); + if (!o.isTemplate() && isConfigurationShared(o) && !existing.contains(getUniqueName(o.getConfiguration()))) { + removed.add(o); + invalidateConfigurationIcon(o); + it.remove(); + } + } + fireRunConfigurationsRemoved(removed); + } + + public void fireRunConfigurationChanged(RunnerAndConfigurationSettings settings) { + invalidateConfigurationIcon(settings); + myDispatcher.getMulticaster().runConfigurationChanged(settings); + } + + private void fireRunConfigurationsRemoved(List removed) { + for (RunnerAndConfigurationSettings settings : removed) { + myDispatcher.getMulticaster().runConfigurationRemoved(settings); + } + } + + private void fireRunConfigurationSelected() { + myDispatcher.getMulticaster().runConfigurationSelected(); + } + + @Override + public void addRunManagerListener(RunManagerListener listener) { + myDispatcher.addListener(listener); + } + + @Override + public void removeRunManagerListener(RunManagerListener listener) { + myDispatcher.removeListener(listener); + } + + public void fireBeforeRunTasksUpdated() { + myDispatcher.getMulticaster().beforeRunTasksChanged(); + } + + private Map, BeforeRunTaskProvider> myBeforeStepsMap; + private Map> myProviderKeysMap; + + @NotNull + private synchronized BeforeRunTaskProvider getProvider(Key providerId) { + if (myBeforeStepsMap == null) { + initProviderMaps(); + } + return myBeforeStepsMap.get(providerId); + } + + @NotNull + private synchronized Key getProviderKey(String keyString) { + if (myProviderKeysMap == null) { + initProviderMaps(); + } + Key id = myProviderKeysMap.get(keyString); + if (id == null) { + final UnknownBeforeRunTaskProvider provider = new UnknownBeforeRunTaskProvider(keyString); + id = provider.getId(); + myProviderKeysMap.put(keyString, id); + myBeforeStepsMap.put(id, provider); + } + return id; + } + + private void initProviderMaps() { + myBeforeStepsMap = new HashMap, BeforeRunTaskProvider>(); + myProviderKeysMap = new HashMap>(); + for (BeforeRunTaskProvider provider : Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { + final Key id = provider.getId(); + myBeforeStepsMap.put(id, provider); + myProviderKeysMap.put(id.toString(), id); + } + } + + +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java index b00640421fb5..18730145d1f3 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java @@ -1,392 +1,392 @@ -/* - * 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.execution.impl; - -import com.intellij.execution.ExecutionBundle; -import com.intellij.execution.Executor; -import com.intellij.execution.RunnerAndConfigurationSettings; -import com.intellij.execution.RunnerRegistry; -import com.intellij.execution.configurations.*; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Factory; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.JDOMExternalizable; -import com.intellij.openapi.util.WriteExternalException; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author dyoma - */ -public class RunnerAndConfigurationSettingsImpl implements JDOMExternalizable, Cloneable, RunnerAndConfigurationSettings, Comparable { - private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.RunnerAndConfigurationSettings"); - - @NonNls - private static final String RUNNER_ELEMENT = "RunnerSettings"; - @NonNls - private static final String CONFIGURATION_ELEMENT = "ConfigurationWrapper"; - @NonNls - private static final String RUNNER_ID = "RunnerId"; - @NonNls - private static final String CONFIGURATION_TYPE_ATTRIBUTE = "type"; - @NonNls - private static final String FACTORY_NAME_ATTRIBUTE = "factoryName"; - @NonNls - private static final String TEMPLATE_FLAG_ATTRIBUTE = "default"; - @NonNls - public static final String NAME_ATTR = "name"; - @NonNls - protected static final String DUMMY_ELEMENT_NANE = "dummy"; - @NonNls - private static final String TEMPORARY_ATTRIBUTE = "temporary"; - @NonNls - private static final String EDIT_BEFORE_RUN = "editBeforeRun"; - - - /** for compatibility */ - @NonNls - private static final String TEMP_CONFIGURATION = "tempConfiguration"; - - private final RunManagerImpl myManager; - private RunConfiguration myConfiguration; - private boolean myIsTemplate; - - private final Map myRunnerSettings = new HashMap(); - private List myUnloadedRunnerSettings = null; - - private final Map myConfigurationPerRunnerSettings = new HashMap(); - private List myUnloadedConfigurationPerRunnerSettings = null; - - private boolean myTemporary; - private boolean myEditBeforeRun; - - public RunnerAndConfigurationSettingsImpl(RunManagerImpl manager) { - myManager = manager; - } - - public RunnerAndConfigurationSettingsImpl(RunManagerImpl manager, RunConfiguration configuration, boolean isTemplate) { - myManager = manager; - myConfiguration = configuration; - myIsTemplate = isTemplate; - } - - @Nullable - public ConfigurationFactory getFactory() { - return myConfiguration == null ? null : myConfiguration.getFactory(); - } - - public boolean isTemplate() { - return myIsTemplate; - } - - public boolean isTemporary() { - return myTemporary; - } - - public void setTemporary(boolean temporary) { - myTemporary = temporary; - } - - public RunConfiguration getConfiguration() { - return myConfiguration; - } - - public Factory createFactory() { - return new Factory() { - public RunnerAndConfigurationSettings create() { - RunConfiguration configuration = myConfiguration.getFactory().createConfiguration(ExecutionBundle.message("default.run.configuration.name"), myConfiguration); - return new RunnerAndConfigurationSettingsImpl(myManager, configuration, false); - } - }; - } - - public void setName(String name) { - myConfiguration.setName(name); - } - - public String getName() { - return myConfiguration.getName(); - } - - @Override - public void setEditBeforeRun(boolean b) { - myEditBeforeRun = b; - } - - @Override - public boolean isEditBeforeRun() { - return myEditBeforeRun; - } - - @Nullable - private ConfigurationFactory getFactory(final Element element) { - final String typeName = element.getAttributeValue(CONFIGURATION_TYPE_ATTRIBUTE); - String factoryName = element.getAttributeValue(FACTORY_NAME_ATTRIBUTE); - return myManager.getFactory(typeName, factoryName); - } - - public void readExternal(Element element) throws InvalidDataException { - myIsTemplate = Boolean.valueOf(element.getAttributeValue(TEMPLATE_FLAG_ATTRIBUTE)).booleanValue(); - myTemporary = Boolean.valueOf(element.getAttributeValue(TEMPORARY_ATTRIBUTE)).booleanValue() || TEMP_CONFIGURATION.equals(element.getName()); - myEditBeforeRun = Boolean.valueOf(element.getAttributeValue(EDIT_BEFORE_RUN)).booleanValue(); - - final ConfigurationFactory factory = getFactory(element); - if (factory == null) return; - - if (myIsTemplate) { - myConfiguration = myManager.getConfigurationTemplate(factory).getConfiguration(); - } else { - final String name = element.getAttributeValue(NAME_ATTR); - // souldn't call createConfiguration since it calls StepBeforeRunProviders that - // may not be loaded yet. This creates initialization order issue. - myConfiguration = myManager.doCreateConfiguration(name, factory); - } - - myConfiguration.readExternal(element); - List runners = element.getChildren(RUNNER_ELEMENT); - myUnloadedRunnerSettings = null; - for (final Object runner1 : runners) { - Element runnerElement = (Element) runner1; - String id = runnerElement.getAttributeValue(RUNNER_ID); - ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(id); - if (runner != null) { - RunnerSettings settings = createRunnerSettings(runner); - settings.readExternal(runnerElement); - myRunnerSettings.put(runner, settings); - } else { - if (myUnloadedRunnerSettings == null) myUnloadedRunnerSettings = new ArrayList(1); - myUnloadedRunnerSettings.add(runnerElement); - } - } - - List configurations = element.getChildren(CONFIGURATION_ELEMENT); - myUnloadedConfigurationPerRunnerSettings = null; - for (final Object configuration : configurations) { - Element configurationElement = (Element) configuration; - String id = configurationElement.getAttributeValue(RUNNER_ID); - ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(id); - if (runner != null) { - ConfigurationPerRunnerSettings settings = - new ConfigurationPerRunnerSettings(id, myConfiguration.createRunnerSettings(new InfoProvider(runner))); - settings.readExternal(configurationElement); - myConfigurationPerRunnerSettings.put(runner, settings); - } else { - if (myUnloadedConfigurationPerRunnerSettings == null) - myUnloadedConfigurationPerRunnerSettings = new ArrayList(1); - myUnloadedConfigurationPerRunnerSettings.add(configurationElement); - } - } - } - - public void writeExternal(final Element element) throws WriteExternalException { - final ConfigurationFactory factory = myConfiguration.getFactory(); - - if (!(myConfiguration instanceof UnknownRunConfiguration)) { - element.setAttribute(TEMPLATE_FLAG_ATTRIBUTE, String.valueOf(myIsTemplate)); - if (!myIsTemplate) { - element.setAttribute(NAME_ATTR, myConfiguration.getName()); - } - element.setAttribute(CONFIGURATION_TYPE_ATTRIBUTE, factory.getType().getId()); - element.setAttribute(FACTORY_NAME_ATTRIBUTE, factory.getName()); - - if (isEditBeforeRun()) element.setAttribute(EDIT_BEFORE_RUN, String.valueOf(true)); - if (myTemporary) { - element.setAttribute(TEMPORARY_ATTRIBUTE, Boolean.toString(myTemporary)); - } - } - - myConfiguration.writeExternal(element); - - if (!(myConfiguration instanceof UnknownRunConfiguration)) { - final Comparator runnerComparator = createRunnerComparator(); - writeRunnerSettings(runnerComparator, element); - writeConfigurationPerRunnerSettings(runnerComparator, element); - } - } - - private void writeConfigurationPerRunnerSettings(final Comparator runnerComparator, final Element element) - throws WriteExternalException { - final ArrayList configurationPerRunnerSettings = new ArrayList(); - for (ProgramRunner runner : myConfigurationPerRunnerSettings.keySet()) { - ConfigurationPerRunnerSettings settings = myConfigurationPerRunnerSettings.get(runner); - Element runnerElement = new Element(CONFIGURATION_ELEMENT); - settings.writeExternal(runnerElement); - runnerElement.setAttribute(RUNNER_ID, runner.getRunnerId()); - configurationPerRunnerSettings.add(runnerElement); - } - if (myUnloadedConfigurationPerRunnerSettings != null) { - for (Element unloadedCRunnerSetting : myUnloadedConfigurationPerRunnerSettings) { - configurationPerRunnerSettings.add((Element) unloadedCRunnerSetting.clone()); - } - } - Collections.sort(configurationPerRunnerSettings, runnerComparator); - for (Element runnerConfigurationSetting : configurationPerRunnerSettings) { - element.addContent(runnerConfigurationSetting); - } - } - - private void writeRunnerSettings(final Comparator runnerComparator, final Element element) throws WriteExternalException { - final ArrayList runnerSettings = new ArrayList(); - for (ProgramRunner runner : myRunnerSettings.keySet()) { - RunnerSettings settings = myRunnerSettings.get(runner); - Element runnerElement = new Element(RUNNER_ELEMENT); - settings.writeExternal(runnerElement); - runnerElement.setAttribute(RUNNER_ID, runner.getRunnerId()); - runnerSettings.add(runnerElement); - } - if (myUnloadedRunnerSettings != null) { - for (Element unloadedRunnerSetting : myUnloadedRunnerSettings) { - runnerSettings.add((Element) unloadedRunnerSetting.clone()); - } - } - Collections.sort(runnerSettings, runnerComparator); - for (Element runnerSetting : runnerSettings) { - element.addContent(runnerSetting); - } - } - - public void checkSettings() throws RuntimeConfigurationException { - checkSettings(null); - } - - public void checkSettings(@Nullable Executor executor) throws RuntimeConfigurationException { - myConfiguration.checkConfiguration(); - if (myConfiguration instanceof RunConfigurationBase) { - final RunConfigurationBase runConfigurationBase = (RunConfigurationBase) myConfiguration; - Set runners = new HashSet(); - runners.addAll(myRunnerSettings.keySet()); - runners.addAll(myConfigurationPerRunnerSettings.keySet()); - for (ProgramRunner runner : runners) { - if (executor == null || runner.canRun(executor.getId(), myConfiguration)) { - runConfigurationBase.checkRunnerSettings(runner, myRunnerSettings.get(runner), myConfigurationPerRunnerSettings.get(runner)); - } - } - } - } - - private static Comparator createRunnerComparator() { - return new Comparator() { - public int compare(final Element o1, final Element o2) { - final String attributeValue1 = o1.getAttributeValue(RUNNER_ID); - if (attributeValue1 == null) { - return 1; - - } - final String attributeValue2 = o2.getAttributeValue(RUNNER_ID); - if (attributeValue2 == null) { - return -1; - } - return attributeValue1.compareTo(attributeValue2); - } - }; - } - - public RunnerSettings getRunnerSettings(ProgramRunner runner) { - RunnerSettings settings = myRunnerSettings.get(runner); - if (settings == null) { - settings = createRunnerSettings(runner); - myRunnerSettings.put(runner, settings); - } - return settings; - } - - public ConfigurationPerRunnerSettings getConfigurationSettings(ProgramRunner runner) { - ConfigurationPerRunnerSettings settings = myConfigurationPerRunnerSettings.get(runner); - if (settings == null) { - settings = new ConfigurationPerRunnerSettings(runner.getRunnerId(), myConfiguration.createRunnerSettings(new InfoProvider(runner))); - myConfigurationPerRunnerSettings.put(runner, settings); - } - return settings; - } - - @Nullable - public ConfigurationType getType() { - return myConfiguration == null ? null : myConfiguration.getType(); - } - - public RunnerAndConfigurationSettings clone() { - RunnerAndConfigurationSettingsImpl copy = new RunnerAndConfigurationSettingsImpl(myManager, myConfiguration.clone(), false); - copy.importRunnerAndConfigurationSettings(this); - return copy; - } - - public void importRunnerAndConfigurationSettings(RunnerAndConfigurationSettingsImpl template) { - try { - for (ProgramRunner runner : template.myRunnerSettings.keySet()) { - RunnerSettings data = createRunnerSettings(runner); - myRunnerSettings.put(runner, data); - Element temp = new Element(DUMMY_ELEMENT_NANE); - template.myRunnerSettings.get(runner).writeExternal(temp); - data.readExternal(temp); - } - - for (ProgramRunner runner : template.myConfigurationPerRunnerSettings.keySet()) { - ConfigurationPerRunnerSettings data = - new ConfigurationPerRunnerSettings(runner.getRunnerId(), myConfiguration.createRunnerSettings(new InfoProvider(runner))); - myConfigurationPerRunnerSettings.put(runner, data); - Element temp = new Element(DUMMY_ELEMENT_NANE); - template.myConfigurationPerRunnerSettings.get(runner).writeExternal(temp); - data.readExternal(temp); - } - } - catch (WriteExternalException e) { - LOG.error(e); - } - catch (InvalidDataException e) { - LOG.error(e); - } - } - - private RunnerSettings createRunnerSettings(final ProgramRunner runner) { - return new RunnerSettings(runner.createConfigurationData(new InfoProvider(runner)), myConfiguration); - } - - public int compareTo(final Object o) { - if (o instanceof RunnerAndConfigurationSettings) { - return getName().compareTo(((RunnerAndConfigurationSettings) o).getName()); - } - return 0; - } - - private class InfoProvider implements ConfigurationInfoProvider { - private final ProgramRunner myRunner; - - public InfoProvider(ProgramRunner runner) { - myRunner = runner; - } - - public ProgramRunner getRunner() { - return myRunner; - } - - public RunConfiguration getConfiguration() { - return myConfiguration; - } - - public RunnerSettings getRunnerSettings() { - return RunnerAndConfigurationSettingsImpl.this.getRunnerSettings(myRunner); - } - - public ConfigurationPerRunnerSettings getConfigurationSettings() { - return RunnerAndConfigurationSettingsImpl.this.getConfigurationSettings(myRunner); - } - } -} +/* + * 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.execution.impl; + +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.Executor; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.RunnerRegistry; +import com.intellij.execution.configurations.*; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Factory; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.JDOMExternalizable; +import com.intellij.openapi.util.WriteExternalException; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author dyoma + */ +public class RunnerAndConfigurationSettingsImpl implements JDOMExternalizable, Cloneable, RunnerAndConfigurationSettings, Comparable { + private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.RunnerAndConfigurationSettings"); + + @NonNls + private static final String RUNNER_ELEMENT = "RunnerSettings"; + @NonNls + private static final String CONFIGURATION_ELEMENT = "ConfigurationWrapper"; + @NonNls + private static final String RUNNER_ID = "RunnerId"; + @NonNls + private static final String CONFIGURATION_TYPE_ATTRIBUTE = "type"; + @NonNls + private static final String FACTORY_NAME_ATTRIBUTE = "factoryName"; + @NonNls + private static final String TEMPLATE_FLAG_ATTRIBUTE = "default"; + @NonNls + public static final String NAME_ATTR = "name"; + @NonNls + protected static final String DUMMY_ELEMENT_NANE = "dummy"; + @NonNls + private static final String TEMPORARY_ATTRIBUTE = "temporary"; + @NonNls + private static final String EDIT_BEFORE_RUN = "editBeforeRun"; + + + /** for compatibility */ + @NonNls + private static final String TEMP_CONFIGURATION = "tempConfiguration"; + + private final RunManagerImpl myManager; + private RunConfiguration myConfiguration; + private boolean myIsTemplate; + + private final Map myRunnerSettings = new HashMap(); + private List myUnloadedRunnerSettings = null; + + private final Map myConfigurationPerRunnerSettings = new HashMap(); + private List myUnloadedConfigurationPerRunnerSettings = null; + + private boolean myTemporary; + private boolean myEditBeforeRun; + + public RunnerAndConfigurationSettingsImpl(RunManagerImpl manager) { + myManager = manager; + } + + public RunnerAndConfigurationSettingsImpl(RunManagerImpl manager, RunConfiguration configuration, boolean isTemplate) { + myManager = manager; + myConfiguration = configuration; + myIsTemplate = isTemplate; + } + + @Nullable + public ConfigurationFactory getFactory() { + return myConfiguration == null ? null : myConfiguration.getFactory(); + } + + public boolean isTemplate() { + return myIsTemplate; + } + + public boolean isTemporary() { + return myTemporary; + } + + public void setTemporary(boolean temporary) { + myTemporary = temporary; + } + + public RunConfiguration getConfiguration() { + return myConfiguration; + } + + public Factory createFactory() { + return new Factory() { + public RunnerAndConfigurationSettings create() { + RunConfiguration configuration = myConfiguration.getFactory().createConfiguration(ExecutionBundle.message("default.run.configuration.name"), myConfiguration); + return new RunnerAndConfigurationSettingsImpl(myManager, configuration, false); + } + }; + } + + public void setName(String name) { + myConfiguration.setName(name); + } + + public String getName() { + return myConfiguration.getName(); + } + + @Override + public void setEditBeforeRun(boolean b) { + myEditBeforeRun = b; + } + + @Override + public boolean isEditBeforeRun() { + return myEditBeforeRun; + } + + @Nullable + private ConfigurationFactory getFactory(final Element element) { + final String typeName = element.getAttributeValue(CONFIGURATION_TYPE_ATTRIBUTE); + String factoryName = element.getAttributeValue(FACTORY_NAME_ATTRIBUTE); + return myManager.getFactory(typeName, factoryName); + } + + public void readExternal(Element element) throws InvalidDataException { + myIsTemplate = Boolean.valueOf(element.getAttributeValue(TEMPLATE_FLAG_ATTRIBUTE)).booleanValue(); + myTemporary = Boolean.valueOf(element.getAttributeValue(TEMPORARY_ATTRIBUTE)).booleanValue() || TEMP_CONFIGURATION.equals(element.getName()); + myEditBeforeRun = Boolean.valueOf(element.getAttributeValue(EDIT_BEFORE_RUN)).booleanValue(); + + final ConfigurationFactory factory = getFactory(element); + if (factory == null) return; + + if (myIsTemplate) { + myConfiguration = myManager.getConfigurationTemplate(factory).getConfiguration(); + } else { + final String name = element.getAttributeValue(NAME_ATTR); + // souldn't call createConfiguration since it calls StepBeforeRunProviders that + // may not be loaded yet. This creates initialization order issue. + myConfiguration = myManager.doCreateConfiguration(name, factory); + } + + myConfiguration.readExternal(element); + List runners = element.getChildren(RUNNER_ELEMENT); + myUnloadedRunnerSettings = null; + for (final Object runner1 : runners) { + Element runnerElement = (Element) runner1; + String id = runnerElement.getAttributeValue(RUNNER_ID); + ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(id); + if (runner != null) { + RunnerSettings settings = createRunnerSettings(runner); + settings.readExternal(runnerElement); + myRunnerSettings.put(runner, settings); + } else { + if (myUnloadedRunnerSettings == null) myUnloadedRunnerSettings = new ArrayList(1); + myUnloadedRunnerSettings.add(runnerElement); + } + } + + List configurations = element.getChildren(CONFIGURATION_ELEMENT); + myUnloadedConfigurationPerRunnerSettings = null; + for (final Object configuration : configurations) { + Element configurationElement = (Element) configuration; + String id = configurationElement.getAttributeValue(RUNNER_ID); + ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(id); + if (runner != null) { + ConfigurationPerRunnerSettings settings = + new ConfigurationPerRunnerSettings(id, myConfiguration.createRunnerSettings(new InfoProvider(runner))); + settings.readExternal(configurationElement); + myConfigurationPerRunnerSettings.put(runner, settings); + } else { + if (myUnloadedConfigurationPerRunnerSettings == null) + myUnloadedConfigurationPerRunnerSettings = new ArrayList(1); + myUnloadedConfigurationPerRunnerSettings.add(configurationElement); + } + } + } + + public void writeExternal(final Element element) throws WriteExternalException { + final ConfigurationFactory factory = myConfiguration.getFactory(); + + if (!(myConfiguration instanceof UnknownRunConfiguration)) { + element.setAttribute(TEMPLATE_FLAG_ATTRIBUTE, String.valueOf(myIsTemplate)); + if (!myIsTemplate) { + element.setAttribute(NAME_ATTR, myConfiguration.getName()); + } + element.setAttribute(CONFIGURATION_TYPE_ATTRIBUTE, factory.getType().getId()); + element.setAttribute(FACTORY_NAME_ATTRIBUTE, factory.getName()); + + if (isEditBeforeRun()) element.setAttribute(EDIT_BEFORE_RUN, String.valueOf(true)); + if (myTemporary) { + element.setAttribute(TEMPORARY_ATTRIBUTE, Boolean.toString(myTemporary)); + } + } + + myConfiguration.writeExternal(element); + + if (!(myConfiguration instanceof UnknownRunConfiguration)) { + final Comparator runnerComparator = createRunnerComparator(); + writeRunnerSettings(runnerComparator, element); + writeConfigurationPerRunnerSettings(runnerComparator, element); + } + } + + private void writeConfigurationPerRunnerSettings(final Comparator runnerComparator, final Element element) + throws WriteExternalException { + final ArrayList configurationPerRunnerSettings = new ArrayList(); + for (ProgramRunner runner : myConfigurationPerRunnerSettings.keySet()) { + ConfigurationPerRunnerSettings settings = myConfigurationPerRunnerSettings.get(runner); + Element runnerElement = new Element(CONFIGURATION_ELEMENT); + settings.writeExternal(runnerElement); + runnerElement.setAttribute(RUNNER_ID, runner.getRunnerId()); + configurationPerRunnerSettings.add(runnerElement); + } + if (myUnloadedConfigurationPerRunnerSettings != null) { + for (Element unloadedCRunnerSetting : myUnloadedConfigurationPerRunnerSettings) { + configurationPerRunnerSettings.add((Element) unloadedCRunnerSetting.clone()); + } + } + Collections.sort(configurationPerRunnerSettings, runnerComparator); + for (Element runnerConfigurationSetting : configurationPerRunnerSettings) { + element.addContent(runnerConfigurationSetting); + } + } + + private void writeRunnerSettings(final Comparator runnerComparator, final Element element) throws WriteExternalException { + final ArrayList runnerSettings = new ArrayList(); + for (ProgramRunner runner : myRunnerSettings.keySet()) { + RunnerSettings settings = myRunnerSettings.get(runner); + Element runnerElement = new Element(RUNNER_ELEMENT); + settings.writeExternal(runnerElement); + runnerElement.setAttribute(RUNNER_ID, runner.getRunnerId()); + runnerSettings.add(runnerElement); + } + if (myUnloadedRunnerSettings != null) { + for (Element unloadedRunnerSetting : myUnloadedRunnerSettings) { + runnerSettings.add((Element) unloadedRunnerSetting.clone()); + } + } + Collections.sort(runnerSettings, runnerComparator); + for (Element runnerSetting : runnerSettings) { + element.addContent(runnerSetting); + } + } + + public void checkSettings() throws RuntimeConfigurationException { + checkSettings(null); + } + + public void checkSettings(@Nullable Executor executor) throws RuntimeConfigurationException { + myConfiguration.checkConfiguration(); + if (myConfiguration instanceof RunConfigurationBase) { + final RunConfigurationBase runConfigurationBase = (RunConfigurationBase) myConfiguration; + Set runners = new HashSet(); + runners.addAll(myRunnerSettings.keySet()); + runners.addAll(myConfigurationPerRunnerSettings.keySet()); + for (ProgramRunner runner : runners) { + if (executor == null || runner.canRun(executor.getId(), myConfiguration)) { + runConfigurationBase.checkRunnerSettings(runner, myRunnerSettings.get(runner), myConfigurationPerRunnerSettings.get(runner)); + } + } + } + } + + private static Comparator createRunnerComparator() { + return new Comparator() { + public int compare(final Element o1, final Element o2) { + final String attributeValue1 = o1.getAttributeValue(RUNNER_ID); + if (attributeValue1 == null) { + return 1; + + } + final String attributeValue2 = o2.getAttributeValue(RUNNER_ID); + if (attributeValue2 == null) { + return -1; + } + return attributeValue1.compareTo(attributeValue2); + } + }; + } + + public RunnerSettings getRunnerSettings(ProgramRunner runner) { + RunnerSettings settings = myRunnerSettings.get(runner); + if (settings == null) { + settings = createRunnerSettings(runner); + myRunnerSettings.put(runner, settings); + } + return settings; + } + + public ConfigurationPerRunnerSettings getConfigurationSettings(ProgramRunner runner) { + ConfigurationPerRunnerSettings settings = myConfigurationPerRunnerSettings.get(runner); + if (settings == null) { + settings = new ConfigurationPerRunnerSettings(runner.getRunnerId(), myConfiguration.createRunnerSettings(new InfoProvider(runner))); + myConfigurationPerRunnerSettings.put(runner, settings); + } + return settings; + } + + @Nullable + public ConfigurationType getType() { + return myConfiguration == null ? null : myConfiguration.getType(); + } + + public RunnerAndConfigurationSettings clone() { + RunnerAndConfigurationSettingsImpl copy = new RunnerAndConfigurationSettingsImpl(myManager, myConfiguration.clone(), false); + copy.importRunnerAndConfigurationSettings(this); + return copy; + } + + public void importRunnerAndConfigurationSettings(RunnerAndConfigurationSettingsImpl template) { + try { + for (ProgramRunner runner : template.myRunnerSettings.keySet()) { + RunnerSettings data = createRunnerSettings(runner); + myRunnerSettings.put(runner, data); + Element temp = new Element(DUMMY_ELEMENT_NANE); + template.myRunnerSettings.get(runner).writeExternal(temp); + data.readExternal(temp); + } + + for (ProgramRunner runner : template.myConfigurationPerRunnerSettings.keySet()) { + ConfigurationPerRunnerSettings data = + new ConfigurationPerRunnerSettings(runner.getRunnerId(), myConfiguration.createRunnerSettings(new InfoProvider(runner))); + myConfigurationPerRunnerSettings.put(runner, data); + Element temp = new Element(DUMMY_ELEMENT_NANE); + template.myConfigurationPerRunnerSettings.get(runner).writeExternal(temp); + data.readExternal(temp); + } + } + catch (WriteExternalException e) { + LOG.error(e); + } + catch (InvalidDataException e) { + LOG.error(e); + } + } + + private RunnerSettings createRunnerSettings(final ProgramRunner runner) { + return new RunnerSettings(runner.createConfigurationData(new InfoProvider(runner)), myConfiguration); + } + + public int compareTo(final Object o) { + if (o instanceof RunnerAndConfigurationSettings) { + return getName().compareTo(((RunnerAndConfigurationSettings) o).getName()); + } + return 0; + } + + private class InfoProvider implements ConfigurationInfoProvider { + private final ProgramRunner myRunner; + + public InfoProvider(ProgramRunner runner) { + myRunner = runner; + } + + public ProgramRunner getRunner() { + return myRunner; + } + + public RunConfiguration getConfiguration() { + return myConfiguration; + } + + public RunnerSettings getRunnerSettings() { + return RunnerAndConfigurationSettingsImpl.this.getRunnerSettings(myRunner); + } + + public ConfigurationPerRunnerSettings getConfigurationSettings() { + return RunnerAndConfigurationSettingsImpl.this.getConfigurationSettings(myRunner); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/rmi/RemoteProcessSupport.java b/platform/lang-impl/src/com/intellij/execution/rmi/RemoteProcessSupport.java index d141540cae37..b1bcb1b50522 100644 --- a/platform/lang-impl/src/com/intellij/execution/rmi/RemoteProcessSupport.java +++ b/platform/lang-impl/src/com/intellij/execution/rmi/RemoteProcessSupport.java @@ -1,333 +1,333 @@ -package com.intellij.execution.rmi; - -import com.intellij.execution.ExecutionException; -import com.intellij.execution.ExecutionResult; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.RunProfile; -import com.intellij.execution.configurations.RunProfileState; -import com.intellij.execution.executors.DefaultRunExecutor; -import com.intellij.execution.process.ProcessEvent; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.process.ProcessListener; -import com.intellij.execution.process.ProcessOutputTypes; -import com.intellij.execution.runners.DefaultProgramRunner; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.ThrowableComputable; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.rmi.PortableRemoteObject; -import java.rmi.Remote; -import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * @author Gregory.Shrago - */ -public abstract class RemoteProcessSupport { - public static final Logger LOG = Logger.getInstance("#" + RemoteProcessSupport.class); - - private final Class myValueClass; - private final HashMap, Object> myProcMap = - new HashMap, Object>(); - - static { - RemoteServer.setupRMI(); - } - - public RemoteProcessSupport(Class valueClass) { - myValueClass = valueClass; - } - - protected abstract void fireModificationCountChanged(); - - protected abstract String getName(Target target); - - protected void logText(Parameters configuration, ProcessEvent event, Key outputType, Object info) { - } - - public void stopAll() { - stopAll(false); - } - - public void stopAll(boolean wait) { - final ArrayList allHandlers = new ArrayList(); - synchronized (myProcMap) { - for (Object o : myProcMap.values()) { - final ProcessHandler handler = o instanceof PendingInfo ? ((PendingInfo)o).handler : o instanceof Info ? ((Info)o).handler : null; - ContainerUtil.addIfNotNull(handler, allHandlers); - } - myProcMap.clear(); - } - for (ProcessHandler handler : allHandlers) { - handler.destroyProcess(); - if (wait) handler.waitFor(); - } - } - - public List getActiveConfigurations(@NotNull Target target) { - final ArrayList result = new ArrayList(); - synchronized (myProcMap) { - for (Pair pair : myProcMap.keySet()) { - if (pair.first == target) { - result.add(pair.second); - } - } - } - return result; - } - - public EntryPoint acquire(@NotNull final Target target, @NotNull final Parameters configuration) throws Exception { - ApplicationManagerEx.getApplicationEx().assertTimeConsuming(); - - final Ref ref = Ref.create(null); - final Pair key = Pair.create(target, configuration); - if (!getExistingInfo(ref, key)) { - startProcess(target, configuration, key); - if (ref.isNull()) { - try { - synchronized (ref) { - while (ref.isNull()) { - ref.wait(1000); - ProgressManager.checkCanceled(); - } - } - } - catch (InterruptedException e) { - ProgressManager.checkCanceled(); - } - } - } - if (ref.isNull()) throw new RuntimeException("Unable to acquire remote proxy for: " + getName(target)); - final Info info = ref.get(); - if (info.handler == null) throw new RuntimeException(info.name); - return acquire(info); - } - - public void release(@NotNull Target target, @Nullable Parameters configuration) { - final ArrayList handlersToStop = new ArrayList(); - final ArrayList> keysToRemove = new ArrayList>(); - synchronized (myProcMap) { - for (Pair pair : myProcMap.keySet()) { - if (pair.first == target && (configuration == null || pair.second == configuration)) { - final Object o = myProcMap.get(pair); - final ProcessHandler handler = o instanceof PendingInfo ? ((PendingInfo)o).handler : o instanceof Info ? ((Info)o).handler : null; - if (handler != null) { - handlersToStop.add(handler); - keysToRemove.add(pair); - } - else { - // todo what to do??? - } - } - } - myProcMap.keySet().removeAll(keysToRemove); - } - for (ProcessHandler handler : handlersToStop) { - handler.destroyProcess(); - } - fireModificationCountChanged(); - } - - private void startProcess(Target target, Parameters configuration, Pair key) { - final ProgramRunner runner = new DefaultProgramRunner() { - @NotNull - public String getRunnerId() { - return "MyRunner"; - } - - public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) { - return true; - } - }; - try { - final Executor executor = DefaultRunExecutor.getRunExecutorInstance(); - final RunProfileState state = getRunProfileState(target, configuration, executor); - final ExecutionResult result = state.execute(executor, runner); - final ProcessHandler processHandler = result.getProcessHandler(); - processHandler.addProcessListener(getProcessListener(key)); - processHandler.startNotify(); - } - catch (ExecutionException e) { - handleProcessTerminated(key, e.getMessage()); - } - } - - protected abstract RunProfileState getRunProfileState(Target target, Parameters configuration, Executor executor) - throws ExecutionException; - - private boolean getExistingInfo(Ref ref, final Pair key) { - Object info; - synchronized (myProcMap) { - info = myProcMap.get(key); - try { - while (info != null && - (!(info instanceof Info) || ((Info)info).handler.isProcessTerminating() || ((Info)info).handler.isProcessTerminated())) { - myProcMap.wait(1000); - ProgressManager.checkCanceled(); - info = myProcMap.get(key); - } - } - catch (InterruptedException e) { - ProgressManager.checkCanceled(); - } - if (info == null) { - myProcMap.put(key, new PendingInfo(ref, null)); - } - } - if (info != null) { - if (info instanceof Info) { - synchronized (ref) { - ref.set((Info)info); - ref.notifyAll(); - } - } - return true; - } - return false; - } - - private EntryPoint acquire(final Info port) throws Exception { - final EntryPoint result = RemoteUtil.executeWithClassLoader(new ThrowableComputable() { - public EntryPoint compute() throws Exception { - final Registry registry = LocateRegistry.getRegistry("localhost", port.port); - final Remote remote = registry.lookup(port.name); - if (Remote.class.isAssignableFrom(myValueClass)) { - return RemoteUtil.substituteClassLoader(narrowImpl(remote, myValueClass), myValueClass.getClassLoader()); - } - else { - return RemoteUtil.castToLocal(remote, myValueClass); - } - } - }, getClass().getClassLoader()); // should be the loader of client plugin - // init hard ref that will keep it from DGC and thus preventing from System.exit - port.entryPointHardRef = result; - return result; - } - - private static T narrowImpl(Remote remote, Class to) { - return (T)(to.isInstance(remote) ? remote : PortableRemoteObject.narrow(remote, to)); - } - - private ProcessListener getProcessListener(final Pair key) { - return new ProcessListener() { - public void startNotified(ProcessEvent event) { - final ProcessHandler processHandler = event.getProcessHandler(); - processHandler.putUserData(ProcessHandler.SILENTLY_DESTROY_ON_CLOSE, Boolean.TRUE); - final Object o; - synchronized (myProcMap) { - o = myProcMap.get(key); - if (o instanceof PendingInfo) { - myProcMap.put(key, new PendingInfo(((PendingInfo)o).ref, processHandler)); - } - } - } - - public void processTerminated(ProcessEvent event) { - handleProcessTerminated(key, null); - fireModificationCountChanged(); - } - - public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) { - } - - public void onTextAvailable(ProcessEvent event, Key outputType) { - final String text = StringUtil.notNullize(event.getText()); - if (outputType == ProcessOutputTypes.STDERR) { - LOG.warn(text.trim()); - } - else { - LOG.info(text.trim()); - } - - Info result = null; - final PendingInfo info; - synchronized (myProcMap) { - final Object o = myProcMap.get(key); - logText(key.second, event, outputType, o); - if (o instanceof PendingInfo) { - info = (PendingInfo)o; - if (outputType == ProcessOutputTypes.STDOUT) { - final String prefix = "Port/ID:"; - if (text != null && text.startsWith(prefix)) { - final String pair = text.substring(prefix.length()).trim(); - final int idx = pair.indexOf("/"); - result = new Info(info.handler, Integer.parseInt(pair.substring(0, idx)), pair.substring(idx + 1)); - myProcMap.put(key, result); - myProcMap.notifyAll(); - } - } - else if (outputType == ProcessOutputTypes.STDERR) { - info.stderr.append(text); - } - } - else { - info = null; - } - } - if (result != null) { - synchronized (info.ref) { - info.ref.set(result); - info.ref.notifyAll(); - } - fireModificationCountChanged(); - } - } - }; - } - - private void handleProcessTerminated(Pair key, String errorMessage) { - Object o; - final PendingInfo pendingInfo; - synchronized (myProcMap) { - o = myProcMap.remove(key); - pendingInfo = o instanceof PendingInfo ? (PendingInfo)o : null; - if (pendingInfo != null && (pendingInfo.stderr.length() > 0 || pendingInfo.ref.isNull())) { - if (errorMessage != null) pendingInfo.stderr.append(errorMessage); - pendingInfo.ref.set(new Info(null, -1, pendingInfo.stderr.toString())); - } - myProcMap.notifyAll(); - } - if (pendingInfo != null) { - synchronized (pendingInfo.ref) { - pendingInfo.ref.notifyAll(); - } - } - } - - private static class PendingInfo { - final Ref ref; - final ProcessHandler handler; - final StringBuilder stderr = new StringBuilder(); - - private PendingInfo(Ref ref, ProcessHandler handler) { - this.ref = ref; - this.handler = handler; - } - } - - private static class Info { - final ProcessHandler handler; - final int port; - final String name; - Object entryPointHardRef; - - private Info(ProcessHandler handler, int port, String name) { - this.handler = handler; - this.port = port; - this.name = name; - } - } - -} +package com.intellij.execution.rmi; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessListener; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.runners.DefaultProgramRunner; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.rmi.PortableRemoteObject; +import java.rmi.Remote; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * @author Gregory.Shrago + */ +public abstract class RemoteProcessSupport { + public static final Logger LOG = Logger.getInstance("#" + RemoteProcessSupport.class); + + private final Class myValueClass; + private final HashMap, Object> myProcMap = + new HashMap, Object>(); + + static { + RemoteServer.setupRMI(); + } + + public RemoteProcessSupport(Class valueClass) { + myValueClass = valueClass; + } + + protected abstract void fireModificationCountChanged(); + + protected abstract String getName(Target target); + + protected void logText(Parameters configuration, ProcessEvent event, Key outputType, Object info) { + } + + public void stopAll() { + stopAll(false); + } + + public void stopAll(boolean wait) { + final ArrayList allHandlers = new ArrayList(); + synchronized (myProcMap) { + for (Object o : myProcMap.values()) { + final ProcessHandler handler = o instanceof PendingInfo ? ((PendingInfo)o).handler : o instanceof Info ? ((Info)o).handler : null; + ContainerUtil.addIfNotNull(handler, allHandlers); + } + myProcMap.clear(); + } + for (ProcessHandler handler : allHandlers) { + handler.destroyProcess(); + if (wait) handler.waitFor(); + } + } + + public List getActiveConfigurations(@NotNull Target target) { + final ArrayList result = new ArrayList(); + synchronized (myProcMap) { + for (Pair pair : myProcMap.keySet()) { + if (pair.first == target) { + result.add(pair.second); + } + } + } + return result; + } + + public EntryPoint acquire(@NotNull final Target target, @NotNull final Parameters configuration) throws Exception { + ApplicationManagerEx.getApplicationEx().assertTimeConsuming(); + + final Ref ref = Ref.create(null); + final Pair key = Pair.create(target, configuration); + if (!getExistingInfo(ref, key)) { + startProcess(target, configuration, key); + if (ref.isNull()) { + try { + synchronized (ref) { + while (ref.isNull()) { + ref.wait(1000); + ProgressManager.checkCanceled(); + } + } + } + catch (InterruptedException e) { + ProgressManager.checkCanceled(); + } + } + } + if (ref.isNull()) throw new RuntimeException("Unable to acquire remote proxy for: " + getName(target)); + final Info info = ref.get(); + if (info.handler == null) throw new RuntimeException(info.name); + return acquire(info); + } + + public void release(@NotNull Target target, @Nullable Parameters configuration) { + final ArrayList handlersToStop = new ArrayList(); + final ArrayList> keysToRemove = new ArrayList>(); + synchronized (myProcMap) { + for (Pair pair : myProcMap.keySet()) { + if (pair.first == target && (configuration == null || pair.second == configuration)) { + final Object o = myProcMap.get(pair); + final ProcessHandler handler = o instanceof PendingInfo ? ((PendingInfo)o).handler : o instanceof Info ? ((Info)o).handler : null; + if (handler != null) { + handlersToStop.add(handler); + keysToRemove.add(pair); + } + else { + // todo what to do??? + } + } + } + myProcMap.keySet().removeAll(keysToRemove); + } + for (ProcessHandler handler : handlersToStop) { + handler.destroyProcess(); + } + fireModificationCountChanged(); + } + + private void startProcess(Target target, Parameters configuration, Pair key) { + final ProgramRunner runner = new DefaultProgramRunner() { + @NotNull + public String getRunnerId() { + return "MyRunner"; + } + + public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) { + return true; + } + }; + try { + final Executor executor = DefaultRunExecutor.getRunExecutorInstance(); + final RunProfileState state = getRunProfileState(target, configuration, executor); + final ExecutionResult result = state.execute(executor, runner); + final ProcessHandler processHandler = result.getProcessHandler(); + processHandler.addProcessListener(getProcessListener(key)); + processHandler.startNotify(); + } + catch (ExecutionException e) { + handleProcessTerminated(key, e.getMessage()); + } + } + + protected abstract RunProfileState getRunProfileState(Target target, Parameters configuration, Executor executor) + throws ExecutionException; + + private boolean getExistingInfo(Ref ref, final Pair key) { + Object info; + synchronized (myProcMap) { + info = myProcMap.get(key); + try { + while (info != null && + (!(info instanceof Info) || ((Info)info).handler.isProcessTerminating() || ((Info)info).handler.isProcessTerminated())) { + myProcMap.wait(1000); + ProgressManager.checkCanceled(); + info = myProcMap.get(key); + } + } + catch (InterruptedException e) { + ProgressManager.checkCanceled(); + } + if (info == null) { + myProcMap.put(key, new PendingInfo(ref, null)); + } + } + if (info != null) { + if (info instanceof Info) { + synchronized (ref) { + ref.set((Info)info); + ref.notifyAll(); + } + } + return true; + } + return false; + } + + private EntryPoint acquire(final Info port) throws Exception { + final EntryPoint result = RemoteUtil.executeWithClassLoader(new ThrowableComputable() { + public EntryPoint compute() throws Exception { + final Registry registry = LocateRegistry.getRegistry("localhost", port.port); + final Remote remote = registry.lookup(port.name); + if (Remote.class.isAssignableFrom(myValueClass)) { + return RemoteUtil.substituteClassLoader(narrowImpl(remote, myValueClass), myValueClass.getClassLoader()); + } + else { + return RemoteUtil.castToLocal(remote, myValueClass); + } + } + }, getClass().getClassLoader()); // should be the loader of client plugin + // init hard ref that will keep it from DGC and thus preventing from System.exit + port.entryPointHardRef = result; + return result; + } + + private static T narrowImpl(Remote remote, Class to) { + return (T)(to.isInstance(remote) ? remote : PortableRemoteObject.narrow(remote, to)); + } + + private ProcessListener getProcessListener(final Pair key) { + return new ProcessListener() { + public void startNotified(ProcessEvent event) { + final ProcessHandler processHandler = event.getProcessHandler(); + processHandler.putUserData(ProcessHandler.SILENTLY_DESTROY_ON_CLOSE, Boolean.TRUE); + final Object o; + synchronized (myProcMap) { + o = myProcMap.get(key); + if (o instanceof PendingInfo) { + myProcMap.put(key, new PendingInfo(((PendingInfo)o).ref, processHandler)); + } + } + } + + public void processTerminated(ProcessEvent event) { + handleProcessTerminated(key, null); + fireModificationCountChanged(); + } + + public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) { + } + + public void onTextAvailable(ProcessEvent event, Key outputType) { + final String text = StringUtil.notNullize(event.getText()); + if (outputType == ProcessOutputTypes.STDERR) { + LOG.warn(text.trim()); + } + else { + LOG.info(text.trim()); + } + + Info result = null; + final PendingInfo info; + synchronized (myProcMap) { + final Object o = myProcMap.get(key); + logText(key.second, event, outputType, o); + if (o instanceof PendingInfo) { + info = (PendingInfo)o; + if (outputType == ProcessOutputTypes.STDOUT) { + final String prefix = "Port/ID:"; + if (text != null && text.startsWith(prefix)) { + final String pair = text.substring(prefix.length()).trim(); + final int idx = pair.indexOf("/"); + result = new Info(info.handler, Integer.parseInt(pair.substring(0, idx)), pair.substring(idx + 1)); + myProcMap.put(key, result); + myProcMap.notifyAll(); + } + } + else if (outputType == ProcessOutputTypes.STDERR) { + info.stderr.append(text); + } + } + else { + info = null; + } + } + if (result != null) { + synchronized (info.ref) { + info.ref.set(result); + info.ref.notifyAll(); + } + fireModificationCountChanged(); + } + } + }; + } + + private void handleProcessTerminated(Pair key, String errorMessage) { + Object o; + final PendingInfo pendingInfo; + synchronized (myProcMap) { + o = myProcMap.remove(key); + pendingInfo = o instanceof PendingInfo ? (PendingInfo)o : null; + if (pendingInfo != null && (pendingInfo.stderr.length() > 0 || pendingInfo.ref.isNull())) { + if (errorMessage != null) pendingInfo.stderr.append(errorMessage); + pendingInfo.ref.set(new Info(null, -1, pendingInfo.stderr.toString())); + } + myProcMap.notifyAll(); + } + if (pendingInfo != null) { + synchronized (pendingInfo.ref) { + pendingInfo.ref.notifyAll(); + } + } + } + + private static class PendingInfo { + final Ref ref; + final ProcessHandler handler; + final StringBuilder stderr = new StringBuilder(); + + private PendingInfo(Ref ref, ProcessHandler handler) { + this.ref = ref; + this.handler = handler; + } + } + + private static class Info { + final ProcessHandler handler; + final int port; + final String name; + Object entryPointHardRef; + + private Info(ProcessHandler handler, int port, String name) { + this.handler = handler; + this.port = port; + this.name = name; + } + } + +} diff --git a/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java b/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java index 779c63f58641..ef446b26b6a3 100644 --- a/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java +++ b/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java @@ -1,708 +1,708 @@ -/* - * 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.formatting; - -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiWhiteSpace; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.formatter.FormattingDocumentModelImpl; - -import java.util.ArrayList; - -/** - * Object-level representation of continuous white space at document. Either line feed or tabulation or space is considered to be 'white-space'. - * I.e. document text fragment like {@code '\t \t\t\n\t \t'} may be considered as a continuous white space and may be represented as a {@link WhiteSpace} object. - *

- * Provides number of properties that describe encapsulated continuous white space: - *

    - *
  • {@link #getLineFeeds() lineFeeds};
  • - *
  • {@link #getSpaces() spaces};
  • - *
  • {@link #getIndentSpaces()};
  • - *
- *

- * Provides ability to build string representation of the managed settings taking into consideration user settings for tabulation vs white space usage, tabulation - * size etc. - *

- * Not thread-safe. - */ -class WhiteSpace { - - private static final char LINE_FEED = '\n'; - - private final int myStart; - private int myEnd; - - private int mySpaces; - private int myIndentSpaces; - - private CharSequence myInitial; - private int myFlags; - private boolean myForceSkipTabulationsUsage; - - private static final byte FIRST = 1; - private static final byte SAFE = 0x2; - private static final byte KEEP_FIRST_COLUMN = 0x4; - private static final byte LINE_FEEDS_ARE_READ_ONLY = 0x8; - private static final byte READ_ONLY = 0x10; - private static final byte CONTAINS_LF_INITIALLY = 0x20; - private static final byte CONTAINS_SPACES_INITIALLY = 0x40; - private static final int LF_COUNT_SHIFT = 7; - private static final int MAX_LF_COUNT = 1 << 24; - - /** - * Creates new WhiteSpace object with the given start offset and a flag that shows if current white space is - * the first white space. - *

- * Note: {@link #getEndOffset() end offset} value is the same as the {@link #getStartOffset() start offset} for - * the newly constructed object. {@link #append(int, FormattingDocumentModel, CodeStyleSettings.IndentOptions)} should be - * called in order to apply desired end offset. - * - * @param startOffset start offset to use - * @param isFirst flag that shows if current white space is the first - */ - public WhiteSpace(int startOffset, boolean isFirst) { - myStart = startOffset; - myEnd = startOffset; - setIsFirstWhiteSpace(isFirst); - } - - /** - * Applies new end offset to the current {@link WhiteSpace} object. - *

- * Namely, performs the following: - *

    - *
  1. Checks if new end offset can be applied, return in case of negative answer;
  2. - *
  3. - * Processes all new symbols introduced by the new end offset value, calculates number of line feeds, - * white spaces and tabulations between them and updates {@link #getLineFeeds() lineFeeds}, {@link #getSpaces() spaces}, - * {@link #getIndentSpaces() indentSpaces} and {@link #getTotalSpaces() totalSpaces} properties accordingly; - *
  4. - *
- * - * @param newEndOffset new end offset value - * @param model formatting model that is used to access to the underlying document text - * @param options indent formatting options - */ - public void append(int newEndOffset, FormattingDocumentModel model, CodeStyleSettings.IndentOptions options) { - final int oldEndOffset = myEnd; - if (newEndOffset == oldEndOffset) return; - if (myStart >= newEndOffset) { - InitialInfoBuilder.assertInvalidRanges(myStart, - newEndOffset, - model, - "some block intersects with whitespace" - ); - } - - myEnd = newEndOffset; - TextRange range = new TextRange(myStart, myEnd); - CharSequence oldText = myInitial; - myInitial = model.getText(range); - - if (!coveredByBlock(model)) { - InitialInfoBuilder.assertInvalidRanges(myStart, - myEnd, - model, - "nonempty text is not covered by block" - ); - } - - // There is a possible case that this method is called more than once on the same object. We want to - if (newEndOffset > oldEndOffset) { - refreshStateOnEndOffsetIncrease(newEndOffset, oldEndOffset, options.TAB_SIZE); - } else { - refreshStateOnEndOffsetDecrease(oldText, newEndOffset, oldEndOffset, options.TAB_SIZE); - } - - if (getLineFeeds() > 0) myFlags |= CONTAINS_LF_INITIALLY; - else myFlags &= ~CONTAINS_LF_INITIALLY; - - final int totalSpaces = getTotalSpaces(); - if (totalSpaces > 0) myFlags |= CONTAINS_SPACES_INITIALLY; - else myFlags &=~ CONTAINS_SPACES_INITIALLY; - } - - /** - * Allows to check if 'myInitial' property value stands for continuous white space text. - *

- * The text is considered to be continuous 'white space' at following cases: - *

    - *
  • 'myInitial' is empty string or string that contains white spaces only;
  • - *
  • 'myInitial' is a CDATA string which content is empty or consists from white spaces only;
  • - *
  • 'myInitial' string belongs to the same {@link PsiWhiteSpace} element;
  • - *
- * - * @param model formatting model that is used to provide access to the PSI API if necessary - * @return true if 'myInitial' property value stands for white space; - * false otherwise - */ - private boolean coveredByBlock(final FormattingDocumentModel model) { - if (myInitial == null) return true; - if (model.containsWhiteSpaceSymbolsOnly(myStart, myEnd)) return true; - - if (!(model instanceof FormattingDocumentModelImpl)) return false; - PsiFile psiFile = ((FormattingDocumentModelImpl)model).getFile(); - if (psiFile == null) return false; - PsiElement start = psiFile.findElementAt(myStart); - PsiElement end = psiFile.findElementAt(myEnd-1); - return start == end && start instanceof PsiWhiteSpace; // there maybe non-white text inside CDATA-encoded injected elements - } - - private void refreshStateOnEndOffsetIncrease(int newEndOffset, int oldEndOffset, int tabSize) { - assert newEndOffset > oldEndOffset; - for (int i = oldEndOffset - myStart; i < newEndOffset - myStart; i++) { - switch (myInitial.charAt(i)) { - case LINE_FEED: - setLineFeeds(getLineFeeds() + 1); - mySpaces = 0; - myIndentSpaces = 0; - break; - case '\t': - myIndentSpaces += tabSize; - break; - default: mySpaces++; - } - } - } - - private void refreshStateOnEndOffsetDecrease(CharSequence oldText, int newEndOffset, int oldEndOffset, int tabSize) { - assert newEndOffset < oldEndOffset; - int lineFeedsNumberAtRemovedText = 0; - int spacesNumberAtRemovedText = 0; - int indentSpacesNumberAtRemovedText = 0; - for (int i = oldEndOffset - 1; i >= newEndOffset; i--) { - switch (oldText.charAt(i)) { - case LINE_FEED: ++lineFeedsNumberAtRemovedText; break; - case ' ': ++spacesNumberAtRemovedText; break; - case '\t': indentSpacesNumberAtRemovedText += tabSize; break; - } - } - - // There are no line feeds at remained text, hence, we can just subtract number of spaces and indent spaces - // from removed text and finish. - if (getLineFeeds() - lineFeedsNumberAtRemovedText <= 0) { - setLineFeeds(0); - mySpaces -= spacesNumberAtRemovedText; - myIndentSpaces -= indentSpacesNumberAtRemovedText; - return; - } - - // There are white spaces at remained text, hence, we need to calculate number of spaces and indent spaces between - // last line feed and new end offset. - int newLineFeedsNumber = getLineFeeds() - lineFeedsNumberAtRemovedText; - assert newLineFeedsNumber >= 0; - newLineFeedsNumber = newLineFeedsNumber < 0 ? 0 : newLineFeedsNumber; // Never expect the defense to be triggered. - setLineFeeds(newLineFeedsNumber); - mySpaces = 0; - myIndentSpaces = 0; - for (int i = newEndOffset - 1, c = oldText.charAt(i); c != LINE_FEED; c = oldText.charAt(--i)) { - switch (c) { - case ' ': ++mySpaces; break; - case '\t': myIndentSpaces += tabSize; break; - } - } - } - - /** - * Builds string that contains line feeds, white spaces and tabulation symbols known to the current {@link WhiteSpace} object. - * - * @param options indentation formatting options - * @return string that contains line feeds, white spaces and tabulation symbols known to the current - * {@link WhiteSpace} object - */ - public String generateWhiteSpace(CodeStyleSettings.IndentOptions options) { - return new IndentInfo(getLineFeeds(), myIndentSpaces, mySpaces, myForceSkipTabulationsUsage).generateNewWhiteSpace(options); - } - - /** - * Tries to apply given values to {@link #getSpaces() spaces} and {@link #getIndentSpaces() indentSpaces} properties accordingly. - *

- * The action is not guaranteed to be executed (i.e. the it's not guaranteed that target properties return given values after - * this method call - see {@link #performModification(Runnable)} for more details). - *

- * Moreover, the action is guaranteed to be not executed if {@link #isKeepFirstColumn() keepFirstColumn} property - * is unset and target document string doesn't contain spaces. - * - * @param spaces new value for the {@link #getSpaces() spaces} property - * @param indent new value for the {@link #getIndentSpaces()} indentSpaces} property - */ - public void setSpaces(final int spaces, final int indent) { - performModification(new Runnable() { - public void run() { - if (!isKeepFirstColumn() || (myFlags & CONTAINS_SPACES_INITIALLY) != 0) { - mySpaces = spaces; - myIndentSpaces = indent; - } - } - }); - } - - private boolean doesNotContainAnySpaces() { - return getTotalSpaces() == 0 && getLineFeeds() == 0; - } - - public int getStartOffset() { - return myStart; - } - - public int getEndOffset() { - return myEnd; - } - - /** - * Execute given action in a safe manner. - *

- * 'Safe manner' here means the following: - *

    - *
  • don't execute the action if {@link #isIsReadOnly() isReadOnly} property value is set to true;
  • - *
  • - * ensure that number of line feeds after action execution is preserved if line feeds are - * {@link #isLineFeedsAreReadOnly() read only}; - *
  • - *
  • - * ensure the following if {@link #isIsSafe() isSafe} property is set to true: - *
      - *
    • - * cut all white spaces and line feeds appeared after given action execution to single white space if there - * were no line feeds and white spaces before; - *
    • - *
    - *
  • - *
- * - * @param action action that should be performed in a safe manner - */ - private void performModification(Runnable action) { - if (isIsReadOnly()) return; - final boolean before = doesNotContainAnySpaces(); - final int lineFeedsBefore = getLineFeeds(); - action.run(); - if (isLineFeedsAreReadOnly()) { - setLineFeeds(lineFeedsBefore); - } - if (isIsSafe()) { - final boolean after = doesNotContainAnySpaces(); - if (before && !after) { - // Actions below seem to be useless if 'after' value is 'false'. Are kept as historical heritage. - mySpaces = 0; - myIndentSpaces = 0; - setLineFeeds(0); - } - else if (!before && after) { - mySpaces = 1; - myIndentSpaces = 0; - } - } - } - - /** - * Tries to arrange {@link #getSpaces() spaces} property value to belong to - * [{@link SpacingImpl#getMinSpaces() min}; {@link SpacingImpl#getMaxSpaces()}] bounds defined by the given spacing object - * if {@link #getTotalSpaces() totalSpaces} property value lays out of the same bounds. - *

- * The action is not performed if there are line feeds configured for the - * current {@link WhiteSpace} object ({@link #getSpaces()} != 0). - * - * @param spaceProperty spacing settings holder - */ - public void arrangeSpaces(final SpacingImpl spaceProperty) { - performModification(new Runnable() { - public void run() { - if (spaceProperty != null) { - if (getLineFeeds() == 0) { - if (spaceProperty.getMinSpaces() >= 0 && getTotalSpaces() < spaceProperty.getMinSpaces()) { - setSpaces(spaceProperty.getMinSpaces(), 0); - } - if (spaceProperty.getMaxSpaces() >= 0 && getTotalSpaces() > spaceProperty.getMaxSpaces()) { - setSpaces(spaceProperty.getMaxSpaces(), 0); - } - } - } - } - }); - } - - /** - * Tries to ensure that number of line feeds managed by the current {@link WhiteSpace} is consistent to the settings - * defined at the given spacing property. - * - * @param spaceProperty space settings holder - * @param formatProcessor format processor to use for space settings state refreshing - */ - public void arrangeLineFeeds(final SpacingImpl spaceProperty, final FormatProcessor formatProcessor) { - performModification(new Runnable() { - public void run() { - if (spaceProperty != null) { - spaceProperty.refresh(formatProcessor); - - if (spaceProperty.getMinLineFeeds() >= 0 && getLineFeeds() < spaceProperty.getMinLineFeeds()) { - setLineFeeds(spaceProperty.getMinLineFeeds()); - } - if (getLineFeeds() > 0) { - if (spaceProperty.getKeepBlankLines() > 0) { - if (getLineFeeds() >= spaceProperty.getKeepBlankLines() + 1) { - setLineFeeds(spaceProperty.getKeepBlankLines() + 1); - } - } - else { - if (getLineFeeds() > spaceProperty.getMinLineFeeds()) { - if (spaceProperty.shouldKeepLineFeeds()) { - setLineFeeds(Math.max(spaceProperty.getMinLineFeeds(), 1)); - } - else { - setLineFeeds(spaceProperty.getMinLineFeeds()); - if (getLineFeeds() == 0) mySpaces = 0; - } - } - } - if (getLineFeeds() == 1 && !spaceProperty.shouldKeepLineFeeds() && spaceProperty.getMinLineFeeds() == 0) { - setLineFeeds(0); - mySpaces = 0; - } - - if (getLineFeeds() > 0 && getLineFeeds() < spaceProperty.getPrefLineFeeds()) { - setLineFeeds(spaceProperty.getPrefLineFeeds()); - } - } - - } else if (isFirst()) { - setLineFeeds(0); - mySpaces = 0; - } - } - }); - - } - - /** - * There is a possible case that particular indent info is applied to the code block that is not the first block on a line. - * E.g. we may want to align field name during 'align fields in columns' processing: - *

-   *         public class Test {
-   *             private Object o;
-   *             private int {@code }i;
-   *         }
-   *     
- * We may not want to use tabulation characters then even if user configured - * {@link CodeStyleSettings.IndentOptions#USE_TAB_CHARACTER their usage} because we can't be sure how many visual columns - * will be used for tab representation if there are non-white space symbols before it (IJ editor may use different number of columns - * for single tabulation symbol representation). - *

- * Hence, we can ask current white space object to avoid using tabulation symbols. - * - * @param skip indicates if tabulations symbols usage should be suppressed - */ - public void setForceSkipTabulationsUsage(boolean skip) { - myForceSkipTabulationsUsage = skip; - } - - /** - * Allows to get information if target text document continuous 'white space' represented by the current object contained line feed - * symbol(s) initially or contains line feed(s) now. - * - * @return true if this object contained line feeds initially or contains them now; false otherwise - * @see #containsLineFeedsInitially() - */ - public boolean containsLineFeeds() { - return isIsFirstWhiteSpace() || getLineFeeds() > 0; - } - - public int getTotalSpaces() { - return mySpaces + myIndentSpaces; - } - - /** - * Tries to ensure that current {@link WhiteSpace} object contains at least one line feed. - */ - public void ensureLineFeed() { - performModification(new Runnable() { - public void run() { - if (!containsLineFeeds()) { - setLineFeeds(1); - mySpaces = 0; - } - } - }); - } - - public boolean isReadOnly() { - return isIsReadOnly() || (isIsSafe() && doesNotContainAnySpaces()); - } - - /** - * @param ws char sequence to check - * @return true if given char sequence is equal to the target document text identified by - * start/end offsets managed by the current {@link WhiteSpace} object; false otherwise - */ - public boolean equalsToString(CharSequence ws) { - if (myInitial == null) return ws.length() == 0; - return Comparing.equal(ws,myInitial,true); - } - - public void setIsSafe(final boolean value) { - setFlag(SAFE, value); - } - - private void setFlag(final int mask, final boolean value) { - if (value) { - myFlags |= mask; - } - else { - myFlags &= ~mask; - } - } - - private boolean getFlag(final int mask) { - return (myFlags & mask) != 0; - } - - private boolean isFirst() { - return isIsFirstWhiteSpace(); - } - - /** - * Allows to get information if current object contained line feed(s) initially. It's considered to contain them if - * line feeds were found at the target text document fragment after - * new {@link #append(int, FormattingDocumentModel, CodeStyleSettings.IndentOptions) end offset appliance}. - * - * @return true if current object contained line feeds initially; false otherwise - */ - public boolean containsLineFeedsInitially() { - if (myInitial == null) return false; - return (myFlags & CONTAINS_LF_INITIALLY) != 0; - } - - /** - * Tries to ensure that number of line feeds and white spaces managed by the given {@link WhiteSpace} object is the - * same as the one defined by the given 'spacing' object. - *

- * This method may be considered a shortcut for calling {@link #arrangeLineFeeds(SpacingImpl, FormatProcessor)} and - * {@link #arrangeSpaces(SpacingImpl)}. - * - * @param spacing spacing settings holder - * @param formatProcessor format processor to use to refresh state of the given 'spacing' object - */ - public void removeLineFeeds(final SpacingImpl spacing, final FormatProcessor formatProcessor) { - performModification(new Runnable() { - public void run() { - setLineFeeds(0); - mySpaces = 0; - myIndentSpaces = 0; - } - }); - arrangeLineFeeds(spacing, formatProcessor); - arrangeSpaces(spacing); - } - - public int getIndentOffset() { - return myIndentSpaces; - } - - /** - * Allows to get information about number of 'pure' white space symbols at the last line of continuous white space document - * text fragment represented by the current {@link WhiteSpace} object. - *

- * Note: pay special attention to 'last line' qualification here. Consider the following target continuous - * white space document fragment: - *

-   *        ' ws11ws12
-   *        'ws21'
-   * 
- *

- * Here 'wsnm' is a m-th white space symbol at the n-th line. 'Spaces' property of - * {@link WhiteSpace} object for such white-space text has a value not 2 or 3 but 1. - * - * @return number of white spaces at the last line of target continuous white space text document fragment - */ - public int getSpaces() { - return mySpaces; - } - - public void setKeepFirstColumn(final boolean b) { - setFlag(KEEP_FIRST_COLUMN, b); - } - - public void setLineFeedsAreReadOnly() { - setLineFeedsAreReadOnly(true); - } - - public void setReadOnly(final boolean isReadOnly) { - setIsReadOnly(isReadOnly); - } - - public boolean isIsFirstWhiteSpace() { - return getFlag(FIRST); - } - - public boolean isIsSafe() { - return getFlag(SAFE); - } - - public boolean isKeepFirstColumn() { - return getFlag(KEEP_FIRST_COLUMN); - } - - public boolean isLineFeedsAreReadOnly() { - return getFlag(LINE_FEEDS_ARE_READ_ONLY); - } - - public void setLineFeedsAreReadOnly(final boolean lineFeedsAreReadOnly) { - setFlag(LINE_FEEDS_ARE_READ_ONLY, lineFeedsAreReadOnly); - } - - public boolean isIsReadOnly() { - return getFlag(READ_ONLY); - } - - public void setIsReadOnly(final boolean isReadOnly) { - setFlag(READ_ONLY, isReadOnly); - } - - public void setIsFirstWhiteSpace(final boolean isFirstWhiteSpace) { - setFlag(FIRST, isFirstWhiteSpace); - } - - public StringBuilder generateWhiteSpace(final CodeStyleSettings.IndentOptions indentOptions, - final int offset, - final IndentInfo indent) { - final StringBuilder result = new StringBuilder(); - int currentOffset = getStartOffset(); - CharSequence[] lines = getInitialLines(); - int currentLine = 0; - for (int i = 0; i < lines.length - 1 && currentOffset + lines[i].length() <= offset; i++) { - result.append(lines[i]); - currentOffset += lines[i].length(); - result.append(LINE_FEED); - currentOffset++; - currentLine++; - if (currentOffset == offset) { - break; - } - - } - final String newIndentSpaces = indent.generateNewWhiteSpace(indentOptions); - result.append(newIndentSpaces); - appendNonWhitespaces(result, lines, currentLine); - if (currentLine + 1 < lines.length) { - result.append(LINE_FEED); - for (int i = currentLine + 1; i < lines.length - 1; i++) { - result.append(lines[i]); - result.append(LINE_FEED); - } - appendNonWhitespaces(result, lines, lines.length-1); - result.append(lines[lines.length - 1]); - - } - return result; - } - - private static void appendNonWhitespaces(StringBuilder result, CharSequence[] lines, int currentLine) { - // It looks like regexp usage is too heavy here. - if (currentLine != lines.length && !lines[currentLine].toString().matches("\\s*")) { - result.append(lines[currentLine]); - } - } - - /** - * Shows target document text fragment identified by start/end offsets as a sequence of strings. - *

- * Note: arrays usage here is considered to be a historical heritage. - * - * @return target document text fragment as a sequence of strings - */ - private CharSequence[] getInitialLines() { - if (myInitial == null) return new CharSequence[]{""}; - final ArrayList result = new ArrayList(); - StringBuilder currentLine = new StringBuilder(); - for (int i = 0; i < myInitial.length(); i++) { - final char c = myInitial.charAt(i); - if (c == LINE_FEED) { - result.add(currentLine); - currentLine = new StringBuilder(); - } - else { - currentLine.append(c); - } - } - result.add(currentLine); - return result.toArray(new CharSequence[result.size()]); - } - - /** - * Provides access to the information about indent white spaces at last line of continuous white space text document - * fragment represented by the current {@link WhiteSpace} object. - *

- * 'Indent white space' here is a white space representation of tabulation symbol. User may define that - * he or she wants to use particular number of white spaces instead of tabulation - * ({@link CodeStyleSettings.IndentOptions#TAB_SIZE}). So, {@link WhiteSpace} object uses corresponding - * number of 'indent white spaces' for each tabulation symbol encountered at target continuous white space text document fragment. - *

- * Note: pay special attention to 'last line' qualification here. Consider the following target - * continuous white space document fragment: - *

-   *        ' \t\t
-   *        '\t'
-   * 
- *

- * Let's consider that 'tab size' is defined as four white spaces (default setting). - * 'IndentSpaces' property of {@link WhiteSpace} object for such white-space text has a value not - * 8 or 12 but 4, i.e. tabulation symbols from last line - * only are counted. - * - * @return number of indent spaces at the last line of target continuous white space text document fragment - */ - public int getIndentSpaces() { - return myIndentSpaces; - } - - public int getLength() { - return myEnd - myStart; - } - - /** - * Provides access to the line feed symbols number at continuous white space text document fragment represented - * by the current {@link WhiteSpace} object. - * - * @return line feed symbols number - */ - public final int getLineFeeds() { - return myFlags >>> LF_COUNT_SHIFT; - } - - public void setLineFeeds(final int lineFeeds) { - assert lineFeeds < MAX_LF_COUNT; - final int flags = myFlags; - myFlags &= ~0xFFFFFF80; // keep only seven lower bits, i.e. drop all line feeds registered before if any - myFlags |= (lineFeeds << LF_COUNT_SHIFT); - - assert getLineFeeds() == lineFeeds; - assert (flags & 0x7F) == (myFlags & 0x7F); - } - - public TextRange getTextRange() { - return new TextRange(myStart, myEnd); - } - - @Override - public String toString() { - return "WhiteSpace(" + myStart + "-" + myEnd + " spaces=" + mySpaces + " LFs=" + getLineFeeds() + ")"; - } -} - +/* + * 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.formatting; + +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.formatter.FormattingDocumentModelImpl; + +import java.util.ArrayList; + +/** + * Object-level representation of continuous white space at document. Either line feed or tabulation or space is considered to be 'white-space'. + * I.e. document text fragment like {@code '\t \t\t\n\t \t'} may be considered as a continuous white space and may be represented as a {@link WhiteSpace} object. + *

+ * Provides number of properties that describe encapsulated continuous white space: + *

    + *
  • {@link #getLineFeeds() lineFeeds};
  • + *
  • {@link #getSpaces() spaces};
  • + *
  • {@link #getIndentSpaces()};
  • + *
+ *

+ * Provides ability to build string representation of the managed settings taking into consideration user settings for tabulation vs white space usage, tabulation + * size etc. + *

+ * Not thread-safe. + */ +class WhiteSpace { + + private static final char LINE_FEED = '\n'; + + private final int myStart; + private int myEnd; + + private int mySpaces; + private int myIndentSpaces; + + private CharSequence myInitial; + private int myFlags; + private boolean myForceSkipTabulationsUsage; + + private static final byte FIRST = 1; + private static final byte SAFE = 0x2; + private static final byte KEEP_FIRST_COLUMN = 0x4; + private static final byte LINE_FEEDS_ARE_READ_ONLY = 0x8; + private static final byte READ_ONLY = 0x10; + private static final byte CONTAINS_LF_INITIALLY = 0x20; + private static final byte CONTAINS_SPACES_INITIALLY = 0x40; + private static final int LF_COUNT_SHIFT = 7; + private static final int MAX_LF_COUNT = 1 << 24; + + /** + * Creates new WhiteSpace object with the given start offset and a flag that shows if current white space is + * the first white space. + *

+ * Note: {@link #getEndOffset() end offset} value is the same as the {@link #getStartOffset() start offset} for + * the newly constructed object. {@link #append(int, FormattingDocumentModel, CodeStyleSettings.IndentOptions)} should be + * called in order to apply desired end offset. + * + * @param startOffset start offset to use + * @param isFirst flag that shows if current white space is the first + */ + public WhiteSpace(int startOffset, boolean isFirst) { + myStart = startOffset; + myEnd = startOffset; + setIsFirstWhiteSpace(isFirst); + } + + /** + * Applies new end offset to the current {@link WhiteSpace} object. + *

+ * Namely, performs the following: + *

    + *
  1. Checks if new end offset can be applied, return in case of negative answer;
  2. + *
  3. + * Processes all new symbols introduced by the new end offset value, calculates number of line feeds, + * white spaces and tabulations between them and updates {@link #getLineFeeds() lineFeeds}, {@link #getSpaces() spaces}, + * {@link #getIndentSpaces() indentSpaces} and {@link #getTotalSpaces() totalSpaces} properties accordingly; + *
  4. + *
+ * + * @param newEndOffset new end offset value + * @param model formatting model that is used to access to the underlying document text + * @param options indent formatting options + */ + public void append(int newEndOffset, FormattingDocumentModel model, CodeStyleSettings.IndentOptions options) { + final int oldEndOffset = myEnd; + if (newEndOffset == oldEndOffset) return; + if (myStart >= newEndOffset) { + InitialInfoBuilder.assertInvalidRanges(myStart, + newEndOffset, + model, + "some block intersects with whitespace" + ); + } + + myEnd = newEndOffset; + TextRange range = new TextRange(myStart, myEnd); + CharSequence oldText = myInitial; + myInitial = model.getText(range); + + if (!coveredByBlock(model)) { + InitialInfoBuilder.assertInvalidRanges(myStart, + myEnd, + model, + "nonempty text is not covered by block" + ); + } + + // There is a possible case that this method is called more than once on the same object. We want to + if (newEndOffset > oldEndOffset) { + refreshStateOnEndOffsetIncrease(newEndOffset, oldEndOffset, options.TAB_SIZE); + } else { + refreshStateOnEndOffsetDecrease(oldText, newEndOffset, oldEndOffset, options.TAB_SIZE); + } + + if (getLineFeeds() > 0) myFlags |= CONTAINS_LF_INITIALLY; + else myFlags &= ~CONTAINS_LF_INITIALLY; + + final int totalSpaces = getTotalSpaces(); + if (totalSpaces > 0) myFlags |= CONTAINS_SPACES_INITIALLY; + else myFlags &=~ CONTAINS_SPACES_INITIALLY; + } + + /** + * Allows to check if 'myInitial' property value stands for continuous white space text. + *

+ * The text is considered to be continuous 'white space' at following cases: + *

    + *
  • 'myInitial' is empty string or string that contains white spaces only;
  • + *
  • 'myInitial' is a CDATA string which content is empty or consists from white spaces only;
  • + *
  • 'myInitial' string belongs to the same {@link PsiWhiteSpace} element;
  • + *
+ * + * @param model formatting model that is used to provide access to the PSI API if necessary + * @return true if 'myInitial' property value stands for white space; + * false otherwise + */ + private boolean coveredByBlock(final FormattingDocumentModel model) { + if (myInitial == null) return true; + if (model.containsWhiteSpaceSymbolsOnly(myStart, myEnd)) return true; + + if (!(model instanceof FormattingDocumentModelImpl)) return false; + PsiFile psiFile = ((FormattingDocumentModelImpl)model).getFile(); + if (psiFile == null) return false; + PsiElement start = psiFile.findElementAt(myStart); + PsiElement end = psiFile.findElementAt(myEnd-1); + return start == end && start instanceof PsiWhiteSpace; // there maybe non-white text inside CDATA-encoded injected elements + } + + private void refreshStateOnEndOffsetIncrease(int newEndOffset, int oldEndOffset, int tabSize) { + assert newEndOffset > oldEndOffset; + for (int i = oldEndOffset - myStart; i < newEndOffset - myStart; i++) { + switch (myInitial.charAt(i)) { + case LINE_FEED: + setLineFeeds(getLineFeeds() + 1); + mySpaces = 0; + myIndentSpaces = 0; + break; + case '\t': + myIndentSpaces += tabSize; + break; + default: mySpaces++; + } + } + } + + private void refreshStateOnEndOffsetDecrease(CharSequence oldText, int newEndOffset, int oldEndOffset, int tabSize) { + assert newEndOffset < oldEndOffset; + int lineFeedsNumberAtRemovedText = 0; + int spacesNumberAtRemovedText = 0; + int indentSpacesNumberAtRemovedText = 0; + for (int i = oldEndOffset - 1; i >= newEndOffset; i--) { + switch (oldText.charAt(i)) { + case LINE_FEED: ++lineFeedsNumberAtRemovedText; break; + case ' ': ++spacesNumberAtRemovedText; break; + case '\t': indentSpacesNumberAtRemovedText += tabSize; break; + } + } + + // There are no line feeds at remained text, hence, we can just subtract number of spaces and indent spaces + // from removed text and finish. + if (getLineFeeds() - lineFeedsNumberAtRemovedText <= 0) { + setLineFeeds(0); + mySpaces -= spacesNumberAtRemovedText; + myIndentSpaces -= indentSpacesNumberAtRemovedText; + return; + } + + // There are white spaces at remained text, hence, we need to calculate number of spaces and indent spaces between + // last line feed and new end offset. + int newLineFeedsNumber = getLineFeeds() - lineFeedsNumberAtRemovedText; + assert newLineFeedsNumber >= 0; + newLineFeedsNumber = newLineFeedsNumber < 0 ? 0 : newLineFeedsNumber; // Never expect the defense to be triggered. + setLineFeeds(newLineFeedsNumber); + mySpaces = 0; + myIndentSpaces = 0; + for (int i = newEndOffset - 1, c = oldText.charAt(i); c != LINE_FEED; c = oldText.charAt(--i)) { + switch (c) { + case ' ': ++mySpaces; break; + case '\t': myIndentSpaces += tabSize; break; + } + } + } + + /** + * Builds string that contains line feeds, white spaces and tabulation symbols known to the current {@link WhiteSpace} object. + * + * @param options indentation formatting options + * @return string that contains line feeds, white spaces and tabulation symbols known to the current + * {@link WhiteSpace} object + */ + public String generateWhiteSpace(CodeStyleSettings.IndentOptions options) { + return new IndentInfo(getLineFeeds(), myIndentSpaces, mySpaces, myForceSkipTabulationsUsage).generateNewWhiteSpace(options); + } + + /** + * Tries to apply given values to {@link #getSpaces() spaces} and {@link #getIndentSpaces() indentSpaces} properties accordingly. + *

+ * The action is not guaranteed to be executed (i.e. the it's not guaranteed that target properties return given values after + * this method call - see {@link #performModification(Runnable)} for more details). + *

+ * Moreover, the action is guaranteed to be not executed if {@link #isKeepFirstColumn() keepFirstColumn} property + * is unset and target document string doesn't contain spaces. + * + * @param spaces new value for the {@link #getSpaces() spaces} property + * @param indent new value for the {@link #getIndentSpaces()} indentSpaces} property + */ + public void setSpaces(final int spaces, final int indent) { + performModification(new Runnable() { + public void run() { + if (!isKeepFirstColumn() || (myFlags & CONTAINS_SPACES_INITIALLY) != 0) { + mySpaces = spaces; + myIndentSpaces = indent; + } + } + }); + } + + private boolean doesNotContainAnySpaces() { + return getTotalSpaces() == 0 && getLineFeeds() == 0; + } + + public int getStartOffset() { + return myStart; + } + + public int getEndOffset() { + return myEnd; + } + + /** + * Execute given action in a safe manner. + *

+ * 'Safe manner' here means the following: + *

    + *
  • don't execute the action if {@link #isIsReadOnly() isReadOnly} property value is set to true;
  • + *
  • + * ensure that number of line feeds after action execution is preserved if line feeds are + * {@link #isLineFeedsAreReadOnly() read only}; + *
  • + *
  • + * ensure the following if {@link #isIsSafe() isSafe} property is set to true: + *
      + *
    • + * cut all white spaces and line feeds appeared after given action execution to single white space if there + * were no line feeds and white spaces before; + *
    • + *
    + *
  • + *
+ * + * @param action action that should be performed in a safe manner + */ + private void performModification(Runnable action) { + if (isIsReadOnly()) return; + final boolean before = doesNotContainAnySpaces(); + final int lineFeedsBefore = getLineFeeds(); + action.run(); + if (isLineFeedsAreReadOnly()) { + setLineFeeds(lineFeedsBefore); + } + if (isIsSafe()) { + final boolean after = doesNotContainAnySpaces(); + if (before && !after) { + // Actions below seem to be useless if 'after' value is 'false'. Are kept as historical heritage. + mySpaces = 0; + myIndentSpaces = 0; + setLineFeeds(0); + } + else if (!before && after) { + mySpaces = 1; + myIndentSpaces = 0; + } + } + } + + /** + * Tries to arrange {@link #getSpaces() spaces} property value to belong to + * [{@link SpacingImpl#getMinSpaces() min}; {@link SpacingImpl#getMaxSpaces()}] bounds defined by the given spacing object + * if {@link #getTotalSpaces() totalSpaces} property value lays out of the same bounds. + *

+ * The action is not performed if there are line feeds configured for the + * current {@link WhiteSpace} object ({@link #getSpaces()} != 0). + * + * @param spaceProperty spacing settings holder + */ + public void arrangeSpaces(final SpacingImpl spaceProperty) { + performModification(new Runnable() { + public void run() { + if (spaceProperty != null) { + if (getLineFeeds() == 0) { + if (spaceProperty.getMinSpaces() >= 0 && getTotalSpaces() < spaceProperty.getMinSpaces()) { + setSpaces(spaceProperty.getMinSpaces(), 0); + } + if (spaceProperty.getMaxSpaces() >= 0 && getTotalSpaces() > spaceProperty.getMaxSpaces()) { + setSpaces(spaceProperty.getMaxSpaces(), 0); + } + } + } + } + }); + } + + /** + * Tries to ensure that number of line feeds managed by the current {@link WhiteSpace} is consistent to the settings + * defined at the given spacing property. + * + * @param spaceProperty space settings holder + * @param formatProcessor format processor to use for space settings state refreshing + */ + public void arrangeLineFeeds(final SpacingImpl spaceProperty, final FormatProcessor formatProcessor) { + performModification(new Runnable() { + public void run() { + if (spaceProperty != null) { + spaceProperty.refresh(formatProcessor); + + if (spaceProperty.getMinLineFeeds() >= 0 && getLineFeeds() < spaceProperty.getMinLineFeeds()) { + setLineFeeds(spaceProperty.getMinLineFeeds()); + } + if (getLineFeeds() > 0) { + if (spaceProperty.getKeepBlankLines() > 0) { + if (getLineFeeds() >= spaceProperty.getKeepBlankLines() + 1) { + setLineFeeds(spaceProperty.getKeepBlankLines() + 1); + } + } + else { + if (getLineFeeds() > spaceProperty.getMinLineFeeds()) { + if (spaceProperty.shouldKeepLineFeeds()) { + setLineFeeds(Math.max(spaceProperty.getMinLineFeeds(), 1)); + } + else { + setLineFeeds(spaceProperty.getMinLineFeeds()); + if (getLineFeeds() == 0) mySpaces = 0; + } + } + } + if (getLineFeeds() == 1 && !spaceProperty.shouldKeepLineFeeds() && spaceProperty.getMinLineFeeds() == 0) { + setLineFeeds(0); + mySpaces = 0; + } + + if (getLineFeeds() > 0 && getLineFeeds() < spaceProperty.getPrefLineFeeds()) { + setLineFeeds(spaceProperty.getPrefLineFeeds()); + } + } + + } else if (isFirst()) { + setLineFeeds(0); + mySpaces = 0; + } + } + }); + + } + + /** + * There is a possible case that particular indent info is applied to the code block that is not the first block on a line. + * E.g. we may want to align field name during 'align fields in columns' processing: + *

+   *         public class Test {
+   *             private Object o;
+   *             private int {@code }i;
+   *         }
+   *     
+ * We may not want to use tabulation characters then even if user configured + * {@link CodeStyleSettings.IndentOptions#USE_TAB_CHARACTER their usage} because we can't be sure how many visual columns + * will be used for tab representation if there are non-white space symbols before it (IJ editor may use different number of columns + * for single tabulation symbol representation). + *

+ * Hence, we can ask current white space object to avoid using tabulation symbols. + * + * @param skip indicates if tabulations symbols usage should be suppressed + */ + public void setForceSkipTabulationsUsage(boolean skip) { + myForceSkipTabulationsUsage = skip; + } + + /** + * Allows to get information if target text document continuous 'white space' represented by the current object contained line feed + * symbol(s) initially or contains line feed(s) now. + * + * @return true if this object contained line feeds initially or contains them now; false otherwise + * @see #containsLineFeedsInitially() + */ + public boolean containsLineFeeds() { + return isIsFirstWhiteSpace() || getLineFeeds() > 0; + } + + public int getTotalSpaces() { + return mySpaces + myIndentSpaces; + } + + /** + * Tries to ensure that current {@link WhiteSpace} object contains at least one line feed. + */ + public void ensureLineFeed() { + performModification(new Runnable() { + public void run() { + if (!containsLineFeeds()) { + setLineFeeds(1); + mySpaces = 0; + } + } + }); + } + + public boolean isReadOnly() { + return isIsReadOnly() || (isIsSafe() && doesNotContainAnySpaces()); + } + + /** + * @param ws char sequence to check + * @return true if given char sequence is equal to the target document text identified by + * start/end offsets managed by the current {@link WhiteSpace} object; false otherwise + */ + public boolean equalsToString(CharSequence ws) { + if (myInitial == null) return ws.length() == 0; + return Comparing.equal(ws,myInitial,true); + } + + public void setIsSafe(final boolean value) { + setFlag(SAFE, value); + } + + private void setFlag(final int mask, final boolean value) { + if (value) { + myFlags |= mask; + } + else { + myFlags &= ~mask; + } + } + + private boolean getFlag(final int mask) { + return (myFlags & mask) != 0; + } + + private boolean isFirst() { + return isIsFirstWhiteSpace(); + } + + /** + * Allows to get information if current object contained line feed(s) initially. It's considered to contain them if + * line feeds were found at the target text document fragment after + * new {@link #append(int, FormattingDocumentModel, CodeStyleSettings.IndentOptions) end offset appliance}. + * + * @return true if current object contained line feeds initially; false otherwise + */ + public boolean containsLineFeedsInitially() { + if (myInitial == null) return false; + return (myFlags & CONTAINS_LF_INITIALLY) != 0; + } + + /** + * Tries to ensure that number of line feeds and white spaces managed by the given {@link WhiteSpace} object is the + * same as the one defined by the given 'spacing' object. + *

+ * This method may be considered a shortcut for calling {@link #arrangeLineFeeds(SpacingImpl, FormatProcessor)} and + * {@link #arrangeSpaces(SpacingImpl)}. + * + * @param spacing spacing settings holder + * @param formatProcessor format processor to use to refresh state of the given 'spacing' object + */ + public void removeLineFeeds(final SpacingImpl spacing, final FormatProcessor formatProcessor) { + performModification(new Runnable() { + public void run() { + setLineFeeds(0); + mySpaces = 0; + myIndentSpaces = 0; + } + }); + arrangeLineFeeds(spacing, formatProcessor); + arrangeSpaces(spacing); + } + + public int getIndentOffset() { + return myIndentSpaces; + } + + /** + * Allows to get information about number of 'pure' white space symbols at the last line of continuous white space document + * text fragment represented by the current {@link WhiteSpace} object. + *

+ * Note: pay special attention to 'last line' qualification here. Consider the following target continuous + * white space document fragment: + *

+   *        ' ws11ws12
+   *        'ws21'
+   * 
+ *

+ * Here 'wsnm' is a m-th white space symbol at the n-th line. 'Spaces' property of + * {@link WhiteSpace} object for such white-space text has a value not 2 or 3 but 1. + * + * @return number of white spaces at the last line of target continuous white space text document fragment + */ + public int getSpaces() { + return mySpaces; + } + + public void setKeepFirstColumn(final boolean b) { + setFlag(KEEP_FIRST_COLUMN, b); + } + + public void setLineFeedsAreReadOnly() { + setLineFeedsAreReadOnly(true); + } + + public void setReadOnly(final boolean isReadOnly) { + setIsReadOnly(isReadOnly); + } + + public boolean isIsFirstWhiteSpace() { + return getFlag(FIRST); + } + + public boolean isIsSafe() { + return getFlag(SAFE); + } + + public boolean isKeepFirstColumn() { + return getFlag(KEEP_FIRST_COLUMN); + } + + public boolean isLineFeedsAreReadOnly() { + return getFlag(LINE_FEEDS_ARE_READ_ONLY); + } + + public void setLineFeedsAreReadOnly(final boolean lineFeedsAreReadOnly) { + setFlag(LINE_FEEDS_ARE_READ_ONLY, lineFeedsAreReadOnly); + } + + public boolean isIsReadOnly() { + return getFlag(READ_ONLY); + } + + public void setIsReadOnly(final boolean isReadOnly) { + setFlag(READ_ONLY, isReadOnly); + } + + public void setIsFirstWhiteSpace(final boolean isFirstWhiteSpace) { + setFlag(FIRST, isFirstWhiteSpace); + } + + public StringBuilder generateWhiteSpace(final CodeStyleSettings.IndentOptions indentOptions, + final int offset, + final IndentInfo indent) { + final StringBuilder result = new StringBuilder(); + int currentOffset = getStartOffset(); + CharSequence[] lines = getInitialLines(); + int currentLine = 0; + for (int i = 0; i < lines.length - 1 && currentOffset + lines[i].length() <= offset; i++) { + result.append(lines[i]); + currentOffset += lines[i].length(); + result.append(LINE_FEED); + currentOffset++; + currentLine++; + if (currentOffset == offset) { + break; + } + + } + final String newIndentSpaces = indent.generateNewWhiteSpace(indentOptions); + result.append(newIndentSpaces); + appendNonWhitespaces(result, lines, currentLine); + if (currentLine + 1 < lines.length) { + result.append(LINE_FEED); + for (int i = currentLine + 1; i < lines.length - 1; i++) { + result.append(lines[i]); + result.append(LINE_FEED); + } + appendNonWhitespaces(result, lines, lines.length-1); + result.append(lines[lines.length - 1]); + + } + return result; + } + + private static void appendNonWhitespaces(StringBuilder result, CharSequence[] lines, int currentLine) { + // It looks like regexp usage is too heavy here. + if (currentLine != lines.length && !lines[currentLine].toString().matches("\\s*")) { + result.append(lines[currentLine]); + } + } + + /** + * Shows target document text fragment identified by start/end offsets as a sequence of strings. + *

+ * Note: arrays usage here is considered to be a historical heritage. + * + * @return target document text fragment as a sequence of strings + */ + private CharSequence[] getInitialLines() { + if (myInitial == null) return new CharSequence[]{""}; + final ArrayList result = new ArrayList(); + StringBuilder currentLine = new StringBuilder(); + for (int i = 0; i < myInitial.length(); i++) { + final char c = myInitial.charAt(i); + if (c == LINE_FEED) { + result.add(currentLine); + currentLine = new StringBuilder(); + } + else { + currentLine.append(c); + } + } + result.add(currentLine); + return result.toArray(new CharSequence[result.size()]); + } + + /** + * Provides access to the information about indent white spaces at last line of continuous white space text document + * fragment represented by the current {@link WhiteSpace} object. + *

+ * 'Indent white space' here is a white space representation of tabulation symbol. User may define that + * he or she wants to use particular number of white spaces instead of tabulation + * ({@link CodeStyleSettings.IndentOptions#TAB_SIZE}). So, {@link WhiteSpace} object uses corresponding + * number of 'indent white spaces' for each tabulation symbol encountered at target continuous white space text document fragment. + *

+ * Note: pay special attention to 'last line' qualification here. Consider the following target + * continuous white space document fragment: + *

+   *        ' \t\t
+   *        '\t'
+   * 
+ *

+ * Let's consider that 'tab size' is defined as four white spaces (default setting). + * 'IndentSpaces' property of {@link WhiteSpace} object for such white-space text has a value not + * 8 or 12 but 4, i.e. tabulation symbols from last line + * only are counted. + * + * @return number of indent spaces at the last line of target continuous white space text document fragment + */ + public int getIndentSpaces() { + return myIndentSpaces; + } + + public int getLength() { + return myEnd - myStart; + } + + /** + * Provides access to the line feed symbols number at continuous white space text document fragment represented + * by the current {@link WhiteSpace} object. + * + * @return line feed symbols number + */ + public final int getLineFeeds() { + return myFlags >>> LF_COUNT_SHIFT; + } + + public void setLineFeeds(final int lineFeeds) { + assert lineFeeds < MAX_LF_COUNT; + final int flags = myFlags; + myFlags &= ~0xFFFFFF80; // keep only seven lower bits, i.e. drop all line feeds registered before if any + myFlags |= (lineFeeds << LF_COUNT_SHIFT); + + assert getLineFeeds() == lineFeeds; + assert (flags & 0x7F) == (myFlags & 0x7F); + } + + public TextRange getTextRange() { + return new TextRange(myStart, myEnd); + } + + @Override + public String toString() { + return "WhiteSpace(" + myStart + "-" + myEnd + " spaces=" + mySpaces + " LFs=" + getLineFeeds() + ")"; + } +} + diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java index 55fd7c8fca80..4c69b3a20065 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoActionBase.java @@ -1,88 +1,88 @@ -/* - * 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.ide.actions; - -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; - -/** - * Author: msk - */ -public abstract class GotoActionBase extends AnAction { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.GotoActionBase"); - - protected static Class myInAction = null; - - public static String getInitialText(Editor editor) { - if (editor == null) { - return ""; - } - final String selectedText = editor.getSelectionModel().getSelectedText(); - if (selectedText != null && selectedText.indexOf("\n") < 0) { - return selectedText; - } - return ""; - } - - public final void actionPerformed(AnActionEvent e) { - LOG.assertTrue (!getClass ().equals (myInAction)); - try { - myInAction = getClass(); - gotoActionPerformed (e); - } - catch (Throwable t) { - LOG.error(t); - myInAction = null; - } - } - - protected abstract void gotoActionPerformed(AnActionEvent e); - - public void update(final AnActionEvent event) { - final Presentation presentation = event.getPresentation(); - final DataContext dataContext = event.getDataContext(); - final Project project = PlatformDataKeys.PROJECT.getData(dataContext); - presentation.setEnabled(!getClass().equals (myInAction) && project != null && hasContributors(dataContext)); - presentation.setVisible(hasContributors(dataContext)); - } - - protected boolean hasContributors(final DataContext dataContext) { - return true; - } - - public static PsiElement getPsiContext(final AnActionEvent e) { - PsiFile file = e.getData(LangDataKeys.PSI_FILE); - if (file != null) return file; - Project project = e.getData(PlatformDataKeys.PROJECT); - return getPsiContext(project); - } - - public static PsiElement getPsiContext(final Project project) { - if (project == null) return null; - Editor selectedEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - if (selectedEditor == null) return null; - Document document = selectedEditor.getDocument(); - return PsiDocumentManager.getInstance(project).getPsiFile(document); - } -} +/* + * 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.ide.actions; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; + +/** + * Author: msk + */ +public abstract class GotoActionBase extends AnAction { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.GotoActionBase"); + + protected static Class myInAction = null; + + public static String getInitialText(Editor editor) { + if (editor == null) { + return ""; + } + final String selectedText = editor.getSelectionModel().getSelectedText(); + if (selectedText != null && selectedText.indexOf("\n") < 0) { + return selectedText; + } + return ""; + } + + public final void actionPerformed(AnActionEvent e) { + LOG.assertTrue (!getClass ().equals (myInAction)); + try { + myInAction = getClass(); + gotoActionPerformed (e); + } + catch (Throwable t) { + LOG.error(t); + myInAction = null; + } + } + + protected abstract void gotoActionPerformed(AnActionEvent e); + + public void update(final AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final DataContext dataContext = event.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + presentation.setEnabled(!getClass().equals (myInAction) && project != null && hasContributors(dataContext)); + presentation.setVisible(hasContributors(dataContext)); + } + + protected boolean hasContributors(final DataContext dataContext) { + return true; + } + + public static PsiElement getPsiContext(final AnActionEvent e) { + PsiFile file = e.getData(LangDataKeys.PSI_FILE); + if (file != null) return file; + Project project = e.getData(PlatformDataKeys.PROJECT); + return getPsiContext(project); + } + + public static PsiElement getPsiContext(final Project project) { + if (project == null) return null; + Editor selectedEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + if (selectedEditor == null) return null; + Document document = selectedEditor.getDocument(); + return PsiDocumentManager.getInstance(project).getPsiFile(document); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoClassAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoClassAction.java index f05bf60e80e3..7dd49cbbf3c5 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoClassAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoClassAction.java @@ -1,76 +1,76 @@ -/* - * 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.ide.actions; - -import com.intellij.codeInsight.navigation.NavigationUtil; -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.ide.util.gotoByName.*; -import com.intellij.navigation.ChooseByNameRegistry; -import com.intellij.navigation.NavigationItem; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; - -public class GotoClassAction extends GotoActionBase implements DumbAware { - public void gotoActionPerformed(AnActionEvent e) { - final Project project = e.getData(PlatformDataKeys.PROJECT); - assert project != null; - if (DumbService.getInstance(project).isDumb()) { - DumbService.getInstance(project).showDumbModeNotification("Goto Class action is not available until indices are built, using Goto File instead"); - - myInAction = null; - new GotoFileAction().gotoActionPerformed(e); - return; - } - - FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.class"); - PsiDocumentManager.getInstance(project).commitAllDocuments(); - - final GotoClassModel2 model = new GotoClassModel2(project); - final ChooseByNamePopup popup = ChooseByNamePopup.createPopup(project, model, getPsiContext(e), - getInitialText(e.getData(PlatformDataKeys.EDITOR))); - final ChooseByNameFilter filterUI = new ChooseByNameLanguageFilter(popup, model, GotoClassSymbolConfiguration.getInstance(project), project); - - popup.invoke(new ChooseByNamePopupComponent.Callback() { - public void onClose() { - if (GotoClassAction.class.equals(myInAction)) { - myInAction = null; - } - filterUI.close(); - } - - public void elementChosen(Object element) { - if (element instanceof PsiElement) { - NavigationUtil.activateFileWithPsiElement((PsiElement)element); - } - else { - ((NavigationItem)element).navigate(true); - } - } - }, ModalityState.current(), true); - } - - protected boolean hasContributors(DataContext dataContext) { - return ChooseByNameRegistry.getInstance().getClassModelContributors().length > 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.ide.actions; + +import com.intellij.codeInsight.navigation.NavigationUtil; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.util.gotoByName.*; +import com.intellij.navigation.ChooseByNameRegistry; +import com.intellij.navigation.NavigationItem; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; + +public class GotoClassAction extends GotoActionBase implements DumbAware { + public void gotoActionPerformed(AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + assert project != null; + if (DumbService.getInstance(project).isDumb()) { + DumbService.getInstance(project).showDumbModeNotification("Goto Class action is not available until indices are built, using Goto File instead"); + + myInAction = null; + new GotoFileAction().gotoActionPerformed(e); + return; + } + + FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.class"); + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + final GotoClassModel2 model = new GotoClassModel2(project); + final ChooseByNamePopup popup = ChooseByNamePopup.createPopup(project, model, getPsiContext(e), + getInitialText(e.getData(PlatformDataKeys.EDITOR))); + final ChooseByNameFilter filterUI = new ChooseByNameLanguageFilter(popup, model, GotoClassSymbolConfiguration.getInstance(project), project); + + popup.invoke(new ChooseByNamePopupComponent.Callback() { + public void onClose() { + if (GotoClassAction.class.equals(myInAction)) { + myInAction = null; + } + filterUI.close(); + } + + public void elementChosen(Object element) { + if (element instanceof PsiElement) { + NavigationUtil.activateFileWithPsiElement((PsiElement)element); + } + else { + ((NavigationItem)element).navigate(true); + } + } + }, ModalityState.current(), true); + } + + protected boolean hasContributors(DataContext dataContext) { + return ChooseByNameRegistry.getInstance().getClassModelContributors().length > 0; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoFileAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoFileAction.java index c49202e17c50..823b1e8a7b72 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoFileAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoFileAction.java @@ -1,144 +1,144 @@ -/* - * 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.ide.actions; - -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.ide.util.gotoByName.*; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.FileTypes; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.Navigatable; -import com.intellij.psi.PsiFile; -import com.intellij.util.containers.ContainerUtil; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * "Go to | File" action implementation. - * - * @author Eugene Belyaev - * @author Constantine.Plotnikov - */ -public class GotoFileAction extends GotoActionBase implements DumbAware { - - @Override - public void gotoActionPerformed(AnActionEvent e) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.file"); - final Project project = e.getData(PlatformDataKeys.PROJECT); - final GotoFileModel gotoFileModel = new GotoFileModel(project); - final ChooseByNamePopup popup = ChooseByNamePopup.createPopup(project, gotoFileModel, getPsiContext(e), - getInitialText(e.getData(PlatformDataKeys.EDITOR))); - final ChooseByNameFilter filterUI = new GotoFileFilter(popup, gotoFileModel, project); - popup.invoke(new ChooseByNamePopupComponent.Callback() { - public void onClose() { - if (GotoFileAction.class.equals(myInAction)) { - myInAction = null; - } - filterUI.close(); - } - - public void elementChosen(final Object element) { - if (element == null) return; - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - Navigatable n = (Navigatable)element; - - //this is for better cursor position - if (element instanceof PsiFile) { - VirtualFile vfile = ((PsiFile)element).getVirtualFile(); - if (vfile == null) return; - n = new OpenFileDescriptor(project, vfile, popup.getLinePosition(), popup.getColumnPosition()); - } - - if (!n.canNavigate()) return; - n.navigate(true); - } - }, ModalityState.NON_MODAL); - } - }, ModalityState.current(), true); - } - - protected static class GotoFileFilter extends ChooseByNameFilter { - GotoFileFilter(final ChooseByNamePopup popup, GotoFileModel model, final Project project) { - super(popup, model, GotoFileConfiguration.getInstance(project), project); - } - - protected List getAllFilterValues() { - List elements = new ArrayList(); - ContainerUtil.addAll(elements, FileTypeManager.getInstance().getRegisteredFileTypes()); - Collections.sort(elements, FileTypeComparator.INSTANCE); - return elements; - } - - protected String textForFilterValue(FileType value) { - return value.getName(); - } - - protected Icon iconForFilterValue(FileType value) { - return value.getIcon(); - } - } - - /** - * A file type comparator. The comparison rules are applied in the following order. - *

    - *
  1. Unknown file type is greatest.
  2. - *
  3. Text files are less then binary ones.
  4. - *
  5. File type with greater name is greater (case is ignored).
  6. - *
- */ - static class FileTypeComparator implements Comparator { - /** - * an instance of comparator - */ - static final Comparator INSTANCE = new FileTypeComparator(); - - /** - * {@inheritDoc} - */ - public int compare(final FileType o1, final FileType o2) { - if (o1 == o2) { - return 0; - } - if (o1 == FileTypes.UNKNOWN) { - return 1; - } - if (o2 == FileTypes.UNKNOWN) { - return -1; - } - if (o1.isBinary() && !o2.isBinary()) { - return 1; - } - if (!o1.isBinary() && o2.isBinary()) { - return -1; - } - return o1.getName().compareToIgnoreCase(o2.getName()); - } - } -} +/* + * 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.ide.actions; + +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.util.gotoByName.*; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.Navigatable; +import com.intellij.psi.PsiFile; +import com.intellij.util.containers.ContainerUtil; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * "Go to | File" action implementation. + * + * @author Eugene Belyaev + * @author Constantine.Plotnikov + */ +public class GotoFileAction extends GotoActionBase implements DumbAware { + + @Override + public void gotoActionPerformed(AnActionEvent e) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.file"); + final Project project = e.getData(PlatformDataKeys.PROJECT); + final GotoFileModel gotoFileModel = new GotoFileModel(project); + final ChooseByNamePopup popup = ChooseByNamePopup.createPopup(project, gotoFileModel, getPsiContext(e), + getInitialText(e.getData(PlatformDataKeys.EDITOR))); + final ChooseByNameFilter filterUI = new GotoFileFilter(popup, gotoFileModel, project); + popup.invoke(new ChooseByNamePopupComponent.Callback() { + public void onClose() { + if (GotoFileAction.class.equals(myInAction)) { + myInAction = null; + } + filterUI.close(); + } + + public void elementChosen(final Object element) { + if (element == null) return; + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + Navigatable n = (Navigatable)element; + + //this is for better cursor position + if (element instanceof PsiFile) { + VirtualFile vfile = ((PsiFile)element).getVirtualFile(); + if (vfile == null) return; + n = new OpenFileDescriptor(project, vfile, popup.getLinePosition(), popup.getColumnPosition()); + } + + if (!n.canNavigate()) return; + n.navigate(true); + } + }, ModalityState.NON_MODAL); + } + }, ModalityState.current(), true); + } + + protected static class GotoFileFilter extends ChooseByNameFilter { + GotoFileFilter(final ChooseByNamePopup popup, GotoFileModel model, final Project project) { + super(popup, model, GotoFileConfiguration.getInstance(project), project); + } + + protected List getAllFilterValues() { + List elements = new ArrayList(); + ContainerUtil.addAll(elements, FileTypeManager.getInstance().getRegisteredFileTypes()); + Collections.sort(elements, FileTypeComparator.INSTANCE); + return elements; + } + + protected String textForFilterValue(FileType value) { + return value.getName(); + } + + protected Icon iconForFilterValue(FileType value) { + return value.getIcon(); + } + } + + /** + * A file type comparator. The comparison rules are applied in the following order. + *
    + *
  1. Unknown file type is greatest.
  2. + *
  3. Text files are less then binary ones.
  4. + *
  5. File type with greater name is greater (case is ignored).
  6. + *
+ */ + static class FileTypeComparator implements Comparator { + /** + * an instance of comparator + */ + static final Comparator INSTANCE = new FileTypeComparator(); + + /** + * {@inheritDoc} + */ + public int compare(final FileType o1, final FileType o2) { + if (o1 == o2) { + return 0; + } + if (o1 == FileTypes.UNKNOWN) { + return 1; + } + if (o2 == FileTypes.UNKNOWN) { + return -1; + } + if (o1.isBinary() && !o2.isBinary()) { + return 1; + } + if (!o1.isBinary() && o2.isBinary()) { + return -1; + } + return o1.getName().compareToIgnoreCase(o2.getName()); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoSymbolAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoSymbolAction.java index ba653e8f4c49..ef2d7197d441 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoSymbolAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoSymbolAction.java @@ -1,60 +1,60 @@ -/* - * 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.ide.actions; - -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.ide.util.gotoByName.*; -import com.intellij.navigation.NavigationItem; -import com.intellij.navigation.ChooseByNameRegistry; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; - -public class GotoSymbolAction extends GotoActionBase { - - public void gotoActionPerformed(AnActionEvent e) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.symbol"); - final Project project = e.getData(PlatformDataKeys.PROJECT); - - PsiDocumentManager.getInstance(project).commitAllDocuments(); - - final GotoSymbolModel2 model = new GotoSymbolModel2(project); - final ChooseByNamePopup popup = ChooseByNamePopup.createPopup(project, model, getPsiContext(e), - getInitialText(e.getData(PlatformDataKeys.EDITOR))); - final ChooseByNameFilter filterUI = new ChooseByNameLanguageFilter(popup, model, GotoClassSymbolConfiguration.getInstance(project), - project); - popup.invoke(new ChooseByNamePopupComponent.Callback() { - public void onClose() { - if (GotoSymbolAction.class.equals(myInAction)) { - myInAction = null; - } - filterUI.close(); - } - - public void elementChosen(Object element) { - ((NavigationItem)element).navigate(true); - } - }, ModalityState.current(), true); - } - - protected boolean hasContributors(DataContext dataContext) { - return ChooseByNameRegistry.getInstance().getSymbolModelContributors().length > 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.ide.actions; + +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.util.gotoByName.*; +import com.intellij.navigation.NavigationItem; +import com.intellij.navigation.ChooseByNameRegistry; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; + +public class GotoSymbolAction extends GotoActionBase { + + public void gotoActionPerformed(AnActionEvent e) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.symbol"); + final Project project = e.getData(PlatformDataKeys.PROJECT); + + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + final GotoSymbolModel2 model = new GotoSymbolModel2(project); + final ChooseByNamePopup popup = ChooseByNamePopup.createPopup(project, model, getPsiContext(e), + getInitialText(e.getData(PlatformDataKeys.EDITOR))); + final ChooseByNameFilter filterUI = new ChooseByNameLanguageFilter(popup, model, GotoClassSymbolConfiguration.getInstance(project), + project); + popup.invoke(new ChooseByNamePopupComponent.Callback() { + public void onClose() { + if (GotoSymbolAction.class.equals(myInAction)) { + myInAction = null; + } + filterUI.close(); + } + + public void elementChosen(Object element) { + ((NavigationItem)element).navigate(true); + } + }, ModalityState.current(), true); + } + + protected boolean hasContributors(DataContext dataContext) { + return ChooseByNameRegistry.getInstance().getSymbolModelContributors().length > 0; + } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/commander/ProjectListBuilder.java b/platform/lang-impl/src/com/intellij/ide/commander/ProjectListBuilder.java index aa2f3e65743b..bd7c9682fd48 100644 --- a/platform/lang-impl/src/com/intellij/ide/commander/ProjectListBuilder.java +++ b/platform/lang-impl/src/com/intellij/ide/commander/ProjectListBuilder.java @@ -1,231 +1,231 @@ -/* - * 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.ide.commander; - -import com.intellij.ide.CopyPasteUtil; -import com.intellij.ide.projectView.ProjectViewNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.ide.util.treeView.AbstractTreeStructure; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.ide.CopyPasteManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.vcs.FileStatusListener; -import com.intellij.openapi.vcs.FileStatusManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiModificationTracker; -import com.intellij.util.Alarm; - -import java.awt.datatransfer.Transferable; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -public class ProjectListBuilder extends AbstractListBuilder { - private final MyPsiTreeChangeListener myPsiTreeChangeListener; - private final MyFileStatusListener myFileStatusListener; - private final CopyPasteManager.ContentChangedListener myCopyPasteListener; - private final Alarm myUpdateAlarm; - - public ProjectListBuilder(final Project project, - final CommanderPanel panel, - final AbstractTreeStructure treeStructure, - final Comparator comparator, - final boolean showRoot) { - super(project, panel.getList(), panel.getModel(), treeStructure, comparator, showRoot); - - myList.setCellRenderer(new ColoredCommanderRenderer(panel)); - myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, myProject); - - myPsiTreeChangeListener = new MyPsiTreeChangeListener(); - PsiManager.getInstance(myProject).addPsiTreeChangeListener(myPsiTreeChangeListener); - myFileStatusListener = new MyFileStatusListener(); - FileStatusManager.getInstance(myProject).addFileStatusListener(myFileStatusListener); - myCopyPasteListener = new MyCopyPasteListener(); - CopyPasteManager.getInstance().addContentChangedListener(myCopyPasteListener); - buildRoot(); - } - - protected void updateParentTitle() { - if (myParentTitle == null) return; - - AbstractTreeNode node = getParentNode(); - if (node instanceof ProjectViewNode) { - myParentTitle.setText(((ProjectViewNode)node).getTitle()); - } - else { - myParentTitle.setText(null); - } - } - - protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) { - return Comparing.equal(node.getValue(), element); - } - - protected List getAllAcceptableNodes(final Object[] childElements, VirtualFile file) { - ArrayList result = new ArrayList(); - - for (int i = 0; i < childElements.length; i++) { - ProjectViewNode childElement = (ProjectViewNode)childElements[i]; - if (childElement.contains(file)) result.add(childElement); - } - - return result; - } - - public void dispose() { - super.dispose(); - PsiManager.getInstance(myProject).removePsiTreeChangeListener(myPsiTreeChangeListener); - FileStatusManager.getInstance(myProject).removeFileStatusListener(myFileStatusListener); - CopyPasteManager.getInstance().removeContentChangedListener(myCopyPasteListener); - } - - public void addUpdateRequest() { - addUpdateRequest(false); - } - public void addUpdateRequest(final boolean shouldRefreshSelection) { - final Runnable request = new Runnable() { - public void run() { - if (!myProject.isDisposed()) { - // Rely on project view to commit PSI and wait until it's updated. - if (myTreeStructure.hasSomethingToCommit() ) { - myUpdateAlarm.cancelAllRequests(); - myUpdateAlarm.addRequest(this, 300, ModalityState.stateForComponent(myList)); - return; - } - updateList(shouldRefreshSelection); - } - } - }; - - if (!ApplicationManager.getApplication().isUnitTestMode()) { - myUpdateAlarm.cancelAllRequests(); - myUpdateAlarm.addRequest(request, 300, ModalityState.stateForComponent(myList)); - } - else { - request.run(); - } - } - - public void updateList(final boolean shouldRefreshSelection) { - updateList(); - if (shouldRefreshSelection) { - refreshSelection(); - } - } - - protected void refreshSelection() {} - - private final class MyPsiTreeChangeListener extends PsiTreeChangeAdapter { - private final PsiModificationTracker myModificationTracker; - private long myOutOfCodeBlockModificationCount; - - private MyPsiTreeChangeListener() { - myModificationTracker = PsiManager.getInstance(myProject).getModificationTracker(); - myOutOfCodeBlockModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount(); - } - - public void childRemoved(final PsiTreeChangeEvent event) { - final PsiElement child = event.getOldChild(); - if (child instanceof PsiWhiteSpace) return; //optimization - childrenChanged(); - } - - public void childAdded(final PsiTreeChangeEvent event) { - final PsiElement child = event.getNewChild(); - if (child instanceof PsiWhiteSpace) return; //optimization - childrenChanged(); - } - - public void childReplaced(final PsiTreeChangeEvent event) { - final PsiElement oldChild = event.getOldChild(); - final PsiElement newChild = event.getNewChild(); - if (oldChild instanceof PsiWhiteSpace && newChild instanceof PsiWhiteSpace) return; //optimization - childrenChanged(); - } - - public void childMoved(final PsiTreeChangeEvent event) { - childrenChanged(); - } - - public void childrenChanged(final PsiTreeChangeEvent event) { - childrenChanged(); - } - - private void childrenChanged() { - long newModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount(); - if (newModificationCount == myOutOfCodeBlockModificationCount) return; - myOutOfCodeBlockModificationCount = newModificationCount; - addUpdateRequest(); - } - - public void propertyChanged(final PsiTreeChangeEvent event) { - final String propertyName = event.getPropertyName(); - if (propertyName.equals(PsiTreeChangeEvent.PROP_ROOTS)) { - addUpdateRequest(); - } - else if (propertyName.equals(PsiTreeChangeEvent.PROP_WRITABLE)){ - childrenChanged(); - } - else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_NAME) || propertyName.equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)){ - childrenChanged(); - } - else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_TYPES)){ - addUpdateRequest(); - } - } - } - - private final class MyFileStatusListener implements FileStatusListener { - public void fileStatusesChanged() { - addUpdateRequest(); - } - - public void fileStatusChanged(final VirtualFile vFile) { - final PsiManager manager = PsiManager.getInstance(myProject); - - if (vFile.isDirectory()) { - final PsiDirectory directory = manager.findDirectory(vFile); - if (directory != null) { - myPsiTreeChangeListener.childrenChanged(); - } - } - else { - final PsiFile file = manager.findFile(vFile); - if (file != null){ - myPsiTreeChangeListener.childrenChanged(); - } - } - } - } - - private final class MyCopyPasteListener implements CopyPasteManager.ContentChangedListener { - public void contentChanged(final Transferable oldTransferable, final Transferable newTransferable) { - updateByTransferable(oldTransferable); - updateByTransferable(newTransferable); - } - - private void updateByTransferable(final Transferable t) { - final PsiElement[] psiElements = CopyPasteUtil.getElementsInTransferable(t); - for (int i = 0; i < psiElements.length; i++) { - myPsiTreeChangeListener.childrenChanged(); - } - } - } -} +/* + * 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.ide.commander; + +import com.intellij.ide.CopyPasteUtil; +import com.intellij.ide.projectView.ProjectViewNode; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.ide.util.treeView.AbstractTreeStructure; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vcs.FileStatusListener; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.util.Alarm; + +import java.awt.datatransfer.Transferable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class ProjectListBuilder extends AbstractListBuilder { + private final MyPsiTreeChangeListener myPsiTreeChangeListener; + private final MyFileStatusListener myFileStatusListener; + private final CopyPasteManager.ContentChangedListener myCopyPasteListener; + private final Alarm myUpdateAlarm; + + public ProjectListBuilder(final Project project, + final CommanderPanel panel, + final AbstractTreeStructure treeStructure, + final Comparator comparator, + final boolean showRoot) { + super(project, panel.getList(), panel.getModel(), treeStructure, comparator, showRoot); + + myList.setCellRenderer(new ColoredCommanderRenderer(panel)); + myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, myProject); + + myPsiTreeChangeListener = new MyPsiTreeChangeListener(); + PsiManager.getInstance(myProject).addPsiTreeChangeListener(myPsiTreeChangeListener); + myFileStatusListener = new MyFileStatusListener(); + FileStatusManager.getInstance(myProject).addFileStatusListener(myFileStatusListener); + myCopyPasteListener = new MyCopyPasteListener(); + CopyPasteManager.getInstance().addContentChangedListener(myCopyPasteListener); + buildRoot(); + } + + protected void updateParentTitle() { + if (myParentTitle == null) return; + + AbstractTreeNode node = getParentNode(); + if (node instanceof ProjectViewNode) { + myParentTitle.setText(((ProjectViewNode)node).getTitle()); + } + else { + myParentTitle.setText(null); + } + } + + protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) { + return Comparing.equal(node.getValue(), element); + } + + protected List getAllAcceptableNodes(final Object[] childElements, VirtualFile file) { + ArrayList result = new ArrayList(); + + for (int i = 0; i < childElements.length; i++) { + ProjectViewNode childElement = (ProjectViewNode)childElements[i]; + if (childElement.contains(file)) result.add(childElement); + } + + return result; + } + + public void dispose() { + super.dispose(); + PsiManager.getInstance(myProject).removePsiTreeChangeListener(myPsiTreeChangeListener); + FileStatusManager.getInstance(myProject).removeFileStatusListener(myFileStatusListener); + CopyPasteManager.getInstance().removeContentChangedListener(myCopyPasteListener); + } + + public void addUpdateRequest() { + addUpdateRequest(false); + } + public void addUpdateRequest(final boolean shouldRefreshSelection) { + final Runnable request = new Runnable() { + public void run() { + if (!myProject.isDisposed()) { + // Rely on project view to commit PSI and wait until it's updated. + if (myTreeStructure.hasSomethingToCommit() ) { + myUpdateAlarm.cancelAllRequests(); + myUpdateAlarm.addRequest(this, 300, ModalityState.stateForComponent(myList)); + return; + } + updateList(shouldRefreshSelection); + } + } + }; + + if (!ApplicationManager.getApplication().isUnitTestMode()) { + myUpdateAlarm.cancelAllRequests(); + myUpdateAlarm.addRequest(request, 300, ModalityState.stateForComponent(myList)); + } + else { + request.run(); + } + } + + public void updateList(final boolean shouldRefreshSelection) { + updateList(); + if (shouldRefreshSelection) { + refreshSelection(); + } + } + + protected void refreshSelection() {} + + private final class MyPsiTreeChangeListener extends PsiTreeChangeAdapter { + private final PsiModificationTracker myModificationTracker; + private long myOutOfCodeBlockModificationCount; + + private MyPsiTreeChangeListener() { + myModificationTracker = PsiManager.getInstance(myProject).getModificationTracker(); + myOutOfCodeBlockModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount(); + } + + public void childRemoved(final PsiTreeChangeEvent event) { + final PsiElement child = event.getOldChild(); + if (child instanceof PsiWhiteSpace) return; //optimization + childrenChanged(); + } + + public void childAdded(final PsiTreeChangeEvent event) { + final PsiElement child = event.getNewChild(); + if (child instanceof PsiWhiteSpace) return; //optimization + childrenChanged(); + } + + public void childReplaced(final PsiTreeChangeEvent event) { + final PsiElement oldChild = event.getOldChild(); + final PsiElement newChild = event.getNewChild(); + if (oldChild instanceof PsiWhiteSpace && newChild instanceof PsiWhiteSpace) return; //optimization + childrenChanged(); + } + + public void childMoved(final PsiTreeChangeEvent event) { + childrenChanged(); + } + + public void childrenChanged(final PsiTreeChangeEvent event) { + childrenChanged(); + } + + private void childrenChanged() { + long newModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount(); + if (newModificationCount == myOutOfCodeBlockModificationCount) return; + myOutOfCodeBlockModificationCount = newModificationCount; + addUpdateRequest(); + } + + public void propertyChanged(final PsiTreeChangeEvent event) { + final String propertyName = event.getPropertyName(); + if (propertyName.equals(PsiTreeChangeEvent.PROP_ROOTS)) { + addUpdateRequest(); + } + else if (propertyName.equals(PsiTreeChangeEvent.PROP_WRITABLE)){ + childrenChanged(); + } + else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_NAME) || propertyName.equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)){ + childrenChanged(); + } + else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_TYPES)){ + addUpdateRequest(); + } + } + } + + private final class MyFileStatusListener implements FileStatusListener { + public void fileStatusesChanged() { + addUpdateRequest(); + } + + public void fileStatusChanged(final VirtualFile vFile) { + final PsiManager manager = PsiManager.getInstance(myProject); + + if (vFile.isDirectory()) { + final PsiDirectory directory = manager.findDirectory(vFile); + if (directory != null) { + myPsiTreeChangeListener.childrenChanged(); + } + } + else { + final PsiFile file = manager.findFile(vFile); + if (file != null){ + myPsiTreeChangeListener.childrenChanged(); + } + } + } + } + + private final class MyCopyPasteListener implements CopyPasteManager.ContentChangedListener { + public void contentChanged(final Transferable oldTransferable, final Transferable newTransferable) { + updateByTransferable(oldTransferable); + updateByTransferable(newTransferable); + } + + private void updateByTransferable(final Transferable t) { + final PsiElement[] psiElements = CopyPasteUtil.getElementsInTransferable(t); + for (int i = 0; i < psiElements.length; i++) { + myPsiTreeChangeListener.childrenChanged(); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java index cafecb6d4ec2..0c8b736da947 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java @@ -1,373 +1,373 @@ -/* - * 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.ide.fileTemplates; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.fileTemplates.impl.FileTemplateImpl; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.application.ex.PathManagerEx; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypes; -import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.ClassLoaderUtil; -import com.intellij.openapi.util.ThrowableComputable; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.util.ArrayUtil; -import org.apache.commons.collections.ExtendedProperties; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.exception.VelocityException; -import org.apache.velocity.runtime.RuntimeConstants; -import org.apache.velocity.runtime.RuntimeServices; -import org.apache.velocity.runtime.RuntimeSingleton; -import org.apache.velocity.runtime.log.LogSystem; -import org.apache.velocity.runtime.parser.ParseException; -import org.apache.velocity.runtime.parser.node.ASTReference; -import org.apache.velocity.runtime.parser.node.Node; -import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; -import org.apache.velocity.runtime.resource.loader.FileResourceLoader; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.lang.reflect.Field; -import java.util.*; - -import static com.intellij.util.containers.CollectionFactory.ar; - -/** - * @author MYakovlev - */ -public class FileTemplateUtil{ - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.fileTemplates.FileTemplateUtil"); - private static boolean ourVelocityInitialized = false; - private static final CreateFromTemplateHandler ourDefaultCreateFromTemplateHandler = new DefaultCreateFromTemplateHandler(); - - private FileTemplateUtil() { - } - - public static String[] calculateAttributes(String templateContent, Properties properties, boolean includeDummies) throws ParseException { - initVelocity(); - final Set unsetAttributes = new HashSet(); - //noinspection HardCodedStringLiteral - addAttributesToVector(unsetAttributes, RuntimeSingleton.parse(new StringReader(templateContent), "MyTemplate"), properties, includeDummies); - return ArrayUtil.toStringArray(unsetAttributes); - } - - private static void addAttributesToVector(Set references, Node apacheNode, Properties properties, boolean includeDummies){ - int childCount = apacheNode.jjtGetNumChildren(); - for(int i = 0; i < childCount; i++){ - Node apacheChild = apacheNode.jjtGetChild(i); - addAttributesToVector(references, apacheChild, properties, includeDummies); - if(apacheChild instanceof ASTReference){ - ASTReference apacheReference = (ASTReference)apacheChild; - String s = apacheReference.literal(); - s = referenceToAttribute(s, includeDummies); - if (s != null && s.length() > 0 && properties.getProperty(s) == null) references.add(s); - } - } - } - - - /** - * Removes each two leading '\', removes leading $, removes {} - * Examples: - * $qqq -> qqq - * \$qqq -> qqq if dummy attributes are collected too, null otherwise - * \\$qqq -> qqq - * ${qqq} -> qqq - */ - @Nullable - private static String referenceToAttribute(String attrib, boolean includeDummies) { - while (attrib.startsWith("\\\\")) { - attrib = attrib.substring(2); - } - if (attrib.startsWith("\\$")) { - if (includeDummies) { - attrib = attrib.substring(1); - } - else return null; - } - if (!StringUtil.startsWithChar(attrib, '$')) { - return null; - } - attrib = attrib.substring(1); - if (StringUtil.startsWithChar(attrib, '{')) { - String cleanAttribute = null; - for (int i = 1; i < attrib.length(); i++) { - char currChar = attrib.charAt(i); - if (currChar == '{' || currChar == '.') { - // Invalid match - cleanAttribute = null; - break; - } - else if (currChar == '}') { - // Valid match - cleanAttribute = attrib.substring(1, i); - break; - } - } - attrib = cleanAttribute; - } - else { - for (int i = 0; i < attrib.length(); i++) { - char currChar = attrib.charAt(i); - if (currChar == '{' || currChar == '}' || currChar == '.') { - attrib = attrib.substring(0, i); - break; - } - } - } - return attrib; - } - - public static String mergeTemplate(Map attributes, String content) throws IOException{ - initVelocity(); - VelocityContext context = new VelocityContext(); - for (final Object o : attributes.keySet()) { - String name = (String)o; - context.put(name, attributes.get(name)); - } - return mergeTemplate(content, context); - } - - public static String mergeTemplate(Properties attributes, String content) throws IOException{ - initVelocity(); - VelocityContext context = new VelocityContext(); - Enumeration names = attributes.propertyNames(); - while (names.hasMoreElements()){ - String name = (String)names.nextElement(); - context.put(name, attributes.getProperty(name)); - } - return mergeTemplate(content, context); - } - - private static String mergeTemplate(String templateContent, final VelocityContext context) throws IOException { - initVelocity(); - StringWriter stringWriter = new StringWriter(); - try { - Velocity.evaluate(context, stringWriter, "", templateContent); - } - catch (VelocityException e) { - LOG.error("Error evaluating template:\n"+templateContent,e); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - Messages.showErrorDialog(IdeBundle.message("error.parsing.file.template"), - IdeBundle.message("title.velocity.error")); - } - }); - } - return stringWriter.toString(); - } - - public static FileTemplate cloneTemplate(FileTemplate template){ - FileTemplateImpl templateImpl = (FileTemplateImpl) template; - return (FileTemplate)templateImpl.clone(); - } - - public static void copyTemplate(FileTemplate src, FileTemplate dest){ - dest.setExtension(src.getExtension()); - dest.setName(src.getName()); - dest.setText(src.getText()); - dest.setAdjust(src.isAdjust()); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private static synchronized void initVelocity(){ - try{ - if (ourVelocityInitialized) { - return; - } - File modifiedPatternsPath = new File(PathManager.getConfigPath()); - modifiedPatternsPath = new File(modifiedPatternsPath, "fileTemplates"); - modifiedPatternsPath = new File(modifiedPatternsPath, "includes"); - - LogSystem emptyLogSystem = new LogSystem() { - public void init(RuntimeServices runtimeServices) throws Exception { - } - - public void logVelocityMessage(int i, String s) { - //todo[myakovlev] log somethere? - } - }; - Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, emptyLogSystem); - Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "file,class"); - //todo[myakovlev] implement my own Loader, with ability to load templates from classpath - Velocity.setProperty("file.resource.loader.class", MyFileResourceLoader.class.getName()); - Velocity.setProperty("class.resource.loader.class", MyClasspathResourceLoader.class.getName()); - Velocity.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, modifiedPatternsPath.getAbsolutePath()); - Velocity.setProperty(RuntimeConstants.INPUT_ENCODING, FileTemplate.ourEncoding); - Velocity.setProperty(RuntimeConstants.PARSER_POOL_SIZE, 3); - Velocity.init(); - ourVelocityInitialized = true; - } - catch (Exception e){ - LOG.error("Unable to init Velocity", e); - } - } - - public static PsiElement createFromTemplate(@NotNull final FileTemplate template, - @NonNls @Nullable final String fileName, - @Nullable Properties props, - @NotNull final PsiDirectory directory) throws Exception { - return createFromTemplate(template, fileName, props, directory, null); - } - - public static PsiElement createFromTemplate(@NotNull final FileTemplate template, - @NonNls @Nullable final String fileName, - @Nullable Properties props, - @NotNull final PsiDirectory directory, - @Nullable ClassLoader classLoader) throws Exception { - @NotNull final Project project = directory.getProject(); - if (props == null) { - props = FileTemplateManager.getInstance().getDefaultProperties(); - } - FileTemplateManager.getInstance().addRecentName(template.getName()); - fillDefaultProperties(props, directory); - - if (fileName != null && props.getProperty(FileTemplate.ATTRIBUTE_NAME) == null) { - props.setProperty(FileTemplate.ATTRIBUTE_NAME, fileName); - } - - //Set escaped references to dummy values to remove leading "\" (if not already explicitely set) - String[] dummyRefs = calculateAttributes(template.getText(), props, true); - for (String dummyRef : dummyRefs) { - props.setProperty(dummyRef, ""); - } - - if (template.isJavaClassTemplate()){ - String packageName = props.getProperty(FileTemplate.ATTRIBUTE_PACKAGE_NAME); - if(packageName == null || packageName.length() == 0){ - props = new Properties(props); - props.setProperty(FileTemplate.ATTRIBUTE_PACKAGE_NAME, FileTemplate.ATTRIBUTE_PACKAGE_NAME); - } - } - - final Properties props_ = props; - String mergedText = ClassLoaderUtil.runWithClassLoader(classLoader != null ? classLoader : FileTemplateUtil.class.getClassLoader(), - new ThrowableComputable() { - @Override - public String compute() throws IOException { - return template.getText(props_); - } - }); - final String templateText = StringUtil.convertLineSeparators(mergedText); - final Exception[] commandException = new Exception[1]; - final PsiElement[] result = new PsiElement[1]; - final Properties finalProps = props; - CommandProcessor.getInstance().executeCommand(project, new Runnable(){ - public void run(){ - final Runnable run = new Runnable(){ - public void run(){ - try{ - CreateFromTemplateHandler handler = findHandler(template); - result [0] = handler.createFromTemplate(project, directory, fileName, template, templateText, finalProps); - } - catch (Exception ex){ - commandException[0] = ex; - } - } - }; - ApplicationManager.getApplication().runWriteAction(run); - } - }, template.isJavaClassTemplate() - ? IdeBundle.message("command.create.class.from.template") - : IdeBundle.message("command.create.file.from.template"), null); - if(commandException[0] != null){ - throw commandException[0]; - } - return result[0]; - } - - private static CreateFromTemplateHandler findHandler(final FileTemplate template) { - for(CreateFromTemplateHandler handler: Extensions.getExtensions(CreateFromTemplateHandler.EP_NAME)) { - if (handler.handlesTemplate(template)) { - return handler; - } - } - return ourDefaultCreateFromTemplateHandler; - } - - public static void fillDefaultProperties(final Properties props, final PsiDirectory directory) { - final DefaultTemplatePropertiesProvider[] providers = Extensions.getExtensions(DefaultTemplatePropertiesProvider.EP_NAME); - for(DefaultTemplatePropertiesProvider provider: providers) { - provider.fillProperties(directory, props); - } - } - - public static String indent(String methodText, Project project, FileType fileType) { - int indent = CodeStyleSettingsManager.getSettings(project).getIndentSize(fileType); - return methodText.replaceAll("\n", "\n" + StringUtil.repeatSymbol(' ',indent)); - } - - @NonNls private static final String INCLUDES_PATH = "fileTemplates/includes/"; - - public static class MyClasspathResourceLoader extends ClasspathResourceLoader{ - @NonNls private static final String FT_EXTENSION = ".ft"; - - public synchronized InputStream getResourceStream(String name) throws ResourceNotFoundException{ - return super.getResourceStream(INCLUDES_PATH + name + FT_EXTENSION); - } - } - - public static class MyFileResourceLoader extends FileResourceLoader{ - public void init(ExtendedProperties configuration){ - super.init(configuration); - - File modifiedPatternsPath = new File(PathManager.getConfigPath()); - modifiedPatternsPath = new File(modifiedPatternsPath, INCLUDES_PATH); - - try { - Field pathsField = FileResourceLoader.class.getDeclaredField("paths"); - pathsField.setAccessible(true); - Collection paths = (Collection)pathsField.get(this); - paths.clear(); - paths.add(modifiedPatternsPath.getAbsolutePath()); - if(ApplicationManager.getApplication().isUnitTestMode()){ - // todo this is for FileTemplatesTest - // todo it should register its own loader and not depend on in what kind of test velocity is first needed - for (PathManagerEx.TestDataLookupStrategy strategy : ar(PathManagerEx.TestDataLookupStrategy.COMMUNITY_FROM_ULTIMATE, PathManagerEx.TestDataLookupStrategy.COMMUNITY)) { - File testsDir = new File(new File(PathManagerEx.getTestDataPath(strategy), "ide"), "fileTemplates"); - paths.add(testsDir.getAbsolutePath()); - } - } - } - catch (Exception e) { - LOG.error(e); - throw new RuntimeException(e); - } - } - } - - public static boolean canCreateFromTemplate (PsiDirectory[] dirs, FileTemplate template) { - FileType fileType = FileTypeManagerEx.getInstanceEx().getFileTypeByExtension(template.getExtension()); - if (fileType.equals(FileTypes.UNKNOWN)) return false; - CreateFromTemplateHandler handler = findHandler(template); - return handler.canCreate(dirs); - } -} +/* + * 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.ide.fileTemplates; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.fileTemplates.impl.FileTemplateImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.ClassLoaderUtil; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.util.ArrayUtil; +import org.apache.commons.collections.ExtendedProperties; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.RuntimeSingleton; +import org.apache.velocity.runtime.log.LogSystem; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.node.ASTReference; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; +import org.apache.velocity.runtime.resource.loader.FileResourceLoader; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.lang.reflect.Field; +import java.util.*; + +import static com.intellij.util.containers.CollectionFactory.ar; + +/** + * @author MYakovlev + */ +public class FileTemplateUtil{ + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.fileTemplates.FileTemplateUtil"); + private static boolean ourVelocityInitialized = false; + private static final CreateFromTemplateHandler ourDefaultCreateFromTemplateHandler = new DefaultCreateFromTemplateHandler(); + + private FileTemplateUtil() { + } + + public static String[] calculateAttributes(String templateContent, Properties properties, boolean includeDummies) throws ParseException { + initVelocity(); + final Set unsetAttributes = new HashSet(); + //noinspection HardCodedStringLiteral + addAttributesToVector(unsetAttributes, RuntimeSingleton.parse(new StringReader(templateContent), "MyTemplate"), properties, includeDummies); + return ArrayUtil.toStringArray(unsetAttributes); + } + + private static void addAttributesToVector(Set references, Node apacheNode, Properties properties, boolean includeDummies){ + int childCount = apacheNode.jjtGetNumChildren(); + for(int i = 0; i < childCount; i++){ + Node apacheChild = apacheNode.jjtGetChild(i); + addAttributesToVector(references, apacheChild, properties, includeDummies); + if(apacheChild instanceof ASTReference){ + ASTReference apacheReference = (ASTReference)apacheChild; + String s = apacheReference.literal(); + s = referenceToAttribute(s, includeDummies); + if (s != null && s.length() > 0 && properties.getProperty(s) == null) references.add(s); + } + } + } + + + /** + * Removes each two leading '\', removes leading $, removes {} + * Examples: + * $qqq -> qqq + * \$qqq -> qqq if dummy attributes are collected too, null otherwise + * \\$qqq -> qqq + * ${qqq} -> qqq + */ + @Nullable + private static String referenceToAttribute(String attrib, boolean includeDummies) { + while (attrib.startsWith("\\\\")) { + attrib = attrib.substring(2); + } + if (attrib.startsWith("\\$")) { + if (includeDummies) { + attrib = attrib.substring(1); + } + else return null; + } + if (!StringUtil.startsWithChar(attrib, '$')) { + return null; + } + attrib = attrib.substring(1); + if (StringUtil.startsWithChar(attrib, '{')) { + String cleanAttribute = null; + for (int i = 1; i < attrib.length(); i++) { + char currChar = attrib.charAt(i); + if (currChar == '{' || currChar == '.') { + // Invalid match + cleanAttribute = null; + break; + } + else if (currChar == '}') { + // Valid match + cleanAttribute = attrib.substring(1, i); + break; + } + } + attrib = cleanAttribute; + } + else { + for (int i = 0; i < attrib.length(); i++) { + char currChar = attrib.charAt(i); + if (currChar == '{' || currChar == '}' || currChar == '.') { + attrib = attrib.substring(0, i); + break; + } + } + } + return attrib; + } + + public static String mergeTemplate(Map attributes, String content) throws IOException{ + initVelocity(); + VelocityContext context = new VelocityContext(); + for (final Object o : attributes.keySet()) { + String name = (String)o; + context.put(name, attributes.get(name)); + } + return mergeTemplate(content, context); + } + + public static String mergeTemplate(Properties attributes, String content) throws IOException{ + initVelocity(); + VelocityContext context = new VelocityContext(); + Enumeration names = attributes.propertyNames(); + while (names.hasMoreElements()){ + String name = (String)names.nextElement(); + context.put(name, attributes.getProperty(name)); + } + return mergeTemplate(content, context); + } + + private static String mergeTemplate(String templateContent, final VelocityContext context) throws IOException { + initVelocity(); + StringWriter stringWriter = new StringWriter(); + try { + Velocity.evaluate(context, stringWriter, "", templateContent); + } + catch (VelocityException e) { + LOG.error("Error evaluating template:\n"+templateContent,e); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + Messages.showErrorDialog(IdeBundle.message("error.parsing.file.template"), + IdeBundle.message("title.velocity.error")); + } + }); + } + return stringWriter.toString(); + } + + public static FileTemplate cloneTemplate(FileTemplate template){ + FileTemplateImpl templateImpl = (FileTemplateImpl) template; + return (FileTemplate)templateImpl.clone(); + } + + public static void copyTemplate(FileTemplate src, FileTemplate dest){ + dest.setExtension(src.getExtension()); + dest.setName(src.getName()); + dest.setText(src.getText()); + dest.setAdjust(src.isAdjust()); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static synchronized void initVelocity(){ + try{ + if (ourVelocityInitialized) { + return; + } + File modifiedPatternsPath = new File(PathManager.getConfigPath()); + modifiedPatternsPath = new File(modifiedPatternsPath, "fileTemplates"); + modifiedPatternsPath = new File(modifiedPatternsPath, "includes"); + + LogSystem emptyLogSystem = new LogSystem() { + public void init(RuntimeServices runtimeServices) throws Exception { + } + + public void logVelocityMessage(int i, String s) { + //todo[myakovlev] log somethere? + } + }; + Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, emptyLogSystem); + Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "file,class"); + //todo[myakovlev] implement my own Loader, with ability to load templates from classpath + Velocity.setProperty("file.resource.loader.class", MyFileResourceLoader.class.getName()); + Velocity.setProperty("class.resource.loader.class", MyClasspathResourceLoader.class.getName()); + Velocity.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, modifiedPatternsPath.getAbsolutePath()); + Velocity.setProperty(RuntimeConstants.INPUT_ENCODING, FileTemplate.ourEncoding); + Velocity.setProperty(RuntimeConstants.PARSER_POOL_SIZE, 3); + Velocity.init(); + ourVelocityInitialized = true; + } + catch (Exception e){ + LOG.error("Unable to init Velocity", e); + } + } + + public static PsiElement createFromTemplate(@NotNull final FileTemplate template, + @NonNls @Nullable final String fileName, + @Nullable Properties props, + @NotNull final PsiDirectory directory) throws Exception { + return createFromTemplate(template, fileName, props, directory, null); + } + + public static PsiElement createFromTemplate(@NotNull final FileTemplate template, + @NonNls @Nullable final String fileName, + @Nullable Properties props, + @NotNull final PsiDirectory directory, + @Nullable ClassLoader classLoader) throws Exception { + @NotNull final Project project = directory.getProject(); + if (props == null) { + props = FileTemplateManager.getInstance().getDefaultProperties(); + } + FileTemplateManager.getInstance().addRecentName(template.getName()); + fillDefaultProperties(props, directory); + + if (fileName != null && props.getProperty(FileTemplate.ATTRIBUTE_NAME) == null) { + props.setProperty(FileTemplate.ATTRIBUTE_NAME, fileName); + } + + //Set escaped references to dummy values to remove leading "\" (if not already explicitely set) + String[] dummyRefs = calculateAttributes(template.getText(), props, true); + for (String dummyRef : dummyRefs) { + props.setProperty(dummyRef, ""); + } + + if (template.isJavaClassTemplate()){ + String packageName = props.getProperty(FileTemplate.ATTRIBUTE_PACKAGE_NAME); + if(packageName == null || packageName.length() == 0){ + props = new Properties(props); + props.setProperty(FileTemplate.ATTRIBUTE_PACKAGE_NAME, FileTemplate.ATTRIBUTE_PACKAGE_NAME); + } + } + + final Properties props_ = props; + String mergedText = ClassLoaderUtil.runWithClassLoader(classLoader != null ? classLoader : FileTemplateUtil.class.getClassLoader(), + new ThrowableComputable() { + @Override + public String compute() throws IOException { + return template.getText(props_); + } + }); + final String templateText = StringUtil.convertLineSeparators(mergedText); + final Exception[] commandException = new Exception[1]; + final PsiElement[] result = new PsiElement[1]; + final Properties finalProps = props; + CommandProcessor.getInstance().executeCommand(project, new Runnable(){ + public void run(){ + final Runnable run = new Runnable(){ + public void run(){ + try{ + CreateFromTemplateHandler handler = findHandler(template); + result [0] = handler.createFromTemplate(project, directory, fileName, template, templateText, finalProps); + } + catch (Exception ex){ + commandException[0] = ex; + } + } + }; + ApplicationManager.getApplication().runWriteAction(run); + } + }, template.isJavaClassTemplate() + ? IdeBundle.message("command.create.class.from.template") + : IdeBundle.message("command.create.file.from.template"), null); + if(commandException[0] != null){ + throw commandException[0]; + } + return result[0]; + } + + private static CreateFromTemplateHandler findHandler(final FileTemplate template) { + for(CreateFromTemplateHandler handler: Extensions.getExtensions(CreateFromTemplateHandler.EP_NAME)) { + if (handler.handlesTemplate(template)) { + return handler; + } + } + return ourDefaultCreateFromTemplateHandler; + } + + public static void fillDefaultProperties(final Properties props, final PsiDirectory directory) { + final DefaultTemplatePropertiesProvider[] providers = Extensions.getExtensions(DefaultTemplatePropertiesProvider.EP_NAME); + for(DefaultTemplatePropertiesProvider provider: providers) { + provider.fillProperties(directory, props); + } + } + + public static String indent(String methodText, Project project, FileType fileType) { + int indent = CodeStyleSettingsManager.getSettings(project).getIndentSize(fileType); + return methodText.replaceAll("\n", "\n" + StringUtil.repeatSymbol(' ',indent)); + } + + @NonNls private static final String INCLUDES_PATH = "fileTemplates/includes/"; + + public static class MyClasspathResourceLoader extends ClasspathResourceLoader{ + @NonNls private static final String FT_EXTENSION = ".ft"; + + public synchronized InputStream getResourceStream(String name) throws ResourceNotFoundException{ + return super.getResourceStream(INCLUDES_PATH + name + FT_EXTENSION); + } + } + + public static class MyFileResourceLoader extends FileResourceLoader{ + public void init(ExtendedProperties configuration){ + super.init(configuration); + + File modifiedPatternsPath = new File(PathManager.getConfigPath()); + modifiedPatternsPath = new File(modifiedPatternsPath, INCLUDES_PATH); + + try { + Field pathsField = FileResourceLoader.class.getDeclaredField("paths"); + pathsField.setAccessible(true); + Collection paths = (Collection)pathsField.get(this); + paths.clear(); + paths.add(modifiedPatternsPath.getAbsolutePath()); + if(ApplicationManager.getApplication().isUnitTestMode()){ + // todo this is for FileTemplatesTest + // todo it should register its own loader and not depend on in what kind of test velocity is first needed + for (PathManagerEx.TestDataLookupStrategy strategy : ar(PathManagerEx.TestDataLookupStrategy.COMMUNITY_FROM_ULTIMATE, PathManagerEx.TestDataLookupStrategy.COMMUNITY)) { + File testsDir = new File(new File(PathManagerEx.getTestDataPath(strategy), "ide"), "fileTemplates"); + paths.add(testsDir.getAbsolutePath()); + } + } + } + catch (Exception e) { + LOG.error(e); + throw new RuntimeException(e); + } + } + } + + public static boolean canCreateFromTemplate (PsiDirectory[] dirs, FileTemplate template) { + FileType fileType = FileTypeManagerEx.getInstanceEx().getFileTypeByExtension(template.getExtension()); + if (fileType.equals(FileTypes.UNKNOWN)) return false; + CreateFromTemplateHandler handler = findHandler(template); + return handler.canCreate(dirs); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java index c58ea7b4dce2..bc4b12ebf655 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java @@ -1,1330 +1,1330 @@ -/* - * 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.ide.navigationToolbar; - -import com.intellij.ProjectTopics; -import com.intellij.codeInsight.hint.HintManagerImpl; -import com.intellij.ide.CopyPasteDelegator; -import com.intellij.ide.DataManager; -import com.intellij.ide.IdeView; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.ide.projectView.impl.AbstractProjectViewPane; -import com.intellij.ide.projectView.impl.ProjectRootsUtil; -import com.intellij.ide.ui.UISettings; -import com.intellij.ide.ui.customization.CustomActionsSchema; -import com.intellij.ide.util.DeleteHandler; -import com.intellij.ide.util.DirectoryChooserUtil; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.markup.EffectType; -import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.*; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.FileStatusListener; -import com.intellij.openapi.vcs.FileStatusManager; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.pom.Navigatable; -import com.intellij.problems.WolfTheProblemSolver; -import com.intellij.psi.*; -import com.intellij.ui.*; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.components.panels.OpaquePanel; -import com.intellij.ui.popup.AbstractPopup; -import com.intellij.ui.popup.PopupOwner; -import com.intellij.ui.popup.list.ListPopupImpl; -import com.intellij.util.Alarm; -import com.intellij.util.Icons; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.update.MergingUpdateQueue; -import com.intellij.util.ui.update.Update; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * User: anna - * Date: 03-Nov-2005 - */ -public class NavBarPanel extends OpaquePanel.List implements DataProvider, PopupOwner { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.navigationToolbar.NavigationToolbarPanel"); - /*private static final Icon LEFT_ICON = IconLoader.getIcon("/general/splitLeft.png"); - private static final Icon RIGHT_ICON = IconLoader.getIcon("/general/splitRight.png"); -*/ - private final ArrayList myList = new ArrayList(); - - private final NavBarModel myModel; - private final Project myProject; - - private Runnable myDetacher; - private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider(); - - private final IdeView myIdeView = new MyIdeView(); - private final CopyPasteDelegator myCopyPasteDelegator; - private LightweightHint myHint = null; - private ListPopupImpl myNodePopup = null; - - private JComponent myHintContainer; - private Component myContextComponent; - private Runnable myRunWhenListRebuilt; - - - private final MergingUpdateQueue myUpdateQueue; - private AtomicBoolean myModelUpdating = new AtomicBoolean(Boolean.FALSE); - private MyItemLabel myContextObject; - - public NavBarPanel(final Project project) { - super(new FlowLayout(FlowLayout.LEFT, 5, 0), UIUtil.isUnderGTKLookAndFeel() ? Color.WHITE : UIUtil.getListBackground()); - - myProject = project; - myModel = new NavBarModel(myProject); - - myUpdateQueue = new MergingUpdateQueue("NavBar", 150, true, MergingUpdateQueue.ANY_COMPONENT, project, null); - - PopupHandler.installPopupHandler(this, IdeActions.GROUP_PROJECT_VIEW_POPUP, ActionPlaces.NAVIGATION_BAR); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - shiftFocus(-1); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), WHEN_FOCUSED); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(final ActionEvent e) { - shiftFocus(1); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), WHEN_FOCUSED); - - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - shiftFocus(-myModel.getSelectedIndex()); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), WHEN_FOCUSED); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(final ActionEvent e) { - shiftFocus(myModel.size() - 1 - myModel.getSelectedIndex()); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), WHEN_FOCUSED); - - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (myModel.getSelectedIndex() != -1) { - ctrlClick(myModel.getSelectedIndex()); - } - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), WHEN_FOCUSED); - - final ActionListener dblClickAction = new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (myModel.getSelectedIndex() != -1) { - doubleClick(myModel.getSelectedIndex()); - } - } - }; - - registerKeyboardAction(dblClickAction, KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), WHEN_FOCUSED); - - registerKeyboardAction(new AbstractAction() { - public void actionPerformed(ActionEvent e) { - final Object o = myModel.getSelectedValue(); - navigateInsideBar(optimizeTarget(o)); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED); - - registerKeyboardAction(new AbstractAction() { - public void actionPerformed(ActionEvent e) { - myModel.setSelectedIndex(-1); - ToolWindowManager.getInstance(project).activateEditorComponent(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_FOCUSED); - - addFocusListener(new FocusListener() { - public void focusGained(final FocusEvent e) { - updateItems(); - - if (!isInFloatingMode() && myList.size() > 0) { - myContextObject = myList.get(myList.size() - 1); - } else { - myContextObject = null; - } - } - - public void focusLost(final FocusEvent e) { - if (myProject.isDisposed()) { - myContextObject = null; - hideHint(); - return; - } - - // required invokeLater since in current call sequence KeyboardFocusManager is not initialized yet - // but future focused component - SwingUtilities.invokeLater(new Runnable() { - public void run() { - processFocusLost(e); - } - }); - } - }); - - installBorder(-1, false); - - myCopyPasteDelegator = new CopyPasteDelegator(myProject, NavBarPanel.this) { - @NotNull - protected PsiElement[] getSelectedElements() { - final PsiElement element = getSelectedElement(PsiElement.class); - return element == null ? PsiElement.EMPTY_ARRAY : new PsiElement[]{element}; - } - }; - - - queueModelUpdateFromFocus(); - queueRebuildUi(); - } - - private void queueModelUpdate(DataContext context) { - _queueModelUpdate(context, null); - } - - private void queueModelUpdateFromFocus() { - _queueModelUpdate(null, myContextObject); - } - - private void queueModelUpdateForObject(Object object) { - _queueModelUpdate(null, object); - } - - private void _queueModelUpdate(@Nullable final DataContext context, final @Nullable Object object) { - if (myModelUpdating.getAndSet(true)) return; - - myUpdateQueue.cancelAllUpdates(); - - myUpdateQueue.queue(new Update("model", 0) { - @Override - public void run() { - if (context != null || object != null) { - _updateModelFrom(context, object); - } - else { - DataManager.getInstance().getDataContextFromFocus().doWhenDone(new AsyncResult.Handler() { - @Override - public void run(DataContext dataContext) { - _updateModelFrom(dataContext, null); - } - }); - } - } - - @Override - public void setRejected() { - super.setRejected(); - myModelUpdating.set(false); - } - }); - } - - private void _updateModelFrom(DataContext dataContext, Object object) { - if (dataContext != null) { - boolean a = LangDataKeys.IDE_VIEW.getData(dataContext) == myIdeView; - boolean b = PlatformDataKeys.PROJECT.getData(dataContext) != myProject; - if (b || isNodePopupShowing()) { - myModelUpdating.set(false); - queueModelUpdateFromFocus(); - return; - } - myModel.updateModel(dataContext); - } else { - myModel.updateModel(object); - } - - myModelUpdating.set(false); - } - - private void queueRebuildUi() { - myUpdateQueue.queue(new AfterModel("ui", 1) { - @Override - protected void _run() { - rebuildUi(); - } - }); - } - - private void queueRevalidate(@Nullable final Runnable after) { - myUpdateQueue.queue(new AfterModel("revalidate", 2) { - @Override - protected void _run() { - if (myHint != null) { - getHintContainerShowPoint().doWhenDone(new AsyncResult.Handler() { - @Override - public void run(final RelativePoint relativePoint) { - myHint.setSize(getPreferredSize()); - myHint.setLocation(relativePoint); - if (after != null) { - after.run(); - } - } - }); - } - else { - if (after != null) { - after.run(); - } - } - } - }); - } - - private void queueSelect(final Runnable runnable) { - myUpdateQueue.queue(new AfterModel("select", 3) { - @Override - protected void _run() { - runnable.run(); - } - }); - } - - private void queueAfterAll(final Runnable runnable) { - myUpdateQueue.queue(new AfterModel(new Object(), 4) { - @Override - protected void _run() { - runnable.run(); - } - }); - } - - private abstract class AfterModel extends Update { - private AfterModel(Object identity, int priority) { - super(identity, priority); - } - - @Override - public void run() { - if (myModelUpdating.get()) { - myUpdateQueue.queue(this); - } - else { - _run(); - } - } - - protected abstract void _run(); - } - - private static Object optimizeTarget(Object target) { - if (target instanceof PsiDirectory && ((PsiDirectory)target).getFiles().length == 0) { - final PsiDirectory[] subDir = ((PsiDirectory)target).getSubdirectories(); - if (subDir.length == 1) { - return optimizeTarget(subDir[0]); - } - } - return target; - } - - private void processFocusLost(final FocusEvent e) { - final boolean nodePopupInactive = myNodePopup == null || !myNodePopup.isVisible() || !myNodePopup.isFocused(); - boolean childPopupInactive = !JBPopupFactory.getInstance().isChildPopupFocused(this); - if (nodePopupInactive && childPopupInactive) { - final Component opposite = e.getOppositeComponent(); - if (opposite != null && opposite != this && !isAncestorOf(opposite) && !e.isTemporary()) { - myContextObject = null; - hideHint(); - } - } - - updateItems(); - } - - private void updateItems() { - for (MyItemLabel item : myList) { - item.update(); - } - } - - public void selectTail() { - queueModelUpdateFromFocus(); - queueRebuildUi(); - - queueSelect(new Runnable() { - @Override - public void run() { - if (!myList.isEmpty()) { - myModel.setSelectedIndex(myList.size() - 1); - IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); - } - } - }); - } - - private void shiftFocus(int direction) { - myModel.setSelectedIndex(myModel.getIndexByModel(myModel.getSelectedIndex() + direction)); - } - - private void scrollSelectionToVisible() { - final int selectedIndex = myModel.getSelectedIndex(); - if (selectedIndex == -1 || selectedIndex >= myList.size()) return; - - MyItemLabel selectedItem = myList.get(selectedIndex); - Rectangle rect = selectedItem.getBounds(); - scrollRectToVisible(rect); - } - - @Nullable - private MyItemLabel getItem(int index) { - if (index != -1 && index < myList.size()) { - return myList.get(index); - } - return null; - } - - private boolean isInFloatingMode() { - return myHint != null && myHint.isVisible(); - } - - - @Override - public Dimension getPreferredSize() { - if (!myList.isEmpty()) { - return super.getPreferredSize(); - } - else { - return new MyItemLabel(null, 0, Icons.DIRECTORY_OPEN_ICON, "Sample", SimpleTextAttributes.REGULAR_ATTRIBUTES).getPreferredSize(); - } - } - - private void rebuildUi() { - myList.clear(); - for (int index = 0; index < myModel.size(); index++) { - final Object object = myModel.get(index); - Icon closedIcon = getIcon(object, false); - Icon openIcon = getIcon(object, true); - - if (closedIcon == null && openIcon != null) closedIcon = openIcon; - if (openIcon == null && closedIcon != null) openIcon = closedIcon; - if (openIcon == null) { - openIcon = closedIcon = new EmptyIcon(5, 5); - } - - final MyItemLabel label = - new MyItemLabel(object, index, wrapIcon(openIcon, closedIcon, index), NavBarModel.getPresentableText(object, getWindow()), - myModel.getTextAttributes(object, false)); - - installActions(index, label); - myList.add(label); - - } - - rebuildComponent(); - - if (myRunWhenListRebuilt != null) { - Runnable r = myRunWhenListRebuilt; - myRunWhenListRebuilt = null; - r.run(); - } - } - - @Nullable - private static Icon getIcon(final Object object, final boolean isopen) { - if (!NavBarModel.checkValid(object)) return null; - if (object instanceof Project) return IconLoader.getIcon("/nodes/project.png"); - if (object instanceof Module) return ((Module)object).getModuleType().getNodeIcon(false); - try { - if (object instanceof PsiElement) { - return ApplicationManager.getApplication().runReadAction(new Computable() { - public Icon compute() { - return ((PsiElement)object).isValid() ? ((PsiElement)object) - .getIcon(isopen ? Iconable.ICON_FLAG_OPEN : Iconable.ICON_FLAG_CLOSED) : null; - } - }); - } - } - catch (IndexNotReadyException e) { - return null; - } - if (object instanceof JdkOrderEntry) return ((JdkOrderEntry)object).getJdk().getSdkType().getIcon(); - if (object instanceof LibraryOrderEntry) return IconLoader.getIcon("/nodes/ppLibClosed.png"); - if (object instanceof ModuleOrderEntry) return ((ModuleOrderEntry)object).getModule().getModuleType().getNodeIcon(false); - return null; - } - - - private Icon wrapIcon(final Icon openIcon, final Icon closedIcon, final int idx) { - return new Icon() { - public void paintIcon(Component c, Graphics g, int x, int y) { - if (myModel.getSelectedIndex() == idx && myNodePopup != null && myNodePopup.isVisible()) { - openIcon.paintIcon(c, g, x, y); - } - else { - closedIcon.paintIcon(c, g, x, y); - } - } - - public int getIconWidth() { - return openIcon.getIconWidth(); - } - - public int getIconHeight() { - return openIcon.getIconHeight(); - } - }; - } - - private void rebuildComponent() { - removeAll(); - - for (MyItemLabel item : myList) { - add(item); - } - - revalidate(); - repaint(); - - queueAfterAll(new Runnable() { - @Override - public void run() { - scrollSelectionToVisible(); - } - }); - } - - private Window getWindow() { - return !isShowing() ? null : (Window)UIUtil.findUltimateParent(this); - } - - // ------ NavBar actions ------------------------- - private void installActions(final int index, final MyItemLabel component) { - ListenerUtil.addMouseListener(component, new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (!e.isConsumed() && !e.isPopupTrigger() && e.getClickCount() == 2) { - myModel.setSelectedIndex(index); - IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); - doubleClick(index); - e.consume(); - } - } - }); - - ListenerUtil.addMouseListener(component, new MouseAdapter() { - public void mouseReleased(final MouseEvent e) { - if (SystemInfo.isWindows) { - click(e); - } - } - - public void mousePressed(final MouseEvent e) { - if (!SystemInfo.isWindows) { - click(e); - } - } - - private void click(final MouseEvent e) { - if (!e.isConsumed() && e.isPopupTrigger()) { - myModel.setSelectedIndex(index); - IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); - rightClick(index); - e.consume(); - } - } - }); - - ListenerUtil.addMouseListener(component, new MouseAdapter() { - public void mouseReleased(final MouseEvent e) { - if (SystemInfo.isWindows) { - click(e); - } - } - - public void mousePressed(final MouseEvent e) { - if (!SystemInfo.isWindows) { - click(e); - } - } - - private void click(final MouseEvent e) { - if (!e.isConsumed() && !e.isPopupTrigger() && e.getClickCount() == 1) { - ctrlClick(index); - myModel.setSelectedIndex(index); - e.consume(); - } - } - }); - } - - private void doubleClick(final int index) { - doubleClick(myModel.getElement(index)); - } - - private void doubleClick(final Object object) { - if (object instanceof Navigatable) { - final Navigatable navigatable = (Navigatable)object; - if (navigatable.canNavigate()) { - navigatable.navigate(true); - } - } - else if (object instanceof Module) { - final ProjectView projectView = ProjectView.getInstance(myProject); - final AbstractProjectViewPane projectViewPane = projectView.getProjectViewPaneById(projectView.getCurrentViewId()); - projectViewPane.selectModule((Module)object, true); - } - else if (object instanceof Project) { - return; - } - hideHint(); - } - - private void ctrlClick(final int index) { - if (isNodePopupShowing()) { - cancelPopup(); - if (myModel.getSelectedIndex() == index) { - return; - } - } - - final Object object = myModel.getElement(index); - final java.util.List objects = myModel.calcElementChildren(object); - - if (!objects.isEmpty()) { - final Object[] siblings = new Object[objects.size()]; - final Icon[] icons = new Icon[objects.size()]; - for (int i = 0; i < objects.size(); i++) { - siblings[i] = objects.get(i); - icons[i] = getIcon(siblings[i], false); - } - final MyItemLabel item = getItem(index); - LOG.assertTrue(item != null); - final BaseListPopupStep step = new BaseListPopupStep("", siblings, icons) { - public boolean isSpeedSearchEnabled() { - return true; - } - - @NotNull - public String getTextFor(final Object value) { - return NavBarModel.getPresentableText(value, null); - } - - public boolean isSelectable(Object value) { - return true; - } - - public PopupStep onChosen(final Object selectedValue, final boolean finalChoice) { - return doFinalStep(new Runnable() { - public void run() { - navigateInsideBar(optimizeTarget(selectedValue)); - } - }); - } - - /* - public void canceled() { - super.canceled(); - item.getLabel().setIcon(wrapIcon(NavBarModel.getIcon(object), index, Color.gray)); - } - */ - }; - step.setDefaultOptionIndex(index < myModel.size() - 1 ? objects.indexOf(myModel.getElement(index + 1)) : 0); - myNodePopup = new ListPopupImpl(step) { - protected ListCellRenderer getListElementRenderer() { - return new MySiblingsListCellRenderer(); - } - - }; - myNodePopup.registerAction("left", KeyEvent.VK_LEFT, 0, new AbstractAction() { - public void actionPerformed(ActionEvent e) { - myNodePopup.goBack(); - shiftFocus(-1); - restorePopup(); - } - }); - myNodePopup.registerAction("right", KeyEvent.VK_RIGHT, 0, new AbstractAction() { - public void actionPerformed(ActionEvent e) { - myNodePopup.goBack(); - shiftFocus(1); - restorePopup(); - } - }); - - ListenerUtil.addMouseListener(myNodePopup.getComponent(), new MouseAdapter() { - public void mouseReleased(final MouseEvent e) { - if (SystemInfo.isWindows) { - click(e); - } - } - - public void mousePressed(final MouseEvent e) { - if (!SystemInfo.isWindows) { - click(e); - } - } - - private void click(final MouseEvent e) { - if (!e.isConsumed() && e.isPopupTrigger()) { - myModel.setSelectedIndex(index); - IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); - rightClick(index); - e.consume(); - } - } - }); - - if (!isValid()) { - validate(); - } - - if (item.isShowing()) { - myNodePopup.showUnderneathOf(item); - } - } - } - - private boolean isNodePopupShowing() { - return myNodePopup != null && myNodePopup.isVisible(); - } - - private void navigateInsideBar(final Object object) { - myContextObject = null; - - myUpdateQueue.cancelAllUpdates(); - - queueModelUpdateForObject(object); - queueRebuildUi(); - - queueRevalidate(new Runnable() { - public void run() { - if (myModel.hasChildren(object)) { - restorePopup(); - } - else { - doubleClick(object); - } - } - }); - } - - private void rightClick(final int index) { - final ActionManager actionManager = ActionManager.getInstance(); - final ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_NAVBAR_POPUP); - final ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.NAVIGATION_BAR, group); - final MyItemLabel item = getItem(index); - if (item != null) { - popupMenu.getComponent().show(this, item.getX(), item.getY() + item.getHeight()); - } - } - - private void restorePopup() { - cancelPopup(); - ctrlClick(myModel.getSelectedIndex()); - } - - private void cancelPopup() { - if (myNodePopup != null) { - myNodePopup.cancel(); - myNodePopup = null; - } - } - - private void hideHint() { - if (myHint != null) { - myHint.hide(); - myHint = null; - } - } - - @Nullable - public Object getData(String dataId) { - if (PlatformDataKeys.PROJECT.is(dataId)) { - return !myProject.isDisposed() ? myProject : null; - } - if (LangDataKeys.MODULE.is(dataId)) { - final Module module = getSelectedElement(Module.class); - if (module != null && !module.isDisposed()) return module; - final PsiElement element = getSelectedElement(PsiElement.class); - if (element != null) { - return ModuleUtil.findModuleForPsiElement(element); - } - return null; - } - if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { - final PsiDirectory directory = getSelectedElement(PsiDirectory.class); - if (directory != null) { - final VirtualFile dir = directory.getVirtualFile(); - if (ProjectRootsUtil.isModuleContentRoot(dir, myProject)) { - return ModuleUtil.findModuleForPsiElement(directory); - } - } - return null; - } - if (LangDataKeys.PSI_ELEMENT.is(dataId)) { - final PsiElement element = getSelectedElement(PsiElement.class); - return element != null && element.isValid() ? element : null; - } - if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { - final PsiElement element = getSelectedElement(PsiElement.class); - return element != null && element.isValid() ? new PsiElement[]{element} : null; - } - - if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) { - PsiElement[] psiElements = (PsiElement[])getData(LangDataKeys.PSI_ELEMENT_ARRAY.getName()); - if (psiElements == null) return null; - Set files = new LinkedHashSet(); - for (PsiElement element : psiElements) { - if (element instanceof PsiFileSystemItem) { - files.add(((PsiFileSystemItem)element).getVirtualFile()); - } - } - return files.size() > 0 ? VfsUtil.toVirtualFileArray(files) : null; - } - - if (PlatformDataKeys.CONTEXT_COMPONENT.is(dataId)) { - return this; - } - if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { - return myCopyPasteDelegator.getCutProvider(); - } - if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { - return myCopyPasteDelegator.getCopyProvider(); - } - if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { - return myCopyPasteDelegator.getPasteProvider(); - } - if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { - return getSelectedElement(Module.class) != null ? myDeleteModuleProvider : new DeleteHandler.DefaultDeleteProvider(); - } - - if (LangDataKeys.IDE_VIEW.is(dataId)) { - return myIdeView; - } - - return null; - } - - @Nullable - @SuppressWarnings({"unchecked"}) - private T getSelectedElement(Class klass) { - Object selectedValue1 = myModel.getSelectedValue(); - if (selectedValue1 == null) { - final int modelSize = myModel.size(); - if (modelSize > 0) { - selectedValue1 = myModel.getElement(modelSize - 1); - } - } - final Object selectedValue = selectedValue1; - return selectedValue != null && klass.isAssignableFrom(selectedValue.getClass()) ? (T)selectedValue : null; - } - - public Point getBestPopupPosition() { - int index = myModel.getSelectedIndex(); - final int modelSize = myModel.size(); - if (index == -1) { - index = modelSize - 1; - } - if (index > -1 && index < modelSize) { - final MyItemLabel item = getItem(index); - if (item != null) { - return new Point(item.getX(), item.getY() + item.getHeight()); - } - } - return null; - } - - // ----- inplace NavBar ----------- - public void installListeners() { - final MyPsiTreeChangeAdapter psiListener = new MyPsiTreeChangeAdapter(); - final MyProblemListener problemListener = new MyProblemListener(); - final MyFileStatusListener fileStatusListener = new MyFileStatusListener(); - final MyTimerListener timerListener = new MyTimerListener(); - - - PsiManager.getInstance(myProject).addPsiTreeChangeListener(psiListener); - WolfTheProblemSolver.getInstance(myProject).addProblemListener(problemListener); - FileStatusManager.getInstance(myProject).addFileStatusListener(fileStatusListener); - - final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx(); - actionManager.addTimerListener(10000, timerListener); - - final MessageBusConnection busConnection = myProject.getMessageBus().connect(); - busConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener()); - busConnection.subscribe(NavBarModelListener.NAV_BAR, new NavBarModelListener() { - public void modelChanged() { - queueRebuildUi(); - } - - public void selectionChanged() { - updateItems(); - - scrollSelectionToVisible(); - } - }); - - if (myDetacher != null) uninstallListeners(); - - myDetacher = new Runnable() { - public void run() { - ActionManagerEx.getInstanceEx().removeTimerListener(timerListener); - busConnection.disconnect(); - - WolfTheProblemSolver.getInstance(myProject).removeProblemListener(problemListener); - PsiManager.getInstance(myProject).removePsiTreeChangeListener(psiListener); - FileStatusManager.getInstance(myProject).removeFileStatusListener(fileStatusListener); - } - }; - } - - public void uninstallListeners() { - myDetacher.run(); - myDetacher = null; - } - - public void installBorder(final int rightOffset, final boolean isDocked) { - setBorder(new Border() { - public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { - if (!isDocked) return; - - g.setColor(c.getBackground() != null ? c.getBackground().darker() : Color.darkGray); - - boolean drawTopBorder = true; - if (isDocked) { - if (!UISettings.getInstance().SHOW_MAIN_TOOLBAR) { - drawTopBorder = false; - } - } - - if (rightOffset == -1) { - if (drawTopBorder) { - g.drawLine(0, 0, width - 1, 0); - } - } - else { - if (drawTopBorder) { - g.drawLine(0, 0, width - rightOffset + 3, 0); - } - } - g.drawLine(0, height - 1, width, height - 1); - - if (rightOffset == -1) { - g.drawLine(0, 0, 0, height); - g.drawLine(width - 1, 0, width - 1, height - 1); - } - } - - public Insets getBorderInsets(final Component c) { - return new Insets(3, 4, 3, 4); - } - - public boolean isBorderOpaque() { - return true; - } - }); - } - - public void addNotify() { - super.addNotify(); - installListeners(); - } - - public void removeNotify() { - super.removeNotify(); - uninstallListeners(); - } - - public void updateState(final boolean show) { - queueModelUpdateFromFocus(); - if (isShowing()) { - queueRebuildUi(); - queueAfterAll(new Runnable() { - @Override - public void run() { - final int selectedIndex = myModel.getSelectedIndex(); - if (show && selectedIndex > -1 && selectedIndex < myModel.size()) { - final MyItemLabel item = getItem(selectedIndex); - if (item != null) { - IdeFocusManager.getInstance(myProject).requestFocus(item, true); - } - } - } - }); - } - } - - // ------ popup NavBar ---------- - public void showHint(@Nullable final Editor editor, final DataContext dataContext) { - queueModelUpdate(dataContext); - queueAfterAll(new Runnable() { - @Override - public void run() { - if (myModel.isEmpty()) return; - myHint = new LightweightHint(NavBarPanel.this) { - public void hide() { - super.hide(); - cancelPopup(); - } - }; - myHint.setForceShowAsPopup(true); - myHint.setFocusRequestor(NavBarPanel.this); - registerKeyboardAction(new AbstractAction() { - public void actionPerformed(ActionEvent e) { - hideHint(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_FOCUSED); - final KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - if (editor == null) { - myContextComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext); - getHintContainerShowPoint().doWhenDone(new AsyncResult.Handler() { - @Override - public void run(RelativePoint relativePoint) { - final Component owner = focusManager.getFocusOwner(); - final Component cmp = relativePoint.getComponent(); - if (cmp instanceof JComponent && cmp.isShowing()) { - myHint.show((JComponent)cmp, relativePoint.getPoint().x, relativePoint.getPoint().y, - owner instanceof JComponent ? (JComponent)owner : null, - new HintHint(relativePoint.getComponent(), relativePoint.getPoint())); - } - } - }); - } - else { - myHintContainer = editor.getContentComponent(); - getHintContainerShowPoint().doWhenDone(new AsyncResult.Handler() { - @Override - public void run(RelativePoint rp) { - Point p = rp.getPointOn(myHintContainer).getPoint(); - HintManagerImpl.getInstanceImpl() - .showEditorHint(myHint, editor, p, HintManagerImpl.HIDE_BY_ESCAPE, 0, true, new HintHint(editor, p)); - } - }); - } - selectTail(); - } - }); - } - - private AsyncResult getHintContainerShowPoint() { - final AsyncResult result = new AsyncResult(); - if (myHintContainer != null) { - final Point p = AbstractPopup.getCenterOf(myHintContainer, this); - p.y -= myHintContainer.getVisibleRect().height / 4; - - result.setDone(RelativePoint.fromScreen(p)); - } - else { - if (myContextComponent != null) { - result.setDone(JBPopupFactory.getInstance().guessBestPopupLocation(DataManager.getInstance().getDataContext(myContextComponent))); - } - else { - DataManager.getInstance().getDataContextFromFocus().doWhenDone(new AsyncResult.Handler() { - @Override - public void run(DataContext dataContext) { - myContextComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext); - result - .setDone(JBPopupFactory.getInstance().guessBestPopupLocation(DataManager.getInstance().getDataContext(myContextComponent))); - } - }); - } - } - return result; - } - - public static boolean wolfHasProblemFilesBeneath(final PsiElement scope) { - return WolfTheProblemSolver.getInstance(scope.getProject()).hasProblemFilesBeneath(new Condition() { - public boolean value(final VirtualFile virtualFile) { - if (scope instanceof PsiDirectory) { - final PsiDirectory directory = (PsiDirectory)scope; - if (!VfsUtil.isAncestor(directory.getVirtualFile(), virtualFile, false)) return false; - return ModuleUtil.findModuleForFile(virtualFile, scope.getProject()) == ModuleUtil.findModuleForPsiElement(scope); - } - else if (scope instanceof PsiDirectoryContainer) { // TODO: remove. It doesn't look like we'll have packages in navbar ever again - final PsiDirectory[] psiDirectories = ((PsiDirectoryContainer)scope).getDirectories(); - for (PsiDirectory directory : psiDirectories) { - if (VfsUtil.isAncestor(directory.getVirtualFile(), virtualFile, false)) { - return true; - } - } - } - return false; - } - }); - } - - protected class MyItemLabel extends SimpleColoredComponent { - private final String myText; - private final SimpleTextAttributes myAttributes; - private final int myIndex; - private final Icon myIcon; - private Object myObject; - - public MyItemLabel(Object object, int idx, Icon icon, String presentableText, SimpleTextAttributes textAttributes) { - myObject = object; - myIndex = idx; - myText = presentableText; - myIcon = icon; - myAttributes = textAttributes; - - setIpad(new Insets(1, 2, 1, 2)); - - update(); - } - - public Object getObject() { - return myObject; - } - - private void update() { - clear(); - - setIcon(myIcon); - boolean focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == NavBarPanel.this; - - boolean selected = myModel.getSelectedIndex() == myIndex; - - setPaintFocusBorder(!focused && selected); - setFocusBorderAroundIcon(false); - - setBackground(selected && focused - ? UIUtil.getListSelectionBackground() - : (UIUtil.isUnderGTKLookAndFeel() ? Color.WHITE : UIUtil.getListBackground())); - - final Color fg = selected && focused - ? UIUtil.getListSelectionForeground() - : myModel.getSelectedIndex() < myIndex && myModel.getSelectedIndex() != -1 - ? UIUtil.getInactiveTextColor() - : myAttributes.getFgColor(); - - final Color bg = selected && focused ? UIUtil.getListSelectionBackground() : myAttributes.getBgColor(); - - append(myText, new SimpleTextAttributes(bg, fg, myAttributes.getWaveColor(), myAttributes.getStyle())); - - repaint(); - } - } - - private final class MyTimerListener implements TimerListener { - - public ModalityState getModalityState() { - return ModalityState.stateForComponent(NavBarPanel.this); - } - - public void run() { - if (!isShowing()) { - return; - } - - IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { - @Override - public void run() { - Window mywindow = SwingUtilities.windowForComponent(NavBarPanel.this); - if (mywindow != null && !mywindow.isActive()) return; - - final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); - if (window instanceof Dialog) { - final Dialog dialog = (Dialog)window; - if (dialog.isModal() && !SwingUtilities.isDescendingFrom(NavBarPanel.this, dialog)) { - return; - } - } - - queueModelUpdateFromFocus(); - } - }); - } - } - - private final class MyIdeView implements IdeView { - - public void selectElement(PsiElement element) { - myModel.updateModel(element); - - if (element instanceof Navigatable) { - final Navigatable navigatable = (Navigatable)element; - if (navigatable.canNavigate()) { - ((Navigatable)element).navigate(true); - } - } - hideHint(); - } - - public PsiDirectory[] getDirectories() { - final PsiDirectory dir = getSelectedElement(PsiDirectory.class); - if (dir != null && dir.isValid()) { - return new PsiDirectory[]{dir}; - } - final PsiElement element = getSelectedElement(PsiElement.class); - if (element != null && element.isValid()) { - final PsiFile file = element.getContainingFile(); - if (file != null) { - final PsiDirectory psiDirectory = file.getContainingDirectory(); - return psiDirectory != null ? new PsiDirectory[]{psiDirectory} : PsiDirectory.EMPTY_ARRAY; - } - } - final PsiDirectoryContainer directoryContainer = getSelectedElement(PsiDirectoryContainer.class); - if (directoryContainer != null) { - return directoryContainer.getDirectories(); - } - final Module module = getSelectedElement(Module.class); - if (module != null && !module.isDisposed()) { - ArrayList dirs = new ArrayList(); - final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(); - final PsiManager psiManager = PsiManager.getInstance(myProject); - for (VirtualFile virtualFile : sourceRoots) { - final PsiDirectory directory = psiManager.findDirectory(virtualFile); - if (directory != null && directory.isValid()) { - dirs.add(directory); - } - } - return dirs.toArray(new PsiDirectory[dirs.size()]); - } - return PsiDirectory.EMPTY_ARRAY; - } - - public PsiDirectory getOrChooseDirectory() { - return DirectoryChooserUtil.getOrChooseDirectory(this); - } - - } - - private class MyPsiTreeChangeAdapter extends PsiTreeChangeAdapter { - public void childAdded(PsiTreeChangeEvent event) { - queueModelUpdateFromFocus(); - } - - public void childReplaced(PsiTreeChangeEvent event) { - queueModelUpdateFromFocus(); - } - - public void childMoved(PsiTreeChangeEvent event) { - queueModelUpdateFromFocus(); - } - - public void childrenChanged(PsiTreeChangeEvent event) { - queueModelUpdateFromFocus(); - } - - public void propertyChanged(final PsiTreeChangeEvent event) { - queueModelUpdateFromFocus(); - } - } - - private class MyModuleRootListener implements ModuleRootListener { - public void beforeRootsChange(ModuleRootEvent event) { - } - - public void rootsChanged(ModuleRootEvent event) { - queueModelUpdateFromFocus(); - } - } - - private class MyProblemListener extends WolfTheProblemSolver.ProblemListener { - - public void problemsAppeared(VirtualFile file) { - queueModelUpdateFromFocus(); - } - - public void problemsDisappeared(VirtualFile file) { - queueModelUpdateFromFocus(); - } - - } - - private class MyFileStatusListener implements FileStatusListener { - - public void fileStatusesChanged() { - queueRebuildUi(); - } - - public void fileStatusChanged(@NotNull VirtualFile virtualFile) { - queueRebuildUi(); - } - } - - private class MySiblingsListCellRenderer extends ColoredListCellRenderer { - protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { - setFocusBorderAroundIcon(false); - String name = NavBarModel.getPresentableText(value, getWindow()); - - Color color = list.getForeground(); - boolean isProblemFile = false; - if (value instanceof PsiElement) { - final PsiElement psiElement = (PsiElement)value; - PsiFile psiFile = psiElement.getContainingFile(); - if (psiFile != null) { - VirtualFile vFile = psiFile.getVirtualFile(); - if (vFile != null) { - if (WolfTheProblemSolver.getInstance(myProject).isProblemFile(vFile)) { - isProblemFile = true; - } - FileStatus status = FileStatusManager.getInstance(myProject).getStatus(vFile); - color = status.getColor(); - } - } - else { - isProblemFile = wolfHasProblemFilesBeneath(psiElement); - } - } - else if (value instanceof Module) { - final Module module = (Module)value; - isProblemFile = WolfTheProblemSolver.getInstance(myProject).hasProblemFilesBeneath(module); - } - else if (value instanceof Project) { - final Module[] modules = ModuleManager.getInstance((Project)value).getModules(); - for (Module module : modules) { - if (WolfTheProblemSolver.getInstance(myProject).hasProblemFilesBeneath(module)) { - isProblemFile = true; - break; - } - } - } - SimpleTextAttributes nameAttributes; - if (isProblemFile) { - TextAttributes attributes = new TextAttributes(color, null, Color.red, EffectType.WAVE_UNDERSCORE, Font.PLAIN); - nameAttributes = SimpleTextAttributes.fromTextAttributes(attributes); - } - else { - nameAttributes = new SimpleTextAttributes(Font.PLAIN, color); - } - append(name, nameAttributes); - // manually set icon opaque to prevent background artifacts - setIconOpaque(false); - setIcon(NavBarPanel.getIcon(value, false)); - setPaintFocusBorder(false); - setBackground(selected ? UIUtil.getListSelectionBackground() : UIUtil.getListBackground()); - } - } -} +/* + * 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.ide.navigationToolbar; + +import com.intellij.ProjectTopics; +import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.ide.CopyPasteDelegator; +import com.intellij.ide.DataManager; +import com.intellij.ide.IdeView; +import com.intellij.ide.projectView.ProjectView; +import com.intellij.ide.projectView.impl.AbstractProjectViewPane; +import com.intellij.ide.projectView.impl.ProjectRootsUtil; +import com.intellij.ide.ui.UISettings; +import com.intellij.ide.ui.customization.CustomActionsSchema; +import com.intellij.ide.util.DeleteHandler; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.markup.EffectType; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.*; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusListener; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.pom.Navigatable; +import com.intellij.problems.WolfTheProblemSolver; +import com.intellij.psi.*; +import com.intellij.ui.*; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.panels.OpaquePanel; +import com.intellij.ui.popup.AbstractPopup; +import com.intellij.ui.popup.PopupOwner; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.util.Alarm; +import com.intellij.util.Icons; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * User: anna + * Date: 03-Nov-2005 + */ +public class NavBarPanel extends OpaquePanel.List implements DataProvider, PopupOwner { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.navigationToolbar.NavigationToolbarPanel"); + /*private static final Icon LEFT_ICON = IconLoader.getIcon("/general/splitLeft.png"); + private static final Icon RIGHT_ICON = IconLoader.getIcon("/general/splitRight.png"); +*/ + private final ArrayList myList = new ArrayList(); + + private final NavBarModel myModel; + private final Project myProject; + + private Runnable myDetacher; + private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider(); + + private final IdeView myIdeView = new MyIdeView(); + private final CopyPasteDelegator myCopyPasteDelegator; + private LightweightHint myHint = null; + private ListPopupImpl myNodePopup = null; + + private JComponent myHintContainer; + private Component myContextComponent; + private Runnable myRunWhenListRebuilt; + + + private final MergingUpdateQueue myUpdateQueue; + private AtomicBoolean myModelUpdating = new AtomicBoolean(Boolean.FALSE); + private MyItemLabel myContextObject; + + public NavBarPanel(final Project project) { + super(new FlowLayout(FlowLayout.LEFT, 5, 0), UIUtil.isUnderGTKLookAndFeel() ? Color.WHITE : UIUtil.getListBackground()); + + myProject = project; + myModel = new NavBarModel(myProject); + + myUpdateQueue = new MergingUpdateQueue("NavBar", 150, true, MergingUpdateQueue.ANY_COMPONENT, project, null); + + PopupHandler.installPopupHandler(this, IdeActions.GROUP_PROJECT_VIEW_POPUP, ActionPlaces.NAVIGATION_BAR); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + shiftFocus(-1); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), WHEN_FOCUSED); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + shiftFocus(1); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), WHEN_FOCUSED); + + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + shiftFocus(-myModel.getSelectedIndex()); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), WHEN_FOCUSED); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + shiftFocus(myModel.size() - 1 - myModel.getSelectedIndex()); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), WHEN_FOCUSED); + + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (myModel.getSelectedIndex() != -1) { + ctrlClick(myModel.getSelectedIndex()); + } + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), WHEN_FOCUSED); + + final ActionListener dblClickAction = new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (myModel.getSelectedIndex() != -1) { + doubleClick(myModel.getSelectedIndex()); + } + } + }; + + registerKeyboardAction(dblClickAction, KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), WHEN_FOCUSED); + + registerKeyboardAction(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + final Object o = myModel.getSelectedValue(); + navigateInsideBar(optimizeTarget(o)); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED); + + registerKeyboardAction(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + myModel.setSelectedIndex(-1); + ToolWindowManager.getInstance(project).activateEditorComponent(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_FOCUSED); + + addFocusListener(new FocusListener() { + public void focusGained(final FocusEvent e) { + updateItems(); + + if (!isInFloatingMode() && myList.size() > 0) { + myContextObject = myList.get(myList.size() - 1); + } else { + myContextObject = null; + } + } + + public void focusLost(final FocusEvent e) { + if (myProject.isDisposed()) { + myContextObject = null; + hideHint(); + return; + } + + // required invokeLater since in current call sequence KeyboardFocusManager is not initialized yet + // but future focused component + SwingUtilities.invokeLater(new Runnable() { + public void run() { + processFocusLost(e); + } + }); + } + }); + + installBorder(-1, false); + + myCopyPasteDelegator = new CopyPasteDelegator(myProject, NavBarPanel.this) { + @NotNull + protected PsiElement[] getSelectedElements() { + final PsiElement element = getSelectedElement(PsiElement.class); + return element == null ? PsiElement.EMPTY_ARRAY : new PsiElement[]{element}; + } + }; + + + queueModelUpdateFromFocus(); + queueRebuildUi(); + } + + private void queueModelUpdate(DataContext context) { + _queueModelUpdate(context, null); + } + + private void queueModelUpdateFromFocus() { + _queueModelUpdate(null, myContextObject); + } + + private void queueModelUpdateForObject(Object object) { + _queueModelUpdate(null, object); + } + + private void _queueModelUpdate(@Nullable final DataContext context, final @Nullable Object object) { + if (myModelUpdating.getAndSet(true)) return; + + myUpdateQueue.cancelAllUpdates(); + + myUpdateQueue.queue(new Update("model", 0) { + @Override + public void run() { + if (context != null || object != null) { + _updateModelFrom(context, object); + } + else { + DataManager.getInstance().getDataContextFromFocus().doWhenDone(new AsyncResult.Handler() { + @Override + public void run(DataContext dataContext) { + _updateModelFrom(dataContext, null); + } + }); + } + } + + @Override + public void setRejected() { + super.setRejected(); + myModelUpdating.set(false); + } + }); + } + + private void _updateModelFrom(DataContext dataContext, Object object) { + if (dataContext != null) { + boolean a = LangDataKeys.IDE_VIEW.getData(dataContext) == myIdeView; + boolean b = PlatformDataKeys.PROJECT.getData(dataContext) != myProject; + if (b || isNodePopupShowing()) { + myModelUpdating.set(false); + queueModelUpdateFromFocus(); + return; + } + myModel.updateModel(dataContext); + } else { + myModel.updateModel(object); + } + + myModelUpdating.set(false); + } + + private void queueRebuildUi() { + myUpdateQueue.queue(new AfterModel("ui", 1) { + @Override + protected void _run() { + rebuildUi(); + } + }); + } + + private void queueRevalidate(@Nullable final Runnable after) { + myUpdateQueue.queue(new AfterModel("revalidate", 2) { + @Override + protected void _run() { + if (myHint != null) { + getHintContainerShowPoint().doWhenDone(new AsyncResult.Handler() { + @Override + public void run(final RelativePoint relativePoint) { + myHint.setSize(getPreferredSize()); + myHint.setLocation(relativePoint); + if (after != null) { + after.run(); + } + } + }); + } + else { + if (after != null) { + after.run(); + } + } + } + }); + } + + private void queueSelect(final Runnable runnable) { + myUpdateQueue.queue(new AfterModel("select", 3) { + @Override + protected void _run() { + runnable.run(); + } + }); + } + + private void queueAfterAll(final Runnable runnable) { + myUpdateQueue.queue(new AfterModel(new Object(), 4) { + @Override + protected void _run() { + runnable.run(); + } + }); + } + + private abstract class AfterModel extends Update { + private AfterModel(Object identity, int priority) { + super(identity, priority); + } + + @Override + public void run() { + if (myModelUpdating.get()) { + myUpdateQueue.queue(this); + } + else { + _run(); + } + } + + protected abstract void _run(); + } + + private static Object optimizeTarget(Object target) { + if (target instanceof PsiDirectory && ((PsiDirectory)target).getFiles().length == 0) { + final PsiDirectory[] subDir = ((PsiDirectory)target).getSubdirectories(); + if (subDir.length == 1) { + return optimizeTarget(subDir[0]); + } + } + return target; + } + + private void processFocusLost(final FocusEvent e) { + final boolean nodePopupInactive = myNodePopup == null || !myNodePopup.isVisible() || !myNodePopup.isFocused(); + boolean childPopupInactive = !JBPopupFactory.getInstance().isChildPopupFocused(this); + if (nodePopupInactive && childPopupInactive) { + final Component opposite = e.getOppositeComponent(); + if (opposite != null && opposite != this && !isAncestorOf(opposite) && !e.isTemporary()) { + myContextObject = null; + hideHint(); + } + } + + updateItems(); + } + + private void updateItems() { + for (MyItemLabel item : myList) { + item.update(); + } + } + + public void selectTail() { + queueModelUpdateFromFocus(); + queueRebuildUi(); + + queueSelect(new Runnable() { + @Override + public void run() { + if (!myList.isEmpty()) { + myModel.setSelectedIndex(myList.size() - 1); + IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); + } + } + }); + } + + private void shiftFocus(int direction) { + myModel.setSelectedIndex(myModel.getIndexByModel(myModel.getSelectedIndex() + direction)); + } + + private void scrollSelectionToVisible() { + final int selectedIndex = myModel.getSelectedIndex(); + if (selectedIndex == -1 || selectedIndex >= myList.size()) return; + + MyItemLabel selectedItem = myList.get(selectedIndex); + Rectangle rect = selectedItem.getBounds(); + scrollRectToVisible(rect); + } + + @Nullable + private MyItemLabel getItem(int index) { + if (index != -1 && index < myList.size()) { + return myList.get(index); + } + return null; + } + + private boolean isInFloatingMode() { + return myHint != null && myHint.isVisible(); + } + + + @Override + public Dimension getPreferredSize() { + if (!myList.isEmpty()) { + return super.getPreferredSize(); + } + else { + return new MyItemLabel(null, 0, Icons.DIRECTORY_OPEN_ICON, "Sample", SimpleTextAttributes.REGULAR_ATTRIBUTES).getPreferredSize(); + } + } + + private void rebuildUi() { + myList.clear(); + for (int index = 0; index < myModel.size(); index++) { + final Object object = myModel.get(index); + Icon closedIcon = getIcon(object, false); + Icon openIcon = getIcon(object, true); + + if (closedIcon == null && openIcon != null) closedIcon = openIcon; + if (openIcon == null && closedIcon != null) openIcon = closedIcon; + if (openIcon == null) { + openIcon = closedIcon = new EmptyIcon(5, 5); + } + + final MyItemLabel label = + new MyItemLabel(object, index, wrapIcon(openIcon, closedIcon, index), NavBarModel.getPresentableText(object, getWindow()), + myModel.getTextAttributes(object, false)); + + installActions(index, label); + myList.add(label); + + } + + rebuildComponent(); + + if (myRunWhenListRebuilt != null) { + Runnable r = myRunWhenListRebuilt; + myRunWhenListRebuilt = null; + r.run(); + } + } + + @Nullable + private static Icon getIcon(final Object object, final boolean isopen) { + if (!NavBarModel.checkValid(object)) return null; + if (object instanceof Project) return IconLoader.getIcon("/nodes/project.png"); + if (object instanceof Module) return ((Module)object).getModuleType().getNodeIcon(false); + try { + if (object instanceof PsiElement) { + return ApplicationManager.getApplication().runReadAction(new Computable() { + public Icon compute() { + return ((PsiElement)object).isValid() ? ((PsiElement)object) + .getIcon(isopen ? Iconable.ICON_FLAG_OPEN : Iconable.ICON_FLAG_CLOSED) : null; + } + }); + } + } + catch (IndexNotReadyException e) { + return null; + } + if (object instanceof JdkOrderEntry) return ((JdkOrderEntry)object).getJdk().getSdkType().getIcon(); + if (object instanceof LibraryOrderEntry) return IconLoader.getIcon("/nodes/ppLibClosed.png"); + if (object instanceof ModuleOrderEntry) return ((ModuleOrderEntry)object).getModule().getModuleType().getNodeIcon(false); + return null; + } + + + private Icon wrapIcon(final Icon openIcon, final Icon closedIcon, final int idx) { + return new Icon() { + public void paintIcon(Component c, Graphics g, int x, int y) { + if (myModel.getSelectedIndex() == idx && myNodePopup != null && myNodePopup.isVisible()) { + openIcon.paintIcon(c, g, x, y); + } + else { + closedIcon.paintIcon(c, g, x, y); + } + } + + public int getIconWidth() { + return openIcon.getIconWidth(); + } + + public int getIconHeight() { + return openIcon.getIconHeight(); + } + }; + } + + private void rebuildComponent() { + removeAll(); + + for (MyItemLabel item : myList) { + add(item); + } + + revalidate(); + repaint(); + + queueAfterAll(new Runnable() { + @Override + public void run() { + scrollSelectionToVisible(); + } + }); + } + + private Window getWindow() { + return !isShowing() ? null : (Window)UIUtil.findUltimateParent(this); + } + + // ------ NavBar actions ------------------------- + private void installActions(final int index, final MyItemLabel component) { + ListenerUtil.addMouseListener(component, new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (!e.isConsumed() && !e.isPopupTrigger() && e.getClickCount() == 2) { + myModel.setSelectedIndex(index); + IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); + doubleClick(index); + e.consume(); + } + } + }); + + ListenerUtil.addMouseListener(component, new MouseAdapter() { + public void mouseReleased(final MouseEvent e) { + if (SystemInfo.isWindows) { + click(e); + } + } + + public void mousePressed(final MouseEvent e) { + if (!SystemInfo.isWindows) { + click(e); + } + } + + private void click(final MouseEvent e) { + if (!e.isConsumed() && e.isPopupTrigger()) { + myModel.setSelectedIndex(index); + IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); + rightClick(index); + e.consume(); + } + } + }); + + ListenerUtil.addMouseListener(component, new MouseAdapter() { + public void mouseReleased(final MouseEvent e) { + if (SystemInfo.isWindows) { + click(e); + } + } + + public void mousePressed(final MouseEvent e) { + if (!SystemInfo.isWindows) { + click(e); + } + } + + private void click(final MouseEvent e) { + if (!e.isConsumed() && !e.isPopupTrigger() && e.getClickCount() == 1) { + ctrlClick(index); + myModel.setSelectedIndex(index); + e.consume(); + } + } + }); + } + + private void doubleClick(final int index) { + doubleClick(myModel.getElement(index)); + } + + private void doubleClick(final Object object) { + if (object instanceof Navigatable) { + final Navigatable navigatable = (Navigatable)object; + if (navigatable.canNavigate()) { + navigatable.navigate(true); + } + } + else if (object instanceof Module) { + final ProjectView projectView = ProjectView.getInstance(myProject); + final AbstractProjectViewPane projectViewPane = projectView.getProjectViewPaneById(projectView.getCurrentViewId()); + projectViewPane.selectModule((Module)object, true); + } + else if (object instanceof Project) { + return; + } + hideHint(); + } + + private void ctrlClick(final int index) { + if (isNodePopupShowing()) { + cancelPopup(); + if (myModel.getSelectedIndex() == index) { + return; + } + } + + final Object object = myModel.getElement(index); + final java.util.List objects = myModel.calcElementChildren(object); + + if (!objects.isEmpty()) { + final Object[] siblings = new Object[objects.size()]; + final Icon[] icons = new Icon[objects.size()]; + for (int i = 0; i < objects.size(); i++) { + siblings[i] = objects.get(i); + icons[i] = getIcon(siblings[i], false); + } + final MyItemLabel item = getItem(index); + LOG.assertTrue(item != null); + final BaseListPopupStep step = new BaseListPopupStep("", siblings, icons) { + public boolean isSpeedSearchEnabled() { + return true; + } + + @NotNull + public String getTextFor(final Object value) { + return NavBarModel.getPresentableText(value, null); + } + + public boolean isSelectable(Object value) { + return true; + } + + public PopupStep onChosen(final Object selectedValue, final boolean finalChoice) { + return doFinalStep(new Runnable() { + public void run() { + navigateInsideBar(optimizeTarget(selectedValue)); + } + }); + } + + /* + public void canceled() { + super.canceled(); + item.getLabel().setIcon(wrapIcon(NavBarModel.getIcon(object), index, Color.gray)); + } + */ + }; + step.setDefaultOptionIndex(index < myModel.size() - 1 ? objects.indexOf(myModel.getElement(index + 1)) : 0); + myNodePopup = new ListPopupImpl(step) { + protected ListCellRenderer getListElementRenderer() { + return new MySiblingsListCellRenderer(); + } + + }; + myNodePopup.registerAction("left", KeyEvent.VK_LEFT, 0, new AbstractAction() { + public void actionPerformed(ActionEvent e) { + myNodePopup.goBack(); + shiftFocus(-1); + restorePopup(); + } + }); + myNodePopup.registerAction("right", KeyEvent.VK_RIGHT, 0, new AbstractAction() { + public void actionPerformed(ActionEvent e) { + myNodePopup.goBack(); + shiftFocus(1); + restorePopup(); + } + }); + + ListenerUtil.addMouseListener(myNodePopup.getComponent(), new MouseAdapter() { + public void mouseReleased(final MouseEvent e) { + if (SystemInfo.isWindows) { + click(e); + } + } + + public void mousePressed(final MouseEvent e) { + if (!SystemInfo.isWindows) { + click(e); + } + } + + private void click(final MouseEvent e) { + if (!e.isConsumed() && e.isPopupTrigger()) { + myModel.setSelectedIndex(index); + IdeFocusManager.getInstance(myProject).requestFocus(NavBarPanel.this, true); + rightClick(index); + e.consume(); + } + } + }); + + if (!isValid()) { + validate(); + } + + if (item.isShowing()) { + myNodePopup.showUnderneathOf(item); + } + } + } + + private boolean isNodePopupShowing() { + return myNodePopup != null && myNodePopup.isVisible(); + } + + private void navigateInsideBar(final Object object) { + myContextObject = null; + + myUpdateQueue.cancelAllUpdates(); + + queueModelUpdateForObject(object); + queueRebuildUi(); + + queueRevalidate(new Runnable() { + public void run() { + if (myModel.hasChildren(object)) { + restorePopup(); + } + else { + doubleClick(object); + } + } + }); + } + + private void rightClick(final int index) { + final ActionManager actionManager = ActionManager.getInstance(); + final ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_NAVBAR_POPUP); + final ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.NAVIGATION_BAR, group); + final MyItemLabel item = getItem(index); + if (item != null) { + popupMenu.getComponent().show(this, item.getX(), item.getY() + item.getHeight()); + } + } + + private void restorePopup() { + cancelPopup(); + ctrlClick(myModel.getSelectedIndex()); + } + + private void cancelPopup() { + if (myNodePopup != null) { + myNodePopup.cancel(); + myNodePopup = null; + } + } + + private void hideHint() { + if (myHint != null) { + myHint.hide(); + myHint = null; + } + } + + @Nullable + public Object getData(String dataId) { + if (PlatformDataKeys.PROJECT.is(dataId)) { + return !myProject.isDisposed() ? myProject : null; + } + if (LangDataKeys.MODULE.is(dataId)) { + final Module module = getSelectedElement(Module.class); + if (module != null && !module.isDisposed()) return module; + final PsiElement element = getSelectedElement(PsiElement.class); + if (element != null) { + return ModuleUtil.findModuleForPsiElement(element); + } + return null; + } + if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { + final PsiDirectory directory = getSelectedElement(PsiDirectory.class); + if (directory != null) { + final VirtualFile dir = directory.getVirtualFile(); + if (ProjectRootsUtil.isModuleContentRoot(dir, myProject)) { + return ModuleUtil.findModuleForPsiElement(directory); + } + } + return null; + } + if (LangDataKeys.PSI_ELEMENT.is(dataId)) { + final PsiElement element = getSelectedElement(PsiElement.class); + return element != null && element.isValid() ? element : null; + } + if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { + final PsiElement element = getSelectedElement(PsiElement.class); + return element != null && element.isValid() ? new PsiElement[]{element} : null; + } + + if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) { + PsiElement[] psiElements = (PsiElement[])getData(LangDataKeys.PSI_ELEMENT_ARRAY.getName()); + if (psiElements == null) return null; + Set files = new LinkedHashSet(); + for (PsiElement element : psiElements) { + if (element instanceof PsiFileSystemItem) { + files.add(((PsiFileSystemItem)element).getVirtualFile()); + } + } + return files.size() > 0 ? VfsUtil.toVirtualFileArray(files) : null; + } + + if (PlatformDataKeys.CONTEXT_COMPONENT.is(dataId)) { + return this; + } + if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { + return myCopyPasteDelegator.getCutProvider(); + } + if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { + return myCopyPasteDelegator.getCopyProvider(); + } + if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { + return myCopyPasteDelegator.getPasteProvider(); + } + if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { + return getSelectedElement(Module.class) != null ? myDeleteModuleProvider : new DeleteHandler.DefaultDeleteProvider(); + } + + if (LangDataKeys.IDE_VIEW.is(dataId)) { + return myIdeView; + } + + return null; + } + + @Nullable + @SuppressWarnings({"unchecked"}) + private T getSelectedElement(Class klass) { + Object selectedValue1 = myModel.getSelectedValue(); + if (selectedValue1 == null) { + final int modelSize = myModel.size(); + if (modelSize > 0) { + selectedValue1 = myModel.getElement(modelSize - 1); + } + } + final Object selectedValue = selectedValue1; + return selectedValue != null && klass.isAssignableFrom(selectedValue.getClass()) ? (T)selectedValue : null; + } + + public Point getBestPopupPosition() { + int index = myModel.getSelectedIndex(); + final int modelSize = myModel.size(); + if (index == -1) { + index = modelSize - 1; + } + if (index > -1 && index < modelSize) { + final MyItemLabel item = getItem(index); + if (item != null) { + return new Point(item.getX(), item.getY() + item.getHeight()); + } + } + return null; + } + + // ----- inplace NavBar ----------- + public void installListeners() { + final MyPsiTreeChangeAdapter psiListener = new MyPsiTreeChangeAdapter(); + final MyProblemListener problemListener = new MyProblemListener(); + final MyFileStatusListener fileStatusListener = new MyFileStatusListener(); + final MyTimerListener timerListener = new MyTimerListener(); + + + PsiManager.getInstance(myProject).addPsiTreeChangeListener(psiListener); + WolfTheProblemSolver.getInstance(myProject).addProblemListener(problemListener); + FileStatusManager.getInstance(myProject).addFileStatusListener(fileStatusListener); + + final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx(); + actionManager.addTimerListener(10000, timerListener); + + final MessageBusConnection busConnection = myProject.getMessageBus().connect(); + busConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener()); + busConnection.subscribe(NavBarModelListener.NAV_BAR, new NavBarModelListener() { + public void modelChanged() { + queueRebuildUi(); + } + + public void selectionChanged() { + updateItems(); + + scrollSelectionToVisible(); + } + }); + + if (myDetacher != null) uninstallListeners(); + + myDetacher = new Runnable() { + public void run() { + ActionManagerEx.getInstanceEx().removeTimerListener(timerListener); + busConnection.disconnect(); + + WolfTheProblemSolver.getInstance(myProject).removeProblemListener(problemListener); + PsiManager.getInstance(myProject).removePsiTreeChangeListener(psiListener); + FileStatusManager.getInstance(myProject).removeFileStatusListener(fileStatusListener); + } + }; + } + + public void uninstallListeners() { + myDetacher.run(); + myDetacher = null; + } + + public void installBorder(final int rightOffset, final boolean isDocked) { + setBorder(new Border() { + public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { + if (!isDocked) return; + + g.setColor(c.getBackground() != null ? c.getBackground().darker() : Color.darkGray); + + boolean drawTopBorder = true; + if (isDocked) { + if (!UISettings.getInstance().SHOW_MAIN_TOOLBAR) { + drawTopBorder = false; + } + } + + if (rightOffset == -1) { + if (drawTopBorder) { + g.drawLine(0, 0, width - 1, 0); + } + } + else { + if (drawTopBorder) { + g.drawLine(0, 0, width - rightOffset + 3, 0); + } + } + g.drawLine(0, height - 1, width, height - 1); + + if (rightOffset == -1) { + g.drawLine(0, 0, 0, height); + g.drawLine(width - 1, 0, width - 1, height - 1); + } + } + + public Insets getBorderInsets(final Component c) { + return new Insets(3, 4, 3, 4); + } + + public boolean isBorderOpaque() { + return true; + } + }); + } + + public void addNotify() { + super.addNotify(); + installListeners(); + } + + public void removeNotify() { + super.removeNotify(); + uninstallListeners(); + } + + public void updateState(final boolean show) { + queueModelUpdateFromFocus(); + if (isShowing()) { + queueRebuildUi(); + queueAfterAll(new Runnable() { + @Override + public void run() { + final int selectedIndex = myModel.getSelectedIndex(); + if (show && selectedIndex > -1 && selectedIndex < myModel.size()) { + final MyItemLabel item = getItem(selectedIndex); + if (item != null) { + IdeFocusManager.getInstance(myProject).requestFocus(item, true); + } + } + } + }); + } + } + + // ------ popup NavBar ---------- + public void showHint(@Nullable final Editor editor, final DataContext dataContext) { + queueModelUpdate(dataContext); + queueAfterAll(new Runnable() { + @Override + public void run() { + if (myModel.isEmpty()) return; + myHint = new LightweightHint(NavBarPanel.this) { + public void hide() { + super.hide(); + cancelPopup(); + } + }; + myHint.setForceShowAsPopup(true); + myHint.setFocusRequestor(NavBarPanel.this); + registerKeyboardAction(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + hideHint(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_FOCUSED); + final KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + if (editor == null) { + myContextComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext); + getHintContainerShowPoint().doWhenDone(new AsyncResult.Handler() { + @Override + public void run(RelativePoint relativePoint) { + final Component owner = focusManager.getFocusOwner(); + final Component cmp = relativePoint.getComponent(); + if (cmp instanceof JComponent && cmp.isShowing()) { + myHint.show((JComponent)cmp, relativePoint.getPoint().x, relativePoint.getPoint().y, + owner instanceof JComponent ? (JComponent)owner : null, + new HintHint(relativePoint.getComponent(), relativePoint.getPoint())); + } + } + }); + } + else { + myHintContainer = editor.getContentComponent(); + getHintContainerShowPoint().doWhenDone(new AsyncResult.Handler() { + @Override + public void run(RelativePoint rp) { + Point p = rp.getPointOn(myHintContainer).getPoint(); + HintManagerImpl.getInstanceImpl() + .showEditorHint(myHint, editor, p, HintManagerImpl.HIDE_BY_ESCAPE, 0, true, new HintHint(editor, p)); + } + }); + } + selectTail(); + } + }); + } + + private AsyncResult getHintContainerShowPoint() { + final AsyncResult result = new AsyncResult(); + if (myHintContainer != null) { + final Point p = AbstractPopup.getCenterOf(myHintContainer, this); + p.y -= myHintContainer.getVisibleRect().height / 4; + + result.setDone(RelativePoint.fromScreen(p)); + } + else { + if (myContextComponent != null) { + result.setDone(JBPopupFactory.getInstance().guessBestPopupLocation(DataManager.getInstance().getDataContext(myContextComponent))); + } + else { + DataManager.getInstance().getDataContextFromFocus().doWhenDone(new AsyncResult.Handler() { + @Override + public void run(DataContext dataContext) { + myContextComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext); + result + .setDone(JBPopupFactory.getInstance().guessBestPopupLocation(DataManager.getInstance().getDataContext(myContextComponent))); + } + }); + } + } + return result; + } + + public static boolean wolfHasProblemFilesBeneath(final PsiElement scope) { + return WolfTheProblemSolver.getInstance(scope.getProject()).hasProblemFilesBeneath(new Condition() { + public boolean value(final VirtualFile virtualFile) { + if (scope instanceof PsiDirectory) { + final PsiDirectory directory = (PsiDirectory)scope; + if (!VfsUtil.isAncestor(directory.getVirtualFile(), virtualFile, false)) return false; + return ModuleUtil.findModuleForFile(virtualFile, scope.getProject()) == ModuleUtil.findModuleForPsiElement(scope); + } + else if (scope instanceof PsiDirectoryContainer) { // TODO: remove. It doesn't look like we'll have packages in navbar ever again + final PsiDirectory[] psiDirectories = ((PsiDirectoryContainer)scope).getDirectories(); + for (PsiDirectory directory : psiDirectories) { + if (VfsUtil.isAncestor(directory.getVirtualFile(), virtualFile, false)) { + return true; + } + } + } + return false; + } + }); + } + + protected class MyItemLabel extends SimpleColoredComponent { + private final String myText; + private final SimpleTextAttributes myAttributes; + private final int myIndex; + private final Icon myIcon; + private Object myObject; + + public MyItemLabel(Object object, int idx, Icon icon, String presentableText, SimpleTextAttributes textAttributes) { + myObject = object; + myIndex = idx; + myText = presentableText; + myIcon = icon; + myAttributes = textAttributes; + + setIpad(new Insets(1, 2, 1, 2)); + + update(); + } + + public Object getObject() { + return myObject; + } + + private void update() { + clear(); + + setIcon(myIcon); + boolean focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == NavBarPanel.this; + + boolean selected = myModel.getSelectedIndex() == myIndex; + + setPaintFocusBorder(!focused && selected); + setFocusBorderAroundIcon(false); + + setBackground(selected && focused + ? UIUtil.getListSelectionBackground() + : (UIUtil.isUnderGTKLookAndFeel() ? Color.WHITE : UIUtil.getListBackground())); + + final Color fg = selected && focused + ? UIUtil.getListSelectionForeground() + : myModel.getSelectedIndex() < myIndex && myModel.getSelectedIndex() != -1 + ? UIUtil.getInactiveTextColor() + : myAttributes.getFgColor(); + + final Color bg = selected && focused ? UIUtil.getListSelectionBackground() : myAttributes.getBgColor(); + + append(myText, new SimpleTextAttributes(bg, fg, myAttributes.getWaveColor(), myAttributes.getStyle())); + + repaint(); + } + } + + private final class MyTimerListener implements TimerListener { + + public ModalityState getModalityState() { + return ModalityState.stateForComponent(NavBarPanel.this); + } + + public void run() { + if (!isShowing()) { + return; + } + + IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + Window mywindow = SwingUtilities.windowForComponent(NavBarPanel.this); + if (mywindow != null && !mywindow.isActive()) return; + + final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); + if (window instanceof Dialog) { + final Dialog dialog = (Dialog)window; + if (dialog.isModal() && !SwingUtilities.isDescendingFrom(NavBarPanel.this, dialog)) { + return; + } + } + + queueModelUpdateFromFocus(); + } + }); + } + } + + private final class MyIdeView implements IdeView { + + public void selectElement(PsiElement element) { + myModel.updateModel(element); + + if (element instanceof Navigatable) { + final Navigatable navigatable = (Navigatable)element; + if (navigatable.canNavigate()) { + ((Navigatable)element).navigate(true); + } + } + hideHint(); + } + + public PsiDirectory[] getDirectories() { + final PsiDirectory dir = getSelectedElement(PsiDirectory.class); + if (dir != null && dir.isValid()) { + return new PsiDirectory[]{dir}; + } + final PsiElement element = getSelectedElement(PsiElement.class); + if (element != null && element.isValid()) { + final PsiFile file = element.getContainingFile(); + if (file != null) { + final PsiDirectory psiDirectory = file.getContainingDirectory(); + return psiDirectory != null ? new PsiDirectory[]{psiDirectory} : PsiDirectory.EMPTY_ARRAY; + } + } + final PsiDirectoryContainer directoryContainer = getSelectedElement(PsiDirectoryContainer.class); + if (directoryContainer != null) { + return directoryContainer.getDirectories(); + } + final Module module = getSelectedElement(Module.class); + if (module != null && !module.isDisposed()) { + ArrayList dirs = new ArrayList(); + final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(); + final PsiManager psiManager = PsiManager.getInstance(myProject); + for (VirtualFile virtualFile : sourceRoots) { + final PsiDirectory directory = psiManager.findDirectory(virtualFile); + if (directory != null && directory.isValid()) { + dirs.add(directory); + } + } + return dirs.toArray(new PsiDirectory[dirs.size()]); + } + return PsiDirectory.EMPTY_ARRAY; + } + + public PsiDirectory getOrChooseDirectory() { + return DirectoryChooserUtil.getOrChooseDirectory(this); + } + + } + + private class MyPsiTreeChangeAdapter extends PsiTreeChangeAdapter { + public void childAdded(PsiTreeChangeEvent event) { + queueModelUpdateFromFocus(); + } + + public void childReplaced(PsiTreeChangeEvent event) { + queueModelUpdateFromFocus(); + } + + public void childMoved(PsiTreeChangeEvent event) { + queueModelUpdateFromFocus(); + } + + public void childrenChanged(PsiTreeChangeEvent event) { + queueModelUpdateFromFocus(); + } + + public void propertyChanged(final PsiTreeChangeEvent event) { + queueModelUpdateFromFocus(); + } + } + + private class MyModuleRootListener implements ModuleRootListener { + public void beforeRootsChange(ModuleRootEvent event) { + } + + public void rootsChanged(ModuleRootEvent event) { + queueModelUpdateFromFocus(); + } + } + + private class MyProblemListener extends WolfTheProblemSolver.ProblemListener { + + public void problemsAppeared(VirtualFile file) { + queueModelUpdateFromFocus(); + } + + public void problemsDisappeared(VirtualFile file) { + queueModelUpdateFromFocus(); + } + + } + + private class MyFileStatusListener implements FileStatusListener { + + public void fileStatusesChanged() { + queueRebuildUi(); + } + + public void fileStatusChanged(@NotNull VirtualFile virtualFile) { + queueRebuildUi(); + } + } + + private class MySiblingsListCellRenderer extends ColoredListCellRenderer { + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + setFocusBorderAroundIcon(false); + String name = NavBarModel.getPresentableText(value, getWindow()); + + Color color = list.getForeground(); + boolean isProblemFile = false; + if (value instanceof PsiElement) { + final PsiElement psiElement = (PsiElement)value; + PsiFile psiFile = psiElement.getContainingFile(); + if (psiFile != null) { + VirtualFile vFile = psiFile.getVirtualFile(); + if (vFile != null) { + if (WolfTheProblemSolver.getInstance(myProject).isProblemFile(vFile)) { + isProblemFile = true; + } + FileStatus status = FileStatusManager.getInstance(myProject).getStatus(vFile); + color = status.getColor(); + } + } + else { + isProblemFile = wolfHasProblemFilesBeneath(psiElement); + } + } + else if (value instanceof Module) { + final Module module = (Module)value; + isProblemFile = WolfTheProblemSolver.getInstance(myProject).hasProblemFilesBeneath(module); + } + else if (value instanceof Project) { + final Module[] modules = ModuleManager.getInstance((Project)value).getModules(); + for (Module module : modules) { + if (WolfTheProblemSolver.getInstance(myProject).hasProblemFilesBeneath(module)) { + isProblemFile = true; + break; + } + } + } + SimpleTextAttributes nameAttributes; + if (isProblemFile) { + TextAttributes attributes = new TextAttributes(color, null, Color.red, EffectType.WAVE_UNDERSCORE, Font.PLAIN); + nameAttributes = SimpleTextAttributes.fromTextAttributes(attributes); + } + else { + nameAttributes = new SimpleTextAttributes(Font.PLAIN, color); + } + append(name, nameAttributes); + // manually set icon opaque to prevent background artifacts + setIconOpaque(false); + setIcon(NavBarPanel.getIcon(value, false)); + setPaintFocusBorder(false); + setBackground(selected ? UIUtil.getListSelectionBackground() : UIUtil.getListBackground()); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java index e018c34938b2..054001b07770 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java @@ -1,45 +1,45 @@ -/* - * 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.ide.projectView.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; - -/** - * @author yole - */ -public class MarkExcludeRootAction extends MarkRootAction { - public MarkExcludeRootAction() { - super(false, true); - } - - @Override - public void actionPerformed(AnActionEvent e) { - VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); - String message = vFiles.length == 1 ? FileUtil.toSystemDependentName(vFiles [0].getPath()) : vFiles.length + " selected files"; - final int rc = Messages.showOkCancelDialog(e.getData(PlatformDataKeys.PROJECT), - "Are you sure you would you like to exclude " + message + - " from the project?\nYou can restore excluded folders later using the Project Structure dialog.", - "Mark as Excluded", Messages.getQuestionIcon()); - if (rc != 0) { - return; - } - super.actionPerformed(e); - } -} +/* + * 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.ide.projectView.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; + +/** + * @author yole + */ +public class MarkExcludeRootAction extends MarkRootAction { + public MarkExcludeRootAction() { + super(false, true); + } + + @Override + public void actionPerformed(AnActionEvent e) { + VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); + String message = vFiles.length == 1 ? FileUtil.toSystemDependentName(vFiles [0].getPath()) : vFiles.length + " selected files"; + final int rc = Messages.showOkCancelDialog(e.getData(PlatformDataKeys.PROJECT), + "Are you sure you would you like to exclude " + message + + " from the project?\nYou can restore excluded folders later using the Project Structure dialog.", + "Mark as Excluded", Messages.getQuestionIcon()); + if (rc != 0) { + return; + } + super.actionPerformed(e); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootAction.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootAction.java index 4d0c2abc2d3d..c6fde5bd0de6 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootAction.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootAction.java @@ -1,122 +1,122 @@ -/* - * 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.ide.projectView.actions; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.Nullable; - -/** - * @author yole - */ -public class MarkRootAction extends AnAction { - private final boolean myMarkAsTestSources; - private final boolean myMarkAsExcluded; - - public MarkRootAction() { - myMarkAsTestSources = false; - myMarkAsExcluded = false; - } - - protected MarkRootAction(boolean markAsTestSources, boolean markAsExcluded) { - myMarkAsTestSources = markAsTestSources; - myMarkAsExcluded = markAsExcluded; - } - - @Override - public void actionPerformed(AnActionEvent e) { - Module module = e.getData(LangDataKeys.MODULE); - VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); - assert vFiles != null; - final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel(); - for (VirtualFile vFile : vFiles) { - ContentEntry entry = findContentEntry(model, vFile); - if (entry != null) { - final SourceFolder[] sourceFolders = entry.getSourceFolders(); - for (SourceFolder sourceFolder : sourceFolders) { - if (sourceFolder.getFile() == vFile) { - entry.removeSourceFolder(sourceFolder); - break; - } - } - if (myMarkAsExcluded) { - entry.addExcludeFolder(vFile); - } - else { - entry.addSourceFolder(vFile, myMarkAsTestSources); - } - } - } - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - model.commit(); - } - }); - } - - @Nullable - private static ContentEntry findContentEntry(ModifiableRootModel model, VirtualFile vFile) { - final ContentEntry[] contentEntries = model.getContentEntries(); - for (ContentEntry contentEntry : contentEntries) { - final VirtualFile contentEntryFile = contentEntry.getFile(); - if (contentEntryFile != null && VfsUtil.isAncestor(contentEntryFile, vFile, false)) { - return contentEntry; - } - } - return null; - } - - @Override - public void update(AnActionEvent e) { - boolean enabled = canMark(e, myMarkAsTestSources || myMarkAsExcluded, !myMarkAsTestSources || myMarkAsExcluded); - e.getPresentation().setVisible(enabled); - e.getPresentation().setEnabled(enabled); - } - - public static boolean canMark(AnActionEvent e, boolean acceptSourceRoot, boolean acceptTestSourceRoot) { - Module module = e.getData(LangDataKeys.MODULE); - VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); - if (module == null || vFiles == null) { - return false; - } - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(module.getProject()).getFileIndex(); - for (VirtualFile vFile : vFiles) { - if (!vFile.isDirectory()) { - return false; - } - if (!fileIndex.isInContent(vFile)) { - return false; - } - if (fileIndex.getSourceRootForFile(vFile) == vFile) { - boolean isTestSourceRoot = fileIndex.isInTestSourceContent(vFile); - if (acceptSourceRoot && !isTestSourceRoot) return true; - if (acceptTestSourceRoot && isTestSourceRoot) return true; - } - if (fileIndex.isInSourceContent(vFile)) { - return false; - } - } - return true; - } -} +/* + * 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.ide.projectView.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +/** + * @author yole + */ +public class MarkRootAction extends AnAction { + private final boolean myMarkAsTestSources; + private final boolean myMarkAsExcluded; + + public MarkRootAction() { + myMarkAsTestSources = false; + myMarkAsExcluded = false; + } + + protected MarkRootAction(boolean markAsTestSources, boolean markAsExcluded) { + myMarkAsTestSources = markAsTestSources; + myMarkAsExcluded = markAsExcluded; + } + + @Override + public void actionPerformed(AnActionEvent e) { + Module module = e.getData(LangDataKeys.MODULE); + VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); + assert vFiles != null; + final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel(); + for (VirtualFile vFile : vFiles) { + ContentEntry entry = findContentEntry(model, vFile); + if (entry != null) { + final SourceFolder[] sourceFolders = entry.getSourceFolders(); + for (SourceFolder sourceFolder : sourceFolders) { + if (sourceFolder.getFile() == vFile) { + entry.removeSourceFolder(sourceFolder); + break; + } + } + if (myMarkAsExcluded) { + entry.addExcludeFolder(vFile); + } + else { + entry.addSourceFolder(vFile, myMarkAsTestSources); + } + } + } + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + model.commit(); + } + }); + } + + @Nullable + private static ContentEntry findContentEntry(ModifiableRootModel model, VirtualFile vFile) { + final ContentEntry[] contentEntries = model.getContentEntries(); + for (ContentEntry contentEntry : contentEntries) { + final VirtualFile contentEntryFile = contentEntry.getFile(); + if (contentEntryFile != null && VfsUtil.isAncestor(contentEntryFile, vFile, false)) { + return contentEntry; + } + } + return null; + } + + @Override + public void update(AnActionEvent e) { + boolean enabled = canMark(e, myMarkAsTestSources || myMarkAsExcluded, !myMarkAsTestSources || myMarkAsExcluded); + e.getPresentation().setVisible(enabled); + e.getPresentation().setEnabled(enabled); + } + + public static boolean canMark(AnActionEvent e, boolean acceptSourceRoot, boolean acceptTestSourceRoot) { + Module module = e.getData(LangDataKeys.MODULE); + VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); + if (module == null || vFiles == null) { + return false; + } + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(module.getProject()).getFileIndex(); + for (VirtualFile vFile : vFiles) { + if (!vFile.isDirectory()) { + return false; + } + if (!fileIndex.isInContent(vFile)) { + return false; + } + if (fileIndex.getSourceRootForFile(vFile) == vFile) { + boolean isTestSourceRoot = fileIndex.isInTestSourceContent(vFile); + if (acceptSourceRoot && !isTestSourceRoot) return true; + if (acceptTestSourceRoot && isTestSourceRoot) return true; + } + if (fileIndex.isInSourceContent(vFile)) { + return false; + } + } + return true; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootGroup.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootGroup.java index 578263465a73..f61c8e01f21c 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootGroup.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootGroup.java @@ -1,29 +1,29 @@ -/* - * 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.ide.projectView.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; - -/** - * @author yole - */ -public class MarkRootGroup extends DefaultActionGroup { - @Override - public void update(AnActionEvent e) { - e.getPresentation().setVisible(MarkRootAction.canMark(e, true, true)); - } -} +/* + * 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.ide.projectView.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; + +/** + * @author yole + */ +public class MarkRootGroup extends DefaultActionGroup { + @Override + public void update(AnActionEvent e) { + e.getPresentation().setVisible(MarkRootAction.canMark(e, true, true)); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkTestSourceRootAction.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkTestSourceRootAction.java index d95a766e623e..b308ed2974c5 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkTestSourceRootAction.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkTestSourceRootAction.java @@ -1,25 +1,25 @@ -/* - * 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.ide.projectView.actions; - -/** - * @author yole - */ -public class MarkTestSourceRootAction extends MarkRootAction { - public MarkTestSourceRootAction() { - super(true, false); - } -} +/* + * 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.ide.projectView.actions; + +/** + * @author yole + */ +public class MarkTestSourceRootAction extends MarkRootAction { + public MarkTestSourceRootAction() { + super(true, false); + } +} 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 cae14ba2eefd..986d135eea71 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 @@ -1,1840 +1,1840 @@ -/* - * 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.ide.projectView.impl; - -import com.intellij.ProjectTopics; -import com.intellij.history.LocalHistory; -import com.intellij.history.LocalHistoryAction; -import com.intellij.ide.*; -import com.intellij.ide.FileEditorProvider; -import com.intellij.ide.dnd.DnDManager; -import com.intellij.ide.dnd.DnDTarget; -import com.intellij.ide.impl.ProjectViewSelectInTarget; -import com.intellij.ide.projectView.HelpID; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.ide.projectView.ProjectViewNode; -import com.intellij.ide.projectView.impl.nodes.*; -import com.intellij.ide.scopeView.ScopeViewPane; -import com.intellij.ide.ui.ListCellRendererWrapper; -import com.intellij.ide.ui.SplitterProportionsDataImpl; -import com.intellij.ide.util.DeleteHandler; -import com.intellij.ide.util.DirectoryChooserUtil; -import com.intellij.ide.util.EditorHelper; -import com.intellij.ide.util.treeView.AbstractTreeBuilder; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.ide.util.treeView.NodeDescriptor; -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.application.ex.ApplicationInfoEx; -import com.intellij.openapi.command.CommandProcessor; -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.editor.Editor; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.*; -import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.SimpleToolWindowPanel; -import com.intellij.openapi.ui.SplitterProportionsData; -import com.intellij.openapi.ui.popup.PopupChooserBuilder; -import com.intellij.openapi.util.*; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter; -import com.intellij.openapi.wm.ex.ToolWindowManagerEx; -import com.intellij.psi.*; -import com.intellij.psi.impl.file.PsiDirectoryFactory; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.ui.AutoScrollFromSourceHandler; -import com.intellij.ui.AutoScrollToSourceHandler; -import com.intellij.ui.GuiUtils; -import com.intellij.ui.components.JBList; -import com.intellij.ui.content.Content; -import com.intellij.ui.content.ContentManager; -import com.intellij.ui.switcher.QuickActionProvider; -import com.intellij.util.Alarm; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IJSwingUtilities; -import com.intellij.util.Icons; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; -import gnu.trove.THashMap; -import gnu.trove.THashSet; -import org.jdom.Attribute; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; -import java.awt.*; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.util.*; -import java.util.List; - -@State( - name="ProjectView", - storages= { - @Storage( - id="other", - file = "$WORKSPACE_FILE$" - )} -) -public final class ProjectViewImpl extends ProjectView implements PersistentStateComponent, Disposable, QuickActionProvider { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.projectView.impl.ProjectViewImpl"); - private final CopyPasteDelegator myCopyPasteDelegator; - private boolean isInitialized; - private boolean myExtensionsLoaded = false; - private final Project myProject; - - // + options - private final Map myFlattenPackages = new THashMap(); - private static final boolean ourFlattenPackagesDefaults = false; - private final Map myShowMembers = new THashMap(); - private static final boolean ourShowMembersDefaults = false; - private final Map mySortByType = new THashMap(); - private static final boolean ourSortByTypeDefaults = false; - private final Map myShowModules = new THashMap(); - private static final boolean ourShowModulesDefaults = true; - private final Map myShowLibraryContents = new THashMap(); - private static final boolean ourShowLibraryContentsDefaults = true; - private final Map myHideEmptyPackages = new THashMap(); - private static final boolean ourHideEmptyPackagesDefaults = true; - private final Map myAbbreviatePackageNames = new THashMap(); - private static final boolean ourAbbreviatePackagesDefaults = false; - private final Map myAutoscrollToSource = new THashMap(); - private static final boolean ourAutoscrollToSourceDefaults = false; - private final Map myAutoscrollFromSource = new THashMap(); - private static final boolean ourAutoscrollFromSourceDefaults = false; - private static final boolean ourShowStructureDefaults = false; - - private String myCurrentViewId; - private String myCurrentViewSubId; - // - options - - private final AutoScrollToSourceHandler myAutoScrollToSourceHandler; - private final MyAutoScrollFromSourceHandler myAutoScrollFromSourceHandler; - - private final IdeView myIdeView = new MyIdeView(); - private final MyDeletePSIElementProvider myDeletePSIElementProvider = new MyDeletePSIElementProvider(); - private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider(); - - private SimpleToolWindowPanel myPanel; - private final Map myId2Pane = new LinkedHashMap(); - private final Collection myUninitializedPanes = new THashSet(); - - static final DataKey DATA_KEY = DataKey.create("com.intellij.ide.projectView.impl.ProjectViewImpl"); - @Deprecated static final String PROJECT_VIEW_DATA_CONSTANT = DATA_KEY.getName(); - - private DefaultActionGroup myActionGroup; - private final Runnable myTreeChangeListener; - private String mySavedPaneId = ProjectViewPane.ID; - private String mySavedPaneSubId; - private static final Icon COMPACT_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/compactEmptyPackages.png"); - private static final Icon HIDE_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/hideEmptyPackages.png"); - @NonNls private static final String ELEMENT_NAVIGATOR = "navigator"; - @NonNls private static final String ELEMENT_PANES = "panes"; - @NonNls private static final String ELEMENT_PANE = "pane"; - @NonNls private static final String ATTRIBUTE_CURRENT_VIEW = "currentView"; - @NonNls private static final String ATTRIBUTE_CURRENT_SUBVIEW = "currentSubView"; - @NonNls private static final String ELEMENT_FLATTEN_PACKAGES = "flattenPackages"; - @NonNls private static final String ELEMENT_SHOW_MEMBERS = "showMembers"; - @NonNls private static final String ELEMENT_SHOW_MODULES = "showModules"; - @NonNls private static final String ELEMENT_SHOW_LIBRARY_CONTENTS = "showLibraryContents"; - @NonNls private static final String ELEMENT_HIDE_EMPTY_PACKAGES = "hideEmptyPackages"; - @NonNls private static final String ELEMENT_ABBREVIATE_PACKAGE_NAMES = "abbreviatePackageNames"; - @NonNls private static final String ELEMENT_AUTOSCROLL_TO_SOURCE = "autoscrollToSource"; - @NonNls private static final String ELEMENT_AUTOSCROLL_FROM_SOURCE = "autoscrollFromSource"; - @NonNls private static final String ELEMENT_SORT_BY_TYPE = "sortByType"; - private static final String ATTRIBUTE_ID = "id"; - private ComboBox myCombo; - private JPanel myViewContentPanel; - private JPanel myActionGroupPanel; - private JLabel myLabel; - private static final Comparator PANE_WEIGHT_COMPARATOR = new Comparator() { - public int compare(final AbstractProjectViewPane o1, final AbstractProjectViewPane o2) { - return o1.getWeight() - o2.getWeight(); - } - }; - private final FileEditorManager myFileEditorManager; - private final MyPanel myDataProvider; - private final SplitterProportionsData splitterProportions = new SplitterProportionsDataImpl(); - private static final Icon BULLET_ICON = IconLoader.getIcon("/general/bullet.png"); - private final MessageBusConnection myConnection; - private JPanel myTopPanel; - private ActionToolbar myToolBar; - private final Map myUninitializedPaneState = new HashMap(); - private final Map mySelectInTargets = new HashMap(); - - public ProjectViewImpl(Project project, final FileEditorManager fileEditorManager, final ToolWindowManagerEx toolWindowManager) { - myProject = project; - - constructUi(); - - - Disposer.register(myProject, this); - myFileEditorManager = fileEditorManager; - myTreeChangeListener = new Runnable() { - public void run() { - updateToolWindowTitle(); - } - }; - - myConnection = project.getMessageBus().connect(); - myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { - public void beforeRootsChange(ModuleRootEvent event) { - } - - public void rootsChanged(ModuleRootEvent event) { - refresh(); - } - }); - - myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(); - - myDataProvider = new MyPanel(); - myDataProvider.add(myPanel, BorderLayout.CENTER); - myCopyPasteDelegator = new CopyPasteDelegator(myProject, myPanel) { - @NotNull - protected PsiElement[] getSelectedElements() { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - return viewPane == null ? PsiElement.EMPTY_ARRAY : viewPane.getSelectedPSIElements(); - } - }; - myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { - protected boolean isAutoScrollMode() { - return isAutoscrollToSource(myCurrentViewId); - } - - protected void setAutoScrollMode(boolean state) { - setAutoscrollToSource(state, myCurrentViewId); - } - }; - toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter(){ - private boolean toolWindowVisible; - - public void stateChanged() { - ToolWindow window = toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW); - if (window == null) return; - if (window.isVisible() && !toolWindowVisible) { - String id = getCurrentViewId(); - if (isAutoscrollToSource(id)) { - myAutoScrollToSourceHandler.onMouseClicked(getCurrentProjectViewPane().getTree()); - } - if (isAutoscrollFromSource(id)) { - myAutoScrollFromSourceHandler.setAutoScrollMode(true); - } - } - toolWindowVisible = window.isVisible(); - } - }); - } - - private void constructUi() { - myActionGroupPanel = new JPanel(new BorderLayout()); - - myLabel = new JLabel("View as:"); - if (!SystemInfo.isMac) { // See IDEADEV-41315 - myLabel.setDisplayedMnemonic('a'); - } - myCombo = new ComboBox(); - myCombo.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); - myLabel.setLabelFor(myCombo); - - final JPanel combo = new JPanel(new BorderLayout()); - combo.setBorder(new EmptyBorder(4, 4, 4, 4)); - combo.add(myLabel, BorderLayout.WEST); - combo.add(myCombo, BorderLayout.CENTER); - - - myTopPanel = new JPanel(new GridBagLayout()); - myTopPanel.add(combo, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); - myTopPanel.add(myActionGroupPanel, new GridBagConstraints(1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - - myViewContentPanel = new JPanel(); - myPanel = new SimpleToolWindowPanel(true).setProvideQuickActions(false); - myPanel.setToolbar(myTopPanel); - myPanel.setContent(myViewContentPanel); - - myPanel.setBorder(new ToolWindow.Border(true, false, false, false)); - } - - public String getName() { - return "Project"; - } - - public List getActions(boolean originalProvider) { - ArrayList result = new ArrayList(); - - DefaultActionGroup views = new DefaultActionGroup("Change View", true); - boolean lastWasHeader = false; - boolean lastHeaderHadKids = false; - for (int i = 0; i < myCombo.getModel().getSize(); i++) { - Object each = myCombo.getModel().getElementAt(i); - if (each instanceof Pair) { - Pair eachPair = (Pair)each; - - if (eachPair.getSecond() == null) { - if (lastHeaderHadKids) { - views.add(new Separator()); - } else { - if (i + 1 < myCombo.getModel().getSize()) { - Object next = myCombo.getModel().getElementAt(i + 1); - if (next instanceof Pair) { - if (((Pair)next).getSecond() != null) { - views.add(new Separator()); - } - } - } - } - } else { - lastHeaderHadKids = true; - } - - lastWasHeader = eachPair.getSecond() == null; - - views.add(new ChangeViewAction(eachPair.getFirst(), eachPair.getSecond())); - } - } - result.add(views); - result.add(new Separator()); - - - ArrayList secondary = new ArrayList(); - if (myActionGroup != null) { - AnAction[] kids = myActionGroup.getChildren(null); - for (AnAction each : kids) { - if (myActionGroup.isPrimary(each)) { - result.add(each); - } else { - secondary.add(each); - } - } - } - result.add(new Separator()); - result.addAll(secondary); - - return result; - } - - private class ChangeViewAction extends AnAction { - private String myId; - private String mySubId; - - private ChangeViewAction(String id, String subId) { - myId = id; - mySubId = subId; - } - - @Override - public void update(AnActionEvent e) { - AbstractProjectViewPane pane = getProjectViewPaneById(myId); - e.getPresentation().setText(pane.getTitle() + (mySubId != null ? (" - " + pane.getPresentableSubIdName(mySubId)) : "")); - } - - @Override - public void actionPerformed(AnActionEvent e) { - changeView(myId, mySubId); - } - } - - public boolean isCycleRoot() { - return false; - } - - public synchronized void addProjectPane(final AbstractProjectViewPane pane) { - myUninitializedPanes.add(pane); - SelectInTarget selectInTarget = pane.createSelectInTarget(); - if (selectInTarget != null) { - mySelectInTargets.put(pane.getId(), selectInTarget); - } - if (isInitialized) { - doAddUninitializedPanes(); - } - } - - public synchronized void removeProjectPane(AbstractProjectViewPane pane) { - myUninitializedPanes.remove(pane); - //assume we are completely initialized here - String idToRemove = pane.getId(); - - if (!myId2Pane.containsKey(idToRemove)) return; - pane.removeTreeChangeListener(); - for (int i = myCombo.getItemCount() - 1; i >= 0; i--) { - Pair ids = (Pair)myCombo.getItemAt(i); - String id = ids.first; - if (id.equals(idToRemove)) { - if (i == myCombo.getSelectedIndex()) { - myCombo.setSelectedIndex(0); - } - myCombo.removeItemAt(i); - } - } - myId2Pane.remove(idToRemove); - mySelectInTargets.remove(idToRemove); - viewSelectionChanged(); - } - - private synchronized void doAddUninitializedPanes() { - for (AbstractProjectViewPane pane : myUninitializedPanes) { - doAddPane(pane); - } - if (myCombo.getSelectedItem() == null) { //old selection isn't available anymore - final DefaultComboBoxModel comboBoxModel = (DefaultComboBoxModel)myCombo.getModel(); - final int size = comboBoxModel.getSize(); - if (size > 0) { - final Pair ids = (Pair)comboBoxModel.getElementAt(size - 1); - changeView(ids.first, ids.second); - } - } - myUninitializedPanes.clear(); - } - - private void doAddPane(final AbstractProjectViewPane newPane) { - int index; - for (index = 0; index < myCombo.getItemCount(); index++) { - Pair ids = (Pair)myCombo.getItemAt(index); - String id = ids.first; - AbstractProjectViewPane pane = myId2Pane.get(id); - - int comp = PANE_WEIGHT_COMPARATOR.compare(pane, newPane); - if (comp == 0) { - System.out.println("here"); - } - LOG.assertTrue(comp != 0); - if (comp > 0) { - break; - } - } - final String id = newPane.getId(); - myId2Pane.put(id, newPane); - String[] subIds = newPane.getSubIds(); - subIds = ArrayUtil.mergeArrays(new String[]{null}, subIds, String.class); - for (String subId : subIds) { - myCombo.insertItemAt(Pair.create(id, subId), index++); - } - myCombo.setMaximumRowCount(myCombo.getItemCount()); - - if (id.equals(mySavedPaneId)) { - changeView(mySavedPaneId, mySavedPaneSubId); - mySavedPaneId = null; - mySavedPaneSubId = null; - } - - Disposer.register(this, newPane); - } - - private void showPane(AbstractProjectViewPane newPane) { - AbstractProjectViewPane currentPane = getCurrentProjectViewPane(); - PsiElement selectedPsiElement = null; - if (currentPane != null) { - if (currentPane != newPane) { - currentPane.saveExpandedPaths(); - } - final PsiElement[] elements = currentPane.getSelectedPSIElements(); - if (elements.length > 0) { - selectedPsiElement = elements[0]; - } - } - removeLabelFocusListener(); - myViewContentPanel.removeAll(); - JComponent component = newPane.createComponent(); - UIUtil.removeScrollBorder(component); - myViewContentPanel.setLayout(new BorderLayout()); - myViewContentPanel.add(component, BorderLayout.CENTER); - myCurrentViewId = newPane.getId(); - String newSubId = myCurrentViewSubId = newPane.getSubId(); - myViewContentPanel.revalidate(); - myViewContentPanel.repaint(); - createToolbarActions(); - myToolBar.updateActionsImmediately(); - myTopPanel.revalidate(); - - newPane.setTreeChangeListener(myTreeChangeListener); - myAutoScrollToSourceHandler.install(newPane.myTree); - - IdeFocusManager.getInstance(myProject).requestFocus(newPane.getComponentToFocus(), false); - updateToolWindowTitle(); - - newPane.restoreExpandedPaths(); - if (selectedPsiElement != null) { - final VirtualFile virtualFile = PsiUtilBase.getVirtualFile(selectedPsiElement); - if (virtualFile != null && ((ProjectViewSelectInTarget)newPane.createSelectInTarget()).isSubIdSelectable(newSubId, new SelectInContext() { - @NotNull - public Project getProject() { - return myProject; - } - - @NotNull - public VirtualFile getVirtualFile() { - return virtualFile; - } - - public Object getSelectorInFile() { - return null; - } - - public FileEditorProvider getFileEditorProvider() { - return null; - } - })) { - newPane.select(selectedPsiElement, virtualFile, true); - } - } - myAutoScrollToSourceHandler.onMouseClicked(newPane.myTree); - installLabelFocusListener(); - } - - // public for tests - public synchronized void setupImpl(final ToolWindow toolWindow) { - setupImpl(toolWindow, true); - } - - // public for tests - public synchronized void setupImpl(final ToolWindow toolWindow, final boolean loadPaneExtensions) { - myCombo.setRenderer(new ListCellRendererWrapper>(myCombo.getRenderer()){ - @Override - public void customize(final JList list, - final Pair value, - final int index, - final boolean selected, - final boolean cellHasFocus) { - if (value == null) { - return; - } - - final String id = value.first; - final String subId = value.second; - final AbstractProjectViewPane pane = getProjectViewPaneById(id); - if (pane != null) { - if (subId == null) { - setText(pane.getTitle()); - setIcon(pane.getIcon()); - return; - } - final String presentable = pane.getPresentableSubIdName(subId); - if (index == -1) { - setText(presentable); - setIcon(pane.getIcon()); - } - else { - // indent sub id - setText(presentable); - setIcon(BULLET_ICON); - } - } - } - }); - - myCombo.setMinimumAndPreferredWidth(10); - - myActionGroup = new DefaultActionGroup(); - - myAutoScrollFromSourceHandler.install(); - - myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.PROJECT_VIEW_TOOLBAR, myActionGroup, true); - myToolBar.setSecondaryActionsTooltip("View Options"); - JComponent toolbarComponent = myToolBar.getComponent(); - myActionGroupPanel.setLayout(new BorderLayout()); - myActionGroupPanel.add(toolbarComponent, BorderLayout.CENTER); - - if (toolWindow != null) { - final ContentManager contentManager = toolWindow.getContentManager(); - final Content content = contentManager.getFactory().createContent(getComponent(), ToolWindowId.PROJECT_VIEW, false); - contentManager.addContent(content); - - content.setPreferredFocusedComponent(new Computable() { - public JComponent compute() { - final AbstractProjectViewPane current = getCurrentProjectViewPane(); - return current != null ? current.getComponentToFocus() : null; - } - }); - toolWindow.setIcon(IconLoader.getIcon(ApplicationInfoEx.getInstanceEx().getToolWindowIconUrl())); - } - - myCombo.addPopupMenuListener(new PopupMenuListener() { - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - - } - - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - if (!viewSelectionChanged()) { - ToolWindowManager.getInstance(myProject).activateEditorComponent(); - } - } - - public void popupMenuCanceled(PopupMenuEvent e) { - ToolWindowManager.getInstance(myProject).activateEditorComponent(); - } - }); - installLabelFocusListener(); - - GuiUtils.replaceJSplitPaneWithIDEASplitter(myPanel); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - splitterProportions.restoreSplitterProportions(myPanel); - } - }); - - if (loadPaneExtensions) { - ensurePanesLoaded(); - } - - isInitialized = true; - doAddUninitializedPanes(); - } - - private void ensurePanesLoaded() { - if (myExtensionsLoaded) return; - myExtensionsLoaded = true; - for(AbstractProjectViewPane pane: Extensions.getExtensions(AbstractProjectViewPane.EP_NAME, myProject)) { - if (myUninitializedPaneState.containsKey(pane.getId())) { - try { - pane.readExternal(myUninitializedPaneState.get(pane.getId())); - } - catch (InvalidDataException e) { - // ignore - } - myUninitializedPaneState.remove(pane.getId()); - } - if (pane.isInitiallyVisible() && !myId2Pane.containsKey(pane.getId())) { - addProjectPane(pane); - } - Disposer.register(this, pane); - } - } - - private final FocusListener myLabelFocusListener = new FocusListener() { - public void focusGained(FocusEvent e) { - if (!myCombo.isPopupVisible() && myCombo.isShowing()) { - myCombo.requestFocusInWindow(); - myCombo.showPopup(); - } - } - - public void focusLost(FocusEvent e) { - - } - }; - - private void installLabelFocusListener() { - myLabel.addFocusListener(myLabelFocusListener); - } - - private void removeLabelFocusListener() { - myLabel.removeFocusListener(myLabelFocusListener); - } - - private boolean viewSelectionChanged() { - Pair ids = (Pair)myCombo.getSelectedItem(); - if (ids == null) return false; - final String id = ids.first; - String subId = ids.second; - if (ids.equals(Pair.create(myCurrentViewId, myCurrentViewSubId))) return false; - final AbstractProjectViewPane newPane = getProjectViewPaneById(id); - if (newPane == null) return false; - newPane.setSubId(subId); - String[] subIds = newPane.getSubIds(); - - if (subId == null && subIds.length != 0) { - final String firstNonTrivialSubId = subIds[0]; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - changeView(id, firstNonTrivialSubId); - newPane.setSubId(firstNonTrivialSubId); - } - }); - } - else { - showPane(newPane); - } - return true; - } - - private void createToolbarActions() { - myActionGroup.removeAll(); - if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { - myActionGroup.addAction(new PaneOptionAction(myFlattenPackages, IdeBundle.message("action.flatten.packages"), - IdeBundle.message("action.flatten.packages"), Icons.FLATTEN_PACKAGES_ICON, - ourFlattenPackagesDefaults) { - public void setSelected(AnActionEvent event, boolean flag) { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - final SelectionInfo selectionInfo = SelectionInfo.create(viewPane); - - super.setSelected(event, flag); - - selectionInfo.apply(viewPane); - } - }).setAsSecondary(true); - } - - class FlattenPackagesDependableAction extends PaneOptionAction { - FlattenPackagesDependableAction(Map optionsMap, - final String text, - final String description, - final Icon icon, - boolean optionDefaultValue) { - super(optionsMap, text, description, icon, optionDefaultValue); - } - - public void update(AnActionEvent e) { - super.update(e); - final Presentation presentation = e.getPresentation(); - presentation.setVisible(isFlattenPackages(myCurrentViewId)); - } - } - if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) { - myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true); - } - if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { - myActionGroup.addAction(new FlattenPackagesDependableAction(myAbbreviatePackageNames, - IdeBundle.message("action.abbreviate.qualified.package.names"), - IdeBundle.message("action.abbreviate.qualified.package.names"), - IconLoader.getIcon("/objectBrowser/abbreviatePackageNames.png"), - ourAbbreviatePackagesDefaults) { - public boolean isSelected(AnActionEvent event) { - return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId); - } - - - public void update(AnActionEvent e) { - super.update(e); - if (ScopeViewPane.ID.equals(myCurrentViewId)) { - e.getPresentation().setEnabled(false); - } - } - }).setAsSecondary(true); - } - myActionGroup.addAction(new PaneOptionAction(myShowMembers, IdeBundle.message("action.show.members"), - IdeBundle.message("action.show.hide.members"), - IconLoader.getIcon("/objectBrowser/showMembers.png"), ourShowMembersDefaults)).setAsSecondary(true); - myActionGroup.addAction(myAutoScrollToSourceHandler.createToggleAction()).setAsSecondary(true); - myActionGroup.addAction(myAutoScrollFromSourceHandler.createToggleAction()).setAsSecondary(true); - myActionGroup.addAction(new SortByTypeAction()).setAsSecondary(true); - - myActionGroup.addAction(new ScrollFromSourceAction()); - AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() { - public void expandAll() { - - } - - public boolean canExpand() { - return false; - } - - public void collapseAll() { - AbstractProjectViewPane pane = getCurrentProjectViewPane(); - JTree tree = pane.myTree; - if (tree != null) { - TreeUtil.collapseAll(tree, -1); - } - } - - public boolean canCollapse() { - return true; - } - }, getComponent()); - myActionGroup.add(collapseAllAction); - - getCurrentProjectViewPane().addToolbarActions(myActionGroup); - } - - public AbstractProjectViewPane getProjectViewPaneById(String id) { - if (!ApplicationManager.getApplication().isUnitTestMode()) { // most tests don't need all panes to be loaded - ensurePanesLoaded(); - } - - final AbstractProjectViewPane pane = myId2Pane.get(id); - if (pane != null) { - return pane; - } - for (AbstractProjectViewPane viewPane : myUninitializedPanes) { - if (viewPane.getId().equals(id)) { - return viewPane; - } - } - return null; - } - - public AbstractProjectViewPane getCurrentProjectViewPane() { - return getProjectViewPaneById(myCurrentViewId); - } - - public void refresh() { - AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); - if (currentProjectViewPane != null) { - // may be null for e.g. default project - currentProjectViewPane.updateFromRoot(false); - } - } - - public void select(final Object element, VirtualFile file, boolean requestFocus) { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - if (viewPane != null) { - viewPane.select(element, file, requestFocus); - } - } - - public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - if (viewPane != null && viewPane instanceof AbstractProjectViewPSIPane) { - return ((AbstractProjectViewPSIPane) viewPane).selectCB(element, file, requestFocus); - } else { - select(element, file, requestFocus); - return new ActionCallback.Done(); - } - } - - public void dispose() { - myConnection.disconnect(); - } - - public JComponent getComponent() { - return myDataProvider; - } - - public String getCurrentViewId() { - return myCurrentViewId; - } - - private void updateToolWindowTitle() { - ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); - ToolWindow toolWindow = toolWindowManager == null ? null : toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW); - if (toolWindow == null) return; - String title = null; - final AbstractProjectViewPane pane = getCurrentProjectViewPane(); - if (pane != null) { - final DefaultMutableTreeNode selectedNode = pane.getSelectedNode(); - if (selectedNode != null) { - final Object o = selectedNode.getUserObject(); - if (o instanceof ProjectViewNode) { - title = ((ProjectViewNode)o).getTitle(); - } - } - } - if (title == null) { - final PsiElement element = (PsiElement)myDataProvider.getData(LangDataKeys.PSI_ELEMENT.getName()); - if (element != null) { - PsiFile file = element.getContainingFile(); - if (file != null) { - title = file.getVirtualFile().getPresentableUrl(); - } - else if (element instanceof PsiDirectory) { - title = PsiDirectoryFactory.getInstance(myProject).getQualifiedName((PsiDirectory) element, true); - } - else { - title = element.toString(); - } - } - else { - title = ""; - if (myProject != null) { - title = myProject.getPresentableUrl(); - } - } - } - - toolWindow.setTitle(title); - } - - public PsiElement getParentOfCurrentSelection() { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - if (viewPane == null) { - return null; - } - TreePath path = viewPane.getSelectedPath(); - if (path == null) { - return null; - } - path = path.getParentPath(); - if (path == null) { - return null; - } - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - Object userObject = node.getUserObject(); - if (userObject instanceof ProjectViewNode) { - ProjectViewNode descriptor = (ProjectViewNode)userObject; - Object element = descriptor.getValue(); - if (element instanceof PsiElement) { - PsiElement psiElement = (PsiElement)element; - if (!psiElement.isValid()) return null; - return psiElement; - } - else { - return null; - } - } - return null; - } - - - private class PaneOptionAction extends ToggleAction implements DumbAware { - private final Map myOptionsMap; - private final boolean myOptionDefaultValue; - - PaneOptionAction(Map optionsMap, - final String text, - final String description, - final Icon icon, - boolean optionDefaultValue) { - super(text, description, icon); - myOptionsMap = optionsMap; - myOptionDefaultValue = optionDefaultValue; - } - - public boolean isSelected(AnActionEvent event) { - return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue); - } - - public void setSelected(AnActionEvent event, boolean flag) { - setPaneOption(myOptionsMap, flag, myCurrentViewId, true); - } - } - - public void changeView() { - final List views = new ArrayList(myId2Pane.values()); - views.remove(getCurrentProjectViewPane()); - Collections.sort(views, PANE_WEIGHT_COMPARATOR); - - final JList list = new JBList(ArrayUtil.toObjectArray(views)); - list.setCellRenderer(new DefaultListCellRenderer() { - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - AbstractProjectViewPane pane = (AbstractProjectViewPane)value; - setText(pane.getTitle()); - return this; - } - }); - - if (!views.isEmpty()) { - list.setSelectedValue(views.get(0), true); - } - Runnable runnable = new Runnable() { - public void run() { - if (list.getSelectedIndex() < 0) return; - AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue(); - changeView(pane.getId()); - } - }; - - new PopupChooserBuilder(list). - setTitle(IdeBundle.message("title.popup.views")). - setItemChoosenCallback(runnable). - createPopup().showInCenterOf(getComponent()); - } - - public void changeView(@NotNull String viewId) { - changeView(viewId, null); - } - - public void changeView(@NotNull String viewId, @Nullable String subId) { - AbstractProjectViewPane pane = getProjectViewPaneById(viewId); - LOG.assertTrue(pane != null, "Project view pane not found: " + viewId + "; subId:" + subId); - if (!viewId.equals(getCurrentViewId()) - || subId != null && !subId.equals(pane.getSubId()) || - // element not in model anymore - ((DefaultComboBoxModel)myCombo.getModel()).getIndexOf(Pair.create(viewId, pane.getSubId())) == -1) { - myCombo.setSelectedItem(Pair.create(viewId, subId)); - viewSelectionChanged(); - } - } - - private final class MyDeletePSIElementProvider implements DeleteProvider { - public boolean canDeleteElement(DataContext dataContext) { - final PsiElement[] elements = getElementsToDelete(); - return DeleteHandler.shouldEnableDeleteAction(elements); - } - - public void deleteElement(DataContext dataContext) { - List allElements = Arrays.asList(getElementsToDelete()); - List validElements = new ArrayList(); - for (PsiElement psiElement : allElements) { - if (psiElement != null && psiElement.isValid()) validElements.add(psiElement); - } - final PsiElement[] elements = validElements.toArray(new PsiElement[validElements.size()]); - - LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting")); - try { - DeleteHandler.deletePsiElement(elements, myProject); - } - finally { - a.finish(); - } - } - - private PsiElement[] getElementsToDelete() { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - PsiElement[] elements = viewPane.getSelectedPSIElements(); - for (int idx = 0; idx < elements.length; idx++) { - final PsiElement element = elements[idx]; - if (element instanceof PsiDirectory) { - PsiDirectory directory = (PsiDirectory)element; - final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject); - if (isHideEmptyMiddlePackages(viewPane.getId()) && directory.getChildren().length == 0 && !directoryHelper.skipDirectory(directory)) { - while (true) { - PsiDirectory parent = directory.getParentDirectory(); - if (parent == null) break; - if (directoryHelper.skipDirectory(parent) || PsiDirectoryFactory.getInstance(myProject).getQualifiedName(parent, false).length() == 0) break; - PsiElement[] children = parent.getChildren(); - if (children.length == 0 || children.length == 1 && children[0] == directory) { - directory = parent; - } - else { - break; - } - } - elements[idx] = directory; - } - final VirtualFile virtualFile = directory.getVirtualFile(); - final String path = virtualFile.getPath(); - if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root - final VirtualFile vFile = - LocalFileSystem.getInstance().findFileByPath(path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length())); - if (vFile != null) { - final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile); - if (psiFile != null) { - elements[idx] = psiFile; - } - } - } - } - } - return elements; - } - - } - - private final class MyPanel extends JPanel implements DataProvider { - MyPanel() { - super(new BorderLayout()); - } - - private Object getSelectedNodeElement() { - final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); - if (currentProjectViewPane == null) { // can happen if not initialized yet - return null; - } - DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode(); - if (node == null) { - return null; - } - Object userObject = node.getUserObject(); - if (userObject instanceof AbstractTreeNode) { - return ((AbstractTreeNode)userObject).getValue(); - } - if (!(userObject instanceof NodeDescriptor)) { - return null; - } - return ((NodeDescriptor)userObject).getElement(); - } - - public Object getData(String dataId) { - final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); - if (currentProjectViewPane != null) { - final Object paneSpecificData = currentProjectViewPane.getData(dataId); - if (paneSpecificData != null) return paneSpecificData; - } - - if (LangDataKeys.PSI_ELEMENT.is(dataId)) { - if (currentProjectViewPane == null) return null; - final PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements(); - return elements.length == 1 ? elements[0] : null; - } - if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { - if (currentProjectViewPane == null) { - return null; - } - PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements(); - return elements.length == 0 ? null : elements; - } - if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) { - PsiElement[] psiElements = (PsiElement[])getData(LangDataKeys.PSI_ELEMENT_ARRAY.getName()); - if (psiElements == null) return null; - Set files = new LinkedHashSet(); - for (PsiElement element : psiElements) { - if (element instanceof PsiFileSystemItem) { - files.add(((PsiFileSystemItem)element).getVirtualFile()); - } - } - return files.size() > 0 ? VfsUtil.toVirtualFileArray(files) : null; - } - if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) { - return null; - } - if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { - return myCopyPasteDelegator.getCutProvider(); - } - if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { - return myCopyPasteDelegator.getCopyProvider(); - } - if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { - return myCopyPasteDelegator.getPasteProvider(); - } - if (LangDataKeys.IDE_VIEW.is(dataId)) { - return myIdeView; - } - if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { - final Module[] modules = getSelectedModules(); - if (modules != null) { - return myDeleteModuleProvider; - } - final LibraryOrderEntry orderEntry = getSelectedLibrary(); - if (orderEntry != null) { - return new DeleteProvider() { - public void deleteElement(DataContext dataContext) { - detachLibrary(orderEntry, myProject); - } - - public boolean canDeleteElement(DataContext dataContext) { - return true; - } - }; - } - return myDeletePSIElementProvider; - } - if (PlatformDataKeys.HELP_ID.is(dataId)) { - return HelpID.PROJECT_VIEWS; - } - if (ProjectViewImpl.DATA_KEY.is(dataId)) { - return ProjectViewImpl.this; - } - if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) { - Object selected = getSelectedNodeElement(); - return selected instanceof Project ? selected : null; - } - if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { - Object selected = getSelectedNodeElement(); - if (selected instanceof Module) { - return !((Module)selected).isDisposed() ? selected : null; - } - else if (selected instanceof PsiDirectory) { - return moduleByContentRoot(((PsiDirectory)selected).getVirtualFile()); - } - else if (selected instanceof VirtualFile) { - return moduleByContentRoot((VirtualFile)selected); - } - else { - return null; - } - } - - if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) { - return getSelectedModules(); - } - if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) { - final List selectedElements = getSelectedElements(ModuleGroup.class); - return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]); - } - if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) { - final List selectedElements = getSelectedElements(LibraryGroupElement.class); - return selectedElements.isEmpty() ? null : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]); - } - if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) { - final List selectedElements = getSelectedElements(NamedLibraryElement.class); - return selectedElements.isEmpty() ? null : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]); - } - - if (QuickActionProvider.KEY.is(dataId)) { - return ProjectViewImpl.this; - } - - return null; - } - - private LibraryOrderEntry getSelectedLibrary() { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - DefaultMutableTreeNode node = viewPane != null ? viewPane.getSelectedNode() : null; - if (node == null) return null; - DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); - if (parent == null) return null; - Object userObject = parent.getUserObject(); - if (userObject instanceof LibraryGroupNode) { - userObject = node.getUserObject(); - if (userObject instanceof NamedLibraryElementNode) { - NamedLibraryElement element = ((NamedLibraryElementNode)userObject).getValue(); - OrderEntry orderEntry = element.getOrderEntry(); - return orderEntry instanceof LibraryOrderEntry ? (LibraryOrderEntry)orderEntry : null; - } - PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue(); - VirtualFile virtualFile = directory.getVirtualFile(); - Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue(); - - if (module == null) return null; - ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex(); - OrderEntry entry = index.getOrderEntryForFile(virtualFile); - if (entry instanceof LibraryOrderEntry) { - return (LibraryOrderEntry)entry; - } - } - - return null; - } - - private void detachLibrary(final LibraryOrderEntry orderEntry, final Project project) { - final Module module = orderEntry.getOwnerModule(); - String message = IdeBundle.message("detach.library.from.module", orderEntry.getPresentableName(), module.getName()); - String title = IdeBundle.message("detach.library"); - int ret = Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon()); - if (ret != 0) return; - CommandProcessor.getInstance().executeCommand(module.getProject(), new Runnable() { - public void run() { - final Runnable action = new Runnable() { - public void run() { - ModuleRootManager rootManager = ModuleRootManager.getInstance(module); - OrderEntry[] orderEntries = rootManager.getOrderEntries(); - ModifiableRootModel model = rootManager.getModifiableModel(); - OrderEntry[] modifiableEntries = model.getOrderEntries(); - for (int i = 0; i < orderEntries.length; i++) { - OrderEntry entry = orderEntries[i]; - if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == orderEntry.getLibrary()) { - model.removeOrderEntry(modifiableEntries[i]); - } - } - model.commit(); - } - }; - ApplicationManager.getApplication().runWriteAction(action); - } - }, title, null); - } - - @Nullable - private Module[] getSelectedModules() { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - if (viewPane == null) return null; - final Object[] elements = viewPane.getSelectedElements(); - ArrayList result = new ArrayList(); - for (Object element : elements) { - if (element instanceof Module) { - final Module module = (Module)element; - if (!module.isDisposed()) { - result.add(module); - } - } - else if (element instanceof ModuleGroup) { - Collection modules = ((ModuleGroup)element).modulesInGroup(myProject, true); - result.addAll(modules); - } - else if (element instanceof PsiDirectory) { - Module module = moduleByContentRoot(((PsiDirectory)element).getVirtualFile()); - if (module != null) result.add(module); - } - else if (element instanceof VirtualFile) { - Module module = moduleByContentRoot((VirtualFile)element); - if (module != null) result.add(module); - } - } - - if (result.isEmpty()) { - return null; - } - else { - return result.toArray(new Module[result.size()]); - } - } - } - - private Module moduleByContentRoot(VirtualFile file) { - if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) { - Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file); - if (module != null && !module.isDisposed()) { - return module; - } - } - - return null; - } - - private List getSelectedElements(Class klass) { - ArrayList result = new ArrayList(); - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - if (viewPane == null) return result; - final Object[] elements = viewPane.getSelectedElements(); - for (Object element : elements) { - //element still valid - if (element != null && klass.isAssignableFrom(element.getClass())) { - result.add((T)element); - } - } - return result; - } - - private final class MyIdeView implements IdeView { - public void selectElement(PsiElement element) { - selectPsiElement(element, false); - boolean requestFocus = true; - if (element != null) { - final boolean isDirectory = element instanceof PsiDirectory; - if (!isDirectory) { - Editor editor = EditorHelper.openInEditor(element); - if (editor != null) { - ToolWindowManager.getInstance(myProject).activateEditorComponent(); - requestFocus = false; - } - } - } - - if (requestFocus) { - selectPsiElement(element, true); - } - } - - public PsiDirectory[] getDirectories() { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - if (viewPane != null) { - return viewPane.getSelectedDirectories(); - } - - return PsiDirectory.EMPTY_ARRAY; - } - - public PsiDirectory getOrChooseDirectory() { - return DirectoryChooserUtil.getOrChooseDirectory(this); - } - } - - public void selectPsiElement(PsiElement element, boolean requestFocus) { - if (element == null) return; - VirtualFile virtualFile = PsiUtilBase.getVirtualFile(element); - select(element, virtualFile, requestFocus); - } - - - private static void readOption(Element node, Map options) { - if (node == null) return; - List attributes = node.getAttributes(); - for (final Object attribute1 : attributes) { - Attribute attribute = (Attribute)attribute1; - options.put(attribute.getName(), Boolean.TRUE.toString().equals(attribute.getValue()) ? Boolean.TRUE : Boolean.FALSE); - } - } - - private static void writeOption(Element parentNode, Map optionsForPanes, String optionName) { - Element e = new Element(optionName); - for (Map.Entry entry : optionsForPanes.entrySet()) { - final String key = entry.getKey(); - if (key != null) { //SCR48267 - e.setAttribute(key, Boolean.toString(entry.getValue().booleanValue())); - } - } - - parentNode.addContent(e); - } - - public void loadState(Element parentNode) { - Element navigatorElement = parentNode.getChild(ELEMENT_NAVIGATOR); - if (navigatorElement != null) { - mySavedPaneId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_VIEW); - mySavedPaneSubId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_SUBVIEW); - if (mySavedPaneId == null) { - mySavedPaneId = ProjectViewPane.ID; - mySavedPaneSubId = null; - } - readOption(navigatorElement.getChild(ELEMENT_FLATTEN_PACKAGES), myFlattenPackages); - readOption(navigatorElement.getChild(ELEMENT_SHOW_MEMBERS), myShowMembers); - readOption(navigatorElement.getChild(ELEMENT_SHOW_MODULES), myShowModules); - readOption(navigatorElement.getChild(ELEMENT_SHOW_LIBRARY_CONTENTS), myShowLibraryContents); - readOption(navigatorElement.getChild(ELEMENT_HIDE_EMPTY_PACKAGES), myHideEmptyPackages); - readOption(navigatorElement.getChild(ELEMENT_ABBREVIATE_PACKAGE_NAMES), myAbbreviatePackageNames); - readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_TO_SOURCE), myAutoscrollToSource); - readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_FROM_SOURCE), myAutoscrollFromSource); - readOption(navigatorElement.getChild(ELEMENT_SORT_BY_TYPE), mySortByType); - - try { - splitterProportions.readExternal(navigatorElement); - } - catch (InvalidDataException e) { - // ignore - } - } - Element panesElement = parentNode.getChild(ELEMENT_PANES); - if (panesElement != null) { - readPaneState(panesElement); - } - } - - private void readPaneState(Element panesElement) { - @SuppressWarnings({"unchecked"}) - final List paneElements = panesElement.getChildren(ELEMENT_PANE); - - for (Element paneElement : paneElements) { - String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID); - final AbstractProjectViewPane pane = myId2Pane.get(paneId); - if (pane != null) { - try { - pane.readExternal(paneElement); - } - catch (InvalidDataException e) { - // ignore - } - } - else { - myUninitializedPaneState.put(paneId, paneElement); - } - } - } - - public Element getState() { - Element parentNode = new Element("projectView"); - Element navigatorElement = new Element(ELEMENT_NAVIGATOR); - AbstractProjectViewPane currentPane = getCurrentProjectViewPane(); - if (currentPane != null) { - navigatorElement.setAttribute(ATTRIBUTE_CURRENT_VIEW, currentPane.getId()); - String subId = currentPane.getSubId(); - if (subId != null) { - navigatorElement.setAttribute(ATTRIBUTE_CURRENT_SUBVIEW, subId); - } - } - writeOption(navigatorElement, myFlattenPackages, ELEMENT_FLATTEN_PACKAGES); - writeOption(navigatorElement, myShowMembers, ELEMENT_SHOW_MEMBERS); - writeOption(navigatorElement, myShowModules, ELEMENT_SHOW_MODULES); - writeOption(navigatorElement, myShowLibraryContents, ELEMENT_SHOW_LIBRARY_CONTENTS); - writeOption(navigatorElement, myHideEmptyPackages, ELEMENT_HIDE_EMPTY_PACKAGES); - writeOption(navigatorElement, myAbbreviatePackageNames, ELEMENT_ABBREVIATE_PACKAGE_NAMES); - writeOption(navigatorElement, myAutoscrollToSource, ELEMENT_AUTOSCROLL_TO_SOURCE); - writeOption(navigatorElement, myAutoscrollFromSource, ELEMENT_AUTOSCROLL_FROM_SOURCE); - writeOption(navigatorElement, mySortByType, ELEMENT_SORT_BY_TYPE); - - splitterProportions.saveSplitterProportions(myPanel); - try { - splitterProportions.writeExternal(navigatorElement); - } - catch (WriteExternalException e) { - // ignore - } - parentNode.addContent(navigatorElement); - - // for compatibility with idea 5.1 - @Deprecated @NonNls final String ATTRIBUTE_SPLITTER_PROPORTION = "splitterProportion"; - navigatorElement.setAttribute(ATTRIBUTE_SPLITTER_PROPORTION, "0.5"); - - Element panesElement = new Element(ELEMENT_PANES); - writePaneState(panesElement); - parentNode.addContent(panesElement); - return parentNode; - } - - private void writePaneState(Element panesElement) { - for (AbstractProjectViewPane pane : myId2Pane.values()) { - Element paneElement = new Element(ELEMENT_PANE); - paneElement.setAttribute(ATTRIBUTE_ID, pane.getId()); - try { - pane.writeExternal(paneElement); - } - catch (WriteExternalException e) { - continue; - } - panesElement.addContent(paneElement); - } - for (Element element : myUninitializedPaneState.values()) { - panesElement.addContent((Element) element.clone()); - } - } - - public boolean isAutoscrollToSource(String paneId) { - return getPaneOptionValue(myAutoscrollToSource, paneId, ourAutoscrollToSourceDefaults); - } - - private void setAutoscrollToSource(boolean autoscrollMode, String paneId) { - myAutoscrollToSource.put(paneId, autoscrollMode ? Boolean.TRUE : Boolean.FALSE); - } - - public boolean isAutoscrollFromSource(String paneId) { - return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults); - } - - private void setAutoscrollFromSource(boolean autoscrollMode, String paneId) { - setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false); - } - - public boolean isFlattenPackages(String paneId) { - return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults); - } - - public void setFlattenPackages(boolean flattenPackages, String paneId) { - setPaneOption(myFlattenPackages, flattenPackages, paneId, true); - } - - public boolean isShowMembers(String paneId) { - return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults); - } - - public void setShowMembers(boolean showMembers, String paneId) { - setPaneOption(myShowMembers, showMembers, paneId, true); - } - - public boolean isHideEmptyMiddlePackages(String paneId) { - return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults); - } - - public boolean isAbbreviatePackageNames(String paneId) { - return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults); - } - - public boolean isShowLibraryContents(String paneId) { - return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults); - } - - public void setShowLibraryContents(boolean showLibraryContents, String paneId) { - setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true); - } - - public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) { - return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true); - } - - public boolean isShowModules(String paneId) { - return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults); - } - - public void setShowModules(boolean showModules, String paneId) { - setPaneOption(myShowModules, showModules, paneId, true); - } - - public void setHideEmptyPackages(boolean hideEmptyPackages, String paneId) { - setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true); - } - - public void setAbbreviatePackageNames(boolean abbreviatePackageNames, String paneId) { - setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true); - } - - private ActionCallback setPaneOption(Map optionsMap, boolean value, String paneId, final boolean updatePane) { - optionsMap.put(paneId, value ? Boolean.TRUE : Boolean.FALSE); - if (updatePane) { - final AbstractProjectViewPane pane = getProjectViewPaneById(paneId); - if (pane != null) { - return pane.updateFromRoot(false); - } - } - return new ActionCallback.Done(); - } - - private static boolean getPaneOptionValue(Map optionsMap, String paneId, boolean defaultValue) { - final Boolean value = optionsMap.get(paneId); - return value == null ? defaultValue : value.booleanValue(); - } - - private class HideEmptyMiddlePackagesAction extends PaneOptionAction { - private HideEmptyMiddlePackagesAction() { - super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults); - } - - public void setSelected(AnActionEvent event, boolean flag) { - final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); - final SelectionInfo selectionInfo = SelectionInfo.create(viewPane); - - super.setSelected(event, flag); - - selectionInfo.apply(viewPane); - } - - public void update(AnActionEvent e) { - super.update(e); - final Presentation presentation = e.getPresentation(); - if (isFlattenPackages(myCurrentViewId)) { - presentation.setText(IdeBundle.message("action.hide.empty.middle.packages")); - presentation.setDescription(IdeBundle.message("action.show.hide.empty.middle.packages")); - } - else { - presentation.setText(IdeBundle.message("action.compact.empty.middle.packages")); - presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages")); - } - } - } - - private static class SelectionInfo { - private final Object[] myElements; - - private SelectionInfo(Object[] elements) { - myElements = elements; - } - - public void apply(final AbstractProjectViewPane viewPane) { - if (viewPane == null) { - return; - } - AbstractTreeBuilder treeBuilder = viewPane.getTreeBuilder(); - JTree tree = viewPane.myTree; - DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); - List paths = new ArrayList(myElements.length); - for (final Object element : myElements) { - DefaultMutableTreeNode node = treeBuilder.getNodeForElement(element); - if (node == null) { - treeBuilder.buildNodeForElement(element); - node = treeBuilder.getNodeForElement(element); - } - if (node != null) { - paths.add(new TreePath(treeModel.getPathToRoot(node))); - } - } - if (!paths.isEmpty()) { - tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()])); - } - } - - public static SelectionInfo create(final AbstractProjectViewPane viewPane) { - List selectedElements = Collections.emptyList(); - if (viewPane != null) { - final TreePath[] selectionPaths = viewPane.getSelectionPaths(); - if (selectionPaths != null) { - selectedElements = new ArrayList(); - for (TreePath path : selectionPaths) { - final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - final Object userObject = node.getUserObject(); - if (userObject instanceof NodeDescriptor) { - selectedElements.add(((NodeDescriptor)userObject).getElement()); - } - } - } - } - return new SelectionInfo(selectedElements.toArray()); - } - } - - private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler { - private final Alarm myAlarm = new Alarm(myProject); - - private MyAutoScrollFromSourceHandler() { - super(ProjectViewImpl.this.myProject, ProjectViewImpl.this); - } - - public void install() { - FileEditorManagerAdapter myEditorManagerListener = new FileEditorManagerAdapter() { - public void selectionChanged(final FileEditorManagerEvent event) { - final FileEditor newEditor = event.getNewEditor(); - myAlarm.cancelAllRequests(); - myAlarm.addRequest(new Runnable() { - public void run() { - if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return; - if (isAutoscrollFromSource(getCurrentViewId())) { - if (newEditor instanceof TextEditor) { - Editor editor = ((TextEditor)newEditor).getEditor(); - selectElementAtCaretNotLosingFocus(editor); - } else if (newEditor != null) { - final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(newEditor); - if (file != null) { - final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); - if (psiFile != null) { - final SelectInTarget target = mySelectInTargets.get(getCurrentViewId()); - if (target != null) { - final MySelectInContext selectInContext = new MySelectInContext(psiFile, null) { - @Override - public Object getSelectorInFile() { - return psiFile; - } - }; - - if (target.canSelect(selectInContext)) { - target.selectIn(selectInContext, false); - } - } - } - } - } - } - } - }, 300, ModalityState.NON_MODAL); - } - }; - myFileEditorManager.addFileEditorManagerListener(myEditorManagerListener, this); - } - - public void scrollFromSource() { - final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); - final FileEditor[] editors = fileEditorManager.getSelectedEditors(); - for (FileEditor fileEditor : editors) { - if (fileEditor instanceof TextEditor) { - Editor editor = ((TextEditor)fileEditor).getEditor(); - selectElementAtCaret(editor); - return; - } - } - final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles(); - if (selectedFiles.length > 0) { - final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]); - if (file != null) { - scrollFromFile(file, null); - } - } - } - - private void selectElementAtCaretNotLosingFocus(final Editor editor) { - if (IJSwingUtilities.hasFocus(getCurrentProjectViewPane().getComponentToFocus())) return; - selectElementAtCaret(editor); - } - - private void selectElementAtCaret(Editor editor) { - final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument()); - if (file == null) return; - - scrollFromFile(file, editor); - } - - private void scrollFromFile(PsiFile file, @Nullable Editor editor) { - final MySelectInContext selectInContext = new MySelectInContext(file, editor); - - final SelectInTarget target = mySelectInTargets.get(getCurrentViewId()); - if (target != null && target.canSelect(selectInContext)) { - target.selectIn(selectInContext, false); - } - } - - public void dispose() { - } - - protected boolean isAutoScrollMode() { - return isAutoscrollFromSource(myCurrentViewId); - } - - protected void setAutoScrollMode(boolean state) { - setAutoscrollFromSource(state, myCurrentViewId); - if (state) { - final Editor editor = myFileEditorManager.getSelectedTextEditor(); - if (editor != null) { - selectElementAtCaretNotLosingFocus(editor); - } - } - } - - private class MySelectInContext implements SelectInContext { - private final PsiFile myPsiFile; - @Nullable private final Editor myEditor; - - private MySelectInContext(final PsiFile psiFile, @Nullable Editor editor) { - myPsiFile = psiFile; - myEditor = editor; - } - - @NotNull - public Project getProject() { - return myProject; - } - - private PsiFile getPsiFile() { - return myPsiFile; - } - - public FileEditorProvider getFileEditorProvider() { - if (myPsiFile == null) return null; - return new FileEditorProvider() { - public FileEditor openFileEditor() { - return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0]; - } - }; - } - - private PsiElement getPsiElement() { - PsiElement e = null; - if (myEditor != null) { - final int offset = myEditor.getCaretModel().getOffset(); - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - e = getPsiFile().findElementAt(offset); - } - if (e == null) { - e = getPsiFile(); - } - return e; - } - - @NotNull - public VirtualFile getVirtualFile() { - return getPsiFile().getVirtualFile(); - } - - public Object getSelectorInFile() { - return getPsiElement(); - } - } - } - - public boolean isSortByType(String paneId) { - return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults); - } - - public void setSortByType(String paneId, final boolean sortByType) { - setPaneOption(mySortByType, sortByType, paneId, false); - final AbstractProjectViewPane pane = getProjectViewPaneById(paneId); - pane.installComparator(); - } - - private class SortByTypeAction extends ToggleAction { - private SortByTypeAction() { - super(IdeBundle.message("action.sort.by.type"), IdeBundle.message("action.sort.by.type"), - IconLoader.getIcon("/objectBrowser/sortByType.png")); - } - - public boolean isSelected(AnActionEvent event) { - return isSortByType(getCurrentViewId()); - } - - public void setSelected(AnActionEvent event, boolean flag) { - setSortByType(getCurrentViewId(), flag); - } - - public void update(final AnActionEvent e) { - super.update(e); - final Presentation presentation = e.getPresentation(); - presentation.setVisible(getCurrentProjectViewPane() != null); - } - } - - private class ScrollFromSourceAction extends AnAction { - private ScrollFromSourceAction() { - super("Scroll from Source", "Select the file open in the active editor", IconLoader.getIcon("/general/autoscrollFromSource.png")); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myAutoScrollFromSourceHandler.scrollFromSource(); - } - } - - public Collection getPaneIds() { - return myId2Pane.keySet(); - } - - @Override - public Collection getSelectInTargets() { - ensurePanesLoaded(); - return mySelectInTargets.values(); - } -} +/* + * 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.ide.projectView.impl; + +import com.intellij.ProjectTopics; +import com.intellij.history.LocalHistory; +import com.intellij.history.LocalHistoryAction; +import com.intellij.ide.*; +import com.intellij.ide.FileEditorProvider; +import com.intellij.ide.dnd.DnDManager; +import com.intellij.ide.dnd.DnDTarget; +import com.intellij.ide.impl.ProjectViewSelectInTarget; +import com.intellij.ide.projectView.HelpID; +import com.intellij.ide.projectView.ProjectView; +import com.intellij.ide.projectView.ProjectViewNode; +import com.intellij.ide.projectView.impl.nodes.*; +import com.intellij.ide.scopeView.ScopeViewPane; +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.ide.ui.SplitterProportionsDataImpl; +import com.intellij.ide.util.DeleteHandler; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.ide.util.EditorHelper; +import com.intellij.ide.util.treeView.AbstractTreeBuilder; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.ide.util.treeView.NodeDescriptor; +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.application.ex.ApplicationInfoEx; +import com.intellij.openapi.command.CommandProcessor; +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.editor.Editor; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.SimpleToolWindowPanel; +import com.intellij.openapi.ui.SplitterProportionsData; +import com.intellij.openapi.ui.popup.PopupChooserBuilder; +import com.intellij.openapi.util.*; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter; +import com.intellij.openapi.wm.ex.ToolWindowManagerEx; +import com.intellij.psi.*; +import com.intellij.psi.impl.file.PsiDirectoryFactory; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.AutoScrollFromSourceHandler; +import com.intellij.ui.AutoScrollToSourceHandler; +import com.intellij.ui.GuiUtils; +import com.intellij.ui.components.JBList; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentManager; +import com.intellij.ui.switcher.QuickActionProvider; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IJSwingUtilities; +import com.intellij.util.Icons; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import org.jdom.Attribute; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.*; +import java.util.List; + +@State( + name="ProjectView", + storages= { + @Storage( + id="other", + file = "$WORKSPACE_FILE$" + )} +) +public final class ProjectViewImpl extends ProjectView implements PersistentStateComponent, Disposable, QuickActionProvider { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.projectView.impl.ProjectViewImpl"); + private final CopyPasteDelegator myCopyPasteDelegator; + private boolean isInitialized; + private boolean myExtensionsLoaded = false; + private final Project myProject; + + // + options + private final Map myFlattenPackages = new THashMap(); + private static final boolean ourFlattenPackagesDefaults = false; + private final Map myShowMembers = new THashMap(); + private static final boolean ourShowMembersDefaults = false; + private final Map mySortByType = new THashMap(); + private static final boolean ourSortByTypeDefaults = false; + private final Map myShowModules = new THashMap(); + private static final boolean ourShowModulesDefaults = true; + private final Map myShowLibraryContents = new THashMap(); + private static final boolean ourShowLibraryContentsDefaults = true; + private final Map myHideEmptyPackages = new THashMap(); + private static final boolean ourHideEmptyPackagesDefaults = true; + private final Map myAbbreviatePackageNames = new THashMap(); + private static final boolean ourAbbreviatePackagesDefaults = false; + private final Map myAutoscrollToSource = new THashMap(); + private static final boolean ourAutoscrollToSourceDefaults = false; + private final Map myAutoscrollFromSource = new THashMap(); + private static final boolean ourAutoscrollFromSourceDefaults = false; + private static final boolean ourShowStructureDefaults = false; + + private String myCurrentViewId; + private String myCurrentViewSubId; + // - options + + private final AutoScrollToSourceHandler myAutoScrollToSourceHandler; + private final MyAutoScrollFromSourceHandler myAutoScrollFromSourceHandler; + + private final IdeView myIdeView = new MyIdeView(); + private final MyDeletePSIElementProvider myDeletePSIElementProvider = new MyDeletePSIElementProvider(); + private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider(); + + private SimpleToolWindowPanel myPanel; + private final Map myId2Pane = new LinkedHashMap(); + private final Collection myUninitializedPanes = new THashSet(); + + static final DataKey DATA_KEY = DataKey.create("com.intellij.ide.projectView.impl.ProjectViewImpl"); + @Deprecated static final String PROJECT_VIEW_DATA_CONSTANT = DATA_KEY.getName(); + + private DefaultActionGroup myActionGroup; + private final Runnable myTreeChangeListener; + private String mySavedPaneId = ProjectViewPane.ID; + private String mySavedPaneSubId; + private static final Icon COMPACT_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/compactEmptyPackages.png"); + private static final Icon HIDE_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/hideEmptyPackages.png"); + @NonNls private static final String ELEMENT_NAVIGATOR = "navigator"; + @NonNls private static final String ELEMENT_PANES = "panes"; + @NonNls private static final String ELEMENT_PANE = "pane"; + @NonNls private static final String ATTRIBUTE_CURRENT_VIEW = "currentView"; + @NonNls private static final String ATTRIBUTE_CURRENT_SUBVIEW = "currentSubView"; + @NonNls private static final String ELEMENT_FLATTEN_PACKAGES = "flattenPackages"; + @NonNls private static final String ELEMENT_SHOW_MEMBERS = "showMembers"; + @NonNls private static final String ELEMENT_SHOW_MODULES = "showModules"; + @NonNls private static final String ELEMENT_SHOW_LIBRARY_CONTENTS = "showLibraryContents"; + @NonNls private static final String ELEMENT_HIDE_EMPTY_PACKAGES = "hideEmptyPackages"; + @NonNls private static final String ELEMENT_ABBREVIATE_PACKAGE_NAMES = "abbreviatePackageNames"; + @NonNls private static final String ELEMENT_AUTOSCROLL_TO_SOURCE = "autoscrollToSource"; + @NonNls private static final String ELEMENT_AUTOSCROLL_FROM_SOURCE = "autoscrollFromSource"; + @NonNls private static final String ELEMENT_SORT_BY_TYPE = "sortByType"; + private static final String ATTRIBUTE_ID = "id"; + private ComboBox myCombo; + private JPanel myViewContentPanel; + private JPanel myActionGroupPanel; + private JLabel myLabel; + private static final Comparator PANE_WEIGHT_COMPARATOR = new Comparator() { + public int compare(final AbstractProjectViewPane o1, final AbstractProjectViewPane o2) { + return o1.getWeight() - o2.getWeight(); + } + }; + private final FileEditorManager myFileEditorManager; + private final MyPanel myDataProvider; + private final SplitterProportionsData splitterProportions = new SplitterProportionsDataImpl(); + private static final Icon BULLET_ICON = IconLoader.getIcon("/general/bullet.png"); + private final MessageBusConnection myConnection; + private JPanel myTopPanel; + private ActionToolbar myToolBar; + private final Map myUninitializedPaneState = new HashMap(); + private final Map mySelectInTargets = new HashMap(); + + public ProjectViewImpl(Project project, final FileEditorManager fileEditorManager, final ToolWindowManagerEx toolWindowManager) { + myProject = project; + + constructUi(); + + + Disposer.register(myProject, this); + myFileEditorManager = fileEditorManager; + myTreeChangeListener = new Runnable() { + public void run() { + updateToolWindowTitle(); + } + }; + + myConnection = project.getMessageBus().connect(); + myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + public void beforeRootsChange(ModuleRootEvent event) { + } + + public void rootsChanged(ModuleRootEvent event) { + refresh(); + } + }); + + myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(); + + myDataProvider = new MyPanel(); + myDataProvider.add(myPanel, BorderLayout.CENTER); + myCopyPasteDelegator = new CopyPasteDelegator(myProject, myPanel) { + @NotNull + protected PsiElement[] getSelectedElements() { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + return viewPane == null ? PsiElement.EMPTY_ARRAY : viewPane.getSelectedPSIElements(); + } + }; + myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { + protected boolean isAutoScrollMode() { + return isAutoscrollToSource(myCurrentViewId); + } + + protected void setAutoScrollMode(boolean state) { + setAutoscrollToSource(state, myCurrentViewId); + } + }; + toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter(){ + private boolean toolWindowVisible; + + public void stateChanged() { + ToolWindow window = toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW); + if (window == null) return; + if (window.isVisible() && !toolWindowVisible) { + String id = getCurrentViewId(); + if (isAutoscrollToSource(id)) { + myAutoScrollToSourceHandler.onMouseClicked(getCurrentProjectViewPane().getTree()); + } + if (isAutoscrollFromSource(id)) { + myAutoScrollFromSourceHandler.setAutoScrollMode(true); + } + } + toolWindowVisible = window.isVisible(); + } + }); + } + + private void constructUi() { + myActionGroupPanel = new JPanel(new BorderLayout()); + + myLabel = new JLabel("View as:"); + if (!SystemInfo.isMac) { // See IDEADEV-41315 + myLabel.setDisplayedMnemonic('a'); + } + myCombo = new ComboBox(); + myCombo.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); + myLabel.setLabelFor(myCombo); + + final JPanel combo = new JPanel(new BorderLayout()); + combo.setBorder(new EmptyBorder(4, 4, 4, 4)); + combo.add(myLabel, BorderLayout.WEST); + combo.add(myCombo, BorderLayout.CENTER); + + + myTopPanel = new JPanel(new GridBagLayout()); + myTopPanel.add(combo, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + myTopPanel.add(myActionGroupPanel, new GridBagConstraints(1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + myViewContentPanel = new JPanel(); + myPanel = new SimpleToolWindowPanel(true).setProvideQuickActions(false); + myPanel.setToolbar(myTopPanel); + myPanel.setContent(myViewContentPanel); + + myPanel.setBorder(new ToolWindow.Border(true, false, false, false)); + } + + public String getName() { + return "Project"; + } + + public List getActions(boolean originalProvider) { + ArrayList result = new ArrayList(); + + DefaultActionGroup views = new DefaultActionGroup("Change View", true); + boolean lastWasHeader = false; + boolean lastHeaderHadKids = false; + for (int i = 0; i < myCombo.getModel().getSize(); i++) { + Object each = myCombo.getModel().getElementAt(i); + if (each instanceof Pair) { + Pair eachPair = (Pair)each; + + if (eachPair.getSecond() == null) { + if (lastHeaderHadKids) { + views.add(new Separator()); + } else { + if (i + 1 < myCombo.getModel().getSize()) { + Object next = myCombo.getModel().getElementAt(i + 1); + if (next instanceof Pair) { + if (((Pair)next).getSecond() != null) { + views.add(new Separator()); + } + } + } + } + } else { + lastHeaderHadKids = true; + } + + lastWasHeader = eachPair.getSecond() == null; + + views.add(new ChangeViewAction(eachPair.getFirst(), eachPair.getSecond())); + } + } + result.add(views); + result.add(new Separator()); + + + ArrayList secondary = new ArrayList(); + if (myActionGroup != null) { + AnAction[] kids = myActionGroup.getChildren(null); + for (AnAction each : kids) { + if (myActionGroup.isPrimary(each)) { + result.add(each); + } else { + secondary.add(each); + } + } + } + result.add(new Separator()); + result.addAll(secondary); + + return result; + } + + private class ChangeViewAction extends AnAction { + private String myId; + private String mySubId; + + private ChangeViewAction(String id, String subId) { + myId = id; + mySubId = subId; + } + + @Override + public void update(AnActionEvent e) { + AbstractProjectViewPane pane = getProjectViewPaneById(myId); + e.getPresentation().setText(pane.getTitle() + (mySubId != null ? (" - " + pane.getPresentableSubIdName(mySubId)) : "")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + changeView(myId, mySubId); + } + } + + public boolean isCycleRoot() { + return false; + } + + public synchronized void addProjectPane(final AbstractProjectViewPane pane) { + myUninitializedPanes.add(pane); + SelectInTarget selectInTarget = pane.createSelectInTarget(); + if (selectInTarget != null) { + mySelectInTargets.put(pane.getId(), selectInTarget); + } + if (isInitialized) { + doAddUninitializedPanes(); + } + } + + public synchronized void removeProjectPane(AbstractProjectViewPane pane) { + myUninitializedPanes.remove(pane); + //assume we are completely initialized here + String idToRemove = pane.getId(); + + if (!myId2Pane.containsKey(idToRemove)) return; + pane.removeTreeChangeListener(); + for (int i = myCombo.getItemCount() - 1; i >= 0; i--) { + Pair ids = (Pair)myCombo.getItemAt(i); + String id = ids.first; + if (id.equals(idToRemove)) { + if (i == myCombo.getSelectedIndex()) { + myCombo.setSelectedIndex(0); + } + myCombo.removeItemAt(i); + } + } + myId2Pane.remove(idToRemove); + mySelectInTargets.remove(idToRemove); + viewSelectionChanged(); + } + + private synchronized void doAddUninitializedPanes() { + for (AbstractProjectViewPane pane : myUninitializedPanes) { + doAddPane(pane); + } + if (myCombo.getSelectedItem() == null) { //old selection isn't available anymore + final DefaultComboBoxModel comboBoxModel = (DefaultComboBoxModel)myCombo.getModel(); + final int size = comboBoxModel.getSize(); + if (size > 0) { + final Pair ids = (Pair)comboBoxModel.getElementAt(size - 1); + changeView(ids.first, ids.second); + } + } + myUninitializedPanes.clear(); + } + + private void doAddPane(final AbstractProjectViewPane newPane) { + int index; + for (index = 0; index < myCombo.getItemCount(); index++) { + Pair ids = (Pair)myCombo.getItemAt(index); + String id = ids.first; + AbstractProjectViewPane pane = myId2Pane.get(id); + + int comp = PANE_WEIGHT_COMPARATOR.compare(pane, newPane); + if (comp == 0) { + System.out.println("here"); + } + LOG.assertTrue(comp != 0); + if (comp > 0) { + break; + } + } + final String id = newPane.getId(); + myId2Pane.put(id, newPane); + String[] subIds = newPane.getSubIds(); + subIds = ArrayUtil.mergeArrays(new String[]{null}, subIds, String.class); + for (String subId : subIds) { + myCombo.insertItemAt(Pair.create(id, subId), index++); + } + myCombo.setMaximumRowCount(myCombo.getItemCount()); + + if (id.equals(mySavedPaneId)) { + changeView(mySavedPaneId, mySavedPaneSubId); + mySavedPaneId = null; + mySavedPaneSubId = null; + } + + Disposer.register(this, newPane); + } + + private void showPane(AbstractProjectViewPane newPane) { + AbstractProjectViewPane currentPane = getCurrentProjectViewPane(); + PsiElement selectedPsiElement = null; + if (currentPane != null) { + if (currentPane != newPane) { + currentPane.saveExpandedPaths(); + } + final PsiElement[] elements = currentPane.getSelectedPSIElements(); + if (elements.length > 0) { + selectedPsiElement = elements[0]; + } + } + removeLabelFocusListener(); + myViewContentPanel.removeAll(); + JComponent component = newPane.createComponent(); + UIUtil.removeScrollBorder(component); + myViewContentPanel.setLayout(new BorderLayout()); + myViewContentPanel.add(component, BorderLayout.CENTER); + myCurrentViewId = newPane.getId(); + String newSubId = myCurrentViewSubId = newPane.getSubId(); + myViewContentPanel.revalidate(); + myViewContentPanel.repaint(); + createToolbarActions(); + myToolBar.updateActionsImmediately(); + myTopPanel.revalidate(); + + newPane.setTreeChangeListener(myTreeChangeListener); + myAutoScrollToSourceHandler.install(newPane.myTree); + + IdeFocusManager.getInstance(myProject).requestFocus(newPane.getComponentToFocus(), false); + updateToolWindowTitle(); + + newPane.restoreExpandedPaths(); + if (selectedPsiElement != null) { + final VirtualFile virtualFile = PsiUtilBase.getVirtualFile(selectedPsiElement); + if (virtualFile != null && ((ProjectViewSelectInTarget)newPane.createSelectInTarget()).isSubIdSelectable(newSubId, new SelectInContext() { + @NotNull + public Project getProject() { + return myProject; + } + + @NotNull + public VirtualFile getVirtualFile() { + return virtualFile; + } + + public Object getSelectorInFile() { + return null; + } + + public FileEditorProvider getFileEditorProvider() { + return null; + } + })) { + newPane.select(selectedPsiElement, virtualFile, true); + } + } + myAutoScrollToSourceHandler.onMouseClicked(newPane.myTree); + installLabelFocusListener(); + } + + // public for tests + public synchronized void setupImpl(final ToolWindow toolWindow) { + setupImpl(toolWindow, true); + } + + // public for tests + public synchronized void setupImpl(final ToolWindow toolWindow, final boolean loadPaneExtensions) { + myCombo.setRenderer(new ListCellRendererWrapper>(myCombo.getRenderer()){ + @Override + public void customize(final JList list, + final Pair value, + final int index, + final boolean selected, + final boolean cellHasFocus) { + if (value == null) { + return; + } + + final String id = value.first; + final String subId = value.second; + final AbstractProjectViewPane pane = getProjectViewPaneById(id); + if (pane != null) { + if (subId == null) { + setText(pane.getTitle()); + setIcon(pane.getIcon()); + return; + } + final String presentable = pane.getPresentableSubIdName(subId); + if (index == -1) { + setText(presentable); + setIcon(pane.getIcon()); + } + else { + // indent sub id + setText(presentable); + setIcon(BULLET_ICON); + } + } + } + }); + + myCombo.setMinimumAndPreferredWidth(10); + + myActionGroup = new DefaultActionGroup(); + + myAutoScrollFromSourceHandler.install(); + + myToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.PROJECT_VIEW_TOOLBAR, myActionGroup, true); + myToolBar.setSecondaryActionsTooltip("View Options"); + JComponent toolbarComponent = myToolBar.getComponent(); + myActionGroupPanel.setLayout(new BorderLayout()); + myActionGroupPanel.add(toolbarComponent, BorderLayout.CENTER); + + if (toolWindow != null) { + final ContentManager contentManager = toolWindow.getContentManager(); + final Content content = contentManager.getFactory().createContent(getComponent(), ToolWindowId.PROJECT_VIEW, false); + contentManager.addContent(content); + + content.setPreferredFocusedComponent(new Computable() { + public JComponent compute() { + final AbstractProjectViewPane current = getCurrentProjectViewPane(); + return current != null ? current.getComponentToFocus() : null; + } + }); + toolWindow.setIcon(IconLoader.getIcon(ApplicationInfoEx.getInstanceEx().getToolWindowIconUrl())); + } + + myCombo.addPopupMenuListener(new PopupMenuListener() { + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + + } + + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + if (!viewSelectionChanged()) { + ToolWindowManager.getInstance(myProject).activateEditorComponent(); + } + } + + public void popupMenuCanceled(PopupMenuEvent e) { + ToolWindowManager.getInstance(myProject).activateEditorComponent(); + } + }); + installLabelFocusListener(); + + GuiUtils.replaceJSplitPaneWithIDEASplitter(myPanel); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + splitterProportions.restoreSplitterProportions(myPanel); + } + }); + + if (loadPaneExtensions) { + ensurePanesLoaded(); + } + + isInitialized = true; + doAddUninitializedPanes(); + } + + private void ensurePanesLoaded() { + if (myExtensionsLoaded) return; + myExtensionsLoaded = true; + for(AbstractProjectViewPane pane: Extensions.getExtensions(AbstractProjectViewPane.EP_NAME, myProject)) { + if (myUninitializedPaneState.containsKey(pane.getId())) { + try { + pane.readExternal(myUninitializedPaneState.get(pane.getId())); + } + catch (InvalidDataException e) { + // ignore + } + myUninitializedPaneState.remove(pane.getId()); + } + if (pane.isInitiallyVisible() && !myId2Pane.containsKey(pane.getId())) { + addProjectPane(pane); + } + Disposer.register(this, pane); + } + } + + private final FocusListener myLabelFocusListener = new FocusListener() { + public void focusGained(FocusEvent e) { + if (!myCombo.isPopupVisible() && myCombo.isShowing()) { + myCombo.requestFocusInWindow(); + myCombo.showPopup(); + } + } + + public void focusLost(FocusEvent e) { + + } + }; + + private void installLabelFocusListener() { + myLabel.addFocusListener(myLabelFocusListener); + } + + private void removeLabelFocusListener() { + myLabel.removeFocusListener(myLabelFocusListener); + } + + private boolean viewSelectionChanged() { + Pair ids = (Pair)myCombo.getSelectedItem(); + if (ids == null) return false; + final String id = ids.first; + String subId = ids.second; + if (ids.equals(Pair.create(myCurrentViewId, myCurrentViewSubId))) return false; + final AbstractProjectViewPane newPane = getProjectViewPaneById(id); + if (newPane == null) return false; + newPane.setSubId(subId); + String[] subIds = newPane.getSubIds(); + + if (subId == null && subIds.length != 0) { + final String firstNonTrivialSubId = subIds[0]; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + changeView(id, firstNonTrivialSubId); + newPane.setSubId(firstNonTrivialSubId); + } + }); + } + else { + showPane(newPane); + } + return true; + } + + private void createToolbarActions() { + myActionGroup.removeAll(); + if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { + myActionGroup.addAction(new PaneOptionAction(myFlattenPackages, IdeBundle.message("action.flatten.packages"), + IdeBundle.message("action.flatten.packages"), Icons.FLATTEN_PACKAGES_ICON, + ourFlattenPackagesDefaults) { + public void setSelected(AnActionEvent event, boolean flag) { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + final SelectionInfo selectionInfo = SelectionInfo.create(viewPane); + + super.setSelected(event, flag); + + selectionInfo.apply(viewPane); + } + }).setAsSecondary(true); + } + + class FlattenPackagesDependableAction extends PaneOptionAction { + FlattenPackagesDependableAction(Map optionsMap, + final String text, + final String description, + final Icon icon, + boolean optionDefaultValue) { + super(optionsMap, text, description, icon, optionDefaultValue); + } + + public void update(AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setVisible(isFlattenPackages(myCurrentViewId)); + } + } + if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) { + myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true); + } + if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { + myActionGroup.addAction(new FlattenPackagesDependableAction(myAbbreviatePackageNames, + IdeBundle.message("action.abbreviate.qualified.package.names"), + IdeBundle.message("action.abbreviate.qualified.package.names"), + IconLoader.getIcon("/objectBrowser/abbreviatePackageNames.png"), + ourAbbreviatePackagesDefaults) { + public boolean isSelected(AnActionEvent event) { + return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId); + } + + + public void update(AnActionEvent e) { + super.update(e); + if (ScopeViewPane.ID.equals(myCurrentViewId)) { + e.getPresentation().setEnabled(false); + } + } + }).setAsSecondary(true); + } + myActionGroup.addAction(new PaneOptionAction(myShowMembers, IdeBundle.message("action.show.members"), + IdeBundle.message("action.show.hide.members"), + IconLoader.getIcon("/objectBrowser/showMembers.png"), ourShowMembersDefaults)).setAsSecondary(true); + myActionGroup.addAction(myAutoScrollToSourceHandler.createToggleAction()).setAsSecondary(true); + myActionGroup.addAction(myAutoScrollFromSourceHandler.createToggleAction()).setAsSecondary(true); + myActionGroup.addAction(new SortByTypeAction()).setAsSecondary(true); + + myActionGroup.addAction(new ScrollFromSourceAction()); + AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() { + public void expandAll() { + + } + + public boolean canExpand() { + return false; + } + + public void collapseAll() { + AbstractProjectViewPane pane = getCurrentProjectViewPane(); + JTree tree = pane.myTree; + if (tree != null) { + TreeUtil.collapseAll(tree, -1); + } + } + + public boolean canCollapse() { + return true; + } + }, getComponent()); + myActionGroup.add(collapseAllAction); + + getCurrentProjectViewPane().addToolbarActions(myActionGroup); + } + + public AbstractProjectViewPane getProjectViewPaneById(String id) { + if (!ApplicationManager.getApplication().isUnitTestMode()) { // most tests don't need all panes to be loaded + ensurePanesLoaded(); + } + + final AbstractProjectViewPane pane = myId2Pane.get(id); + if (pane != null) { + return pane; + } + for (AbstractProjectViewPane viewPane : myUninitializedPanes) { + if (viewPane.getId().equals(id)) { + return viewPane; + } + } + return null; + } + + public AbstractProjectViewPane getCurrentProjectViewPane() { + return getProjectViewPaneById(myCurrentViewId); + } + + public void refresh() { + AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); + if (currentProjectViewPane != null) { + // may be null for e.g. default project + currentProjectViewPane.updateFromRoot(false); + } + } + + public void select(final Object element, VirtualFile file, boolean requestFocus) { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + if (viewPane != null) { + viewPane.select(element, file, requestFocus); + } + } + + public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + if (viewPane != null && viewPane instanceof AbstractProjectViewPSIPane) { + return ((AbstractProjectViewPSIPane) viewPane).selectCB(element, file, requestFocus); + } else { + select(element, file, requestFocus); + return new ActionCallback.Done(); + } + } + + public void dispose() { + myConnection.disconnect(); + } + + public JComponent getComponent() { + return myDataProvider; + } + + public String getCurrentViewId() { + return myCurrentViewId; + } + + private void updateToolWindowTitle() { + ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); + ToolWindow toolWindow = toolWindowManager == null ? null : toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW); + if (toolWindow == null) return; + String title = null; + final AbstractProjectViewPane pane = getCurrentProjectViewPane(); + if (pane != null) { + final DefaultMutableTreeNode selectedNode = pane.getSelectedNode(); + if (selectedNode != null) { + final Object o = selectedNode.getUserObject(); + if (o instanceof ProjectViewNode) { + title = ((ProjectViewNode)o).getTitle(); + } + } + } + if (title == null) { + final PsiElement element = (PsiElement)myDataProvider.getData(LangDataKeys.PSI_ELEMENT.getName()); + if (element != null) { + PsiFile file = element.getContainingFile(); + if (file != null) { + title = file.getVirtualFile().getPresentableUrl(); + } + else if (element instanceof PsiDirectory) { + title = PsiDirectoryFactory.getInstance(myProject).getQualifiedName((PsiDirectory) element, true); + } + else { + title = element.toString(); + } + } + else { + title = ""; + if (myProject != null) { + title = myProject.getPresentableUrl(); + } + } + } + + toolWindow.setTitle(title); + } + + public PsiElement getParentOfCurrentSelection() { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + if (viewPane == null) { + return null; + } + TreePath path = viewPane.getSelectedPath(); + if (path == null) { + return null; + } + path = path.getParentPath(); + if (path == null) { + return null; + } + DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); + Object userObject = node.getUserObject(); + if (userObject instanceof ProjectViewNode) { + ProjectViewNode descriptor = (ProjectViewNode)userObject; + Object element = descriptor.getValue(); + if (element instanceof PsiElement) { + PsiElement psiElement = (PsiElement)element; + if (!psiElement.isValid()) return null; + return psiElement; + } + else { + return null; + } + } + return null; + } + + + private class PaneOptionAction extends ToggleAction implements DumbAware { + private final Map myOptionsMap; + private final boolean myOptionDefaultValue; + + PaneOptionAction(Map optionsMap, + final String text, + final String description, + final Icon icon, + boolean optionDefaultValue) { + super(text, description, icon); + myOptionsMap = optionsMap; + myOptionDefaultValue = optionDefaultValue; + } + + public boolean isSelected(AnActionEvent event) { + return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue); + } + + public void setSelected(AnActionEvent event, boolean flag) { + setPaneOption(myOptionsMap, flag, myCurrentViewId, true); + } + } + + public void changeView() { + final List views = new ArrayList(myId2Pane.values()); + views.remove(getCurrentProjectViewPane()); + Collections.sort(views, PANE_WEIGHT_COMPARATOR); + + final JList list = new JBList(ArrayUtil.toObjectArray(views)); + list.setCellRenderer(new DefaultListCellRenderer() { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + AbstractProjectViewPane pane = (AbstractProjectViewPane)value; + setText(pane.getTitle()); + return this; + } + }); + + if (!views.isEmpty()) { + list.setSelectedValue(views.get(0), true); + } + Runnable runnable = new Runnable() { + public void run() { + if (list.getSelectedIndex() < 0) return; + AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue(); + changeView(pane.getId()); + } + }; + + new PopupChooserBuilder(list). + setTitle(IdeBundle.message("title.popup.views")). + setItemChoosenCallback(runnable). + createPopup().showInCenterOf(getComponent()); + } + + public void changeView(@NotNull String viewId) { + changeView(viewId, null); + } + + public void changeView(@NotNull String viewId, @Nullable String subId) { + AbstractProjectViewPane pane = getProjectViewPaneById(viewId); + LOG.assertTrue(pane != null, "Project view pane not found: " + viewId + "; subId:" + subId); + if (!viewId.equals(getCurrentViewId()) + || subId != null && !subId.equals(pane.getSubId()) || + // element not in model anymore + ((DefaultComboBoxModel)myCombo.getModel()).getIndexOf(Pair.create(viewId, pane.getSubId())) == -1) { + myCombo.setSelectedItem(Pair.create(viewId, subId)); + viewSelectionChanged(); + } + } + + private final class MyDeletePSIElementProvider implements DeleteProvider { + public boolean canDeleteElement(DataContext dataContext) { + final PsiElement[] elements = getElementsToDelete(); + return DeleteHandler.shouldEnableDeleteAction(elements); + } + + public void deleteElement(DataContext dataContext) { + List allElements = Arrays.asList(getElementsToDelete()); + List validElements = new ArrayList(); + for (PsiElement psiElement : allElements) { + if (psiElement != null && psiElement.isValid()) validElements.add(psiElement); + } + final PsiElement[] elements = validElements.toArray(new PsiElement[validElements.size()]); + + LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting")); + try { + DeleteHandler.deletePsiElement(elements, myProject); + } + finally { + a.finish(); + } + } + + private PsiElement[] getElementsToDelete() { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + PsiElement[] elements = viewPane.getSelectedPSIElements(); + for (int idx = 0; idx < elements.length; idx++) { + final PsiElement element = elements[idx]; + if (element instanceof PsiDirectory) { + PsiDirectory directory = (PsiDirectory)element; + final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject); + if (isHideEmptyMiddlePackages(viewPane.getId()) && directory.getChildren().length == 0 && !directoryHelper.skipDirectory(directory)) { + while (true) { + PsiDirectory parent = directory.getParentDirectory(); + if (parent == null) break; + if (directoryHelper.skipDirectory(parent) || PsiDirectoryFactory.getInstance(myProject).getQualifiedName(parent, false).length() == 0) break; + PsiElement[] children = parent.getChildren(); + if (children.length == 0 || children.length == 1 && children[0] == directory) { + directory = parent; + } + else { + break; + } + } + elements[idx] = directory; + } + final VirtualFile virtualFile = directory.getVirtualFile(); + final String path = virtualFile.getPath(); + if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root + final VirtualFile vFile = + LocalFileSystem.getInstance().findFileByPath(path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length())); + if (vFile != null) { + final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile); + if (psiFile != null) { + elements[idx] = psiFile; + } + } + } + } + } + return elements; + } + + } + + private final class MyPanel extends JPanel implements DataProvider { + MyPanel() { + super(new BorderLayout()); + } + + private Object getSelectedNodeElement() { + final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); + if (currentProjectViewPane == null) { // can happen if not initialized yet + return null; + } + DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode(); + if (node == null) { + return null; + } + Object userObject = node.getUserObject(); + if (userObject instanceof AbstractTreeNode) { + return ((AbstractTreeNode)userObject).getValue(); + } + if (!(userObject instanceof NodeDescriptor)) { + return null; + } + return ((NodeDescriptor)userObject).getElement(); + } + + public Object getData(String dataId) { + final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); + if (currentProjectViewPane != null) { + final Object paneSpecificData = currentProjectViewPane.getData(dataId); + if (paneSpecificData != null) return paneSpecificData; + } + + if (LangDataKeys.PSI_ELEMENT.is(dataId)) { + if (currentProjectViewPane == null) return null; + final PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements(); + return elements.length == 1 ? elements[0] : null; + } + if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { + if (currentProjectViewPane == null) { + return null; + } + PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements(); + return elements.length == 0 ? null : elements; + } + if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) { + PsiElement[] psiElements = (PsiElement[])getData(LangDataKeys.PSI_ELEMENT_ARRAY.getName()); + if (psiElements == null) return null; + Set files = new LinkedHashSet(); + for (PsiElement element : psiElements) { + if (element instanceof PsiFileSystemItem) { + files.add(((PsiFileSystemItem)element).getVirtualFile()); + } + } + return files.size() > 0 ? VfsUtil.toVirtualFileArray(files) : null; + } + if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) { + return null; + } + if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { + return myCopyPasteDelegator.getCutProvider(); + } + if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { + return myCopyPasteDelegator.getCopyProvider(); + } + if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { + return myCopyPasteDelegator.getPasteProvider(); + } + if (LangDataKeys.IDE_VIEW.is(dataId)) { + return myIdeView; + } + if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { + final Module[] modules = getSelectedModules(); + if (modules != null) { + return myDeleteModuleProvider; + } + final LibraryOrderEntry orderEntry = getSelectedLibrary(); + if (orderEntry != null) { + return new DeleteProvider() { + public void deleteElement(DataContext dataContext) { + detachLibrary(orderEntry, myProject); + } + + public boolean canDeleteElement(DataContext dataContext) { + return true; + } + }; + } + return myDeletePSIElementProvider; + } + if (PlatformDataKeys.HELP_ID.is(dataId)) { + return HelpID.PROJECT_VIEWS; + } + if (ProjectViewImpl.DATA_KEY.is(dataId)) { + return ProjectViewImpl.this; + } + if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) { + Object selected = getSelectedNodeElement(); + return selected instanceof Project ? selected : null; + } + if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { + Object selected = getSelectedNodeElement(); + if (selected instanceof Module) { + return !((Module)selected).isDisposed() ? selected : null; + } + else if (selected instanceof PsiDirectory) { + return moduleByContentRoot(((PsiDirectory)selected).getVirtualFile()); + } + else if (selected instanceof VirtualFile) { + return moduleByContentRoot((VirtualFile)selected); + } + else { + return null; + } + } + + if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) { + return getSelectedModules(); + } + if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) { + final List selectedElements = getSelectedElements(ModuleGroup.class); + return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]); + } + if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) { + final List selectedElements = getSelectedElements(LibraryGroupElement.class); + return selectedElements.isEmpty() ? null : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]); + } + if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) { + final List selectedElements = getSelectedElements(NamedLibraryElement.class); + return selectedElements.isEmpty() ? null : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]); + } + + if (QuickActionProvider.KEY.is(dataId)) { + return ProjectViewImpl.this; + } + + return null; + } + + private LibraryOrderEntry getSelectedLibrary() { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + DefaultMutableTreeNode node = viewPane != null ? viewPane.getSelectedNode() : null; + if (node == null) return null; + DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); + if (parent == null) return null; + Object userObject = parent.getUserObject(); + if (userObject instanceof LibraryGroupNode) { + userObject = node.getUserObject(); + if (userObject instanceof NamedLibraryElementNode) { + NamedLibraryElement element = ((NamedLibraryElementNode)userObject).getValue(); + OrderEntry orderEntry = element.getOrderEntry(); + return orderEntry instanceof LibraryOrderEntry ? (LibraryOrderEntry)orderEntry : null; + } + PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue(); + VirtualFile virtualFile = directory.getVirtualFile(); + Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue(); + + if (module == null) return null; + ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex(); + OrderEntry entry = index.getOrderEntryForFile(virtualFile); + if (entry instanceof LibraryOrderEntry) { + return (LibraryOrderEntry)entry; + } + } + + return null; + } + + private void detachLibrary(final LibraryOrderEntry orderEntry, final Project project) { + final Module module = orderEntry.getOwnerModule(); + String message = IdeBundle.message("detach.library.from.module", orderEntry.getPresentableName(), module.getName()); + String title = IdeBundle.message("detach.library"); + int ret = Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon()); + if (ret != 0) return; + CommandProcessor.getInstance().executeCommand(module.getProject(), new Runnable() { + public void run() { + final Runnable action = new Runnable() { + public void run() { + ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + OrderEntry[] orderEntries = rootManager.getOrderEntries(); + ModifiableRootModel model = rootManager.getModifiableModel(); + OrderEntry[] modifiableEntries = model.getOrderEntries(); + for (int i = 0; i < orderEntries.length; i++) { + OrderEntry entry = orderEntries[i]; + if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == orderEntry.getLibrary()) { + model.removeOrderEntry(modifiableEntries[i]); + } + } + model.commit(); + } + }; + ApplicationManager.getApplication().runWriteAction(action); + } + }, title, null); + } + + @Nullable + private Module[] getSelectedModules() { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + if (viewPane == null) return null; + final Object[] elements = viewPane.getSelectedElements(); + ArrayList result = new ArrayList(); + for (Object element : elements) { + if (element instanceof Module) { + final Module module = (Module)element; + if (!module.isDisposed()) { + result.add(module); + } + } + else if (element instanceof ModuleGroup) { + Collection modules = ((ModuleGroup)element).modulesInGroup(myProject, true); + result.addAll(modules); + } + else if (element instanceof PsiDirectory) { + Module module = moduleByContentRoot(((PsiDirectory)element).getVirtualFile()); + if (module != null) result.add(module); + } + else if (element instanceof VirtualFile) { + Module module = moduleByContentRoot((VirtualFile)element); + if (module != null) result.add(module); + } + } + + if (result.isEmpty()) { + return null; + } + else { + return result.toArray(new Module[result.size()]); + } + } + } + + private Module moduleByContentRoot(VirtualFile file) { + if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) { + Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file); + if (module != null && !module.isDisposed()) { + return module; + } + } + + return null; + } + + private List getSelectedElements(Class klass) { + ArrayList result = new ArrayList(); + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + if (viewPane == null) return result; + final Object[] elements = viewPane.getSelectedElements(); + for (Object element : elements) { + //element still valid + if (element != null && klass.isAssignableFrom(element.getClass())) { + result.add((T)element); + } + } + return result; + } + + private final class MyIdeView implements IdeView { + public void selectElement(PsiElement element) { + selectPsiElement(element, false); + boolean requestFocus = true; + if (element != null) { + final boolean isDirectory = element instanceof PsiDirectory; + if (!isDirectory) { + Editor editor = EditorHelper.openInEditor(element); + if (editor != null) { + ToolWindowManager.getInstance(myProject).activateEditorComponent(); + requestFocus = false; + } + } + } + + if (requestFocus) { + selectPsiElement(element, true); + } + } + + public PsiDirectory[] getDirectories() { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + if (viewPane != null) { + return viewPane.getSelectedDirectories(); + } + + return PsiDirectory.EMPTY_ARRAY; + } + + public PsiDirectory getOrChooseDirectory() { + return DirectoryChooserUtil.getOrChooseDirectory(this); + } + } + + public void selectPsiElement(PsiElement element, boolean requestFocus) { + if (element == null) return; + VirtualFile virtualFile = PsiUtilBase.getVirtualFile(element); + select(element, virtualFile, requestFocus); + } + + + private static void readOption(Element node, Map options) { + if (node == null) return; + List attributes = node.getAttributes(); + for (final Object attribute1 : attributes) { + Attribute attribute = (Attribute)attribute1; + options.put(attribute.getName(), Boolean.TRUE.toString().equals(attribute.getValue()) ? Boolean.TRUE : Boolean.FALSE); + } + } + + private static void writeOption(Element parentNode, Map optionsForPanes, String optionName) { + Element e = new Element(optionName); + for (Map.Entry entry : optionsForPanes.entrySet()) { + final String key = entry.getKey(); + if (key != null) { //SCR48267 + e.setAttribute(key, Boolean.toString(entry.getValue().booleanValue())); + } + } + + parentNode.addContent(e); + } + + public void loadState(Element parentNode) { + Element navigatorElement = parentNode.getChild(ELEMENT_NAVIGATOR); + if (navigatorElement != null) { + mySavedPaneId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_VIEW); + mySavedPaneSubId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_SUBVIEW); + if (mySavedPaneId == null) { + mySavedPaneId = ProjectViewPane.ID; + mySavedPaneSubId = null; + } + readOption(navigatorElement.getChild(ELEMENT_FLATTEN_PACKAGES), myFlattenPackages); + readOption(navigatorElement.getChild(ELEMENT_SHOW_MEMBERS), myShowMembers); + readOption(navigatorElement.getChild(ELEMENT_SHOW_MODULES), myShowModules); + readOption(navigatorElement.getChild(ELEMENT_SHOW_LIBRARY_CONTENTS), myShowLibraryContents); + readOption(navigatorElement.getChild(ELEMENT_HIDE_EMPTY_PACKAGES), myHideEmptyPackages); + readOption(navigatorElement.getChild(ELEMENT_ABBREVIATE_PACKAGE_NAMES), myAbbreviatePackageNames); + readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_TO_SOURCE), myAutoscrollToSource); + readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_FROM_SOURCE), myAutoscrollFromSource); + readOption(navigatorElement.getChild(ELEMENT_SORT_BY_TYPE), mySortByType); + + try { + splitterProportions.readExternal(navigatorElement); + } + catch (InvalidDataException e) { + // ignore + } + } + Element panesElement = parentNode.getChild(ELEMENT_PANES); + if (panesElement != null) { + readPaneState(panesElement); + } + } + + private void readPaneState(Element panesElement) { + @SuppressWarnings({"unchecked"}) + final List paneElements = panesElement.getChildren(ELEMENT_PANE); + + for (Element paneElement : paneElements) { + String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID); + final AbstractProjectViewPane pane = myId2Pane.get(paneId); + if (pane != null) { + try { + pane.readExternal(paneElement); + } + catch (InvalidDataException e) { + // ignore + } + } + else { + myUninitializedPaneState.put(paneId, paneElement); + } + } + } + + public Element getState() { + Element parentNode = new Element("projectView"); + Element navigatorElement = new Element(ELEMENT_NAVIGATOR); + AbstractProjectViewPane currentPane = getCurrentProjectViewPane(); + if (currentPane != null) { + navigatorElement.setAttribute(ATTRIBUTE_CURRENT_VIEW, currentPane.getId()); + String subId = currentPane.getSubId(); + if (subId != null) { + navigatorElement.setAttribute(ATTRIBUTE_CURRENT_SUBVIEW, subId); + } + } + writeOption(navigatorElement, myFlattenPackages, ELEMENT_FLATTEN_PACKAGES); + writeOption(navigatorElement, myShowMembers, ELEMENT_SHOW_MEMBERS); + writeOption(navigatorElement, myShowModules, ELEMENT_SHOW_MODULES); + writeOption(navigatorElement, myShowLibraryContents, ELEMENT_SHOW_LIBRARY_CONTENTS); + writeOption(navigatorElement, myHideEmptyPackages, ELEMENT_HIDE_EMPTY_PACKAGES); + writeOption(navigatorElement, myAbbreviatePackageNames, ELEMENT_ABBREVIATE_PACKAGE_NAMES); + writeOption(navigatorElement, myAutoscrollToSource, ELEMENT_AUTOSCROLL_TO_SOURCE); + writeOption(navigatorElement, myAutoscrollFromSource, ELEMENT_AUTOSCROLL_FROM_SOURCE); + writeOption(navigatorElement, mySortByType, ELEMENT_SORT_BY_TYPE); + + splitterProportions.saveSplitterProportions(myPanel); + try { + splitterProportions.writeExternal(navigatorElement); + } + catch (WriteExternalException e) { + // ignore + } + parentNode.addContent(navigatorElement); + + // for compatibility with idea 5.1 + @Deprecated @NonNls final String ATTRIBUTE_SPLITTER_PROPORTION = "splitterProportion"; + navigatorElement.setAttribute(ATTRIBUTE_SPLITTER_PROPORTION, "0.5"); + + Element panesElement = new Element(ELEMENT_PANES); + writePaneState(panesElement); + parentNode.addContent(panesElement); + return parentNode; + } + + private void writePaneState(Element panesElement) { + for (AbstractProjectViewPane pane : myId2Pane.values()) { + Element paneElement = new Element(ELEMENT_PANE); + paneElement.setAttribute(ATTRIBUTE_ID, pane.getId()); + try { + pane.writeExternal(paneElement); + } + catch (WriteExternalException e) { + continue; + } + panesElement.addContent(paneElement); + } + for (Element element : myUninitializedPaneState.values()) { + panesElement.addContent((Element) element.clone()); + } + } + + public boolean isAutoscrollToSource(String paneId) { + return getPaneOptionValue(myAutoscrollToSource, paneId, ourAutoscrollToSourceDefaults); + } + + private void setAutoscrollToSource(boolean autoscrollMode, String paneId) { + myAutoscrollToSource.put(paneId, autoscrollMode ? Boolean.TRUE : Boolean.FALSE); + } + + public boolean isAutoscrollFromSource(String paneId) { + return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults); + } + + private void setAutoscrollFromSource(boolean autoscrollMode, String paneId) { + setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false); + } + + public boolean isFlattenPackages(String paneId) { + return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults); + } + + public void setFlattenPackages(boolean flattenPackages, String paneId) { + setPaneOption(myFlattenPackages, flattenPackages, paneId, true); + } + + public boolean isShowMembers(String paneId) { + return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults); + } + + public void setShowMembers(boolean showMembers, String paneId) { + setPaneOption(myShowMembers, showMembers, paneId, true); + } + + public boolean isHideEmptyMiddlePackages(String paneId) { + return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults); + } + + public boolean isAbbreviatePackageNames(String paneId) { + return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults); + } + + public boolean isShowLibraryContents(String paneId) { + return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults); + } + + public void setShowLibraryContents(boolean showLibraryContents, String paneId) { + setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true); + } + + public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) { + return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true); + } + + public boolean isShowModules(String paneId) { + return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults); + } + + public void setShowModules(boolean showModules, String paneId) { + setPaneOption(myShowModules, showModules, paneId, true); + } + + public void setHideEmptyPackages(boolean hideEmptyPackages, String paneId) { + setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true); + } + + public void setAbbreviatePackageNames(boolean abbreviatePackageNames, String paneId) { + setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true); + } + + private ActionCallback setPaneOption(Map optionsMap, boolean value, String paneId, final boolean updatePane) { + optionsMap.put(paneId, value ? Boolean.TRUE : Boolean.FALSE); + if (updatePane) { + final AbstractProjectViewPane pane = getProjectViewPaneById(paneId); + if (pane != null) { + return pane.updateFromRoot(false); + } + } + return new ActionCallback.Done(); + } + + private static boolean getPaneOptionValue(Map optionsMap, String paneId, boolean defaultValue) { + final Boolean value = optionsMap.get(paneId); + return value == null ? defaultValue : value.booleanValue(); + } + + private class HideEmptyMiddlePackagesAction extends PaneOptionAction { + private HideEmptyMiddlePackagesAction() { + super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults); + } + + public void setSelected(AnActionEvent event, boolean flag) { + final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); + final SelectionInfo selectionInfo = SelectionInfo.create(viewPane); + + super.setSelected(event, flag); + + selectionInfo.apply(viewPane); + } + + public void update(AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + if (isFlattenPackages(myCurrentViewId)) { + presentation.setText(IdeBundle.message("action.hide.empty.middle.packages")); + presentation.setDescription(IdeBundle.message("action.show.hide.empty.middle.packages")); + } + else { + presentation.setText(IdeBundle.message("action.compact.empty.middle.packages")); + presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages")); + } + } + } + + private static class SelectionInfo { + private final Object[] myElements; + + private SelectionInfo(Object[] elements) { + myElements = elements; + } + + public void apply(final AbstractProjectViewPane viewPane) { + if (viewPane == null) { + return; + } + AbstractTreeBuilder treeBuilder = viewPane.getTreeBuilder(); + JTree tree = viewPane.myTree; + DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); + List paths = new ArrayList(myElements.length); + for (final Object element : myElements) { + DefaultMutableTreeNode node = treeBuilder.getNodeForElement(element); + if (node == null) { + treeBuilder.buildNodeForElement(element); + node = treeBuilder.getNodeForElement(element); + } + if (node != null) { + paths.add(new TreePath(treeModel.getPathToRoot(node))); + } + } + if (!paths.isEmpty()) { + tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()])); + } + } + + public static SelectionInfo create(final AbstractProjectViewPane viewPane) { + List selectedElements = Collections.emptyList(); + if (viewPane != null) { + final TreePath[] selectionPaths = viewPane.getSelectionPaths(); + if (selectionPaths != null) { + selectedElements = new ArrayList(); + for (TreePath path : selectionPaths) { + final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); + final Object userObject = node.getUserObject(); + if (userObject instanceof NodeDescriptor) { + selectedElements.add(((NodeDescriptor)userObject).getElement()); + } + } + } + } + return new SelectionInfo(selectedElements.toArray()); + } + } + + private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler { + private final Alarm myAlarm = new Alarm(myProject); + + private MyAutoScrollFromSourceHandler() { + super(ProjectViewImpl.this.myProject, ProjectViewImpl.this); + } + + public void install() { + FileEditorManagerAdapter myEditorManagerListener = new FileEditorManagerAdapter() { + public void selectionChanged(final FileEditorManagerEvent event) { + final FileEditor newEditor = event.getNewEditor(); + myAlarm.cancelAllRequests(); + myAlarm.addRequest(new Runnable() { + public void run() { + if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return; + if (isAutoscrollFromSource(getCurrentViewId())) { + if (newEditor instanceof TextEditor) { + Editor editor = ((TextEditor)newEditor).getEditor(); + selectElementAtCaretNotLosingFocus(editor); + } else if (newEditor != null) { + final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(newEditor); + if (file != null) { + final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); + if (psiFile != null) { + final SelectInTarget target = mySelectInTargets.get(getCurrentViewId()); + if (target != null) { + final MySelectInContext selectInContext = new MySelectInContext(psiFile, null) { + @Override + public Object getSelectorInFile() { + return psiFile; + } + }; + + if (target.canSelect(selectInContext)) { + target.selectIn(selectInContext, false); + } + } + } + } + } + } + } + }, 300, ModalityState.NON_MODAL); + } + }; + myFileEditorManager.addFileEditorManagerListener(myEditorManagerListener, this); + } + + public void scrollFromSource() { + final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); + final FileEditor[] editors = fileEditorManager.getSelectedEditors(); + for (FileEditor fileEditor : editors) { + if (fileEditor instanceof TextEditor) { + Editor editor = ((TextEditor)fileEditor).getEditor(); + selectElementAtCaret(editor); + return; + } + } + final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles(); + if (selectedFiles.length > 0) { + final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]); + if (file != null) { + scrollFromFile(file, null); + } + } + } + + private void selectElementAtCaretNotLosingFocus(final Editor editor) { + if (IJSwingUtilities.hasFocus(getCurrentProjectViewPane().getComponentToFocus())) return; + selectElementAtCaret(editor); + } + + private void selectElementAtCaret(Editor editor) { + final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument()); + if (file == null) return; + + scrollFromFile(file, editor); + } + + private void scrollFromFile(PsiFile file, @Nullable Editor editor) { + final MySelectInContext selectInContext = new MySelectInContext(file, editor); + + final SelectInTarget target = mySelectInTargets.get(getCurrentViewId()); + if (target != null && target.canSelect(selectInContext)) { + target.selectIn(selectInContext, false); + } + } + + public void dispose() { + } + + protected boolean isAutoScrollMode() { + return isAutoscrollFromSource(myCurrentViewId); + } + + protected void setAutoScrollMode(boolean state) { + setAutoscrollFromSource(state, myCurrentViewId); + if (state) { + final Editor editor = myFileEditorManager.getSelectedTextEditor(); + if (editor != null) { + selectElementAtCaretNotLosingFocus(editor); + } + } + } + + private class MySelectInContext implements SelectInContext { + private final PsiFile myPsiFile; + @Nullable private final Editor myEditor; + + private MySelectInContext(final PsiFile psiFile, @Nullable Editor editor) { + myPsiFile = psiFile; + myEditor = editor; + } + + @NotNull + public Project getProject() { + return myProject; + } + + private PsiFile getPsiFile() { + return myPsiFile; + } + + public FileEditorProvider getFileEditorProvider() { + if (myPsiFile == null) return null; + return new FileEditorProvider() { + public FileEditor openFileEditor() { + return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0]; + } + }; + } + + private PsiElement getPsiElement() { + PsiElement e = null; + if (myEditor != null) { + final int offset = myEditor.getCaretModel().getOffset(); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + e = getPsiFile().findElementAt(offset); + } + if (e == null) { + e = getPsiFile(); + } + return e; + } + + @NotNull + public VirtualFile getVirtualFile() { + return getPsiFile().getVirtualFile(); + } + + public Object getSelectorInFile() { + return getPsiElement(); + } + } + } + + public boolean isSortByType(String paneId) { + return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults); + } + + public void setSortByType(String paneId, final boolean sortByType) { + setPaneOption(mySortByType, sortByType, paneId, false); + final AbstractProjectViewPane pane = getProjectViewPaneById(paneId); + pane.installComparator(); + } + + private class SortByTypeAction extends ToggleAction { + private SortByTypeAction() { + super(IdeBundle.message("action.sort.by.type"), IdeBundle.message("action.sort.by.type"), + IconLoader.getIcon("/objectBrowser/sortByType.png")); + } + + public boolean isSelected(AnActionEvent event) { + return isSortByType(getCurrentViewId()); + } + + public void setSelected(AnActionEvent event, boolean flag) { + setSortByType(getCurrentViewId(), flag); + } + + public void update(final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setVisible(getCurrentProjectViewPane() != null); + } + } + + private class ScrollFromSourceAction extends AnAction { + private ScrollFromSourceAction() { + super("Scroll from Source", "Select the file open in the active editor", IconLoader.getIcon("/general/autoscrollFromSource.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + myAutoScrollFromSourceHandler.scrollFromSource(); + } + } + + public Collection getPaneIds() { + return myId2Pane.keySet(); + } + + @Override + public Collection getSelectInTargets() { + ensurePanesLoaded(); + return mySelectInTargets.values(); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractModuleNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractModuleNode.java index 0227050e4d51..2009d454eb16 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractModuleNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractModuleNode.java @@ -1,100 +1,100 @@ -/* - * 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.ide.projectView.impl.nodes; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ProjectViewNode; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.NavigatableWithText; -import com.intellij.ui.SimpleTextAttributes; -import org.jetbrains.annotations.NotNull; - -public abstract class AbstractModuleNode extends ProjectViewNode implements NavigatableWithText { - protected AbstractModuleNode(Project project, Module module, ViewSettings viewSettings) { - super(project, module, viewSettings); - } - - public void update(PresentationData presentation) { - if (getValue().isDisposed()) { - setValue(null); - return; - } - presentation.setPresentableText(getValue().getName()); - if (showModuleNameInBold()) { - presentation.addText(getValue().getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); - } - - presentation.setOpenIcon(getValue().getModuleType().getNodeIcon(true)); - presentation.setClosedIcon(getValue().getModuleType().getNodeIcon(false)); - } - - protected boolean showModuleNameInBold() { - return true; - } - - - public String getTestPresentation() { - return "Module"; - } - - @Override - public boolean contains(@NotNull VirtualFile file) { - Module module = getValue(); - if (module == null) return false; - - final VirtualFile testee; - if (file.getFileSystem() instanceof JarFileSystem) { - testee = JarFileSystem.getInstance().getVirtualFileForJar(file); - if (testee == null) return false; - } - else { - testee = file; - } - for (VirtualFile root : ModuleRootManager.getInstance(module).getContentRoots()) { - if (VfsUtil.isAncestor(root, testee, false)) return true; - } - return false; - } - - public String getToolTip() { - final Module module = getValue(); - return module.getModuleType().getName(); - } - - public void navigate(final boolean requestFocus) { - ProjectSettingsService.getInstance(myProject).openModuleSettings(getValue()); - } - - @Override - public String getNavigateActionText(boolean focusEditor) { - return "Open Module Settings"; - } - - public boolean canNavigate() { - return true; - } - - public boolean canNavigateToSource() { - return false; - } -} +/* + * 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.ide.projectView.impl.nodes; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ProjectViewNode; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.NavigatableWithText; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; + +public abstract class AbstractModuleNode extends ProjectViewNode implements NavigatableWithText { + protected AbstractModuleNode(Project project, Module module, ViewSettings viewSettings) { + super(project, module, viewSettings); + } + + public void update(PresentationData presentation) { + if (getValue().isDisposed()) { + setValue(null); + return; + } + presentation.setPresentableText(getValue().getName()); + if (showModuleNameInBold()) { + presentation.addText(getValue().getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + } + + presentation.setOpenIcon(getValue().getModuleType().getNodeIcon(true)); + presentation.setClosedIcon(getValue().getModuleType().getNodeIcon(false)); + } + + protected boolean showModuleNameInBold() { + return true; + } + + + public String getTestPresentation() { + return "Module"; + } + + @Override + public boolean contains(@NotNull VirtualFile file) { + Module module = getValue(); + if (module == null) return false; + + final VirtualFile testee; + if (file.getFileSystem() instanceof JarFileSystem) { + testee = JarFileSystem.getInstance().getVirtualFileForJar(file); + if (testee == null) return false; + } + else { + testee = file; + } + for (VirtualFile root : ModuleRootManager.getInstance(module).getContentRoots()) { + if (VfsUtil.isAncestor(root, testee, false)) return true; + } + return false; + } + + public String getToolTip() { + final Module module = getValue(); + return module.getModuleType().getName(); + } + + public void navigate(final boolean requestFocus) { + ProjectSettingsService.getInstance(myProject).openModuleSettings(getValue()); + } + + @Override + public String getNavigateActionText(boolean focusEditor) { + return "Open Module Settings"; + } + + public boolean canNavigate() { + return true; + } + + public boolean canNavigateToSource() { + return false; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/NamedLibraryElementNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/NamedLibraryElementNode.java index 57c444bdf60c..0014de0dd56c 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/NamedLibraryElementNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/NamedLibraryElementNode.java @@ -1,132 +1,132 @@ -/* - * 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.ide.projectView.impl.nodes; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ProjectViewNode; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.JdkOrderEntry; -import com.intellij.openapi.roots.LibraryOrderEntry; -import com.intellij.openapi.roots.OrderEntry; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.NavigatableWithText; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class NamedLibraryElementNode extends ProjectViewNode implements NavigatableWithText { - private static final Icon GENERIC_JDK_ICON = IconLoader.getIcon("/general/jdk.png"); - private static final Icon LIB_ICON_OPEN = IconLoader.getIcon("/nodes/ppLibOpen.png"); - private static final Icon LIB_ICON_CLOSED = IconLoader.getIcon("/nodes/ppLibClosed.png"); - - public NamedLibraryElementNode(Project project, NamedLibraryElement value, ViewSettings viewSettings) { - super(project, value, viewSettings); - } - - public NamedLibraryElementNode(final Project project, final Object value, final ViewSettings viewSettings) { - this(project, (NamedLibraryElement)value, viewSettings); - } - - @NotNull - public Collection getChildren() { - final List children = new ArrayList(); - LibraryGroupNode.addLibraryChildren(getValue().getOrderEntry(), children, getProject(), this); - return children; - } - - public String getTestPresentation() { - return "Library: " + getValue().getName(); - } - - private static Icon getJdkIcon(JdkOrderEntry entry, boolean isExpanded) { - final Sdk jdk = entry.getJdk(); - if (jdk == null) { - return GENERIC_JDK_ICON; - } - return isExpanded? jdk.getSdkType().getIconForExpandedTreeNode() : jdk.getSdkType().getIcon(); - } - - public String getName() { - return getValue().getName(); - } - - public boolean contains(@NotNull VirtualFile file) { - return orderEntryContainsFile(getValue().getOrderEntry(), file); - } - - private static boolean orderEntryContainsFile(OrderEntry orderEntry, VirtualFile file) { - for(OrderRootType rootType: OrderRootType.getAllTypes()) { - if (containsFileInOrderType(orderEntry, rootType, file)) return true; - } - return false; - } - - private static boolean containsFileInOrderType(final OrderEntry orderEntry, final OrderRootType orderType, final VirtualFile file) { - if (!orderEntry.isValid()) return false; - VirtualFile[] files = orderEntry.getFiles(orderType); - for (VirtualFile virtualFile : files) { - boolean ancestor = VfsUtil.isAncestor(virtualFile, file, false); - if (ancestor) return true; - } - return false; - } - - public void update(PresentationData presentation) { - presentation.setPresentableText(getValue().getName()); - final OrderEntry orderEntry = getValue().getOrderEntry(); - presentation.setOpenIcon(orderEntry instanceof JdkOrderEntry ? getJdkIcon((JdkOrderEntry)orderEntry, true) : LIB_ICON_OPEN); - presentation.setClosedIcon(orderEntry instanceof JdkOrderEntry ? getJdkIcon((JdkOrderEntry)orderEntry, false) : LIB_ICON_CLOSED); - if (orderEntry instanceof JdkOrderEntry) { - final JdkOrderEntry jdkOrderEntry = (JdkOrderEntry)orderEntry; - final Sdk projectJdk = jdkOrderEntry.getJdk(); - if (projectJdk != null) { //jdk not specified - presentation.setLocationString(FileUtil.toSystemDependentName(projectJdk.getHomePath())); - } - } - } - - protected String getToolTip() { - OrderEntry orderEntry = getValue().getOrderEntry(); - return orderEntry instanceof JdkOrderEntry ? IdeBundle.message("node.projectview.jdk") : StringUtil.capitalize(IdeBundle.message("node.projectview.library", ((LibraryOrderEntry)orderEntry).getLibraryLevel())); - } - - public void navigate(final boolean requestFocus) { - ProjectSettingsService.getInstance(myProject).openProjectLibrarySettings(getValue()); - } - - public boolean canNavigate() { - return true; - } - - @Override - public String getNavigateActionText(boolean focusEditor) { - return "Open Library Settings"; - } -} +/* + * 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.ide.projectView.impl.nodes; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ProjectViewNode; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.JdkOrderEntry; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.NavigatableWithText; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class NamedLibraryElementNode extends ProjectViewNode implements NavigatableWithText { + private static final Icon GENERIC_JDK_ICON = IconLoader.getIcon("/general/jdk.png"); + private static final Icon LIB_ICON_OPEN = IconLoader.getIcon("/nodes/ppLibOpen.png"); + private static final Icon LIB_ICON_CLOSED = IconLoader.getIcon("/nodes/ppLibClosed.png"); + + public NamedLibraryElementNode(Project project, NamedLibraryElement value, ViewSettings viewSettings) { + super(project, value, viewSettings); + } + + public NamedLibraryElementNode(final Project project, final Object value, final ViewSettings viewSettings) { + this(project, (NamedLibraryElement)value, viewSettings); + } + + @NotNull + public Collection getChildren() { + final List children = new ArrayList(); + LibraryGroupNode.addLibraryChildren(getValue().getOrderEntry(), children, getProject(), this); + return children; + } + + public String getTestPresentation() { + return "Library: " + getValue().getName(); + } + + private static Icon getJdkIcon(JdkOrderEntry entry, boolean isExpanded) { + final Sdk jdk = entry.getJdk(); + if (jdk == null) { + return GENERIC_JDK_ICON; + } + return isExpanded? jdk.getSdkType().getIconForExpandedTreeNode() : jdk.getSdkType().getIcon(); + } + + public String getName() { + return getValue().getName(); + } + + public boolean contains(@NotNull VirtualFile file) { + return orderEntryContainsFile(getValue().getOrderEntry(), file); + } + + private static boolean orderEntryContainsFile(OrderEntry orderEntry, VirtualFile file) { + for(OrderRootType rootType: OrderRootType.getAllTypes()) { + if (containsFileInOrderType(orderEntry, rootType, file)) return true; + } + return false; + } + + private static boolean containsFileInOrderType(final OrderEntry orderEntry, final OrderRootType orderType, final VirtualFile file) { + if (!orderEntry.isValid()) return false; + VirtualFile[] files = orderEntry.getFiles(orderType); + for (VirtualFile virtualFile : files) { + boolean ancestor = VfsUtil.isAncestor(virtualFile, file, false); + if (ancestor) return true; + } + return false; + } + + public void update(PresentationData presentation) { + presentation.setPresentableText(getValue().getName()); + final OrderEntry orderEntry = getValue().getOrderEntry(); + presentation.setOpenIcon(orderEntry instanceof JdkOrderEntry ? getJdkIcon((JdkOrderEntry)orderEntry, true) : LIB_ICON_OPEN); + presentation.setClosedIcon(orderEntry instanceof JdkOrderEntry ? getJdkIcon((JdkOrderEntry)orderEntry, false) : LIB_ICON_CLOSED); + if (orderEntry instanceof JdkOrderEntry) { + final JdkOrderEntry jdkOrderEntry = (JdkOrderEntry)orderEntry; + final Sdk projectJdk = jdkOrderEntry.getJdk(); + if (projectJdk != null) { //jdk not specified + presentation.setLocationString(FileUtil.toSystemDependentName(projectJdk.getHomePath())); + } + } + } + + protected String getToolTip() { + OrderEntry orderEntry = getValue().getOrderEntry(); + return orderEntry instanceof JdkOrderEntry ? IdeBundle.message("node.projectview.jdk") : StringUtil.capitalize(IdeBundle.message("node.projectview.library", ((LibraryOrderEntry)orderEntry).getLibraryLevel())); + } + + public void navigate(final boolean requestFocus) { + ProjectSettingsService.getInstance(myProject).openProjectLibrarySettings(getValue()); + } + + public boolean canNavigate() { + return true; + } + + @Override + public String getNavigateActionText(boolean focusEditor) { + return "Open Library Settings"; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java index 07e93c38d0c9..df2e3249a93e 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java @@ -1,277 +1,277 @@ -/* - * 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.ide.projectView.impl.nodes; - -import com.intellij.ide.IconProvider; -import com.intellij.ide.bookmarks.Bookmark; -import com.intellij.ide.bookmarks.BookmarkManager; -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.ProjectRootsUtil; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.NavigatableWithText; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.impl.file.PsiDirectoryFactory; -import com.intellij.ui.LayeredIcon; -import com.intellij.ui.RowIcon; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.Icons; -import com.intellij.util.PathUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.util.Collection; - -public class PsiDirectoryNode extends BasePsiNode implements NavigatableWithText { - public PsiDirectoryNode(Project project, PsiDirectory value, ViewSettings viewSettings) { - super(project, value, viewSettings); - } - - protected void updateImpl(PresentationData data) { - final Project project = getProject(); - final PsiDirectory psiDirectory = getValue(); - final VirtualFile directoryFile = psiDirectory.getVirtualFile(); - - final Object parentValue = getParentValue(); - if (ProjectRootsUtil.isModuleContentRoot(directoryFile, project)) { - ProjectFileIndex fi = ProjectRootManager.getInstance(project).getFileIndex(); - Module module = fi.getModuleForFile(directoryFile); - - data.setPresentableText(directoryFile.getName()); - if (module != null) { - if (!(parentValue instanceof Module )) { - if (Comparing.equal(module.getName(), directoryFile.getName())) { - data.addText(directoryFile.getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); - } - else { - data.addText(directoryFile.getName() + " ", SimpleTextAttributes.REGULAR_ATTRIBUTES); - data.addText("[" + module.getName() + "]", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); - } - } - else { - data.addText(directoryFile.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); - } - - if (parentValue instanceof Project || parentValue instanceof Module) { - data.addText(" (" + directoryFile.getPresentableUrl() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } - else if (ProjectRootsUtil.isSourceOrTestRoot(directoryFile, project)) { - if (ProjectRootsUtil.isInTestSource(directoryFile, project)) { - data.addText(" (test source root)", SimpleTextAttributes.GRAY_ATTRIBUTES); - } - else { - data.addText(" (source root)", SimpleTextAttributes.GRAY_ATTRIBUTES); - } - } - - setupIcon(data, psiDirectory); - - return; - } - } - - final String name = parentValue instanceof Project - ? psiDirectory.getVirtualFile().getPresentableUrl() - : ProjectViewDirectoryHelper.getInstance(psiDirectory.getProject()).getNodeName(getSettings(), parentValue, psiDirectory); - if (name == null) { - setValue(null); - return; - } - - data.setPresentableText(name); - if (ProjectRootsUtil.isLibraryRoot(directoryFile, project)) { - data.setLocationString("library home"); - } - else { - data.setLocationString(ProjectViewDirectoryHelper.getInstance(project).getLocationString(psiDirectory)); - } - - setupIcon(data, psiDirectory); - } - - private void setupIcon(PresentationData data, PsiDirectory psiDirectory) { - final VirtualFile virtualFile = psiDirectory.getVirtualFile(); - for (final IconProvider provider : Extensions.getExtensions(IconProvider.EXTENSION_POINT_NAME)) { - final Icon openIcon = provider.getIcon(psiDirectory, Iconable.ICON_FLAG_OPEN); - if (openIcon != null) { - final Icon closedIcon = provider.getIcon(psiDirectory, Iconable.ICON_FLAG_CLOSED); - if (closedIcon != null) { - data.setOpenIcon(patchIcon(openIcon, virtualFile)); - data.setClosedIcon(patchIcon(closedIcon, virtualFile)); - return; - } - } - } - } - - public Collection getChildrenImpl() { - return ProjectViewDirectoryHelper.getInstance(myProject).getDirectoryChildren(getValue(), getSettings(), true); - } - - public String getTestPresentation() { - return "PsiDirectory: " + getValue().getName(); - } - - public boolean isFQNameShown() { - return ProjectViewDirectoryHelper.getInstance(getProject()).isShowFQName(getSettings(), getParentValue(), getValue()); - } - - public boolean contains(@NotNull VirtualFile file) { - final PsiDirectory value = getValue(); - if (value == null) { - return false; - } - - VirtualFile directory = value.getVirtualFile(); - if (directory.getFileSystem() instanceof LocalFileSystem) { - file = PathUtil.getLocalFile(file); - } - - if (!VfsUtil.isAncestor(directory, file, false)) { - return false; - } - - final Project project = value.getProject(); - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - return !fileIndex.isIgnored(file); - } - - public VirtualFile getVirtualFile() { - PsiDirectory directory = getValue(); - if (directory == null) return null; - return directory.getVirtualFile(); - } - - public boolean canRepresent(final Object element) { - if (super.canRepresent(element)) return true; - PsiDirectory directory = getValue(); - if (directory == null) return false; - return ProjectViewDirectoryHelper.getInstance(getProject()).canRepresent(element, directory); - } - - public boolean canNavigate() { - VirtualFile file = getVirtualFile(); - Project project = getProject(); - - return file != null && (ProjectRootsUtil.isModuleContentRoot(file, project) || - ProjectRootsUtil.isSourceOrTestRoot(file, project) || - ProjectRootsUtil.isLibraryRoot(file, project)); - } - - public boolean canNavigateToSource() { - return false; - } - - public void navigate(final boolean requestFocus) { - Module module = ModuleUtil.findModuleForPsiElement(getValue()); - if (module != null) { - final VirtualFile file = getVirtualFile(); - final Project project = getProject(); - if (ProjectRootsUtil.isModuleContentRoot(file, project)) { - ProjectSettingsService.getInstance(myProject).openModuleSettings(module); - } - else if (ProjectRootsUtil.isLibraryRoot(file, project)) { - ProjectSettingsService.getInstance(myProject).openModuleLibrarySettings(module); - } - else { - ProjectSettingsService.getInstance(myProject).openContentEntriesSettings(module); - } - } - } - - @Override - public String getNavigateActionText(boolean focusEditor) { - VirtualFile file = getVirtualFile(); - Project project = getProject(); - - if (file != null) { - if (ProjectRootsUtil.isModuleContentRoot(file, project) || - ProjectRootsUtil.isSourceOrTestRoot(file, project)) { - return "Open Module Settings"; - } - if (ProjectRootsUtil.isLibraryRoot(file, project)) { - return "Open Library Settings"; - } - } - - return null; - } - - public int getWeight() { - return isFQNameShown() ? 70 : 0; - } - - public String getTitle() { - final PsiDirectory directory = getValue(); - if (directory != null) { - return PsiDirectoryFactory.getInstance(getProject()).getQualifiedName(directory, true); - } - return super.getTitle(); - } - - private Icon patchIcon(Icon original, VirtualFile file) { - Bookmark bookmarkAtFile = BookmarkManager.getInstance(myProject).findFileBookmark(file); - if (bookmarkAtFile != null) { - RowIcon composite = new RowIcon(2); - composite.setIcon(original, 0); - composite.setIcon(bookmarkAtFile.getIcon(), 1); - return addReadMark(composite, file.isWritable()); - } - - return addReadMark(original, file.isWritable()); - } - - private static Icon addReadMark(Icon originalIcon, boolean isWritable) { - if (isWritable) { - return originalIcon; - } - else { - return LayeredIcon.create(originalIcon, Icons.LOCKED_ICON); - } - } - - public String getQualifiedNameSortKey() { - final PsiDirectoryFactory factory = PsiDirectoryFactory.getInstance(getProject()); - return factory.getQualifiedName(getValue(), true); - } - - public int getTypeSortWeight(final boolean sortByType) { - return 3; - } - - @Override - public boolean shouldDrillDownOnEmptyElement() { - return true; - } - - @Override - public boolean isAlwaysShowPlus() { - return true; - } -} +/* + * 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.ide.projectView.impl.nodes; + +import com.intellij.ide.IconProvider; +import com.intellij.ide.bookmarks.Bookmark; +import com.intellij.ide.bookmarks.BookmarkManager; +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.projectView.impl.ProjectRootsUtil; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.NavigatableWithText; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.impl.file.PsiDirectoryFactory; +import com.intellij.ui.LayeredIcon; +import com.intellij.ui.RowIcon; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Icons; +import com.intellij.util.PathUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.Collection; + +public class PsiDirectoryNode extends BasePsiNode implements NavigatableWithText { + public PsiDirectoryNode(Project project, PsiDirectory value, ViewSettings viewSettings) { + super(project, value, viewSettings); + } + + protected void updateImpl(PresentationData data) { + final Project project = getProject(); + final PsiDirectory psiDirectory = getValue(); + final VirtualFile directoryFile = psiDirectory.getVirtualFile(); + + final Object parentValue = getParentValue(); + if (ProjectRootsUtil.isModuleContentRoot(directoryFile, project)) { + ProjectFileIndex fi = ProjectRootManager.getInstance(project).getFileIndex(); + Module module = fi.getModuleForFile(directoryFile); + + data.setPresentableText(directoryFile.getName()); + if (module != null) { + if (!(parentValue instanceof Module )) { + if (Comparing.equal(module.getName(), directoryFile.getName())) { + data.addText(directoryFile.getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + } + else { + data.addText(directoryFile.getName() + " ", SimpleTextAttributes.REGULAR_ATTRIBUTES); + data.addText("[" + module.getName() + "]", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + } + } + else { + data.addText(directoryFile.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + if (parentValue instanceof Project || parentValue instanceof Module) { + data.addText(" (" + directoryFile.getPresentableUrl() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); + } + else if (ProjectRootsUtil.isSourceOrTestRoot(directoryFile, project)) { + if (ProjectRootsUtil.isInTestSource(directoryFile, project)) { + data.addText(" (test source root)", SimpleTextAttributes.GRAY_ATTRIBUTES); + } + else { + data.addText(" (source root)", SimpleTextAttributes.GRAY_ATTRIBUTES); + } + } + + setupIcon(data, psiDirectory); + + return; + } + } + + final String name = parentValue instanceof Project + ? psiDirectory.getVirtualFile().getPresentableUrl() + : ProjectViewDirectoryHelper.getInstance(psiDirectory.getProject()).getNodeName(getSettings(), parentValue, psiDirectory); + if (name == null) { + setValue(null); + return; + } + + data.setPresentableText(name); + if (ProjectRootsUtil.isLibraryRoot(directoryFile, project)) { + data.setLocationString("library home"); + } + else { + data.setLocationString(ProjectViewDirectoryHelper.getInstance(project).getLocationString(psiDirectory)); + } + + setupIcon(data, psiDirectory); + } + + private void setupIcon(PresentationData data, PsiDirectory psiDirectory) { + final VirtualFile virtualFile = psiDirectory.getVirtualFile(); + for (final IconProvider provider : Extensions.getExtensions(IconProvider.EXTENSION_POINT_NAME)) { + final Icon openIcon = provider.getIcon(psiDirectory, Iconable.ICON_FLAG_OPEN); + if (openIcon != null) { + final Icon closedIcon = provider.getIcon(psiDirectory, Iconable.ICON_FLAG_CLOSED); + if (closedIcon != null) { + data.setOpenIcon(patchIcon(openIcon, virtualFile)); + data.setClosedIcon(patchIcon(closedIcon, virtualFile)); + return; + } + } + } + } + + public Collection getChildrenImpl() { + return ProjectViewDirectoryHelper.getInstance(myProject).getDirectoryChildren(getValue(), getSettings(), true); + } + + public String getTestPresentation() { + return "PsiDirectory: " + getValue().getName(); + } + + public boolean isFQNameShown() { + return ProjectViewDirectoryHelper.getInstance(getProject()).isShowFQName(getSettings(), getParentValue(), getValue()); + } + + public boolean contains(@NotNull VirtualFile file) { + final PsiDirectory value = getValue(); + if (value == null) { + return false; + } + + VirtualFile directory = value.getVirtualFile(); + if (directory.getFileSystem() instanceof LocalFileSystem) { + file = PathUtil.getLocalFile(file); + } + + if (!VfsUtil.isAncestor(directory, file, false)) { + return false; + } + + final Project project = value.getProject(); + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + return !fileIndex.isIgnored(file); + } + + public VirtualFile getVirtualFile() { + PsiDirectory directory = getValue(); + if (directory == null) return null; + return directory.getVirtualFile(); + } + + public boolean canRepresent(final Object element) { + if (super.canRepresent(element)) return true; + PsiDirectory directory = getValue(); + if (directory == null) return false; + return ProjectViewDirectoryHelper.getInstance(getProject()).canRepresent(element, directory); + } + + public boolean canNavigate() { + VirtualFile file = getVirtualFile(); + Project project = getProject(); + + return file != null && (ProjectRootsUtil.isModuleContentRoot(file, project) || + ProjectRootsUtil.isSourceOrTestRoot(file, project) || + ProjectRootsUtil.isLibraryRoot(file, project)); + } + + public boolean canNavigateToSource() { + return false; + } + + public void navigate(final boolean requestFocus) { + Module module = ModuleUtil.findModuleForPsiElement(getValue()); + if (module != null) { + final VirtualFile file = getVirtualFile(); + final Project project = getProject(); + if (ProjectRootsUtil.isModuleContentRoot(file, project)) { + ProjectSettingsService.getInstance(myProject).openModuleSettings(module); + } + else if (ProjectRootsUtil.isLibraryRoot(file, project)) { + ProjectSettingsService.getInstance(myProject).openModuleLibrarySettings(module); + } + else { + ProjectSettingsService.getInstance(myProject).openContentEntriesSettings(module); + } + } + } + + @Override + public String getNavigateActionText(boolean focusEditor) { + VirtualFile file = getVirtualFile(); + Project project = getProject(); + + if (file != null) { + if (ProjectRootsUtil.isModuleContentRoot(file, project) || + ProjectRootsUtil.isSourceOrTestRoot(file, project)) { + return "Open Module Settings"; + } + if (ProjectRootsUtil.isLibraryRoot(file, project)) { + return "Open Library Settings"; + } + } + + return null; + } + + public int getWeight() { + return isFQNameShown() ? 70 : 0; + } + + public String getTitle() { + final PsiDirectory directory = getValue(); + if (directory != null) { + return PsiDirectoryFactory.getInstance(getProject()).getQualifiedName(directory, true); + } + return super.getTitle(); + } + + private Icon patchIcon(Icon original, VirtualFile file) { + Bookmark bookmarkAtFile = BookmarkManager.getInstance(myProject).findFileBookmark(file); + if (bookmarkAtFile != null) { + RowIcon composite = new RowIcon(2); + composite.setIcon(original, 0); + composite.setIcon(bookmarkAtFile.getIcon(), 1); + return addReadMark(composite, file.isWritable()); + } + + return addReadMark(original, file.isWritable()); + } + + private static Icon addReadMark(Icon originalIcon, boolean isWritable) { + if (isWritable) { + return originalIcon; + } + else { + return LayeredIcon.create(originalIcon, Icons.LOCKED_ICON); + } + } + + public String getQualifiedNameSortKey() { + final PsiDirectoryFactory factory = PsiDirectoryFactory.getInstance(getProject()); + return factory.getQualifiedName(getValue(), true); + } + + public int getTypeSortWeight(final boolean sortByType) { + return 3; + } + + @Override + public boolean shouldDrillDownOnEmptyElement() { + return true; + } + + @Override + public boolean isAlwaysShowPlus() { + return true; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextConfigurable.java b/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextConfigurable.java index d5639c7f3da1..6a5952fe2ae2 100644 --- a/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextConfigurable.java +++ b/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextConfigurable.java @@ -1,93 +1,93 @@ -/* - * 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.ide.scriptingContext; - -import com.intellij.ide.scriptingContext.ui.ScriptingLibrariesPanelStub; -import com.intellij.ide.scriptingContext.ui.ScriptingContextsConfigurable; -import com.intellij.ide.scriptingContext.ui.ScriptingLibrariesPanel; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryManager; -import org.jetbrains.annotations.Nls; - -import javax.swing.*; - -/** - * @author Rustam Vishnyakov - */ -public abstract class LangScriptingContextConfigurable implements Configurable, Configurable.Composite { - private final ScriptingLibrariesPanelStub myPanel; - private final ScriptingLibraryManager myLibManager; - private ScriptingContextsConfigurable myContextsConfigurable; - private final String productName = System.getProperty("idea.platform.prefix"); - - public LangScriptingContextConfigurable(Project project, LangScriptingContextProvider provider) { - myLibManager = new ScriptingLibraryManager(project, provider.getLibraryType()); - myPanel = useDedicatedLibraryUI() ? new ScriptingLibrariesPanel(provider, project, myLibManager) : new ScriptingLibrariesPanelStub(); - myContextsConfigurable = new ScriptingContextsConfigurable(project, provider.getLibraryMappings(project)); - } - - private boolean useDedicatedLibraryUI() { - //TODO Find a better way to check it - return "WebStorm".equalsIgnoreCase(productName) || "PhpStorm".equalsIgnoreCase(productName); - } - - @Nls - @Override - public String getDisplayName() { - return "Libraries"; - } - - @Override - public JComponent createComponent() { - return myPanel.getPanel(); - } - - @Override - public boolean isModified() { - return myPanel.isModified(); - } - - @Override - public void apply() throws ConfigurationException { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - myLibManager.commitChanges(); - myPanel.resetTable(); - myContextsConfigurable.resetMappings(); - } - }); - } - - @Override - public void reset() { - myLibManager.reset(); - myPanel.resetTable(); - } - - @Override - public void disposeUIResources() { - - } - - @Override - public Configurable[] getConfigurables() { - return new Configurable[] {myContextsConfigurable}; - } -} +/* + * 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.ide.scriptingContext; + +import com.intellij.ide.scriptingContext.ui.ScriptingLibrariesPanelStub; +import com.intellij.ide.scriptingContext.ui.ScriptingContextsConfigurable; +import com.intellij.ide.scriptingContext.ui.ScriptingLibrariesPanel; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryManager; +import org.jetbrains.annotations.Nls; + +import javax.swing.*; + +/** + * @author Rustam Vishnyakov + */ +public abstract class LangScriptingContextConfigurable implements Configurable, Configurable.Composite { + private final ScriptingLibrariesPanelStub myPanel; + private final ScriptingLibraryManager myLibManager; + private ScriptingContextsConfigurable myContextsConfigurable; + private final String productName = System.getProperty("idea.platform.prefix"); + + public LangScriptingContextConfigurable(Project project, LangScriptingContextProvider provider) { + myLibManager = new ScriptingLibraryManager(project, provider.getLibraryType()); + myPanel = useDedicatedLibraryUI() ? new ScriptingLibrariesPanel(provider, project, myLibManager) : new ScriptingLibrariesPanelStub(); + myContextsConfigurable = new ScriptingContextsConfigurable(project, provider.getLibraryMappings(project)); + } + + private boolean useDedicatedLibraryUI() { + //TODO Find a better way to check it + return "WebStorm".equalsIgnoreCase(productName) || "PhpStorm".equalsIgnoreCase(productName); + } + + @Nls + @Override + public String getDisplayName() { + return "Libraries"; + } + + @Override + public JComponent createComponent() { + return myPanel.getPanel(); + } + + @Override + public boolean isModified() { + return myPanel.isModified(); + } + + @Override + public void apply() throws ConfigurationException { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + myLibManager.commitChanges(); + myPanel.resetTable(); + myContextsConfigurable.resetMappings(); + } + }); + } + + @Override + public void reset() { + myLibManager.reset(); + myPanel.resetTable(); + } + + @Override + public void disposeUIResources() { + + } + + @Override + public Configurable[] getConfigurables() { + return new Configurable[] {myContextsConfigurable}; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextProvider.java b/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextProvider.java index 9c1bfc2c36f9..1d72952a1162 100644 --- a/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/scriptingContext/LangScriptingContextProvider.java @@ -1,45 +1,45 @@ -/* - * 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.ide.scriptingContext; - -import com.intellij.lang.Language; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.libraries.LibraryType; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author Rustam Vishnyakov - */ -public abstract class LangScriptingContextProvider { - - @NotNull - public abstract Language getLanguage(); - - public abstract LibraryType getLibraryType(); - - public abstract boolean acceptsExtension(String fileExt); - - public abstract ScriptingLibraryMappings getLibraryMappings(Project project); - - public abstract boolean isCompact(VirtualFile file); - - @Nullable - public abstract String getLibraryTypeName(OrderRootType rootType); - -} +/* + * 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.ide.scriptingContext; + +import com.intellij.lang.Language; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.LibraryType; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Rustam Vishnyakov + */ +public abstract class LangScriptingContextProvider { + + @NotNull + public abstract Language getLanguage(); + + public abstract LibraryType getLibraryType(); + + public abstract boolean acceptsExtension(String fileExt); + + public abstract ScriptingLibraryMappings getLibraryMappings(Project project); + + public abstract boolean isCompact(VirtualFile file); + + @Nullable + public abstract String getLibraryTypeName(OrderRootType rootType); + +} diff --git a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ScriptingLibraryMappings.java b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ScriptingLibraryMappings.java index 2f4e3334a062..c796667b7a03 100644 --- a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ScriptingLibraryMappings.java +++ b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ScriptingLibraryMappings.java @@ -1,205 +1,205 @@ -/* - * 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.ide.scriptingContext; - -import com.intellij.lang.LanguagePerFileMappings; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.libraries.LibraryType; -import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryManager; -import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryTable; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author Rustam Vishnyakov - */ -public class ScriptingLibraryMappings extends LanguagePerFileMappings { - - private final ScriptingLibraryManager myLibraryManager; - private final Map myCompoundLibMap = new HashMap(); - private CompoundLibrary myProjectLibs = new CompoundLibrary(); - - public ScriptingLibraryMappings(final Project project, final LibraryType libraryType) { - super(project); - myLibraryManager = new ScriptingLibraryManager(project, libraryType); - } - - protected String serialize(final ScriptingLibraryTable.LibraryModel library) { - if (library instanceof CompoundLibrary) { - return "{" + library.getName() + "}"; - } - return library.getName(); - } - - public void reset() { - myLibraryManager.reset(); - } - - @NotNull - @Override - protected String getValueAttribute() { - return "libraries"; - } - - @Override - protected ScriptingLibraryTable.LibraryModel handleUnknownMapping(VirtualFile file, String value) { - if (value == null || !value.contains("{")) return null; - String[] libNames = value.replace('{',' ').replace('}', ' ').split(","); - CompoundLibrary compoundLib = new CompoundLibrary(); - for (String libName : libNames) { - ScriptingLibraryTable.LibraryModel libraryModel = myLibraryManager.getLibraryByName(libName.trim()); - if (libraryModel != null) { - compoundLib.toggleLibrary(libraryModel); - } - } - if (file == null) { - myProjectLibs = compoundLib; - } - else { - myCompoundLibMap.put(file, compoundLib); - } - return compoundLib; - } - - @Override - public Collection getAvailableValues(VirtualFile file) { - List libraries = getSingleLibraries(); - if (myCompoundLibMap.containsKey(file)) { - libraries.add(myCompoundLibMap.get(file)); - return libraries; - } - CompoundLibrary compoundLib = new CompoundLibrary(); - myCompoundLibMap.put(file, compoundLib); - libraries.add(compoundLib); - return libraries; - } - - @Override - public ScriptingLibraryTable.LibraryModel chosenToStored(VirtualFile file, ScriptingLibraryTable.LibraryModel value) { - if (value instanceof CompoundLibrary) return value; - CompoundLibrary compoundLib = file == null ? myProjectLibs : myCompoundLibMap.get(file); - if (compoundLib == null) { - compoundLib = new CompoundLibrary(); - myCompoundLibMap.put(file, compoundLib); - } - if (value == null) { - compoundLib.clearLibraries(); - } - else { - compoundLib.toggleLibrary(value); - } - return compoundLib; - } - - @Override - public boolean isSelectable(ScriptingLibraryTable.LibraryModel value) { - return !(value instanceof CompoundLibrary); - } - - @Override - protected List getAvailableValues() { - return getSingleLibraries(); - } - - - @Override - protected ScriptingLibraryTable.LibraryModel getDefaultMapping(@Nullable VirtualFile file) { - return null; - } - - public List getSingleLibraries() { - ArrayList libraryModels = new ArrayList(); - libraryModels.addAll(Arrays.asList(myLibraryManager.getLibraries())); - return libraryModels; - } - - public static class CompoundLibrary extends ScriptingLibraryTable.LibraryModel { - private final Map myLibraries = new TreeMap(); - - public CompoundLibrary() { - super(null); - } - - public void clearLibraries() { - myLibraries.clear(); - } - - public void toggleLibrary(@NotNull ScriptingLibraryTable.LibraryModel library) { - String libName = library.getName(); - if (myLibraries.containsKey(libName)) { - myLibraries.remove(libName); - return; - } - myLibraries.put(libName, library); - } - - @Override - public String getName() { - StringBuffer allNames = new StringBuffer(); - boolean isFirst = true; - for (ScriptingLibraryTable.LibraryModel library : myLibraries.values()) { - allNames.append(isFirst ? "" : ", "); - allNames.append(library.getName()); - isFirst = false; - } - return allNames.toString(); - } - - @Override - public boolean containsFile(VirtualFile file) { - for (ScriptingLibraryTable.LibraryModel library : myLibraries.values()) { - if (library.containsFile(file)) return true; - } - return false; - } - - @Override - public boolean isEmpty() { - return myLibraries.isEmpty(); - } - } - - /** - * Checks if the library file is applicable to the given source file being edited. If the file has - * assigned libraries neither for itself nor for any of its parent directories, returns true. Parent directory - * settings are added to source file settings: if a library file is applicable to a directory, it is also - * applicable to any of source files under that directory. - * - * @param libFile The library file. - * @param srcFile The source file to check the applicability for. - * @return True if applicable, false otherwise. - */ - public boolean isApplicable(VirtualFile libFile, VirtualFile srcFile) { - return isApplicable(libFile, srcFile, false); - } - - private boolean isApplicable(VirtualFile libFile, VirtualFile srcFile, boolean specFound) { - if (srcFile == null) return !specFound; - ScriptingLibraryTable.LibraryModel libraryModel = getMapping(srcFile); - if (libraryModel == null || libraryModel.isEmpty()) { - return isApplicable(libFile, srcFile.getParent(), specFound); - } - if (libraryModel.containsFile(libFile)) { - return true; - } - return isApplicable(libFile, srcFile.getParent(), true); - } - - -} +/* + * 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.ide.scriptingContext; + +import com.intellij.lang.LanguagePerFileMappings; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.libraries.LibraryType; +import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryManager; +import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryTable; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Rustam Vishnyakov + */ +public class ScriptingLibraryMappings extends LanguagePerFileMappings { + + private final ScriptingLibraryManager myLibraryManager; + private final Map myCompoundLibMap = new HashMap(); + private CompoundLibrary myProjectLibs = new CompoundLibrary(); + + public ScriptingLibraryMappings(final Project project, final LibraryType libraryType) { + super(project); + myLibraryManager = new ScriptingLibraryManager(project, libraryType); + } + + protected String serialize(final ScriptingLibraryTable.LibraryModel library) { + if (library instanceof CompoundLibrary) { + return "{" + library.getName() + "}"; + } + return library.getName(); + } + + public void reset() { + myLibraryManager.reset(); + } + + @NotNull + @Override + protected String getValueAttribute() { + return "libraries"; + } + + @Override + protected ScriptingLibraryTable.LibraryModel handleUnknownMapping(VirtualFile file, String value) { + if (value == null || !value.contains("{")) return null; + String[] libNames = value.replace('{',' ').replace('}', ' ').split(","); + CompoundLibrary compoundLib = new CompoundLibrary(); + for (String libName : libNames) { + ScriptingLibraryTable.LibraryModel libraryModel = myLibraryManager.getLibraryByName(libName.trim()); + if (libraryModel != null) { + compoundLib.toggleLibrary(libraryModel); + } + } + if (file == null) { + myProjectLibs = compoundLib; + } + else { + myCompoundLibMap.put(file, compoundLib); + } + return compoundLib; + } + + @Override + public Collection getAvailableValues(VirtualFile file) { + List libraries = getSingleLibraries(); + if (myCompoundLibMap.containsKey(file)) { + libraries.add(myCompoundLibMap.get(file)); + return libraries; + } + CompoundLibrary compoundLib = new CompoundLibrary(); + myCompoundLibMap.put(file, compoundLib); + libraries.add(compoundLib); + return libraries; + } + + @Override + public ScriptingLibraryTable.LibraryModel chosenToStored(VirtualFile file, ScriptingLibraryTable.LibraryModel value) { + if (value instanceof CompoundLibrary) return value; + CompoundLibrary compoundLib = file == null ? myProjectLibs : myCompoundLibMap.get(file); + if (compoundLib == null) { + compoundLib = new CompoundLibrary(); + myCompoundLibMap.put(file, compoundLib); + } + if (value == null) { + compoundLib.clearLibraries(); + } + else { + compoundLib.toggleLibrary(value); + } + return compoundLib; + } + + @Override + public boolean isSelectable(ScriptingLibraryTable.LibraryModel value) { + return !(value instanceof CompoundLibrary); + } + + @Override + protected List getAvailableValues() { + return getSingleLibraries(); + } + + + @Override + protected ScriptingLibraryTable.LibraryModel getDefaultMapping(@Nullable VirtualFile file) { + return null; + } + + public List getSingleLibraries() { + ArrayList libraryModels = new ArrayList(); + libraryModels.addAll(Arrays.asList(myLibraryManager.getLibraries())); + return libraryModels; + } + + public static class CompoundLibrary extends ScriptingLibraryTable.LibraryModel { + private final Map myLibraries = new TreeMap(); + + public CompoundLibrary() { + super(null); + } + + public void clearLibraries() { + myLibraries.clear(); + } + + public void toggleLibrary(@NotNull ScriptingLibraryTable.LibraryModel library) { + String libName = library.getName(); + if (myLibraries.containsKey(libName)) { + myLibraries.remove(libName); + return; + } + myLibraries.put(libName, library); + } + + @Override + public String getName() { + StringBuffer allNames = new StringBuffer(); + boolean isFirst = true; + for (ScriptingLibraryTable.LibraryModel library : myLibraries.values()) { + allNames.append(isFirst ? "" : ", "); + allNames.append(library.getName()); + isFirst = false; + } + return allNames.toString(); + } + + @Override + public boolean containsFile(VirtualFile file) { + for (ScriptingLibraryTable.LibraryModel library : myLibraries.values()) { + if (library.containsFile(file)) return true; + } + return false; + } + + @Override + public boolean isEmpty() { + return myLibraries.isEmpty(); + } + } + + /** + * Checks if the library file is applicable to the given source file being edited. If the file has + * assigned libraries neither for itself nor for any of its parent directories, returns true. Parent directory + * settings are added to source file settings: if a library file is applicable to a directory, it is also + * applicable to any of source files under that directory. + * + * @param libFile The library file. + * @param srcFile The source file to check the applicability for. + * @return True if applicable, false otherwise. + */ + public boolean isApplicable(VirtualFile libFile, VirtualFile srcFile) { + return isApplicable(libFile, srcFile, false); + } + + private boolean isApplicable(VirtualFile libFile, VirtualFile srcFile, boolean specFound) { + if (srcFile == null) return !specFound; + ScriptingLibraryTable.LibraryModel libraryModel = getMapping(srcFile); + if (libraryModel == null || libraryModel.isEmpty()) { + return isApplicable(libFile, srcFile.getParent(), specFound); + } + if (libraryModel.containsFile(libFile)) { + return true; + } + return isApplicable(libFile, srcFile.getParent(), true); + } + + +} diff --git a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.form b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.form index 8bf7b2eba829..803ac485f05e 100644 --- a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.form +++ b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.form @@ -1,98 +1,98 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.java b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.java index 97110b8b3267..0a43981da562 100644 --- a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.java +++ b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/EditLibraryDialog.java @@ -1,327 +1,327 @@ -/* - * 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.ide.scriptingContext.ui; - -import com.intellij.ide.scriptingContext.LangScriptingContextProvider; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryTable; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.table.JBTable; -import com.intellij.util.containers.HashSet; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableColumn; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; -import java.util.Arrays; - -public class EditLibraryDialog extends DialogWrapper { - - private static final int FILE_LOCATION_COL = 0; - private static final int FILE_TYPE_COL = 1; - - private final String mySourceTypeName; - private final String myCompactTypeName; - - private JPanel contentPane; - private JTextField myLibName; - private JButton myAddFileButton; - private JButton myRemoveFileButton; - private JBTable myFileTable; - private JButton myAttachFromButton; - private Project myProject; - private FileTableModel myFileTableModel; - private VirtualFile mySelectedFile; - private LangScriptingContextProvider myProvider; - - public EditLibraryDialog(String title, LangScriptingContextProvider provider, Project project) { - super(true); - setTitle(title); - myProvider = provider; - mySourceTypeName = provider.getLibraryTypeName(OrderRootType.SOURCES); - myCompactTypeName = provider.getLibraryTypeName(OrderRootType.CLASSES); - myAddFileButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - addFiles(); - } - }); - - myAttachFromButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - attachFromDirectory(); - } - }); - - myFileTableModel = new FileTableModel(); - myFileTable.setModel(myFileTableModel); - - myRemoveFileButton.setEnabled(false); - myRemoveFileButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeSelected(); - } - }); - myProject = project; - myFileTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - updateSelection(); - } - }); - init(); - - TableColumn typeCol = myFileTable.getColumnModel().getColumn(FILE_TYPE_COL); - typeCol.setMaxWidth(80); - MyTableCellEditor cellEditor = new MyTableCellEditor(new JComboBox(new String[] {mySourceTypeName, myCompactTypeName}), myFileTableModel); - typeCol.setCellEditor(cellEditor); - } - - public EditLibraryDialog(String title, LangScriptingContextProvider provider, Project project, ScriptingLibraryTable.LibraryModel lib) { - this(title, provider, project); - myLibName.setText(lib.getName()); - myFileTableModel.setFiles(lib.getSourceFiles(), lib.getCompactFiles()); - } - - private class MyTableCellEditor extends DefaultCellEditor { - - private FileTableModel myFileTableModel; - private VirtualFile myFile; - - public MyTableCellEditor(JComboBox comboBox, FileTableModel fileTableModel) { - super(comboBox); - myFileTableModel = fileTableModel; - } - - @Override - public boolean stopCellEditing() { - if (myFile != null) { - Object value = getCellEditorValue(); - myFileTableModel.setFileType(myFile, value.equals(myCompactTypeName)); - } - return super.stopCellEditing(); - } - - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { - myFile = myFileTableModel.getFileAt(row); - return super.getTableCellEditorComponent(table, value, isSelected, row, column); - } - } - - @Override - protected JComponent createCenterPanel() { - return contentPane; - } - - public String getLibName() { - return myLibName.getText(); - } - - private void addFiles() { - FileChooserDescriptor chooserDescriptor = new LibFileChooserDescriptor(); - VirtualFile[] files = FileChooser.chooseFiles(myProject, chooserDescriptor); - if (files.length == 1 && files[0] != null) { - myFileTableModel.addFile(files[0]); - } - } - - private void attachFromDirectory() { - FileChooserDescriptor chooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false); - chooserDescriptor.setTitle("Select a directory to attach files from"); //TODO Move to resources - VirtualFile[] files = FileChooser.chooseFiles(myProject, chooserDescriptor); - if (files.length == 1 && files[0] != null) { - VirtualFile chosenDir = files[0]; - if (chosenDir.isDirectory() && chosenDir.isValid()) { - if (myLibName.getText().isEmpty()) myLibName.setText(chosenDir.getName()); - for (VirtualFile file : chosenDir.getChildren()) { - if (file.isValid() && !file.isDirectory() && myProvider.acceptsExtension(file.getExtension())) { - myFileTableModel.addFile(file); - } - } - } - } - } - - private class LibFileChooserDescriptor extends FileChooserDescriptor { - public LibFileChooserDescriptor() { - super (true, false, false, true, false, false); - setTitle("Select library file"); //TODO Move to resources - } - - @Override - public boolean isFileSelectable(VirtualFile file) { - if (!myProvider.acceptsExtension(file.getExtension())) return false; - return super.isFileSelectable(file); - } - - @Override - public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { - if (!file.isDirectory() && !myProvider.acceptsExtension(file.getExtension())) return false; - return super.isFileVisible(file, showHiddenFiles); - } - } - - private class FileTableModel extends AbstractTableModel { - - @Override - public String getColumnName(int column) { - switch(column) { - case FILE_LOCATION_COL: - return "Location"; //TODO Move to resources - case FILE_TYPE_COL: //TODO Move to resources - return "Type"; - } - return ""; - } - - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - if (columnIndex == FILE_TYPE_COL) { - return true; - } - return super.isCellEditable(rowIndex, columnIndex); - } - - private ArrayList myFiles = new ArrayList(); - private HashSet myCompactFiles = new HashSet(); - - public void addFile(VirtualFile file) { - myFiles.add(file); - if (myProvider.isCompact(file)) { - myCompactFiles.add(file); - } - fireTableDataChanged(); - } - - public void setFiles(VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { - myFiles.clear(); - myFiles.addAll(Arrays.asList(sourceFiles)); - myFiles.addAll(Arrays.asList(compactFiles)); - myCompactFiles.addAll(Arrays.asList(compactFiles)); - } - - public void setFileType(VirtualFile file, boolean isCompact) { - if (isCompact) { - if (!myCompactFiles.contains(file)) { - myCompactFiles.add(file); - } - } - else { - if (myCompactFiles.contains(file)) { - myCompactFiles.remove(file); - } - } - fireTableDataChanged(); - } - - @Override - public int getRowCount() { - return myFiles.size(); - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - VirtualFile file = myFiles.get(rowIndex); - switch (columnIndex) { - case FILE_LOCATION_COL: - return file; - case FILE_TYPE_COL: - return myCompactFiles.contains(file) ? myCompactTypeName : mySourceTypeName; - } - return ""; - } - - @Nullable - public VirtualFile getFileAt(int row) { - if (row < 0 || row >= myFiles.size()) return null; - return myFiles.get(row); - } - - public void removeFile(VirtualFile file) { - if (myFiles.remove(file)) { - fireTableDataChanged(); - } - } - - public VirtualFile[] getSourceFiles() { - ArrayList sourceFiles = new ArrayList(); - for (VirtualFile file : myFiles) { - if (!myCompactFiles.contains(file)) { - sourceFiles.add(file); - } - } - return sourceFiles.toArray(new VirtualFile[sourceFiles.size()]); - } - - public VirtualFile[] getCompactFiles() { - return myCompactFiles.toArray(new VirtualFile[myCompactFiles.size()]); - } - } - - private void updateSelection() { - int selectedRow = myFileTable.getSelectedRow(); - if (selectedRow >= 0) { - mySelectedFile = myFileTableModel.getFileAt(selectedRow); - } - else { - mySelectedFile = null; - } - myRemoveFileButton.setEnabled(mySelectedFile != null); - } - - private void removeSelected() { - myFileTableModel.removeFile(mySelectedFile); - } - - public VirtualFile[] getSourceFiles() { - return myFileTableModel.getSourceFiles(); - } - - public VirtualFile[] getCompactFiles() { - return myFileTableModel.getCompactFiles(); - } - - @Override - protected void doOKAction() { - if (!isLibNameValid(myLibName.getText())) { - Messages.showErrorDialog(myProject, "Invalid library name", "Error"); //TODO Move to resources - return; - } - super.doOKAction(); - } - - private static boolean isLibNameValid(String libName) { - return libName != null && libName.matches("\\w[\\w\\d\\._\\-\\d]*"); - } -} +/* + * 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.ide.scriptingContext.ui; + +import com.intellij.ide.scriptingContext.LangScriptingContextProvider; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryTable; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.table.JBTable; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.Arrays; + +public class EditLibraryDialog extends DialogWrapper { + + private static final int FILE_LOCATION_COL = 0; + private static final int FILE_TYPE_COL = 1; + + private final String mySourceTypeName; + private final String myCompactTypeName; + + private JPanel contentPane; + private JTextField myLibName; + private JButton myAddFileButton; + private JButton myRemoveFileButton; + private JBTable myFileTable; + private JButton myAttachFromButton; + private Project myProject; + private FileTableModel myFileTableModel; + private VirtualFile mySelectedFile; + private LangScriptingContextProvider myProvider; + + public EditLibraryDialog(String title, LangScriptingContextProvider provider, Project project) { + super(true); + setTitle(title); + myProvider = provider; + mySourceTypeName = provider.getLibraryTypeName(OrderRootType.SOURCES); + myCompactTypeName = provider.getLibraryTypeName(OrderRootType.CLASSES); + myAddFileButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + addFiles(); + } + }); + + myAttachFromButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + attachFromDirectory(); + } + }); + + myFileTableModel = new FileTableModel(); + myFileTable.setModel(myFileTableModel); + + myRemoveFileButton.setEnabled(false); + myRemoveFileButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + removeSelected(); + } + }); + myProject = project; + myFileTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + updateSelection(); + } + }); + init(); + + TableColumn typeCol = myFileTable.getColumnModel().getColumn(FILE_TYPE_COL); + typeCol.setMaxWidth(80); + MyTableCellEditor cellEditor = new MyTableCellEditor(new JComboBox(new String[] {mySourceTypeName, myCompactTypeName}), myFileTableModel); + typeCol.setCellEditor(cellEditor); + } + + public EditLibraryDialog(String title, LangScriptingContextProvider provider, Project project, ScriptingLibraryTable.LibraryModel lib) { + this(title, provider, project); + myLibName.setText(lib.getName()); + myFileTableModel.setFiles(lib.getSourceFiles(), lib.getCompactFiles()); + } + + private class MyTableCellEditor extends DefaultCellEditor { + + private FileTableModel myFileTableModel; + private VirtualFile myFile; + + public MyTableCellEditor(JComboBox comboBox, FileTableModel fileTableModel) { + super(comboBox); + myFileTableModel = fileTableModel; + } + + @Override + public boolean stopCellEditing() { + if (myFile != null) { + Object value = getCellEditorValue(); + myFileTableModel.setFileType(myFile, value.equals(myCompactTypeName)); + } + return super.stopCellEditing(); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + myFile = myFileTableModel.getFileAt(row); + return super.getTableCellEditorComponent(table, value, isSelected, row, column); + } + } + + @Override + protected JComponent createCenterPanel() { + return contentPane; + } + + public String getLibName() { + return myLibName.getText(); + } + + private void addFiles() { + FileChooserDescriptor chooserDescriptor = new LibFileChooserDescriptor(); + VirtualFile[] files = FileChooser.chooseFiles(myProject, chooserDescriptor); + if (files.length == 1 && files[0] != null) { + myFileTableModel.addFile(files[0]); + } + } + + private void attachFromDirectory() { + FileChooserDescriptor chooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false); + chooserDescriptor.setTitle("Select a directory to attach files from"); //TODO Move to resources + VirtualFile[] files = FileChooser.chooseFiles(myProject, chooserDescriptor); + if (files.length == 1 && files[0] != null) { + VirtualFile chosenDir = files[0]; + if (chosenDir.isDirectory() && chosenDir.isValid()) { + if (myLibName.getText().isEmpty()) myLibName.setText(chosenDir.getName()); + for (VirtualFile file : chosenDir.getChildren()) { + if (file.isValid() && !file.isDirectory() && myProvider.acceptsExtension(file.getExtension())) { + myFileTableModel.addFile(file); + } + } + } + } + } + + private class LibFileChooserDescriptor extends FileChooserDescriptor { + public LibFileChooserDescriptor() { + super (true, false, false, true, false, false); + setTitle("Select library file"); //TODO Move to resources + } + + @Override + public boolean isFileSelectable(VirtualFile file) { + if (!myProvider.acceptsExtension(file.getExtension())) return false; + return super.isFileSelectable(file); + } + + @Override + public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { + if (!file.isDirectory() && !myProvider.acceptsExtension(file.getExtension())) return false; + return super.isFileVisible(file, showHiddenFiles); + } + } + + private class FileTableModel extends AbstractTableModel { + + @Override + public String getColumnName(int column) { + switch(column) { + case FILE_LOCATION_COL: + return "Location"; //TODO Move to resources + case FILE_TYPE_COL: //TODO Move to resources + return "Type"; + } + return ""; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + if (columnIndex == FILE_TYPE_COL) { + return true; + } + return super.isCellEditable(rowIndex, columnIndex); + } + + private ArrayList myFiles = new ArrayList(); + private HashSet myCompactFiles = new HashSet(); + + public void addFile(VirtualFile file) { + myFiles.add(file); + if (myProvider.isCompact(file)) { + myCompactFiles.add(file); + } + fireTableDataChanged(); + } + + public void setFiles(VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { + myFiles.clear(); + myFiles.addAll(Arrays.asList(sourceFiles)); + myFiles.addAll(Arrays.asList(compactFiles)); + myCompactFiles.addAll(Arrays.asList(compactFiles)); + } + + public void setFileType(VirtualFile file, boolean isCompact) { + if (isCompact) { + if (!myCompactFiles.contains(file)) { + myCompactFiles.add(file); + } + } + else { + if (myCompactFiles.contains(file)) { + myCompactFiles.remove(file); + } + } + fireTableDataChanged(); + } + + @Override + public int getRowCount() { + return myFiles.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + VirtualFile file = myFiles.get(rowIndex); + switch (columnIndex) { + case FILE_LOCATION_COL: + return file; + case FILE_TYPE_COL: + return myCompactFiles.contains(file) ? myCompactTypeName : mySourceTypeName; + } + return ""; + } + + @Nullable + public VirtualFile getFileAt(int row) { + if (row < 0 || row >= myFiles.size()) return null; + return myFiles.get(row); + } + + public void removeFile(VirtualFile file) { + if (myFiles.remove(file)) { + fireTableDataChanged(); + } + } + + public VirtualFile[] getSourceFiles() { + ArrayList sourceFiles = new ArrayList(); + for (VirtualFile file : myFiles) { + if (!myCompactFiles.contains(file)) { + sourceFiles.add(file); + } + } + return sourceFiles.toArray(new VirtualFile[sourceFiles.size()]); + } + + public VirtualFile[] getCompactFiles() { + return myCompactFiles.toArray(new VirtualFile[myCompactFiles.size()]); + } + } + + private void updateSelection() { + int selectedRow = myFileTable.getSelectedRow(); + if (selectedRow >= 0) { + mySelectedFile = myFileTableModel.getFileAt(selectedRow); + } + else { + mySelectedFile = null; + } + myRemoveFileButton.setEnabled(mySelectedFile != null); + } + + private void removeSelected() { + myFileTableModel.removeFile(mySelectedFile); + } + + public VirtualFile[] getSourceFiles() { + return myFileTableModel.getSourceFiles(); + } + + public VirtualFile[] getCompactFiles() { + return myFileTableModel.getCompactFiles(); + } + + @Override + protected void doOKAction() { + if (!isLibNameValid(myLibName.getText())) { + Messages.showErrorDialog(myProject, "Invalid library name", "Error"); //TODO Move to resources + return; + } + super.doOKAction(); + } + + private static boolean isLibNameValid(String libName) { + return libName != null && libName.matches("\\w[\\w\\d\\._\\-\\d]*"); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/ScriptingContextsConfigurable.java b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/ScriptingContextsConfigurable.java index 14068909e4f3..44da63392e45 100644 --- a/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/ScriptingContextsConfigurable.java +++ b/platform/lang-impl/src/com/intellij/ide/scriptingContext/ui/ScriptingContextsConfigurable.java @@ -1,72 +1,72 @@ -/* - * 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.ide.scriptingContext.ui; - -import com.intellij.ide.scriptingContext.ScriptingLibraryMappings; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryTable; -import com.intellij.openapi.util.IconLoader; -import com.intellij.util.ui.tree.LanguagePerFileConfigurable; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -/** - * @author Rustam Vishnyakov - */ -public class ScriptingContextsConfigurable extends LanguagePerFileConfigurable { - - private final ScriptingLibraryMappings myScriptingLibraryMappings; - - public ScriptingContextsConfigurable(final Project project, final ScriptingLibraryMappings mappings) { - super(project, ScriptingLibraryTable.LibraryModel.class, mappings, - "Specify which libraries are used in specific files and/or directories.", "Library", - "Override library settings for child directories and files?", "Override Library Settings"); - myScriptingLibraryMappings = mappings; - } - - public void resetMappings() { - myScriptingLibraryMappings.reset(); - } - - @Override - public void reset() { - resetMappings(); - super.reset(); - } - - @Override - protected String visualize(@NotNull ScriptingLibraryTable.LibraryModel library) { - return library.getName(); - } - - @Nls - @Override - public String getDisplayName() { - return "Usage Scope"; - } - - @Override - public Icon getIcon() { - return IconLoader.getIcon("/general/configurableFileTypes.png"); - } - - @Override - public String getHelpTopic() { - return null; - } -} +/* + * 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.ide.scriptingContext.ui; + +import com.intellij.ide.scriptingContext.ScriptingLibraryMappings; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.libraries.scripting.ScriptingLibraryTable; +import com.intellij.openapi.util.IconLoader; +import com.intellij.util.ui.tree.LanguagePerFileConfigurable; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Rustam Vishnyakov + */ +public class ScriptingContextsConfigurable extends LanguagePerFileConfigurable { + + private final ScriptingLibraryMappings myScriptingLibraryMappings; + + public ScriptingContextsConfigurable(final Project project, final ScriptingLibraryMappings mappings) { + super(project, ScriptingLibraryTable.LibraryModel.class, mappings, + "Specify which libraries are used in specific files and/or directories.", "Library", + "Override library settings for child directories and files?", "Override Library Settings"); + myScriptingLibraryMappings = mappings; + } + + public void resetMappings() { + myScriptingLibraryMappings.reset(); + } + + @Override + public void reset() { + resetMappings(); + super.reset(); + } + + @Override + protected String visualize(@NotNull ScriptingLibraryTable.LibraryModel library) { + return library.getName(); + } + + @Nls + @Override + public String getDisplayName() { + return "Usage Scope"; + } + + @Override + public Icon getIcon() { + return IconLoader.getIcon("/general/configurableFileTypes.png"); + } + + @Override + public String getHelpTopic() { + return null; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java b/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java index 693499018847..c8959d4836cf 100644 --- a/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java +++ b/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java @@ -1,172 +1,172 @@ -/* - * 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.ide.ui.search; - -import com.intellij.application.options.CodeStyleSchemesConfigurable; -import com.intellij.application.options.OptionsContainingConfigurable; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ApplicationStarter; -import com.intellij.openapi.application.ex.ApplicationEx; -import com.intellij.openapi.keymap.impl.ui.KeymapConfigurable; -import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.JDOMUtil; -import org.jdom.Document; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Set; -import java.util.TreeSet; - -/** - * User: anna - * Date: 13-Feb-2006 - */ -@SuppressWarnings({"CallToPrintStackTrace", "SynchronizeOnThis"}) -public class TraverseUIStarter implements ApplicationStarter { - private String OUTPUT_PATH; - @NonNls private static final String OPTIONS = "options"; - @NonNls private static final String CONFIGURABLE = "configurable"; - @NonNls private static final String ID = "id"; - @NonNls private static final String CONFIGURABLE_NAME = "configurable_name"; - @NonNls private static final String OPTION = "option"; - @NonNls private static final String NAME = "name"; - @NonNls private static final String PATH = "path"; - @NonNls private static final String HIT = "hit"; - - @NonNls - public String getCommandName() { - return "traverseUI"; - } - - - public void premain(String[] args) { - OUTPUT_PATH = args[1]; - } - - public void main(String[] args){ - System.out.println("Starting searchable options index builder"); - try { - startup(); - } - catch (Throwable e) { - System.out.println("Searchable options index builder failed"); - e.printStackTrace(); - System.exit(-1); - } - } - - public void startup() throws IOException { - final HashMap> options = - new HashMap>(); - SearchUtil.processProjectConfigurables(ProjectManager.getInstance().getDefaultProject(), options); - Element root = new Element(OPTIONS); - for (SearchableConfigurable configurable : options.keySet()) { - Element configurableElement = new Element(CONFIGURABLE); - final String id = configurable.getId(); - if (id == null) continue; - configurableElement.setAttribute(ID, id); - configurableElement.setAttribute(CONFIGURABLE_NAME, configurable.getDisplayName()); - final TreeSet sortedOptions = options.get(configurable); - for (OptionDescription option : sortedOptions) { - append(option.getPath(), option.getHit(), option.getOption(), configurableElement); - } - if (configurable instanceof KeymapConfigurable){ - processKeymap(configurableElement); - } else if (configurable instanceof OptionsContainingConfigurable){ - processColorAndFontsSettings((OptionsContainingConfigurable)configurable, configurableElement); - } else if (configurable instanceof CodeStyleSchemesConfigurable){ - processCodeStyleConfigurable((CodeStyleSchemesConfigurable)configurable, configurableElement); - } - root.addContent(configurableElement); - configurable.disposeUIResources(); - } - JDOMUtil.writeDocument(new Document(root), OUTPUT_PATH, "\n"); - - System.out.println("Searchable options index builder completed"); - - ((ApplicationEx)ApplicationManager.getApplication()).exit(true); - } - - private static void processCodeStyleConfigurable(final CodeStyleSchemesConfigurable configurable, final Element configurableElement) { - final TreeSet options = new TreeSet(); - options.addAll(configurable.processOptions()); - for (OptionDescription description : options) { - append(description.getPath(), description.getHit(), description.getOption(), configurableElement); - } - } - - private static void processColorAndFontsSettings(final OptionsContainingConfigurable configurable, final Element configurableElement) { - SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance(); - final Set optionsPath = configurable.processListOptions(); - final TreeSet result = new TreeSet(); - for (String opt : optionsPath) { - final Set words = searchableOptionsRegistrar.getProcessedWordsWithoutStemming(opt); - for (String word : words) { - if (word != null){ - result.add(new OptionDescription(word, opt, null)); - } - } - } - for (OptionDescription option : result) { - append(option.getPath(), option.getHit(), option.getOption(), configurableElement); - } - } - - private static void processKeymap(final Element configurableElement){ - final ActionManager actionManager = ActionManager.getInstance(); - final SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance(); - final Set ids = ((ActionManagerImpl)actionManager).getActionIds(); - final TreeSet options = new TreeSet(); - for (String id : ids) { - final AnAction anAction = actionManager.getAction(id); - final String text = anAction.getTemplatePresentation().getText(); - if (text != null) { - final Set strings = searchableOptionsRegistrar.getProcessedWordsWithoutStemming(text); - for (String word : strings) { - options.add(new OptionDescription(word, text, null)); - } - } - final String description = anAction.getTemplatePresentation().getDescription(); - if (description != null) { - final Set strings = searchableOptionsRegistrar.getProcessedWordsWithoutStemming(description); - for (String word : strings) { - options.add(new OptionDescription(word, description, null)); - } - } - } - for (OptionDescription opt : options) { - append(opt.getPath(), opt.getHit(), opt.getOption(), configurableElement); - } - } - - private static void append(String path, String hit, final String word, final Element configurableElement) { - Element optionElement = new Element(OPTION); - optionElement.setAttribute(NAME, word); - if (path != null) { - optionElement.setAttribute(PATH, path); - } - optionElement.setAttribute(HIT, hit); - configurableElement.addContent(optionElement); - } -} +/* + * 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.ide.ui.search; + +import com.intellij.application.options.CodeStyleSchemesConfigurable; +import com.intellij.application.options.OptionsContainingConfigurable; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationStarter; +import com.intellij.openapi.application.ex.ApplicationEx; +import com.intellij.openapi.keymap.impl.ui.KeymapConfigurable; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.JDOMUtil; +import org.jdom.Document; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Set; +import java.util.TreeSet; + +/** + * User: anna + * Date: 13-Feb-2006 + */ +@SuppressWarnings({"CallToPrintStackTrace", "SynchronizeOnThis"}) +public class TraverseUIStarter implements ApplicationStarter { + private String OUTPUT_PATH; + @NonNls private static final String OPTIONS = "options"; + @NonNls private static final String CONFIGURABLE = "configurable"; + @NonNls private static final String ID = "id"; + @NonNls private static final String CONFIGURABLE_NAME = "configurable_name"; + @NonNls private static final String OPTION = "option"; + @NonNls private static final String NAME = "name"; + @NonNls private static final String PATH = "path"; + @NonNls private static final String HIT = "hit"; + + @NonNls + public String getCommandName() { + return "traverseUI"; + } + + + public void premain(String[] args) { + OUTPUT_PATH = args[1]; + } + + public void main(String[] args){ + System.out.println("Starting searchable options index builder"); + try { + startup(); + } + catch (Throwable e) { + System.out.println("Searchable options index builder failed"); + e.printStackTrace(); + System.exit(-1); + } + } + + public void startup() throws IOException { + final HashMap> options = + new HashMap>(); + SearchUtil.processProjectConfigurables(ProjectManager.getInstance().getDefaultProject(), options); + Element root = new Element(OPTIONS); + for (SearchableConfigurable configurable : options.keySet()) { + Element configurableElement = new Element(CONFIGURABLE); + final String id = configurable.getId(); + if (id == null) continue; + configurableElement.setAttribute(ID, id); + configurableElement.setAttribute(CONFIGURABLE_NAME, configurable.getDisplayName()); + final TreeSet sortedOptions = options.get(configurable); + for (OptionDescription option : sortedOptions) { + append(option.getPath(), option.getHit(), option.getOption(), configurableElement); + } + if (configurable instanceof KeymapConfigurable){ + processKeymap(configurableElement); + } else if (configurable instanceof OptionsContainingConfigurable){ + processColorAndFontsSettings((OptionsContainingConfigurable)configurable, configurableElement); + } else if (configurable instanceof CodeStyleSchemesConfigurable){ + processCodeStyleConfigurable((CodeStyleSchemesConfigurable)configurable, configurableElement); + } + root.addContent(configurableElement); + configurable.disposeUIResources(); + } + JDOMUtil.writeDocument(new Document(root), OUTPUT_PATH, "\n"); + + System.out.println("Searchable options index builder completed"); + + ((ApplicationEx)ApplicationManager.getApplication()).exit(true); + } + + private static void processCodeStyleConfigurable(final CodeStyleSchemesConfigurable configurable, final Element configurableElement) { + final TreeSet options = new TreeSet(); + options.addAll(configurable.processOptions()); + for (OptionDescription description : options) { + append(description.getPath(), description.getHit(), description.getOption(), configurableElement); + } + } + + private static void processColorAndFontsSettings(final OptionsContainingConfigurable configurable, final Element configurableElement) { + SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance(); + final Set optionsPath = configurable.processListOptions(); + final TreeSet result = new TreeSet(); + for (String opt : optionsPath) { + final Set words = searchableOptionsRegistrar.getProcessedWordsWithoutStemming(opt); + for (String word : words) { + if (word != null){ + result.add(new OptionDescription(word, opt, null)); + } + } + } + for (OptionDescription option : result) { + append(option.getPath(), option.getHit(), option.getOption(), configurableElement); + } + } + + private static void processKeymap(final Element configurableElement){ + final ActionManager actionManager = ActionManager.getInstance(); + final SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance(); + final Set ids = ((ActionManagerImpl)actionManager).getActionIds(); + final TreeSet options = new TreeSet(); + for (String id : ids) { + final AnAction anAction = actionManager.getAction(id); + final String text = anAction.getTemplatePresentation().getText(); + if (text != null) { + final Set strings = searchableOptionsRegistrar.getProcessedWordsWithoutStemming(text); + for (String word : strings) { + options.add(new OptionDescription(word, text, null)); + } + } + final String description = anAction.getTemplatePresentation().getDescription(); + if (description != null) { + final Set strings = searchableOptionsRegistrar.getProcessedWordsWithoutStemming(description); + for (String word : strings) { + options.add(new OptionDescription(word, description, null)); + } + } + } + for (OptionDescription opt : options) { + append(opt.getPath(), opt.getHit(), opt.getOption(), configurableElement); + } + } + + private static void append(String path, String hit, final String word, final Element configurableElement) { + Element optionElement = new Element(OPTION); + optionElement.setAttribute(NAME, word); + if (path != null) { + optionElement.setAttribute(PATH, path); + } + optionElement.setAttribute(HIT, hit); + configurableElement.addContent(optionElement); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java index 3adb61ca5027..292fb819ebb2 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java @@ -1,476 +1,476 @@ -/* - * 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.ide.util; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.commander.CommanderPanel; -import com.intellij.ide.commander.ProjectListBuilder; -import com.intellij.ide.structureView.StructureViewBuilder; -import com.intellij.ide.structureView.StructureViewModel; -import com.intellij.ide.structureView.StructureViewTreeElement; -import com.intellij.ide.structureView.TreeBasedStructureViewBuilder; -import com.intellij.ide.structureView.newStructureView.TreeActionsOwner; -import com.intellij.ide.structureView.newStructureView.TreeModelWrapper; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.ide.util.treeView.smartTree.Filter; -import com.intellij.ide.util.treeView.smartTree.SmartTreeStructure; -import com.intellij.ide.util.treeView.smartTree.Sorter; -import com.intellij.ide.util.treeView.smartTree.TreeElement; -import com.intellij.lang.LanguageStructureViewBuilder; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; -import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; -import com.intellij.pom.Navigatable; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.ui.IdeBorderFactory; -import com.intellij.ui.ListScrollingUtil; -import com.intellij.ui.SideBorder; -import com.intellij.ui.SpeedSearchBase; -import com.intellij.ui.docking.DockManager; -import com.intellij.ui.speedSearch.SpeedSearchSupply; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.HashSet; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -public class FileStructureDialog extends DialogWrapper { - private final Editor myEditor; - private final Navigatable myNavigatable; - private final Project myProject; - private MyCommanderPanel myCommanderPanel; - private final StructureViewModel myTreeModel; - private final StructureViewModel myBaseTreeModel; - private SmartTreeStructure myTreeStructure; - private final MyTreeActionsOwner myTreeActionsOwner; - - @NonNls private static final String ourPropertyKey = "FileStructure.narrowDown"; - private boolean myShouldNarrowDown = false; - - public FileStructureDialog(StructureViewModel structureViewModel, - @Nullable Editor editor, - Project project, - Navigatable navigatable, - @NotNull final Disposable auxDisposable, - final boolean applySortAndFilter) { - super(project, true); - myProject = project; - myEditor = editor; - myNavigatable = navigatable; - myBaseTreeModel = structureViewModel; - if (applySortAndFilter) { - myTreeActionsOwner = new MyTreeActionsOwner(); - myTreeModel = new TreeModelWrapper(structureViewModel, myTreeActionsOwner); - } - else { - myTreeActionsOwner = null; - myTreeModel = structureViewModel; - } - - PsiFile psiFile = getPsiFile(project); - - final PsiElement psiElement = getCurrentElement(psiFile); - - //myDialog.setUndecorated(true); - init(); - - if (psiElement != null) { - if (structureViewModel.shouldEnterElement(psiElement)) { - myCommanderPanel.getBuilder().enterElement(psiElement, PsiUtilBase.getVirtualFile(psiElement)); - } - else { - myCommanderPanel.getBuilder().selectElement(psiElement, PsiUtilBase.getVirtualFile(psiElement)); - } - } - - Disposer.register(myDisposable, auxDisposable); - } - - protected PsiFile getPsiFile(final Project project) { - return PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument()); - } - - @Nullable - protected Border createContentPaneBorder() { - return null; - } - - public void dispose() { - myCommanderPanel.dispose(); - super.dispose(); - } - - protected String getDimensionServiceKey() { - return DockManager.getInstance(myProject).getDimensionKeyForFocus("#com.intellij.ide.util.FileStructureDialog"); - } - - public JComponent getPreferredFocusedComponent() { - return IdeFocusTraversalPolicy.getPreferredFocusedComponent(myCommanderPanel); - } - - @Nullable - protected PsiElement getCurrentElement(@Nullable final PsiFile psiFile) { - if (psiFile == null) return null; - - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - - Object elementAtCursor = myTreeModel.getCurrentEditorElement(); - if (elementAtCursor instanceof PsiElement) { - return (PsiElement)elementAtCursor; - } - - return null; - } - - protected JComponent createCenterPanel() { - myCommanderPanel = new MyCommanderPanel(myProject); - myTreeStructure = new MyStructureTreeStructure(); - - List fileStructureFilters = new ArrayList(); - if (myTreeActionsOwner != null) { - for(Filter filter: myBaseTreeModel.getFilters()) { - if (filter instanceof FileStructureFilter) { - final FileStructureFilter fsFilter = (FileStructureFilter)filter; - myTreeActionsOwner.setFilterIncluded(fsFilter, true); - fileStructureFilters.add(fsFilter); - } - } - } - - PsiFile psiFile = getPsiFile(myProject); - boolean showRoot = isShowRoot(psiFile); - ProjectListBuilder projectListBuilder = new ProjectListBuilder(myProject, myCommanderPanel, myTreeStructure, null, showRoot) { - protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) { - return Comparing.equal(((StructureViewTreeElement)node.getValue()).getValue(), element); - } - - protected void refreshSelection() { - myCommanderPanel.scrollSelectionInView(); - if (myShouldNarrowDown) { - myCommanderPanel.updateSpeedSearch(); - } - } - - protected List getAllAcceptableNodes(final Object[] childElements, VirtualFile file) { - ArrayList result = new ArrayList(); - for (Object childElement : childElements) { - result.add((AbstractTreeNode)childElement); - } - return result; - } - }; - myCommanderPanel.setBuilder(projectListBuilder); - myCommanderPanel.setTitlePanelVisible(false); - - new AnAction() { - public void actionPerformed(AnActionEvent e) { - final boolean succeeded = myCommanderPanel.navigateSelectedElement(); - if (succeeded) { - unregisterCustomShortcutSet(myCommanderPanel); - } - } - }.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(), myCommanderPanel); - - myCommanderPanel.setPreferredSize(new Dimension(400, 500)); - - JPanel panel = new JPanel(new GridBagLayout()); - - addNarrowDownCheckbox(panel); - - for(FileStructureFilter filter: fileStructureFilters) { - addFilterCheckbox(panel, filter); - } - - myCommanderPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); - panel.add(myCommanderPanel, - new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); - - return panel; - } - - protected boolean isShowRoot(final PsiFile psiFile) { - StructureViewBuilder viewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile); - return viewBuilder instanceof TreeBasedStructureViewBuilder && ((TreeBasedStructureViewBuilder)viewBuilder).isRootNodeShown(); - } - - private void addNarrowDownCheckbox(final JPanel panel) { - final JCheckBox checkBox = new JCheckBox(IdeBundle.message("checkbox.narrow.down.the.list.on.typing")); - checkBox.setSelected(PropertiesComponent.getInstance().isTrueValue(ourPropertyKey)); - checkBox.addChangeListener(new ChangeListener() { - public void stateChanged(ChangeEvent e) { - myShouldNarrowDown = checkBox.isSelected(); - PropertiesComponent.getInstance().setValue(ourPropertyKey, Boolean.toString(myShouldNarrowDown)); - - ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder(); - if (builder == null) { - return; - } - builder.addUpdateRequest(); - } - }); - - checkBox.setFocusable(false); - panel.add(checkBox, - new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); - } - - private void addFilterCheckbox(final JPanel panel, final FileStructureFilter fileStructureFilter) { - final JCheckBox chkFilter = new JCheckBox(); - chkFilter.addActionListener(new ActionListener() { - public void actionPerformed(final ActionEvent e) { - myTreeActionsOwner.setFilterIncluded(fileStructureFilter, !chkFilter.isSelected()); - myTreeStructure.rebuildTree(); - ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder(); - if (builder != null) { - builder.updateList(true); - } - - if (SpeedSearchBase.hasActiveSpeedSearch(myCommanderPanel.getList())) { - final SpeedSearchSupply supply = SpeedSearchBase.getSupply(myCommanderPanel.getList()); - if (supply != null && supply.isPopupActive()) supply.refreshSelection(); - } - } - }); - chkFilter.setFocusable(false); - String text = fileStructureFilter.getCheckBoxText(); - final Shortcut[] shortcuts = fileStructureFilter.getShortcut(); - if (shortcuts.length > 0) { - text += " (" + KeymapUtil.getShortcutText(shortcuts [0]) + ")"; - new AnAction() { - public void actionPerformed(final AnActionEvent e) { - chkFilter.doClick(); - } - }.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), panel); - } - chkFilter.setText(text); - panel.add(chkFilter, - new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); - } - - @Nullable - protected JComponent createSouthPanel() { - return null; - } - - public CommanderPanel getPanel() { - return myCommanderPanel; - } - - private class MyCommanderPanel extends CommanderPanel implements DataProvider { - @Override - protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) { - return false; - } - - public MyCommanderPanel(Project _project) { - super(_project, false, true); - myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - myListSpeedSearch.addChangeListener(new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - ProjectListBuilder builder = (ProjectListBuilder)getBuilder(); - if (builder == null) { - return; - } - builder.addUpdateRequest(hasPrefixShortened(evt)); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - int index = myList.getSelectedIndex(); - if (index != -1 && index < myList.getModel().getSize()) { - myList.clearSelection(); - ListScrollingUtil.selectItem(myList, index); - } - else { - ListScrollingUtil.ensureSelectionExists(myList); - } - } - }); - - myList.repaint(); // to update match highlighting - } - }); - myListSpeedSearch.setComparator(createSpeedSearchComparator()); - } - - private boolean hasPrefixShortened(final PropertyChangeEvent evt) { - return evt.getNewValue() != null && evt.getOldValue() != null && - ((String)evt.getNewValue()).length() < ((String)evt.getOldValue()).length(); - } - - public boolean navigateSelectedElement() { - final Ref succeeded = new Ref(); - final CommandProcessor commandProcessor = CommandProcessor.getInstance(); - commandProcessor.executeCommand(myProject, new Runnable() { - public void run() { - succeeded.set(MyCommanderPanel.super.navigateSelectedElement()); - IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); - } - }, "Navigate", null); - if (succeeded.get()) { - close(CANCEL_EXIT_CODE); - } - return succeeded.get(); - } - - public Object getData(String dataId) { - Object selectedElement = myCommanderPanel.getSelectedValue(); - - if (selectedElement instanceof TreeElement) selectedElement = ((StructureViewTreeElement)selectedElement).getValue(); - - if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { - return selectedElement instanceof Navigatable ? selectedElement : myNavigatable; - } - - if (OpenFileDescriptor.NAVIGATE_IN_EDITOR.is(dataId)) return myEditor; - - return getDataImpl(dataId); - } - - public String getEnteredPrefix() { - return myListSpeedSearch.getEnteredPrefix(); - } - - public void updateSpeedSearch() { - myListSpeedSearch.refreshSelection(); - } - - public void scrollSelectionInView() { - int selectedIndex = myList.getSelectedIndex(); - if (selectedIndex >= 0) { - ListScrollingUtil.ensureIndexIsVisible(myList, selectedIndex, 0); - } - } - } - - private class MyStructureTreeStructure extends SmartTreeStructure { - public MyStructureTreeStructure() { - super(FileStructureDialog.this.myProject, myTreeModel); - } - - public Object[] getChildElements(Object element) { - Object[] childElements = super.getChildElements(element); - - if (!myShouldNarrowDown) { - return childElements; - } - - String enteredPrefix = myCommanderPanel.getEnteredPrefix(); - if (enteredPrefix == null) { - return childElements; - } - - ArrayList filteredElements = new ArrayList(childElements.length); - SpeedSearchBase.SpeedSearchComparator speedSearchComparator = createSpeedSearchComparator(); - - for (Object child : childElements) { - if (child instanceof AbstractTreeNode) { - Object value = ((AbstractTreeNode)child).getValue(); - if (value instanceof TreeElement) { - String name = ((TreeElement)value).getPresentation().getPresentableText(); - if (name == null) { - continue; - } - if (!speedSearchComparator.doCompare(enteredPrefix, name)) { - continue; - } - } - } - filteredElements.add(child); - } - return ArrayUtil.toObjectArray(filteredElements); - } - - public void rebuildTree() { - getChildElements(getRootElement()); // for some reason necessary to rebuild tree correctly - super.rebuildTree(); - } - } - - private static SpeedSearchBase.SpeedSearchComparator createSpeedSearchComparator() { - return new SpeedSearchBase.SpeedSearchComparator() { - public void translateCharacter(final StringBuilder buf, final char ch) { - if (ch == '*') { - if (buf.length() > 0 && "^*)(".indexOf(buf.charAt(buf.length() - 1)) == -1) buf.append(')'); - buf.append(".*"); // overrides '*' handling to skip (,) in parameter lists - } - else { - if (ch == ':') { - if (buf.length() > 0 && "^*)(".indexOf(buf.charAt(buf.length() - 1)) == -1) buf.append(')'); - buf.append(".*"); // get:int should match any getter returning int - buf.append('('); - } - super.translateCharacter(buf, ch); - } - } - }; - } - - private class MyTreeActionsOwner implements TreeActionsOwner { - private final Set myFilters = new HashSet(); - - public void setActionActive(String name, boolean state) { - } - - public boolean isActionActive(String name) { - for (final Sorter sorter : myBaseTreeModel.getSorters()) { - if (sorter.getName().equals(name)) { - if (!sorter.isVisible()) return true; - } - } - for(Filter filter: myFilters) { - if (filter.getName().equals(name)) return true; - } - return Sorter.ALPHA_SORTER_ID.equals(name); - } - - public void setFilterIncluded(final FileStructureFilter filter, final boolean selected) { - if (selected) { - myFilters.add(filter); - } - else { - myFilters.remove(filter); - } - } - } -} +/* + * 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.ide.util; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.commander.CommanderPanel; +import com.intellij.ide.commander.ProjectListBuilder; +import com.intellij.ide.structureView.StructureViewBuilder; +import com.intellij.ide.structureView.StructureViewModel; +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.structureView.TreeBasedStructureViewBuilder; +import com.intellij.ide.structureView.newStructureView.TreeActionsOwner; +import com.intellij.ide.structureView.newStructureView.TreeModelWrapper; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.ide.util.treeView.smartTree.Filter; +import com.intellij.ide.util.treeView.smartTree.SmartTreeStructure; +import com.intellij.ide.util.treeView.smartTree.Sorter; +import com.intellij.ide.util.treeView.smartTree.TreeElement; +import com.intellij.lang.LanguageStructureViewBuilder; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; +import com.intellij.pom.Navigatable; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.ListScrollingUtil; +import com.intellij.ui.SideBorder; +import com.intellij.ui.SpeedSearchBase; +import com.intellij.ui.docking.DockManager; +import com.intellij.ui.speedSearch.SpeedSearchSupply; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class FileStructureDialog extends DialogWrapper { + private final Editor myEditor; + private final Navigatable myNavigatable; + private final Project myProject; + private MyCommanderPanel myCommanderPanel; + private final StructureViewModel myTreeModel; + private final StructureViewModel myBaseTreeModel; + private SmartTreeStructure myTreeStructure; + private final MyTreeActionsOwner myTreeActionsOwner; + + @NonNls private static final String ourPropertyKey = "FileStructure.narrowDown"; + private boolean myShouldNarrowDown = false; + + public FileStructureDialog(StructureViewModel structureViewModel, + @Nullable Editor editor, + Project project, + Navigatable navigatable, + @NotNull final Disposable auxDisposable, + final boolean applySortAndFilter) { + super(project, true); + myProject = project; + myEditor = editor; + myNavigatable = navigatable; + myBaseTreeModel = structureViewModel; + if (applySortAndFilter) { + myTreeActionsOwner = new MyTreeActionsOwner(); + myTreeModel = new TreeModelWrapper(structureViewModel, myTreeActionsOwner); + } + else { + myTreeActionsOwner = null; + myTreeModel = structureViewModel; + } + + PsiFile psiFile = getPsiFile(project); + + final PsiElement psiElement = getCurrentElement(psiFile); + + //myDialog.setUndecorated(true); + init(); + + if (psiElement != null) { + if (structureViewModel.shouldEnterElement(psiElement)) { + myCommanderPanel.getBuilder().enterElement(psiElement, PsiUtilBase.getVirtualFile(psiElement)); + } + else { + myCommanderPanel.getBuilder().selectElement(psiElement, PsiUtilBase.getVirtualFile(psiElement)); + } + } + + Disposer.register(myDisposable, auxDisposable); + } + + protected PsiFile getPsiFile(final Project project) { + return PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument()); + } + + @Nullable + protected Border createContentPaneBorder() { + return null; + } + + public void dispose() { + myCommanderPanel.dispose(); + super.dispose(); + } + + protected String getDimensionServiceKey() { + return DockManager.getInstance(myProject).getDimensionKeyForFocus("#com.intellij.ide.util.FileStructureDialog"); + } + + public JComponent getPreferredFocusedComponent() { + return IdeFocusTraversalPolicy.getPreferredFocusedComponent(myCommanderPanel); + } + + @Nullable + protected PsiElement getCurrentElement(@Nullable final PsiFile psiFile) { + if (psiFile == null) return null; + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + Object elementAtCursor = myTreeModel.getCurrentEditorElement(); + if (elementAtCursor instanceof PsiElement) { + return (PsiElement)elementAtCursor; + } + + return null; + } + + protected JComponent createCenterPanel() { + myCommanderPanel = new MyCommanderPanel(myProject); + myTreeStructure = new MyStructureTreeStructure(); + + List fileStructureFilters = new ArrayList(); + if (myTreeActionsOwner != null) { + for(Filter filter: myBaseTreeModel.getFilters()) { + if (filter instanceof FileStructureFilter) { + final FileStructureFilter fsFilter = (FileStructureFilter)filter; + myTreeActionsOwner.setFilterIncluded(fsFilter, true); + fileStructureFilters.add(fsFilter); + } + } + } + + PsiFile psiFile = getPsiFile(myProject); + boolean showRoot = isShowRoot(psiFile); + ProjectListBuilder projectListBuilder = new ProjectListBuilder(myProject, myCommanderPanel, myTreeStructure, null, showRoot) { + protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) { + return Comparing.equal(((StructureViewTreeElement)node.getValue()).getValue(), element); + } + + protected void refreshSelection() { + myCommanderPanel.scrollSelectionInView(); + if (myShouldNarrowDown) { + myCommanderPanel.updateSpeedSearch(); + } + } + + protected List getAllAcceptableNodes(final Object[] childElements, VirtualFile file) { + ArrayList result = new ArrayList(); + for (Object childElement : childElements) { + result.add((AbstractTreeNode)childElement); + } + return result; + } + }; + myCommanderPanel.setBuilder(projectListBuilder); + myCommanderPanel.setTitlePanelVisible(false); + + new AnAction() { + public void actionPerformed(AnActionEvent e) { + final boolean succeeded = myCommanderPanel.navigateSelectedElement(); + if (succeeded) { + unregisterCustomShortcutSet(myCommanderPanel); + } + } + }.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(), myCommanderPanel); + + myCommanderPanel.setPreferredSize(new Dimension(400, 500)); + + JPanel panel = new JPanel(new GridBagLayout()); + + addNarrowDownCheckbox(panel); + + for(FileStructureFilter filter: fileStructureFilters) { + addFilterCheckbox(panel, filter); + } + + myCommanderPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); + panel.add(myCommanderPanel, + new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + + return panel; + } + + protected boolean isShowRoot(final PsiFile psiFile) { + StructureViewBuilder viewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile); + return viewBuilder instanceof TreeBasedStructureViewBuilder && ((TreeBasedStructureViewBuilder)viewBuilder).isRootNodeShown(); + } + + private void addNarrowDownCheckbox(final JPanel panel) { + final JCheckBox checkBox = new JCheckBox(IdeBundle.message("checkbox.narrow.down.the.list.on.typing")); + checkBox.setSelected(PropertiesComponent.getInstance().isTrueValue(ourPropertyKey)); + checkBox.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + myShouldNarrowDown = checkBox.isSelected(); + PropertiesComponent.getInstance().setValue(ourPropertyKey, Boolean.toString(myShouldNarrowDown)); + + ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder(); + if (builder == null) { + return; + } + builder.addUpdateRequest(); + } + }); + + checkBox.setFocusable(false); + panel.add(checkBox, + new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + } + + private void addFilterCheckbox(final JPanel panel, final FileStructureFilter fileStructureFilter) { + final JCheckBox chkFilter = new JCheckBox(); + chkFilter.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myTreeActionsOwner.setFilterIncluded(fileStructureFilter, !chkFilter.isSelected()); + myTreeStructure.rebuildTree(); + ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder(); + if (builder != null) { + builder.updateList(true); + } + + if (SpeedSearchBase.hasActiveSpeedSearch(myCommanderPanel.getList())) { + final SpeedSearchSupply supply = SpeedSearchBase.getSupply(myCommanderPanel.getList()); + if (supply != null && supply.isPopupActive()) supply.refreshSelection(); + } + } + }); + chkFilter.setFocusable(false); + String text = fileStructureFilter.getCheckBoxText(); + final Shortcut[] shortcuts = fileStructureFilter.getShortcut(); + if (shortcuts.length > 0) { + text += " (" + KeymapUtil.getShortcutText(shortcuts [0]) + ")"; + new AnAction() { + public void actionPerformed(final AnActionEvent e) { + chkFilter.doClick(); + } + }.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), panel); + } + chkFilter.setText(text); + panel.add(chkFilter, + new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + } + + @Nullable + protected JComponent createSouthPanel() { + return null; + } + + public CommanderPanel getPanel() { + return myCommanderPanel; + } + + private class MyCommanderPanel extends CommanderPanel implements DataProvider { + @Override + protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) { + return false; + } + + public MyCommanderPanel(Project _project) { + super(_project, false, true); + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myListSpeedSearch.addChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + ProjectListBuilder builder = (ProjectListBuilder)getBuilder(); + if (builder == null) { + return; + } + builder.addUpdateRequest(hasPrefixShortened(evt)); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + int index = myList.getSelectedIndex(); + if (index != -1 && index < myList.getModel().getSize()) { + myList.clearSelection(); + ListScrollingUtil.selectItem(myList, index); + } + else { + ListScrollingUtil.ensureSelectionExists(myList); + } + } + }); + + myList.repaint(); // to update match highlighting + } + }); + myListSpeedSearch.setComparator(createSpeedSearchComparator()); + } + + private boolean hasPrefixShortened(final PropertyChangeEvent evt) { + return evt.getNewValue() != null && evt.getOldValue() != null && + ((String)evt.getNewValue()).length() < ((String)evt.getOldValue()).length(); + } + + public boolean navigateSelectedElement() { + final Ref succeeded = new Ref(); + final CommandProcessor commandProcessor = CommandProcessor.getInstance(); + commandProcessor.executeCommand(myProject, new Runnable() { + public void run() { + succeeded.set(MyCommanderPanel.super.navigateSelectedElement()); + IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); + } + }, "Navigate", null); + if (succeeded.get()) { + close(CANCEL_EXIT_CODE); + } + return succeeded.get(); + } + + public Object getData(String dataId) { + Object selectedElement = myCommanderPanel.getSelectedValue(); + + if (selectedElement instanceof TreeElement) selectedElement = ((StructureViewTreeElement)selectedElement).getValue(); + + if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { + return selectedElement instanceof Navigatable ? selectedElement : myNavigatable; + } + + if (OpenFileDescriptor.NAVIGATE_IN_EDITOR.is(dataId)) return myEditor; + + return getDataImpl(dataId); + } + + public String getEnteredPrefix() { + return myListSpeedSearch.getEnteredPrefix(); + } + + public void updateSpeedSearch() { + myListSpeedSearch.refreshSelection(); + } + + public void scrollSelectionInView() { + int selectedIndex = myList.getSelectedIndex(); + if (selectedIndex >= 0) { + ListScrollingUtil.ensureIndexIsVisible(myList, selectedIndex, 0); + } + } + } + + private class MyStructureTreeStructure extends SmartTreeStructure { + public MyStructureTreeStructure() { + super(FileStructureDialog.this.myProject, myTreeModel); + } + + public Object[] getChildElements(Object element) { + Object[] childElements = super.getChildElements(element); + + if (!myShouldNarrowDown) { + return childElements; + } + + String enteredPrefix = myCommanderPanel.getEnteredPrefix(); + if (enteredPrefix == null) { + return childElements; + } + + ArrayList filteredElements = new ArrayList(childElements.length); + SpeedSearchBase.SpeedSearchComparator speedSearchComparator = createSpeedSearchComparator(); + + for (Object child : childElements) { + if (child instanceof AbstractTreeNode) { + Object value = ((AbstractTreeNode)child).getValue(); + if (value instanceof TreeElement) { + String name = ((TreeElement)value).getPresentation().getPresentableText(); + if (name == null) { + continue; + } + if (!speedSearchComparator.doCompare(enteredPrefix, name)) { + continue; + } + } + } + filteredElements.add(child); + } + return ArrayUtil.toObjectArray(filteredElements); + } + + public void rebuildTree() { + getChildElements(getRootElement()); // for some reason necessary to rebuild tree correctly + super.rebuildTree(); + } + } + + private static SpeedSearchBase.SpeedSearchComparator createSpeedSearchComparator() { + return new SpeedSearchBase.SpeedSearchComparator() { + public void translateCharacter(final StringBuilder buf, final char ch) { + if (ch == '*') { + if (buf.length() > 0 && "^*)(".indexOf(buf.charAt(buf.length() - 1)) == -1) buf.append(')'); + buf.append(".*"); // overrides '*' handling to skip (,) in parameter lists + } + else { + if (ch == ':') { + if (buf.length() > 0 && "^*)(".indexOf(buf.charAt(buf.length() - 1)) == -1) buf.append(')'); + buf.append(".*"); // get:int should match any getter returning int + buf.append('('); + } + super.translateCharacter(buf, ch); + } + } + }; + } + + private class MyTreeActionsOwner implements TreeActionsOwner { + private final Set myFilters = new HashSet(); + + public void setActionActive(String name, boolean state) { + } + + public boolean isActionActive(String name) { + for (final Sorter sorter : myBaseTreeModel.getSorters()) { + if (sorter.getName().equals(name)) { + if (!sorter.isVisible()) return true; + } + } + for(Filter filter: myFilters) { + if (filter.getName().equals(name)) return true; + } + return Sorter.ALPHA_SORTER_ID.equals(name); + } + + public void setFilterIncluded(final FileStructureFilter filter, final boolean selected) { + if (selected) { + myFilters.add(filter); + } + else { + myFilters.remove(filter); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/util/MemberChooser.java b/platform/lang-impl/src/com/intellij/ide/util/MemberChooser.java index 998112285e7b..8b7cdc07aad5 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/MemberChooser.java +++ b/platform/lang-impl/src/com/intellij/ide/util/MemberChooser.java @@ -1,738 +1,738 @@ -/* - * 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.ide.util; - -import com.intellij.codeInsight.generation.*; -import com.intellij.ide.IdeBundle; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.VerticalFlowLayout; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.ui.ColoredTreeCellRenderer; -import com.intellij.ui.NonFocusableCheckBox; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.TreeSpeedSearch; -import com.intellij.ui.treeStructure.Tree; -import com.intellij.util.Icons; -import com.intellij.util.SmartList; -import com.intellij.util.containers.Convertor; -import com.intellij.util.containers.FactoryMap; -import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; -import javax.swing.tree.TreeSelectionModel; -import java.awt.*; -import java.awt.event.*; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.*; -import java.util.List; - -public class MemberChooser extends DialogWrapper implements TypeSafeDataProvider { - protected Tree myTree; - private DefaultTreeModel myTreeModel; - private JCheckBox myCopyJavadocCheckbox; - private JCheckBox myInsertOverrideAnnotationCheckbox; - - private final ArrayList mySelectedNodes = new ArrayList(); - - private boolean mySorted = false; - private boolean myShowClasses = true; - private boolean myAllowEmptySelection = false; - private boolean myAllowMultiSelection; - private final Project myProject; - private final boolean myIsInsertOverrideVisible; - private final JComponent myHeaderPanel; - - private T[] myElements; - private final HashMap myNodeToParentMap = new HashMap(); - private final HashMap myElementToNodeMap = new HashMap(); - private final ArrayList myContainerNodes = new ArrayList(); - private LinkedHashSet mySelectedElements; - - @NonNls private static final String PROP_SORTED = "MemberChooser.sorted"; - @NonNls private static final String PROP_SHOWCLASSES = "MemberChooser.showClasses"; - @NonNls private static final String PROP_COPYJAVADOC = "MemberChooser.copyJavadoc"; - - public MemberChooser(T[] elements, - boolean allowEmptySelection, - boolean allowMultiSelection, - @NotNull Project project) { - this(elements, allowEmptySelection, allowMultiSelection, project, false); - } - - public MemberChooser(T[] elements, - boolean allowEmptySelection, - boolean allowMultiSelection, - @NotNull Project project, - boolean isInsertOverrideVisible) { - this(elements, allowEmptySelection, allowMultiSelection, project, isInsertOverrideVisible, null); - } - - public MemberChooser(T[] elements, - boolean allowEmptySelection, - boolean allowMultiSelection, - @NotNull Project project, - boolean isInsertOverrideVisible, - JComponent headerPanel - ) { - super(project, true); - myAllowEmptySelection = allowEmptySelection; - myAllowMultiSelection = allowMultiSelection; - myProject = project; - myIsInsertOverrideVisible = isInsertOverrideVisible; - myHeaderPanel = headerPanel; - myTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); - resetElements(elements); - init(); - } - - public void resetElements(T[] elements) { - myElements = elements; - mySelectedNodes.clear(); - myNodeToParentMap.clear(); - myElementToNodeMap.clear(); - myContainerNodes.clear(); - - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - myTreeModel = buildModel(); - } - }); - - myTree.setModel(myTreeModel); - myTree.setRootVisible(false); - - doSort(); - - TreeUtil.expandAll(myTree); - myCopyJavadocCheckbox = new NonFocusableCheckBox(IdeBundle.message("checkbox.copy.javadoc")); - if (myIsInsertOverrideVisible) { - myInsertOverrideAnnotationCheckbox = new NonFocusableCheckBox(IdeBundle.message("checkbox.insert.at.override")); - } - - myTree.doLayout(); - } - - /** - * should be invoked in read action - */ - private DefaultTreeModel buildModel() { - final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); - final Ref count = new Ref(0); - final FactoryMap map = new FactoryMap() { - protected ParentNode create(final MemberChooserObject key) { - ParentNode node = null; - if (key instanceof PsiElementMemberChooserObject) { - final ContainerNode containerNode = new ContainerNode(rootNode, key, count); - node = containerNode; - myContainerNodes.add(containerNode); - } - if (node == null) { - node = new ParentNode(rootNode, key, count); - } - return node; - } - }; - - for (T object : myElements) { - final ParentNode parentNode = map.get(object.getParentNodeDelegate()); - final MemberNode elementNode = new MemberNode(parentNode, object, count); - myNodeToParentMap.put(elementNode, parentNode); - myElementToNodeMap.put(object, elementNode); - } - return new DefaultTreeModel(rootNode); - } - - public void selectElements(ClassMember[] elements) { - ArrayList selectionPaths = new ArrayList(); - for (ClassMember element : elements) { - MemberNode treeNode = myElementToNodeMap.get(element); - if (treeNode != null) { - selectionPaths.add(new TreePath(treeNode.getPath())); - } - } - myTree.setSelectionPaths(selectionPaths.toArray(new TreePath[selectionPaths.size()])); - } - - - protected Action[] createActions() { - if (myAllowEmptySelection) { - return new Action[]{getOKAction(), new SelectNoneAction(), getCancelAction()}; - } - else { - return new Action[]{getOKAction(), getCancelAction()}; - } - } - - protected void doHelpAction() { - } - - protected List customizeOptionsPanel() { - final SmartList list = new SmartList(); - - if (myIsInsertOverrideVisible) { - CodeStyleSettings styleSettings = CodeStyleSettingsManager.getInstance(myProject).getCurrentSettings(); - myInsertOverrideAnnotationCheckbox.setSelected(styleSettings.INSERT_OVERRIDE_ANNOTATION); - list.add(myInsertOverrideAnnotationCheckbox); - } - - myCopyJavadocCheckbox.setSelected(PropertiesComponent.getInstance().isTrueValue(PROP_COPYJAVADOC)); - list.add(myCopyJavadocCheckbox); - return list; - } - - protected JComponent createSouthPanel() { - JPanel panel = new JPanel(new GridBagLayout()); - - JPanel optionsPanel = new JPanel(new VerticalFlowLayout()); - for (final JComponent component : customizeOptionsPanel()) { - optionsPanel.add(component); - } - - panel.add( - optionsPanel, - new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, - new Insets(0, 0, 0, 5), 0, 0) - ); - - if (myElements == null || myElements.length == 0) { - setOKActionEnabled(false); - } - panel.add( - super.createSouthPanel(), - new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.SOUTH, GridBagConstraints.NONE, - new Insets(0, 0, 0, 0), 0, 0) - ); - return panel; - } - - @Override - protected JComponent createNorthPanel() { - return myHeaderPanel; - } - - protected JComponent createCenterPanel() { - JPanel panel = new JPanel(new BorderLayout()); - - // Toolbar - - DefaultActionGroup group = new DefaultActionGroup(); - - fillToolbarActions(group); - - group.addSeparator(); - - ExpandAllAction expandAllAction = new ExpandAllAction(); - expandAllAction.registerCustomShortcutSet( - new CustomShortcutSet( - KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)), - myTree); - group.add(expandAllAction); - - CollapseAllAction collapseAllAction = new CollapseAllAction(); - collapseAllAction.registerCustomShortcutSet( - new CustomShortcutSet( - KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)), - myTree); - group.add(collapseAllAction); - - panel.add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(), - BorderLayout.NORTH); - - // Tree - - myTree.setCellRenderer(new ColoredTreeCellRenderer() { - public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, - boolean leaf, int row, boolean hasFocus) { - if (value instanceof ElementNode) { - ((ElementNode) value).getDelegate().renderTreeNode(this, tree); - } - } - } -); - UIUtil.setLineStyleAngled(myTree); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(true); - myTree.addKeyListener(new TreeKeyListener()); - myTree.addTreeSelectionListener(new MyTreeSelectionListener()); - - if (!myAllowMultiSelection) { - myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); - } - - if (getRootNode().getChildCount() > 0) { - myTree.expandRow(0); - myTree.setSelectionRow(1); - } - TreeUtil.expandAll(myTree); - final TreeSpeedSearch treeSpeedSearch = new TreeSpeedSearch(myTree, new Convertor() { - @Nullable - public String convert(TreePath path) { - final ElementNode lastPathComponent = (ElementNode)path.getLastPathComponent(); - if (lastPathComponent == null) return null; - final MemberChooserObject delegate = lastPathComponent.getDelegate(); - return delegate.getText(); - } - }); - - treeSpeedSearch.addChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - myTree.repaint(); // to update match highlighting - } - }); - - myTree.addMouseListener( - new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - if (myTree.getPathForLocation(e.getX(), e.getY()) != null) { - doOKAction(); - } - } - } - } - ); - TreeUtil.installActions(myTree); - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); - scrollPane.setPreferredSize(new Dimension(350, 450)); - panel.add(scrollPane, BorderLayout.CENTER); - - return panel; - } - - protected void fillToolbarActions(DefaultActionGroup group) { - SortEmAction sortAction = new SortEmAction(); - sortAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.ALT_MASK)), myTree); - setSorted(PropertiesComponent.getInstance().isTrueValue(PROP_SORTED)); - group.add(sortAction); - - ShowContainersAction showContainersAction = getShowContainersAction(); - showContainersAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.ALT_MASK)), myTree); - setShowClasses(PropertiesComponent.getInstance().isTrueValue(PROP_SHOWCLASSES)); - group.add(showContainersAction); - } - - protected String getDimensionServiceKey() { - return "#com.intellij.ide.util.MemberChooser"; - } - - public JComponent getPreferredFocusedComponent() { - return myTree; - } - - @Nullable - private LinkedHashSet getSelectedElementsList() { - return getExitCode() == OK_EXIT_CODE ? mySelectedElements : null; - } - - @Nullable - public List getSelectedElements() { - final LinkedHashSet list = getSelectedElementsList(); - return list == null ? null : new ArrayList(list); - } - - @Nullable - public T[] getSelectedElements(T[] a) { - LinkedHashSet list = getSelectedElementsList(); - if (list == null) return null; - return list.toArray(a); - } - - protected final boolean areElementsSelected() { - return mySelectedElements != null && !mySelectedElements.isEmpty(); - } - - public void setCopyJavadocVisible(boolean state) { - myCopyJavadocCheckbox.setVisible(state); - } - - public boolean isCopyJavadoc() { - return myCopyJavadocCheckbox.isSelected(); - } - - public boolean isInsertOverrideAnnotation () { - return myIsInsertOverrideVisible && myInsertOverrideAnnotationCheckbox.isSelected(); - } - - private boolean isSorted() { - return mySorted; - } - - private void setSorted(boolean sorted) { - if (mySorted == sorted) return; - mySorted = sorted; - doSort(); - } - - private void doSort() { - Pair> pair = storeSelection(); - - Enumeration children = getRootNodeChildren(); - while (children.hasMoreElements()) { - ParentNode classNode = children.nextElement(); - sortNode(classNode, mySorted); - myTreeModel.nodeStructureChanged(classNode); - } - - restoreSelection(pair); - } - - private static void sortNode(ParentNode node, boolean sorted) { - ArrayList arrayList = new ArrayList(); - Enumeration children = node.children(); - while (children.hasMoreElements()) { - arrayList.add(children.nextElement()); - } - - Collections.sort(arrayList, sorted ? new AlphaComparator() : new OrderComparator()); - - replaceChildren(node, arrayList); - } - - private static void replaceChildren(final DefaultMutableTreeNode node, final Collection arrayList) { - node.removeAllChildren(); - for (ElementNode child : arrayList) { - node.add(child); - } - } - - private void setShowClasses(boolean showClasses) { - myShowClasses = showClasses; - - Pair> selection = storeSelection(); - - DefaultMutableTreeNode root = getRootNode(); - if (!myShowClasses || myContainerNodes.isEmpty()) { - List otherObjects = new ArrayList(); - Enumeration children = getRootNodeChildren(); - ParentNode newRoot = new ParentNode(null, new MemberChooserObjectBase(getAllContainersNodeName()), new Ref(0)); - while (children.hasMoreElements()) { - final ParentNode nextElement = children.nextElement(); - if (nextElement instanceof ContainerNode) { - final ContainerNode containerNode = (ContainerNode)nextElement; - Enumeration memberNodes = containerNode.children(); - List memberNodesList = new ArrayList(); - while (memberNodes.hasMoreElements()) { - memberNodesList.add(memberNodes.nextElement()); - } - for (MemberNode memberNode : memberNodesList) { - newRoot.add(memberNode); - } - } else { - otherObjects.add(nextElement); - } - } - replaceChildren(root, otherObjects); - sortNode(newRoot, mySorted); - if (newRoot.children().hasMoreElements()) root.add(newRoot); - } - else { - Enumeration children = getRootNodeChildren(); - if (children.hasMoreElements()) { - ParentNode allClassesNode = children.nextElement(); - Enumeration memberNodes = allClassesNode.children(); - ArrayList arrayList = new ArrayList(); - while (memberNodes.hasMoreElements()) { - arrayList.add(memberNodes.nextElement()); - } - for (MemberNode memberNode : arrayList) { - myNodeToParentMap.get(memberNode).add(memberNode); - } - } - replaceChildren(root, myContainerNodes); - } - myTreeModel.nodeStructureChanged(root); - - TreeUtil.expandAll(myTree); - - restoreSelection(selection); - } - - protected String getAllContainersNodeName() { - return IdeBundle.message("node.memberchooser.all.classes"); - } - - private Enumeration getRootNodeChildren() { - return getRootNode().children(); - } - - private DefaultMutableTreeNode getRootNode() { - return (DefaultMutableTreeNode)myTreeModel.getRoot(); - } - - private Pair> storeSelection() { - List selectedNodes = new ArrayList(); - TreePath[] paths = myTree.getSelectionPaths(); - if (paths != null) { - for (TreePath path : paths) { - selectedNodes.add((ElementNode)path.getLastPathComponent()); - } - } - TreePath leadSelectionPath = myTree.getLeadSelectionPath(); - return Pair.create(leadSelectionPath != null ? (ElementNode)leadSelectionPath.getLastPathComponent() : null, selectedNodes); - } - - - private void restoreSelection(Pair> pair) { - List selectedNodes = pair.second; - - DefaultMutableTreeNode root = getRootNode(); - - ArrayList toSelect = new ArrayList(); - for (ElementNode node : selectedNodes) { - if (root.isNodeDescendant(node)) { - toSelect.add(new TreePath(node.getPath())); - } - } - - if (!toSelect.isEmpty()) { - myTree.setSelectionPaths(toSelect.toArray(new TreePath[toSelect.size()])); - } - - ElementNode leadNode = pair.first; - if (leadNode != null) { - myTree.setLeadSelectionPath(new TreePath(leadNode.getPath())); - } - } - - public void dispose() { - PropertiesComponent instance = PropertiesComponent.getInstance(); - instance.setValue(PROP_SORTED, Boolean.toString(isSorted())); - instance.setValue(PROP_SHOWCLASSES, Boolean.toString(myShowClasses)); - instance.setValue(PROP_COPYJAVADOC, Boolean.toString(myCopyJavadocCheckbox.isSelected())); - - getContentPane().removeAll(); - mySelectedNodes.clear(); - myElements = null; - super.dispose(); - } - - public void calcData(final DataKey key, final DataSink sink) { - if (key.equals(LangDataKeys.PSI_ELEMENT)) { - if (mySelectedElements != null && !mySelectedElements.isEmpty()) { - T selectedElement = mySelectedElements.iterator().next(); - if (selectedElement instanceof ClassMemberWithElement) { - sink.put(LangDataKeys.PSI_ELEMENT, ((ClassMemberWithElement) selectedElement).getElement()); - } - } - } - } - - private class MyTreeSelectionListener implements TreeSelectionListener { - public void valueChanged(TreeSelectionEvent e) { - TreePath[] paths = e.getPaths(); - if (paths == null) return; - for (int i = 0; i < paths.length; i++) { - Object node = paths[i].getLastPathComponent(); - if (node instanceof MemberNode) { - final MemberNode memberNode = (MemberNode)node; - if (e.isAddedPath(i)) { - if (!mySelectedNodes.contains(memberNode)) { - mySelectedNodes.add(memberNode); - } - } - else { - mySelectedNodes.remove(memberNode); - } - } - } - mySelectedElements = new LinkedHashSet(); - for (MemberNode selectedNode : mySelectedNodes) { - mySelectedElements.add((T)selectedNode.getDelegate()); - } - } - } - - private abstract static class ElementNode extends DefaultMutableTreeNode { - private final int myOrder; - private final MemberChooserObject myDelegate; - - public ElementNode(@Nullable DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref order) { - myOrder = order.get(); - order.set(myOrder + 1); - myDelegate = delegate; - if (parent != null) { - parent.add(this); - } - } - - public MemberChooserObject getDelegate() { - return myDelegate; - } - - public int getOrder() { - return myOrder; - } - } - - private static class MemberNode extends ElementNode { - public MemberNode(ParentNode parent, ClassMember delegate, Ref order) { - super(parent, delegate, order); - } - } - - private static class ParentNode extends ElementNode { - public ParentNode(@Nullable DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref order) { - super(parent, delegate, order); - } - } - - private static class ContainerNode extends ParentNode { - public ContainerNode(DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref order) { - super(parent, delegate, order); - } - } - - private class SelectNoneAction extends AbstractAction { - public SelectNoneAction() { - super(IdeBundle.message("action.select.none")); - } - - public void actionPerformed(ActionEvent e) { - myTree.clearSelection(); - doOKAction(); - } - } - - private class TreeKeyListener extends KeyAdapter { - public void keyPressed(KeyEvent e) { - TreePath path = myTree.getLeadSelectionPath(); - if (path == null) return; - final Object lastComponent = path.getLastPathComponent(); - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - if (lastComponent instanceof ParentNode) return; - doOKAction(); - e.consume(); - } - else if (e.getKeyCode() == KeyEvent.VK_INSERT) { - if (lastComponent instanceof ElementNode) { - final ElementNode node = (ElementNode)lastComponent; - if (!mySelectedNodes.contains(node)) { - if (node.getNextNode() != null) { - myTree.setSelectionPath(new TreePath(node.getNextNode().getPath())); - } - } - else { - if (node.getNextNode() != null) { - myTree.removeSelectionPath(new TreePath(node.getPath())); - myTree.setSelectionPath(new TreePath(node.getNextNode().getPath())); - myTree.repaint(); - } - } - e.consume(); - } - } - } - } - - private class SortEmAction extends ToggleAction { - public SortEmAction() { - super(IdeBundle.message("action.sort.alphabetically"), - IdeBundle.message("action.sort.alphabetically"), IconLoader.getIcon("/objectBrowser/sorted.png")); - } - - public boolean isSelected(AnActionEvent event) { - return isSorted(); - } - - public void setSelected(AnActionEvent event, boolean flag) { - setSorted(flag); - } - } - - protected ShowContainersAction getShowContainersAction() { - return new ShowContainersAction(IdeBundle.message("action.show.classes"), Icons.CLASS_ICON); - } - - protected class ShowContainersAction extends ToggleAction { - public ShowContainersAction(final String text, final Icon icon) { - super(text, text, icon); - } - - public boolean isSelected(AnActionEvent event) { - return myShowClasses; - } - - public void setSelected(AnActionEvent event, boolean flag) { - setShowClasses(flag); - } - - public void update(AnActionEvent e) { - super.update(e); - Presentation presentation = e.getPresentation(); - presentation.setEnabled(myContainerNodes.size() > 1); - } - } - - private class ExpandAllAction extends AnAction { - public ExpandAllAction() { - super(IdeBundle.message("action.expand.all"), IdeBundle.message("action.expand.all"), - IconLoader.getIcon("/actions/expandall.png")); - } - - public void actionPerformed(AnActionEvent e) { - TreeUtil.expandAll(myTree); - } - } - - private class CollapseAllAction extends AnAction { - public CollapseAllAction() { - super(IdeBundle.message("action.collapse.all"), IdeBundle.message("action.collapse.all"), - IconLoader.getIcon("/actions/collapseall.png")); - } - - public void actionPerformed(AnActionEvent e) { - TreeUtil.collapseAll(myTree, 1); - } - } - - private static class AlphaComparator implements Comparator { - public int compare(ElementNode n1, ElementNode n2) { - return n1.getDelegate().getText().compareToIgnoreCase(n2.getDelegate().getText()); - } - } - - private static class OrderComparator implements Comparator { - public int compare(ElementNode n1, ElementNode n2) { - if (n1.getDelegate() instanceof ClassMemberWithElement - && n2.getDelegate() instanceof ClassMemberWithElement) { - return ((ClassMemberWithElement)n1.getDelegate()).getElement().getTextOffset() - - ((ClassMemberWithElement)n2.getDelegate()).getElement().getTextOffset(); - } - return n1.getOrder() - n2.getOrder(); - } - } -} +/* + * 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.ide.util; + +import com.intellij.codeInsight.generation.*; +import com.intellij.ide.IdeBundle; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.VerticalFlowLayout; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.NonFocusableCheckBox; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.TreeSpeedSearch; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.Icons; +import com.intellij.util.SmartList; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.FactoryMap; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.*; +import java.util.List; + +public class MemberChooser extends DialogWrapper implements TypeSafeDataProvider { + protected Tree myTree; + private DefaultTreeModel myTreeModel; + private JCheckBox myCopyJavadocCheckbox; + private JCheckBox myInsertOverrideAnnotationCheckbox; + + private final ArrayList mySelectedNodes = new ArrayList(); + + private boolean mySorted = false; + private boolean myShowClasses = true; + private boolean myAllowEmptySelection = false; + private boolean myAllowMultiSelection; + private final Project myProject; + private final boolean myIsInsertOverrideVisible; + private final JComponent myHeaderPanel; + + private T[] myElements; + private final HashMap myNodeToParentMap = new HashMap(); + private final HashMap myElementToNodeMap = new HashMap(); + private final ArrayList myContainerNodes = new ArrayList(); + private LinkedHashSet mySelectedElements; + + @NonNls private static final String PROP_SORTED = "MemberChooser.sorted"; + @NonNls private static final String PROP_SHOWCLASSES = "MemberChooser.showClasses"; + @NonNls private static final String PROP_COPYJAVADOC = "MemberChooser.copyJavadoc"; + + public MemberChooser(T[] elements, + boolean allowEmptySelection, + boolean allowMultiSelection, + @NotNull Project project) { + this(elements, allowEmptySelection, allowMultiSelection, project, false); + } + + public MemberChooser(T[] elements, + boolean allowEmptySelection, + boolean allowMultiSelection, + @NotNull Project project, + boolean isInsertOverrideVisible) { + this(elements, allowEmptySelection, allowMultiSelection, project, isInsertOverrideVisible, null); + } + + public MemberChooser(T[] elements, + boolean allowEmptySelection, + boolean allowMultiSelection, + @NotNull Project project, + boolean isInsertOverrideVisible, + JComponent headerPanel + ) { + super(project, true); + myAllowEmptySelection = allowEmptySelection; + myAllowMultiSelection = allowMultiSelection; + myProject = project; + myIsInsertOverrideVisible = isInsertOverrideVisible; + myHeaderPanel = headerPanel; + myTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); + resetElements(elements); + init(); + } + + public void resetElements(T[] elements) { + myElements = elements; + mySelectedNodes.clear(); + myNodeToParentMap.clear(); + myElementToNodeMap.clear(); + myContainerNodes.clear(); + + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + myTreeModel = buildModel(); + } + }); + + myTree.setModel(myTreeModel); + myTree.setRootVisible(false); + + doSort(); + + TreeUtil.expandAll(myTree); + myCopyJavadocCheckbox = new NonFocusableCheckBox(IdeBundle.message("checkbox.copy.javadoc")); + if (myIsInsertOverrideVisible) { + myInsertOverrideAnnotationCheckbox = new NonFocusableCheckBox(IdeBundle.message("checkbox.insert.at.override")); + } + + myTree.doLayout(); + } + + /** + * should be invoked in read action + */ + private DefaultTreeModel buildModel() { + final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); + final Ref count = new Ref(0); + final FactoryMap map = new FactoryMap() { + protected ParentNode create(final MemberChooserObject key) { + ParentNode node = null; + if (key instanceof PsiElementMemberChooserObject) { + final ContainerNode containerNode = new ContainerNode(rootNode, key, count); + node = containerNode; + myContainerNodes.add(containerNode); + } + if (node == null) { + node = new ParentNode(rootNode, key, count); + } + return node; + } + }; + + for (T object : myElements) { + final ParentNode parentNode = map.get(object.getParentNodeDelegate()); + final MemberNode elementNode = new MemberNode(parentNode, object, count); + myNodeToParentMap.put(elementNode, parentNode); + myElementToNodeMap.put(object, elementNode); + } + return new DefaultTreeModel(rootNode); + } + + public void selectElements(ClassMember[] elements) { + ArrayList selectionPaths = new ArrayList(); + for (ClassMember element : elements) { + MemberNode treeNode = myElementToNodeMap.get(element); + if (treeNode != null) { + selectionPaths.add(new TreePath(treeNode.getPath())); + } + } + myTree.setSelectionPaths(selectionPaths.toArray(new TreePath[selectionPaths.size()])); + } + + + protected Action[] createActions() { + if (myAllowEmptySelection) { + return new Action[]{getOKAction(), new SelectNoneAction(), getCancelAction()}; + } + else { + return new Action[]{getOKAction(), getCancelAction()}; + } + } + + protected void doHelpAction() { + } + + protected List customizeOptionsPanel() { + final SmartList list = new SmartList(); + + if (myIsInsertOverrideVisible) { + CodeStyleSettings styleSettings = CodeStyleSettingsManager.getInstance(myProject).getCurrentSettings(); + myInsertOverrideAnnotationCheckbox.setSelected(styleSettings.INSERT_OVERRIDE_ANNOTATION); + list.add(myInsertOverrideAnnotationCheckbox); + } + + myCopyJavadocCheckbox.setSelected(PropertiesComponent.getInstance().isTrueValue(PROP_COPYJAVADOC)); + list.add(myCopyJavadocCheckbox); + return list; + } + + protected JComponent createSouthPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + + JPanel optionsPanel = new JPanel(new VerticalFlowLayout()); + for (final JComponent component : customizeOptionsPanel()) { + optionsPanel.add(component); + } + + panel.add( + optionsPanel, + new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, + new Insets(0, 0, 0, 5), 0, 0) + ); + + if (myElements == null || myElements.length == 0) { + setOKActionEnabled(false); + } + panel.add( + super.createSouthPanel(), + new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.SOUTH, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0) + ); + return panel; + } + + @Override + protected JComponent createNorthPanel() { + return myHeaderPanel; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout()); + + // Toolbar + + DefaultActionGroup group = new DefaultActionGroup(); + + fillToolbarActions(group); + + group.addSeparator(); + + ExpandAllAction expandAllAction = new ExpandAllAction(); + expandAllAction.registerCustomShortcutSet( + new CustomShortcutSet( + KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)), + myTree); + group.add(expandAllAction); + + CollapseAllAction collapseAllAction = new CollapseAllAction(); + collapseAllAction.registerCustomShortcutSet( + new CustomShortcutSet( + KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)), + myTree); + group.add(collapseAllAction); + + panel.add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(), + BorderLayout.NORTH); + + // Tree + + myTree.setCellRenderer(new ColoredTreeCellRenderer() { + public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + if (value instanceof ElementNode) { + ((ElementNode) value).getDelegate().renderTreeNode(this, tree); + } + } + } +); + UIUtil.setLineStyleAngled(myTree); + myTree.setRootVisible(false); + myTree.setShowsRootHandles(true); + myTree.addKeyListener(new TreeKeyListener()); + myTree.addTreeSelectionListener(new MyTreeSelectionListener()); + + if (!myAllowMultiSelection) { + myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + } + + if (getRootNode().getChildCount() > 0) { + myTree.expandRow(0); + myTree.setSelectionRow(1); + } + TreeUtil.expandAll(myTree); + final TreeSpeedSearch treeSpeedSearch = new TreeSpeedSearch(myTree, new Convertor() { + @Nullable + public String convert(TreePath path) { + final ElementNode lastPathComponent = (ElementNode)path.getLastPathComponent(); + if (lastPathComponent == null) return null; + final MemberChooserObject delegate = lastPathComponent.getDelegate(); + return delegate.getText(); + } + }); + + treeSpeedSearch.addChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + myTree.repaint(); // to update match highlighting + } + }); + + myTree.addMouseListener( + new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (myTree.getPathForLocation(e.getX(), e.getY()) != null) { + doOKAction(); + } + } + } + } + ); + TreeUtil.installActions(myTree); + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); + scrollPane.setPreferredSize(new Dimension(350, 450)); + panel.add(scrollPane, BorderLayout.CENTER); + + return panel; + } + + protected void fillToolbarActions(DefaultActionGroup group) { + SortEmAction sortAction = new SortEmAction(); + sortAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.ALT_MASK)), myTree); + setSorted(PropertiesComponent.getInstance().isTrueValue(PROP_SORTED)); + group.add(sortAction); + + ShowContainersAction showContainersAction = getShowContainersAction(); + showContainersAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.ALT_MASK)), myTree); + setShowClasses(PropertiesComponent.getInstance().isTrueValue(PROP_SHOWCLASSES)); + group.add(showContainersAction); + } + + protected String getDimensionServiceKey() { + return "#com.intellij.ide.util.MemberChooser"; + } + + public JComponent getPreferredFocusedComponent() { + return myTree; + } + + @Nullable + private LinkedHashSet getSelectedElementsList() { + return getExitCode() == OK_EXIT_CODE ? mySelectedElements : null; + } + + @Nullable + public List getSelectedElements() { + final LinkedHashSet list = getSelectedElementsList(); + return list == null ? null : new ArrayList(list); + } + + @Nullable + public T[] getSelectedElements(T[] a) { + LinkedHashSet list = getSelectedElementsList(); + if (list == null) return null; + return list.toArray(a); + } + + protected final boolean areElementsSelected() { + return mySelectedElements != null && !mySelectedElements.isEmpty(); + } + + public void setCopyJavadocVisible(boolean state) { + myCopyJavadocCheckbox.setVisible(state); + } + + public boolean isCopyJavadoc() { + return myCopyJavadocCheckbox.isSelected(); + } + + public boolean isInsertOverrideAnnotation () { + return myIsInsertOverrideVisible && myInsertOverrideAnnotationCheckbox.isSelected(); + } + + private boolean isSorted() { + return mySorted; + } + + private void setSorted(boolean sorted) { + if (mySorted == sorted) return; + mySorted = sorted; + doSort(); + } + + private void doSort() { + Pair> pair = storeSelection(); + + Enumeration children = getRootNodeChildren(); + while (children.hasMoreElements()) { + ParentNode classNode = children.nextElement(); + sortNode(classNode, mySorted); + myTreeModel.nodeStructureChanged(classNode); + } + + restoreSelection(pair); + } + + private static void sortNode(ParentNode node, boolean sorted) { + ArrayList arrayList = new ArrayList(); + Enumeration children = node.children(); + while (children.hasMoreElements()) { + arrayList.add(children.nextElement()); + } + + Collections.sort(arrayList, sorted ? new AlphaComparator() : new OrderComparator()); + + replaceChildren(node, arrayList); + } + + private static void replaceChildren(final DefaultMutableTreeNode node, final Collection arrayList) { + node.removeAllChildren(); + for (ElementNode child : arrayList) { + node.add(child); + } + } + + private void setShowClasses(boolean showClasses) { + myShowClasses = showClasses; + + Pair> selection = storeSelection(); + + DefaultMutableTreeNode root = getRootNode(); + if (!myShowClasses || myContainerNodes.isEmpty()) { + List otherObjects = new ArrayList(); + Enumeration children = getRootNodeChildren(); + ParentNode newRoot = new ParentNode(null, new MemberChooserObjectBase(getAllContainersNodeName()), new Ref(0)); + while (children.hasMoreElements()) { + final ParentNode nextElement = children.nextElement(); + if (nextElement instanceof ContainerNode) { + final ContainerNode containerNode = (ContainerNode)nextElement; + Enumeration memberNodes = containerNode.children(); + List memberNodesList = new ArrayList(); + while (memberNodes.hasMoreElements()) { + memberNodesList.add(memberNodes.nextElement()); + } + for (MemberNode memberNode : memberNodesList) { + newRoot.add(memberNode); + } + } else { + otherObjects.add(nextElement); + } + } + replaceChildren(root, otherObjects); + sortNode(newRoot, mySorted); + if (newRoot.children().hasMoreElements()) root.add(newRoot); + } + else { + Enumeration children = getRootNodeChildren(); + if (children.hasMoreElements()) { + ParentNode allClassesNode = children.nextElement(); + Enumeration memberNodes = allClassesNode.children(); + ArrayList arrayList = new ArrayList(); + while (memberNodes.hasMoreElements()) { + arrayList.add(memberNodes.nextElement()); + } + for (MemberNode memberNode : arrayList) { + myNodeToParentMap.get(memberNode).add(memberNode); + } + } + replaceChildren(root, myContainerNodes); + } + myTreeModel.nodeStructureChanged(root); + + TreeUtil.expandAll(myTree); + + restoreSelection(selection); + } + + protected String getAllContainersNodeName() { + return IdeBundle.message("node.memberchooser.all.classes"); + } + + private Enumeration getRootNodeChildren() { + return getRootNode().children(); + } + + private DefaultMutableTreeNode getRootNode() { + return (DefaultMutableTreeNode)myTreeModel.getRoot(); + } + + private Pair> storeSelection() { + List selectedNodes = new ArrayList(); + TreePath[] paths = myTree.getSelectionPaths(); + if (paths != null) { + for (TreePath path : paths) { + selectedNodes.add((ElementNode)path.getLastPathComponent()); + } + } + TreePath leadSelectionPath = myTree.getLeadSelectionPath(); + return Pair.create(leadSelectionPath != null ? (ElementNode)leadSelectionPath.getLastPathComponent() : null, selectedNodes); + } + + + private void restoreSelection(Pair> pair) { + List selectedNodes = pair.second; + + DefaultMutableTreeNode root = getRootNode(); + + ArrayList toSelect = new ArrayList(); + for (ElementNode node : selectedNodes) { + if (root.isNodeDescendant(node)) { + toSelect.add(new TreePath(node.getPath())); + } + } + + if (!toSelect.isEmpty()) { + myTree.setSelectionPaths(toSelect.toArray(new TreePath[toSelect.size()])); + } + + ElementNode leadNode = pair.first; + if (leadNode != null) { + myTree.setLeadSelectionPath(new TreePath(leadNode.getPath())); + } + } + + public void dispose() { + PropertiesComponent instance = PropertiesComponent.getInstance(); + instance.setValue(PROP_SORTED, Boolean.toString(isSorted())); + instance.setValue(PROP_SHOWCLASSES, Boolean.toString(myShowClasses)); + instance.setValue(PROP_COPYJAVADOC, Boolean.toString(myCopyJavadocCheckbox.isSelected())); + + getContentPane().removeAll(); + mySelectedNodes.clear(); + myElements = null; + super.dispose(); + } + + public void calcData(final DataKey key, final DataSink sink) { + if (key.equals(LangDataKeys.PSI_ELEMENT)) { + if (mySelectedElements != null && !mySelectedElements.isEmpty()) { + T selectedElement = mySelectedElements.iterator().next(); + if (selectedElement instanceof ClassMemberWithElement) { + sink.put(LangDataKeys.PSI_ELEMENT, ((ClassMemberWithElement) selectedElement).getElement()); + } + } + } + } + + private class MyTreeSelectionListener implements TreeSelectionListener { + public void valueChanged(TreeSelectionEvent e) { + TreePath[] paths = e.getPaths(); + if (paths == null) return; + for (int i = 0; i < paths.length; i++) { + Object node = paths[i].getLastPathComponent(); + if (node instanceof MemberNode) { + final MemberNode memberNode = (MemberNode)node; + if (e.isAddedPath(i)) { + if (!mySelectedNodes.contains(memberNode)) { + mySelectedNodes.add(memberNode); + } + } + else { + mySelectedNodes.remove(memberNode); + } + } + } + mySelectedElements = new LinkedHashSet(); + for (MemberNode selectedNode : mySelectedNodes) { + mySelectedElements.add((T)selectedNode.getDelegate()); + } + } + } + + private abstract static class ElementNode extends DefaultMutableTreeNode { + private final int myOrder; + private final MemberChooserObject myDelegate; + + public ElementNode(@Nullable DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref order) { + myOrder = order.get(); + order.set(myOrder + 1); + myDelegate = delegate; + if (parent != null) { + parent.add(this); + } + } + + public MemberChooserObject getDelegate() { + return myDelegate; + } + + public int getOrder() { + return myOrder; + } + } + + private static class MemberNode extends ElementNode { + public MemberNode(ParentNode parent, ClassMember delegate, Ref order) { + super(parent, delegate, order); + } + } + + private static class ParentNode extends ElementNode { + public ParentNode(@Nullable DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref order) { + super(parent, delegate, order); + } + } + + private static class ContainerNode extends ParentNode { + public ContainerNode(DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref order) { + super(parent, delegate, order); + } + } + + private class SelectNoneAction extends AbstractAction { + public SelectNoneAction() { + super(IdeBundle.message("action.select.none")); + } + + public void actionPerformed(ActionEvent e) { + myTree.clearSelection(); + doOKAction(); + } + } + + private class TreeKeyListener extends KeyAdapter { + public void keyPressed(KeyEvent e) { + TreePath path = myTree.getLeadSelectionPath(); + if (path == null) return; + final Object lastComponent = path.getLastPathComponent(); + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (lastComponent instanceof ParentNode) return; + doOKAction(); + e.consume(); + } + else if (e.getKeyCode() == KeyEvent.VK_INSERT) { + if (lastComponent instanceof ElementNode) { + final ElementNode node = (ElementNode)lastComponent; + if (!mySelectedNodes.contains(node)) { + if (node.getNextNode() != null) { + myTree.setSelectionPath(new TreePath(node.getNextNode().getPath())); + } + } + else { + if (node.getNextNode() != null) { + myTree.removeSelectionPath(new TreePath(node.getPath())); + myTree.setSelectionPath(new TreePath(node.getNextNode().getPath())); + myTree.repaint(); + } + } + e.consume(); + } + } + } + } + + private class SortEmAction extends ToggleAction { + public SortEmAction() { + super(IdeBundle.message("action.sort.alphabetically"), + IdeBundle.message("action.sort.alphabetically"), IconLoader.getIcon("/objectBrowser/sorted.png")); + } + + public boolean isSelected(AnActionEvent event) { + return isSorted(); + } + + public void setSelected(AnActionEvent event, boolean flag) { + setSorted(flag); + } + } + + protected ShowContainersAction getShowContainersAction() { + return new ShowContainersAction(IdeBundle.message("action.show.classes"), Icons.CLASS_ICON); + } + + protected class ShowContainersAction extends ToggleAction { + public ShowContainersAction(final String text, final Icon icon) { + super(text, text, icon); + } + + public boolean isSelected(AnActionEvent event) { + return myShowClasses; + } + + public void setSelected(AnActionEvent event, boolean flag) { + setShowClasses(flag); + } + + public void update(AnActionEvent e) { + super.update(e); + Presentation presentation = e.getPresentation(); + presentation.setEnabled(myContainerNodes.size() > 1); + } + } + + private class ExpandAllAction extends AnAction { + public ExpandAllAction() { + super(IdeBundle.message("action.expand.all"), IdeBundle.message("action.expand.all"), + IconLoader.getIcon("/actions/expandall.png")); + } + + public void actionPerformed(AnActionEvent e) { + TreeUtil.expandAll(myTree); + } + } + + private class CollapseAllAction extends AnAction { + public CollapseAllAction() { + super(IdeBundle.message("action.collapse.all"), IdeBundle.message("action.collapse.all"), + IconLoader.getIcon("/actions/collapseall.png")); + } + + public void actionPerformed(AnActionEvent e) { + TreeUtil.collapseAll(myTree, 1); + } + } + + private static class AlphaComparator implements Comparator { + public int compare(ElementNode n1, ElementNode n2) { + return n1.getDelegate().getText().compareToIgnoreCase(n2.getDelegate().getText()); + } + } + + private static class OrderComparator implements Comparator { + public int compare(ElementNode n1, ElementNode n2) { + if (n1.getDelegate() instanceof ClassMemberWithElement + && n2.getDelegate() instanceof ClassMemberWithElement) { + return ((ClassMemberWithElement)n1.getDelegate()).getElement().getTextOffset() + - ((ClassMemberWithElement)n2.getDelegate()).getElement().getTextOffset(); + } + return n1.getOrder() - n2.getOrder(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java index cc474c2c63a8..11a5f9aa547c 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java @@ -1,1397 +1,1397 @@ -/* - * 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.ide.util.gotoByName; - -import com.intellij.Patches; -import com.intellij.ide.IdeBundle; -import com.intellij.ide.actions.CopyReferenceAction; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.MnemonicHelper; -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.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.*; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.psi.PsiElement; -import com.intellij.psi.codeStyle.NameUtil; -import com.intellij.psi.statistics.StatisticsInfo; -import com.intellij.psi.statistics.StatisticsManager; -import com.intellij.psi.util.proximity.PsiProximityComparator; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.ListScrollingUtil; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.components.JBList; -import com.intellij.ui.popup.PopupOwner; -import com.intellij.ui.popup.PopupUpdateProcessor; -import com.intellij.util.Alarm; -import com.intellij.util.Function; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.diff.Diff; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.text.DefaultEditorKit; -import java.awt.*; -import java.awt.event.*; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.util.*; -import java.util.List; - -public abstract class ChooseByNameBase { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.gotoByName.ChooseByNameBase"); - - protected final Project myProject; - protected final ChooseByNameModel myModel; - protected final String myInitialText; - private boolean myPreselectInitialText; - private final Reference myContext; - - protected Component myPreviouslyFocusedComponent; - - protected JPanelProvider myTextFieldPanel;// Located in the layered pane - protected MyTextField myTextField; - private JPanel myCardContainer; - private CardLayout myCard; - protected JCheckBox myCheckBox; - /** - * the tool area of the popup, it is just after card box - */ - protected JComponent myToolArea; - - protected JScrollPane myListScrollPane; // Located in the layered pane - protected JList myList; - private DefaultListModel myListModel; - private List> myHistory; - private List> myFuture; - - protected ChooseByNamePopupComponent.Callback myActionListener; - - protected final Alarm myAlarm = new Alarm(); - - private final ListUpdater myListUpdater = new ListUpdater(); - - private volatile boolean myListIsUpToDate = false; - protected boolean myDisposedFlag = false; - private ActionCallback myPosponedOkAction; - - private final String[][] myNames = new String[2][]; - private CalcElementsThread myCalcElementsThread; - private static int VISIBLE_LIST_SIZE_LIMIT = 10; - private static final int MAXIMUM_LIST_SIZE_LIMIT = 30; - private int myMaximumListSizeLimit = MAXIMUM_LIST_SIZE_LIMIT; - @NonNls private static final String NOT_FOUND_IN_PROJECT_CARD = "syslib"; - @NonNls private static final String NOT_FOUND_CARD = "nfound"; - @NonNls private static final String CHECK_BOX_CARD = "chkbox"; - @NonNls private static final String SEARCHING_CARD = "searching"; - private static final int REBUILD_DELAY = 300; - - private final Alarm myHideAlarm = new Alarm(); - private boolean myShowListAfterCompletionKeyStroke = false; - protected JBPopup myTextPopup; - protected JBPopup myDropdownPopup; - - private static class MatchesComparator implements Comparator { - private final String myOriginalPattern; - - private MatchesComparator(final String originalPattern) { - myOriginalPattern = originalPattern.trim(); - } - - public int compare(final String a, final String b) { - boolean aStarts = a.startsWith(myOriginalPattern); - boolean bStarts = b.startsWith(myOriginalPattern); - if (aStarts && bStarts) return a.compareToIgnoreCase(b); - if (aStarts && !bStarts) return -1; - if (bStarts && !aStarts) return 1; - return a.compareToIgnoreCase(b); - } - } - - /** - * @param initialText initial text which will be in the lookup text field - * @param context - */ - protected ChooseByNameBase(Project project, ChooseByNameModel model, String initialText, final PsiElement context) { - myProject = project; - myModel = model; - myInitialText = initialText; - myContext = new WeakReference(context); - } - - public boolean isPreselectInitialText() { - return myPreselectInitialText; - } - - public void setPreselectInitialText(boolean preselectInitialText) { - myPreselectInitialText = preselectInitialText; - } - - public void setShowListAfterCompletionKeyStroke(boolean showListAfterCompletionKeyStroke) { - myShowListAfterCompletionKeyStroke = showListAfterCompletionKeyStroke; - } - - /** - * Set tool area. The method may be called only before invoke. - * - * @param toolArea a tool area component - */ - public void setToolArea(JComponent toolArea) { - if (myCard != null) { - throw new IllegalStateException("Tool area is modifiable only before invoke()"); - } - myToolArea = toolArea; - } - - public void invoke(final ChooseByNamePopupComponent.Callback callback, - final ModalityState modalityState, - boolean allowMultipleSelection) { - initUI(callback, modalityState, allowMultipleSelection); - } - - public class JPanelProvider extends JPanel implements DataProvider { - JBPopup myHint = null; - boolean myFocusRequested = false; - - JPanelProvider() { - } - - public Object getData(String dataId) { - if (PlatformDataKeys.HELP_ID.is(dataId)) { - return myModel.getHelpId(); - } - if (!myListIsUpToDate) { - return null; - } - if (LangDataKeys.PSI_ELEMENT.is(dataId)) { - Object element = getChosenElement(); - - if (element instanceof PsiElement) { - return element; - } - - if (element instanceof DataProvider) { - return ((DataProvider)element).getData(dataId); - } - } - else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { - final List chosenElements = getChosenElements(); - if (chosenElements != null) { - List result = new ArrayList(); - for (Object element : chosenElements) { - if (element instanceof PsiElement) { - result.add((PsiElement)element); - } - } - return result.toArray(new PsiElement[result.size()]); - } - } - else if (PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.is(dataId)) { - return getBounds(); - } - return null; - } - - public void registerHint(JBPopup h) { - if (myHint != null && myHint.isVisible() && myHint != h) { - myHint.cancel(); - } - myHint = h; - } - - public boolean focusRequested() { - boolean focusRequested = myFocusRequested; - - myFocusRequested = false; - - return focusRequested; - } - - public void requestFocus() { - myFocusRequested = true; - } - - public void unregisterHint() { - myHint = null; - } - - public void hideHint() { - if (myHint != null) { - myHint.cancel(); - } - } - - public JBPopup getHint() { - return myHint; - } - - public void updateHint(PsiElement element) { - if (myHint == null || !myHint.isVisible()) return; - final PopupUpdateProcessor updateProcessor = myHint.getUserData(PopupUpdateProcessor.class); - if (updateProcessor != null) { - myHint.cancel(); - updateProcessor.updatePopup(element); - } - } - } - - /** - * @param callback - * @param modalityState - if not null rebuilds list in given {@link ModalityState} - * @param allowMultipleSelection - */ - protected void initUI(final ChooseByNamePopupComponent.Callback callback, - final ModalityState modalityState, - boolean allowMultipleSelection) { - myPreviouslyFocusedComponent = WindowManagerEx.getInstanceEx().getFocusedComponent(myProject); - - myActionListener = callback; - myTextFieldPanel = new JPanelProvider(); - myTextFieldPanel.setLayout(new BoxLayout(myTextFieldPanel, BoxLayout.Y_AXIS)); - - final JPanel hBox = new JPanel(); - hBox.setLayout(new BoxLayout(hBox, BoxLayout.X_AXIS)); - - JPanel caption2Tools = new JPanel(new BorderLayout()); - - if (myModel.getPromptText() != null) { - JLabel label = new JLabel(myModel.getPromptText()); - label.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD)); - caption2Tools.add(label, BorderLayout.WEST); - } - - GridBagLayout gb = new GridBagLayout(); - JPanel eastWrapper = new JPanel(gb); - gb.setConstraints(hBox, new GridBagConstraints(0, 0, 0, 0, 1, 1, GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - eastWrapper.add(hBox); - - caption2Tools.add(eastWrapper, BorderLayout.CENTER); - - myCard = new CardLayout(); - myCardContainer = new JPanel(myCard); - - final JPanel checkBoxPanel = new JPanel(); - myCheckBox = new JCheckBox(myModel.getCheckBoxName()); - myCheckBox.setAlignmentX(SwingConstants.RIGHT); - myCheckBox.setSelected(myModel.loadInitialCheckBoxState()); - - if (myModel.getPromptText() != null) { - checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.X_AXIS)); - checkBoxPanel.add(myCheckBox); - } - else { - checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.LINE_AXIS)); - checkBoxPanel.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); - checkBoxPanel.add(myCheckBox); - } - checkBoxPanel.setVisible(myModel.getCheckBoxName() != null); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(checkBoxPanel, BorderLayout.CENTER); - myCardContainer.add(panel, CHECK_BOX_CARD); - - myCardContainer.add(new HintLabel(myModel.getNotInMessage()), NOT_FOUND_IN_PROJECT_CARD); - myCardContainer.add(new HintLabel(IdeBundle.message("label.choosebyname.no.matches.found")), NOT_FOUND_CARD); - myCardContainer.add(new HintLabel(IdeBundle.message("label.choosebyname.searching")), SEARCHING_CARD); - myCard.show(myCardContainer, CHECK_BOX_CARD); - - if (isCheckboxVisible()) { - hBox.add(myCardContainer); - } - - if (myToolArea != null) { - hBox.add(Box.createHorizontalStrut(5)); - hBox.add(myToolArea); - } - hBox.add(Box.createHorizontalStrut(5)); - myTextFieldPanel.add(caption2Tools); - - myHistory = new ArrayList>(); - myFuture = new ArrayList>(); - myTextField = new MyTextField(); - myTextField.setText(myInitialText); - if (myPreselectInitialText) { - myTextField.select(0, myInitialText.length()); - } - - final ActionMap actionMap = new ActionMap(); - actionMap.setParent(myTextField.getActionMap()); - actionMap.put(DefaultEditorKit.copyAction, new AbstractAction() { - public void actionPerformed(ActionEvent e) { - if (myTextField.getSelectedText() != null) { - actionMap.getParent().get(DefaultEditorKit.copyAction).actionPerformed(e); - return; - } - final Object chosenElement = getChosenElement(); - if (chosenElement instanceof PsiElement) { - CopyReferenceAction.doCopy((PsiElement)chosenElement, myProject); - } - } - }); - myTextField.setActionMap(actionMap); - - myTextFieldPanel.add(myTextField); - EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); - Font editorFont = new Font(scheme.getEditorFontName(), Font.PLAIN, scheme.getEditorFontSize()); - myTextField.setFont(editorFont); - - if (isCloseByFocusLost()) { - myTextField.addFocusListener(new FocusAdapter() { - public void focusLost(final FocusEvent e) { - myHideAlarm.addRequest(new Runnable() { - public void run() { - JBPopup popup = JBPopupFactory.getInstance().getChildFocusedPopup(e.getComponent()); - if (popup != null) { - popup.addListener(new JBPopupListener.Adapter() { - @Override - public void onClosed(LightweightWindowEvent event) { - if (event.isOk()) { - hideHint(); - } - } - }); - } - else { - hideHint(); - } - } - }, 200); - } - }); - } - - myCheckBox.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - rebuildList(); - } - }); - myCheckBox.setFocusable(false); - - myTextField.getDocument().addDocumentListener(new DocumentAdapter() { - protected void textChanged(DocumentEvent e) { - clearPosponedOkAction(false); - rebuildList(); - } - }); - - myTextField.addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent e) { - if (!myListScrollPane.isVisible()) { - return; - } - final int keyCode = e.getKeyCode(); - switch (keyCode) { - case KeyEvent.VK_DOWN: - ListScrollingUtil.moveDown(myList, e.getModifiersEx()); - break; - case KeyEvent.VK_UP: - ListScrollingUtil.moveUp(myList, e.getModifiersEx()); - break; - case KeyEvent.VK_PAGE_UP: - ListScrollingUtil.movePageUp(myList); - break; - case KeyEvent.VK_PAGE_DOWN: - ListScrollingUtil.movePageDown(myList); - break; - case KeyEvent.VK_ENTER: - if (myList.getSelectedValue() == EXTRA_ELEM) { - myMaximumListSizeLimit += MAXIMUM_LIST_SIZE_LIMIT; - rebuildList(myList.getSelectedIndex(), REBUILD_DELAY, null, ModalityState.current(), e); - e.consume(); - } - break; - } - } - }); - - myTextField.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - doClose(true); - } - }); - - myListModel = new DefaultListModel(); - myList = new JBList(myListModel); - myList.setFocusable(false); - myList.setSelectionMode(allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : - ListSelectionModel.SINGLE_SELECTION); - myList.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (!myTextField.hasFocus()) { - myTextField.requestFocus(); - } - - if (e.getClickCount() == 2) { - if (myList.getSelectedValue() == EXTRA_ELEM) { - myMaximumListSizeLimit += MAXIMUM_LIST_SIZE_LIMIT; - rebuildList(myList.getSelectedIndex(), REBUILD_DELAY, null, ModalityState.current(), e); - e.consume(); - } - else { - doClose(true); - } - } - } - }); - myList.setCellRenderer(myModel.getListCellRenderer()); - myList.setFont(editorFont); - - myList.addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - choosenElementMightChange(); - updateDocumentation(); - } - }); - - myListScrollPane = ScrollPaneFactory.createScrollPane(myList); - myListScrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0)); - - myTextFieldPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); - - showTextFieldPanel(); - - if (modalityState != null) { - rebuildList(0, 0, null, modalityState, null); - } - } - - private void hideHint() { - if (!myTextFieldPanel.focusRequested()) { - doClose(false); - myTextFieldPanel.hideHint(); - } - } - - /** - * Default rebuild list. It uses {@link #REBUILD_DELAY} and current modality state. - */ - public void rebuildList() { - // TODO this method is public, because the chooser does not listed for the model. - rebuildList(0, REBUILD_DELAY, null, ModalityState.current(), null); - } - - private void updateDocumentation() { - final JBPopup hint = myTextFieldPanel.getHint(); - final Object element = getChosenElement(); - if (hint != null) { - if (element instanceof PsiElement) { - myTextFieldPanel.updateHint((PsiElement)element); - } - else if (element instanceof DataProvider) { - final Object o = ((DataProvider)element).getData(LangDataKeys.PSI_ELEMENT.getName()); - if (o instanceof PsiElement) { - myTextFieldPanel.updateHint((PsiElement)o); - } - } - } - } - - protected void doClose(final boolean ok) { - if (myDisposedFlag) return; - - if (postponeCloseWhenListReady(ok)) return; - - cancelListUpdater(); - close(ok); - - clearPosponedOkAction(ok); - } - - protected void cancelListUpdater() { - myListUpdater.cancelAll(); - } - - private boolean postponeCloseWhenListReady(boolean ok) { - if (!isToFixLostTyping()) return false; - - final String text = myTextField.getText(); - if (ok && !myListIsUpToDate && text != null && text.trim().length() > 0) { - myPosponedOkAction = new ActionCallback(); - IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(myPosponedOkAction); - return true; - } - - return false; - } - - protected boolean isToFixLostTyping() { - return Registry.is("actionSystem.fixLostTyping"); - } - - private synchronized void ensureNamesLoaded(boolean checkboxState) { - int index = checkboxState ? 1 : 0; - if (myNames[index] != null) return; - - Window window = (Window)SwingUtilities.getAncestorOfClass(Window.class, myTextField); - //LOG.assertTrue (myTextField != null); - //LOG.assertTrue (window != null); - Window ownerWindow = null; - if (window != null) { - window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - ownerWindow = window.getOwner(); - if (ownerWindow != null) { - ownerWindow.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - } - } - myNames[index] = myModel.getNames(checkboxState); - - if (window != null) { - window.setCursor(Cursor.getDefaultCursor()); - if (ownerWindow != null) { - ownerWindow.setCursor(Cursor.getDefaultCursor()); - } - } - } - - protected abstract boolean isCheckboxVisible(); - - protected abstract boolean isShowListForEmptyPattern(); - - protected abstract boolean isCloseByFocusLost(); - - protected void showTextFieldPanel() { - final JLayeredPane layeredPane = getLayeredPane(); - final Dimension preferredTextFieldPanelSize = myTextFieldPanel.getPreferredSize(); - final int x = (layeredPane.getWidth() - preferredTextFieldPanelSize.width) / 2; - final int paneHeight = layeredPane.getHeight(); - final int y = paneHeight / 3 - preferredTextFieldPanelSize.height / 2; - - VISIBLE_LIST_SIZE_LIMIT = Math.max - (10, (paneHeight - (y + preferredTextFieldPanelSize.height)) / (preferredTextFieldPanelSize.height / 2) - 1); - - ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myTextFieldPanel, myTextField); - builder.setCancelCallback(new Computable() { - @Override - public Boolean compute() { - myTextPopup = null; - close(false); - return Boolean.TRUE; - } - }).setFocusable(true).setRequestFocus(true).setForceHeavyweight(true).setModalContext(false).setCancelOnClickOutside(false); - - Point point = new Point(x, y); - SwingUtilities.convertPointToScreen(point, layeredPane); - Rectangle bounds = new Rectangle(point, new Dimension(preferredTextFieldPanelSize.width + 20, preferredTextFieldPanelSize.height)); - myTextPopup = builder.createPopup(); - myTextPopup.setSize(bounds.getSize()); - myTextPopup.setLocation(bounds.getLocation()); - - new MnemonicHelper().register(myTextFieldPanel); - myTextPopup.show(layeredPane); - } - - private JLayeredPane getLayeredPane() { - JLayeredPane layeredPane; - final Window window = WindowManager.getInstance().suggestParentWindow(myProject); - - Component parent = UIUtil.findUltimateParent(window); - - if (parent instanceof JFrame) { - layeredPane = ((JFrame)parent).getLayeredPane(); - } - else if (parent instanceof JDialog) { - layeredPane = ((JDialog)parent).getLayeredPane(); - } - else { - throw new IllegalStateException("cannot find parent window: project=" + myProject + - (myProject != null ? "; open=" + myProject.isOpen() : "") + - "; window=" + window); - } - return layeredPane; - } - - private final Object myRebuildMutex = new Object(); - - protected void rebuildList(final int pos, - final int delay, - final Runnable postRunnable, - final ModalityState modalityState, - final @Nullable ComponentEvent e) { - ApplicationManager.getApplication().assertIsDispatchThread(); - myListIsUpToDate = false; - myAlarm.cancelAllRequests(); - myListUpdater.cancelAll(); - - cancelCalcElementsThread(); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - final String text = myTextField.getText(); - if (!canShowListForEmptyPattern() && - (text == null || text.trim().length() == 0)) { - myListModel.clear(); - hideList(); - myCard.show(myCardContainer, CHECK_BOX_CARD); - return; - } - final Runnable request = new Runnable() { - public void run() { - final CalcElementsCallback callback = new CalcElementsCallback() { - public void run(final Set elements) { - synchronized (myRebuildMutex) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (myDisposedFlag) { - return; - } - - setElementsToList(pos, elements); - - myListIsUpToDate = true; - choosenElementMightChange(); - - if (postRunnable != null) { - postRunnable.run(); - } - } - } - }; - - cancelCalcElementsThread(); - - myCalcElementsThread = new CalcElementsThread(text, myCheckBox.isSelected(), callback, modalityState, postRunnable == null); - ApplicationManager.getApplication().executeOnPooledThread(myCalcElementsThread); - } - }; - - if (delay > 0) { - myAlarm.addRequest(request, delay, ModalityState.stateForComponent(myTextField)); - } - else { - request.run(); - } - } - }, modalityState); - } - - protected boolean isToBuildListOnPolledThread() { - return true; - } - - private boolean isShowListAfterCompletionKeyStroke() { - return myShowListAfterCompletionKeyStroke; - } - - private void cancelCalcElementsThread() { - if (myCalcElementsThread != null) { - myCalcElementsThread.cancel(); - myCalcElementsThread = null; - } - } - - private void setElementsToList(int pos, Set elements) { - myListUpdater.cancelAll(); - if (myDisposedFlag) return; - if (elements.isEmpty()) { - myListModel.clear(); - myTextField.setForeground(Color.red); - myListUpdater.cancelAll(); - hideList(); - clearPosponedOkAction(false); - return; - } - - Object[] oldElements = myListModel.toArray(); - Object[] newElements = elements.toArray(); - Diff.Change change = Diff.buildChanges(oldElements, newElements); - - if (change == null) return; // Nothing changed - - List commands = new ArrayList(); - int inserted = 0; - int deleted = 0; - while (change != null) { - if (change.deleted > 0) { - final int start = change.line0 + inserted - deleted; - commands.add(new RemoveCmd(start, start + change.deleted - 1)); - } - - if (change.inserted > 0) { - for (int i = 0; i < change.inserted; i++) { - commands.add(new InsertCmd(change.line0 + i + inserted - deleted, newElements[change.line1 + i])); - } - } - - deleted += change.deleted; - inserted += change.inserted; - change = change.link; - } - - myTextField.setForeground(UIUtil.getTextFieldForeground()); - if (!commands.isEmpty()) { - showList(); - myListUpdater.appendToModel(commands, pos); - } - else { - if (pos == 0) { - pos = detectBestStatisticalPosition(); - } - - ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1)); - myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize())); - showList(); - } - } - - private int detectBestStatisticalPosition() { - int best = 0; - int bestPosition = 0; - final int count = myListModel.getSize(); - - final String statContext = statisticsContext(); - for (int i = 0; i < count; i++) { - final Object modelElement = myListModel.getElementAt(i); - String text = EXTRA_ELEM.equals(modelElement) ? null : myModel.getFullName(modelElement); - if (text != null) { - int stats = StatisticsManager.getInstance().getUseCount(new StatisticsInfo(statContext, text)); - if (stats > best) { - best = stats; - bestPosition = i; - } - } - } - - return bestPosition; - } - - @NonNls - protected String statisticsContext() { - return "choose_by_name#" + myModel.getPromptText() + "#" + myCheckBox.isSelected() + "#" + myTextField.getText(); - } - - private String getQualifierPattern(String pattern) { - final String[] separators = myModel.getSeparators(); - int lastSeparatorOccurence = 0; - for (String separator : separators) { - lastSeparatorOccurence = Math.max(lastSeparatorOccurence, pattern.lastIndexOf(separator)); - } - return pattern.substring(0, lastSeparatorOccurence); - } - - public String getNamePattern(String pattern) { - final String[] separators = myModel.getSeparators(); - int lastSeparatorOccurence = 0; - for (String separator : separators) { - final int idx = pattern.lastIndexOf(separator); - lastSeparatorOccurence = Math.max(lastSeparatorOccurence, idx == -1 ? idx : idx + separator.length()); - } - - return pattern.substring(lastSeparatorOccurence); - } - - private interface Cmd { - void apply(); - } - - private class RemoveCmd implements Cmd { - private final int start; - private final int end; - - private RemoveCmd(final int start, final int end) { - this.start = start; - this.end = end; - } - - public void apply() { - myListModel.removeRange(start, end); - } - } - - private class InsertCmd implements Cmd { - private final int idx; - private final Object element; - - private InsertCmd(final int idx, final Object element) { - this.idx = idx; - this.element = element; - } - - public void apply() { - if (idx < myListModel.size()) { - myListModel.add(idx, element); - } - else { - myListModel.addElement(element); - } - } - } - - private class ListUpdater { - private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - private static final int DELAY = 10; - private static final int MAX_BLOCKING_TIME = 30; - private final List myCommands = Collections.synchronizedList(new ArrayList()); - - public void cancelAll() { - myCommands.clear(); - myAlarm.cancelAllRequests(); - } - - public void appendToModel(final List commands, final int selectionPos) { - myAlarm.cancelAllRequests(); - myCommands.addAll(commands); - - if (myCommands.isEmpty() || myDisposedFlag) return; - myAlarm.addRequest(new Runnable() { - public void run() { - if (myDisposedFlag) { - return; - } - final long startTime = System.currentTimeMillis(); - while (!myCommands.isEmpty() && System.currentTimeMillis() - startTime < MAX_BLOCKING_TIME) { - final Cmd cmd = myCommands.remove(0); - cmd.apply(); - } - - myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize())); - if (!myListModel.isEmpty()) { - int pos = selectionPos == 0 ? detectBestStatisticalPosition() : selectionPos; - ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1)); - } - - if (!myCommands.isEmpty()) { - myAlarm.addRequest(this, DELAY); - } - else { - doPostponedOkIfNeeded(); - } - if (!myDisposedFlag) { - showList(); - } - } - }, DELAY); - } - - private void doPostponedOkIfNeeded() { - if (myPosponedOkAction != null) { - if (getChosenElement() != null) { - doClose(true); - clearPosponedOkAction(myDisposedFlag); - } - } - } - } - - private void clearPosponedOkAction(boolean success) { - if (myPosponedOkAction != null) { - if (success) { - myPosponedOkAction.setDone(); - } - else { - myPosponedOkAction.setRejected(); - } - } - - myPosponedOkAction = null; - } - - protected abstract void showList(); - - protected abstract void hideList(); - - protected abstract void close(boolean isOk); - - @Nullable - public Object getChosenElement() { - final List elements = getChosenElements(); - return elements != null && elements.size() == 1 ? elements.get(0) : null; - } - - protected List getChosenElements() { - if (myListIsUpToDate) { - List values = new ArrayList(Arrays.asList(myList.getSelectedValues())); - values.remove(EXTRA_ELEM); - return values; - } - - final String text = myTextField.getText(); - final boolean checkBoxState = myCheckBox.isSelected(); - //ensureNamesLoaded(checkBoxState); - final String[] names = checkBoxState ? myNames[1] : myNames[0]; - if (names == null) return Collections.emptyList(); - - Object uniqueElement = null; - - for (final String name : names) { - if (text.equalsIgnoreCase(name)) { - final Object[] elements = myModel.getElementsByName(name, checkBoxState, text); - if (elements.length > 1) return Collections.emptyList(); - if (elements.length == 0) continue; - if (uniqueElement != null) return Collections.emptyList(); - uniqueElement = elements[0]; - } - } - return uniqueElement == null ? Collections.emptyList() : Collections.singletonList(uniqueElement); - } - - protected void choosenElementMightChange() { - } - - protected final class MyTextField extends JTextField implements PopupOwner { - private final KeyStroke myCompletionKeyStroke; - private final KeyStroke forwardStroke; - private final KeyStroke backStroke; - - private boolean completionKeyStrokeHappened = false; - - private MyTextField() { - super(40); - enableEvents(AWTEvent.KEY_EVENT_MASK); - myCompletionKeyStroke = getShortcut(IdeActions.ACTION_CODE_COMPLETION); - forwardStroke = getShortcut(IdeActions.ACTION_GOTO_FORWARD); - backStroke = getShortcut(IdeActions.ACTION_GOTO_BACK); - - } - - private KeyStroke getShortcut(String actionCodeCompletion) { - final Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionCodeCompletion); - for (final Shortcut shortcut : shortcuts) { - if (shortcut instanceof KeyboardShortcut) { - return ((KeyboardShortcut)shortcut).getFirstKeyStroke(); - } - } - return null; - } - - protected void processKeyEvent(KeyEvent e) { - final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e); - - if (myCompletionKeyStroke != null && keyStroke.equals(myCompletionKeyStroke)) { - completionKeyStrokeHappened = true; - e.consume(); - final String pattern = myTextField.getText(); - final String oldText = myTextField.getText(); - final int oldPos = myList.getSelectedIndex(); - myHistory.add(Pair.create(oldText, oldPos)); - final Runnable postRunnable = new Runnable() { - public void run() { - fillInCommonPrefix(pattern); - } - }; - rebuildList(0, 0, postRunnable, ModalityState.current(), e); - return; - } - if (backStroke != null && keyStroke.equals(backStroke)) { - e.consume(); - if (!myHistory.isEmpty()) { - final String oldText = myTextField.getText(); - final int oldPos = myList.getSelectedIndex(); - final Pair last = myHistory.remove(myHistory.size() - 1); - myTextField.setText(last.first); - myFuture.add(Pair.create(oldText, oldPos)); - rebuildList(0, 0, null, ModalityState.current(), e); - } - return; - } - if (forwardStroke != null && keyStroke.equals(forwardStroke)) { - e.consume(); - if (!myFuture.isEmpty()) { - final String oldText = myTextField.getText(); - final int oldPos = myList.getSelectedIndex(); - final Pair next = myFuture.remove(myFuture.size() - 1); - myTextField.setText(next.first); - myHistory.add(Pair.create(oldText, oldPos)); - rebuildList(0, 0, null, ModalityState.current(), e); - } - return; - } - try { - super.processKeyEvent(e); - } - catch (NullPointerException e1) { - if (!Patches.SUN_BUG_6322854) { - throw e1; - } - } - } - - private void fillInCommonPrefix(final String pattern) { - final ArrayList list = new ArrayList(); - getNamesByPattern(myCheckBox.isSelected(), null, list, pattern); - - if (isComplexPattern(pattern)) return; //TODO: support '*' - final String oldText = myTextField.getText(); - final int oldPos = myList.getSelectedIndex(); - - String commonPrefix = null; - if (!list.isEmpty()) { - for (String name : list) { - final String string = name.toLowerCase(); - if (commonPrefix == null) { - commonPrefix = string; - } - else { - while (commonPrefix.length() > 0) { - if (string.startsWith(commonPrefix)) { - break; - } - commonPrefix = commonPrefix.substring(0, commonPrefix.length() - 1); - } - if (commonPrefix.length() == 0) break; - } - } - commonPrefix = list.get(0).substring(0, commonPrefix.length()); - for (int i = 1; i < list.size(); i++) { - final String string = list.get(i).substring(0, commonPrefix.length()); - if (!string.equals(commonPrefix)) { - commonPrefix = commonPrefix.toLowerCase(); - break; - } - } - } - if (commonPrefix == null) commonPrefix = ""; - final String newPattern = commonPrefix; - - myHistory.add(Pair.create(oldText, oldPos)); - myTextField.setText(newPattern); - myTextField.setCaretPosition(newPattern.length()); - - rebuildList(); - } - - private boolean isComplexPattern(final String pattern) { - if (pattern.indexOf('*') >= 0) return true; - for (String s : myModel.getSeparators()) { - if (pattern.contains(s)) return true; - } - - return false; - } - - @Nullable - public Point getBestPopupPosition() { - return new Point(myTextFieldPanel.getWidth(), getHeight()); - } - - protected void paintComponent(final Graphics g) { - UISettings.setupAntialiasing(g); - super.paintComponent(g); - } - - public boolean isCompletionKeyStroke() { - return completionKeyStrokeHappened; - } - } - - private static final String EXTRA_ELEM = "..."; - - private class CalcElementsThread implements Runnable { - private final String myPattern; - private boolean myCheckboxState; - private final CalcElementsCallback myCallback; - private final ModalityState myModalityState; - - private Set myElements = null; - - private volatile boolean myCancelled = false; - private final boolean myCanCancel; - - private CalcElementsThread(String pattern, - boolean checkboxState, - CalcElementsCallback callback, - ModalityState modalityState, - boolean canCancel) { - myPattern = pattern; - myCheckboxState = checkboxState; - myCallback = callback; - myModalityState = modalityState; - myCanCancel = canCancel; - } - - private final Alarm myShowCardAlarm = new Alarm(); - - public void run() { - showCard(SEARCHING_CARD, 200); - - final Set elements = new LinkedHashSet(); - Runnable action = new Runnable() { - public void run() { - try { - ensureNamesLoaded(myCheckboxState); - addElementsByPattern(elements, myPattern); - for (Object elem : elements) { - if (myCancelled) { - break; - } - if (elem instanceof PsiElement) { - final PsiElement psiElement = (PsiElement)elem; - psiElement.isWritable(); // That will cache writable flag in VirtualFile. Taking the action here makes it canceleable. - } - } - } - catch (ProcessCanceledException e) { - //OK - } - } - }; - ApplicationManager.getApplication().runReadAction(action); - - if (myCancelled) { - myShowCardAlarm.cancelAllRequests(); - return; - } - - final String cardToShow; - if (elements.isEmpty() && !myCheckboxState) { - myCheckboxState = true; - ApplicationManager.getApplication().runReadAction(action); - cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : NOT_FOUND_IN_PROJECT_CARD; - } - else { - cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : CHECK_BOX_CARD; - } - showCard(cardToShow, 0); - - myElements = elements; - - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - myCallback.run(myElements); - } - }, myModalityState); - } - - private void showCard(final String card, final int delay) { - myShowCardAlarm.cancelAllRequests(); - myShowCardAlarm.addRequest(new Runnable() { - public void run() { - myCard.show(myCardContainer, card); - } - }, delay, myModalityState); - } - - private void addElementsByPattern(Set elementsArray, String pattern) { - String namePattern = getNamePattern(pattern); - String qualifierPattern = getQualifierPattern(pattern); - - boolean empty = namePattern.length() == 0 || namePattern.equals("@"); // TODO[yole]: remove implicit dependency - if (empty && !canShowListForEmptyPattern()) return; - - List namesList = new ArrayList(); - getNamesByPattern(myCheckboxState, this, namesList, namePattern); - if (myCancelled) { - throw new ProcessCanceledException(); - } - // Here we sort using namePattern to have similar logic with empty qualified patten case - Collections.sort(namesList, new MatchesComparator(namePattern)); - - boolean overflow = false; - List sameNameElements = new SmartList(); - All: - for (String name : namesList) { - if (myCancelled) { - throw new ProcessCanceledException(); - } - final Object[] elements = myModel.getElementsByName(name, myCheckboxState, namePattern); - if (elements.length > 1) { - sameNameElements.clear(); - for (final Object element : elements) { - if (matchesQualifier(element, qualifierPattern)) { - sameNameElements.add(element); - } - } - sortByProximity(sameNameElements); - for (Object element : sameNameElements) { - elementsArray.add(element); - if (elementsArray.size() >= myMaximumListSizeLimit) { - overflow = true; - break All; - } - } - } - else if (elements.length == 1 && matchesQualifier(elements[0], qualifierPattern)) { - elementsArray.add(elements[0]); - if (elementsArray.size() >= myMaximumListSizeLimit) { - overflow = true; - break; - } - } - } - - if (overflow) { - elementsArray.add(EXTRA_ELEM); - } - } - - private void cancel() { - if (myCanCancel) { - myCancelled = true; - } - } - } - - private void sortByProximity(final List sameNameElements) { - Collections.sort(sameNameElements, new PathProximityComparator(myModel, myContext.get())); - } - - private List split(String s) { - List answer = new ArrayList(); - for (String token : StringUtil.tokenize(s, StringUtil.join(myModel.getSeparators(), ""))) { - if (token.length() > 0) { - answer.add(token); - } - } - - return answer.isEmpty() ? Collections.singletonList(s) : answer; - } - - private boolean matchesQualifier(final Object element, final String qualifierPattern) { - final String name = myModel.getFullName(element); - if (name == null) return false; - - final List suspects = split(name); - final List> patternsAndMatchers = - ContainerUtil.map2List(split(qualifierPattern), new Function>() { - public Pair fun(String s) { - final String pattern = getNamePattern(s); - final NameUtil.Matcher matcher = buildPatternMatcher(pattern); - - return new Pair(pattern, matcher); - } - }); - - int matchPosition = 0; - - try { - patterns: - for (Pair patternAndMatcher : patternsAndMatchers) { - final String pattern = patternAndMatcher.first; - final NameUtil.Matcher matcher = patternAndMatcher.second; - if (pattern.length() > 0) { - for (int j = matchPosition; j < suspects.size() - 1; j++) { - String suspect = suspects.get(j); - if (matches(pattern, matcher, suspect)) { - matchPosition = j + 1; - continue patterns; - } - } - - return false; - } - } - } - catch (Exception e) { - // Do nothing. No matches appears valid result for "bad" pattern - return false; - } - - return true; - } - - private void getNamesByPattern(final boolean checkboxState, - CalcElementsThread calcElementsThread, - final List list, - String pattern) throws ProcessCanceledException { - if (!canShowListForEmptyPattern()) { - LOG.assertTrue(pattern.length() > 0); - } - - if (pattern.startsWith("@")) { - pattern = pattern.substring(1); - } - - final String[] names = checkboxState ? myNames[1] : myNames[0]; - final NameUtil.Matcher matcher = buildPatternMatcher(pattern); - - try { - for (String name : names) { - if (calcElementsThread != null && calcElementsThread.myCancelled) { - break; - } - if (matches(pattern, matcher, name)) { - list.add(name); - } - } - } - catch (Exception e) { - // Do nothing. No matches appears valid result for "bad" pattern - } - } - - private boolean canShowListForEmptyPattern() { - return isShowListForEmptyPattern() || (isShowListAfterCompletionKeyStroke() && lastKeyStrokeIsCompletion()); - } - - protected boolean lastKeyStrokeIsCompletion() { - return myTextField.isCompletionKeyStroke(); - } - - private boolean matches(String pattern, NameUtil.Matcher matcher, String name) { - boolean matches = false; - if (name != null) { - if (myModel instanceof CustomMatcherModel) { - if (((CustomMatcherModel)myModel).matches(name, pattern)) { - matches = true; - } - } - else if (pattern.length() == 0 || matcher.matches(name)) { - matches = true; - } - } - return matches; - } - - private NameUtil.Matcher buildPatternMatcher(String pattern) { - return NameUtil.buildMatcher(pattern, 0, true, true, pattern.toLowerCase().equals(pattern)); - } - - private interface CalcElementsCallback { - void run(Set elements); - } - - private static class PathProximityComparator implements Comparator { - private final ChooseByNameModel myModel; - private final PsiProximityComparator myProximityComparator; - - private PathProximityComparator(final ChooseByNameModel model, @Nullable final PsiElement context) { - myModel = model; - myProximityComparator = new PsiProximityComparator(context); - } - - public int compare(final Object o1, final Object o2) { - int rc = myProximityComparator.compare(o1, o2); - if (rc != 0) return rc; - - return Comparing.compare(myModel.getFullName(o1), myModel.getFullName(o2)); - } - } - - private static class HintLabel extends JLabel { - private HintLabel(String text) { - super(text, RIGHT); - setForeground(Color.darkGray); - } - } -} +/* + * 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.ide.util.gotoByName; + +import com.intellij.Patches; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.actions.CopyReferenceAction; +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.MnemonicHelper; +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.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.NameUtil; +import com.intellij.psi.statistics.StatisticsInfo; +import com.intellij.psi.statistics.StatisticsManager; +import com.intellij.psi.util.proximity.PsiProximityComparator; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.ListScrollingUtil; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.components.JBList; +import com.intellij.ui.popup.PopupOwner; +import com.intellij.ui.popup.PopupUpdateProcessor; +import com.intellij.util.Alarm; +import com.intellij.util.Function; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.diff.Diff; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.DefaultEditorKit; +import java.awt.*; +import java.awt.event.*; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.List; + +public abstract class ChooseByNameBase { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.gotoByName.ChooseByNameBase"); + + protected final Project myProject; + protected final ChooseByNameModel myModel; + protected final String myInitialText; + private boolean myPreselectInitialText; + private final Reference myContext; + + protected Component myPreviouslyFocusedComponent; + + protected JPanelProvider myTextFieldPanel;// Located in the layered pane + protected MyTextField myTextField; + private JPanel myCardContainer; + private CardLayout myCard; + protected JCheckBox myCheckBox; + /** + * the tool area of the popup, it is just after card box + */ + protected JComponent myToolArea; + + protected JScrollPane myListScrollPane; // Located in the layered pane + protected JList myList; + private DefaultListModel myListModel; + private List> myHistory; + private List> myFuture; + + protected ChooseByNamePopupComponent.Callback myActionListener; + + protected final Alarm myAlarm = new Alarm(); + + private final ListUpdater myListUpdater = new ListUpdater(); + + private volatile boolean myListIsUpToDate = false; + protected boolean myDisposedFlag = false; + private ActionCallback myPosponedOkAction; + + private final String[][] myNames = new String[2][]; + private CalcElementsThread myCalcElementsThread; + private static int VISIBLE_LIST_SIZE_LIMIT = 10; + private static final int MAXIMUM_LIST_SIZE_LIMIT = 30; + private int myMaximumListSizeLimit = MAXIMUM_LIST_SIZE_LIMIT; + @NonNls private static final String NOT_FOUND_IN_PROJECT_CARD = "syslib"; + @NonNls private static final String NOT_FOUND_CARD = "nfound"; + @NonNls private static final String CHECK_BOX_CARD = "chkbox"; + @NonNls private static final String SEARCHING_CARD = "searching"; + private static final int REBUILD_DELAY = 300; + + private final Alarm myHideAlarm = new Alarm(); + private boolean myShowListAfterCompletionKeyStroke = false; + protected JBPopup myTextPopup; + protected JBPopup myDropdownPopup; + + private static class MatchesComparator implements Comparator { + private final String myOriginalPattern; + + private MatchesComparator(final String originalPattern) { + myOriginalPattern = originalPattern.trim(); + } + + public int compare(final String a, final String b) { + boolean aStarts = a.startsWith(myOriginalPattern); + boolean bStarts = b.startsWith(myOriginalPattern); + if (aStarts && bStarts) return a.compareToIgnoreCase(b); + if (aStarts && !bStarts) return -1; + if (bStarts && !aStarts) return 1; + return a.compareToIgnoreCase(b); + } + } + + /** + * @param initialText initial text which will be in the lookup text field + * @param context + */ + protected ChooseByNameBase(Project project, ChooseByNameModel model, String initialText, final PsiElement context) { + myProject = project; + myModel = model; + myInitialText = initialText; + myContext = new WeakReference(context); + } + + public boolean isPreselectInitialText() { + return myPreselectInitialText; + } + + public void setPreselectInitialText(boolean preselectInitialText) { + myPreselectInitialText = preselectInitialText; + } + + public void setShowListAfterCompletionKeyStroke(boolean showListAfterCompletionKeyStroke) { + myShowListAfterCompletionKeyStroke = showListAfterCompletionKeyStroke; + } + + /** + * Set tool area. The method may be called only before invoke. + * + * @param toolArea a tool area component + */ + public void setToolArea(JComponent toolArea) { + if (myCard != null) { + throw new IllegalStateException("Tool area is modifiable only before invoke()"); + } + myToolArea = toolArea; + } + + public void invoke(final ChooseByNamePopupComponent.Callback callback, + final ModalityState modalityState, + boolean allowMultipleSelection) { + initUI(callback, modalityState, allowMultipleSelection); + } + + public class JPanelProvider extends JPanel implements DataProvider { + JBPopup myHint = null; + boolean myFocusRequested = false; + + JPanelProvider() { + } + + public Object getData(String dataId) { + if (PlatformDataKeys.HELP_ID.is(dataId)) { + return myModel.getHelpId(); + } + if (!myListIsUpToDate) { + return null; + } + if (LangDataKeys.PSI_ELEMENT.is(dataId)) { + Object element = getChosenElement(); + + if (element instanceof PsiElement) { + return element; + } + + if (element instanceof DataProvider) { + return ((DataProvider)element).getData(dataId); + } + } + else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { + final List chosenElements = getChosenElements(); + if (chosenElements != null) { + List result = new ArrayList(); + for (Object element : chosenElements) { + if (element instanceof PsiElement) { + result.add((PsiElement)element); + } + } + return result.toArray(new PsiElement[result.size()]); + } + } + else if (PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.is(dataId)) { + return getBounds(); + } + return null; + } + + public void registerHint(JBPopup h) { + if (myHint != null && myHint.isVisible() && myHint != h) { + myHint.cancel(); + } + myHint = h; + } + + public boolean focusRequested() { + boolean focusRequested = myFocusRequested; + + myFocusRequested = false; + + return focusRequested; + } + + public void requestFocus() { + myFocusRequested = true; + } + + public void unregisterHint() { + myHint = null; + } + + public void hideHint() { + if (myHint != null) { + myHint.cancel(); + } + } + + public JBPopup getHint() { + return myHint; + } + + public void updateHint(PsiElement element) { + if (myHint == null || !myHint.isVisible()) return; + final PopupUpdateProcessor updateProcessor = myHint.getUserData(PopupUpdateProcessor.class); + if (updateProcessor != null) { + myHint.cancel(); + updateProcessor.updatePopup(element); + } + } + } + + /** + * @param callback + * @param modalityState - if not null rebuilds list in given {@link ModalityState} + * @param allowMultipleSelection + */ + protected void initUI(final ChooseByNamePopupComponent.Callback callback, + final ModalityState modalityState, + boolean allowMultipleSelection) { + myPreviouslyFocusedComponent = WindowManagerEx.getInstanceEx().getFocusedComponent(myProject); + + myActionListener = callback; + myTextFieldPanel = new JPanelProvider(); + myTextFieldPanel.setLayout(new BoxLayout(myTextFieldPanel, BoxLayout.Y_AXIS)); + + final JPanel hBox = new JPanel(); + hBox.setLayout(new BoxLayout(hBox, BoxLayout.X_AXIS)); + + JPanel caption2Tools = new JPanel(new BorderLayout()); + + if (myModel.getPromptText() != null) { + JLabel label = new JLabel(myModel.getPromptText()); + label.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD)); + caption2Tools.add(label, BorderLayout.WEST); + } + + GridBagLayout gb = new GridBagLayout(); + JPanel eastWrapper = new JPanel(gb); + gb.setConstraints(hBox, new GridBagConstraints(0, 0, 0, 0, 1, 1, GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + eastWrapper.add(hBox); + + caption2Tools.add(eastWrapper, BorderLayout.CENTER); + + myCard = new CardLayout(); + myCardContainer = new JPanel(myCard); + + final JPanel checkBoxPanel = new JPanel(); + myCheckBox = new JCheckBox(myModel.getCheckBoxName()); + myCheckBox.setAlignmentX(SwingConstants.RIGHT); + myCheckBox.setSelected(myModel.loadInitialCheckBoxState()); + + if (myModel.getPromptText() != null) { + checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.X_AXIS)); + checkBoxPanel.add(myCheckBox); + } + else { + checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.LINE_AXIS)); + checkBoxPanel.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); + checkBoxPanel.add(myCheckBox); + } + checkBoxPanel.setVisible(myModel.getCheckBoxName() != null); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(checkBoxPanel, BorderLayout.CENTER); + myCardContainer.add(panel, CHECK_BOX_CARD); + + myCardContainer.add(new HintLabel(myModel.getNotInMessage()), NOT_FOUND_IN_PROJECT_CARD); + myCardContainer.add(new HintLabel(IdeBundle.message("label.choosebyname.no.matches.found")), NOT_FOUND_CARD); + myCardContainer.add(new HintLabel(IdeBundle.message("label.choosebyname.searching")), SEARCHING_CARD); + myCard.show(myCardContainer, CHECK_BOX_CARD); + + if (isCheckboxVisible()) { + hBox.add(myCardContainer); + } + + if (myToolArea != null) { + hBox.add(Box.createHorizontalStrut(5)); + hBox.add(myToolArea); + } + hBox.add(Box.createHorizontalStrut(5)); + myTextFieldPanel.add(caption2Tools); + + myHistory = new ArrayList>(); + myFuture = new ArrayList>(); + myTextField = new MyTextField(); + myTextField.setText(myInitialText); + if (myPreselectInitialText) { + myTextField.select(0, myInitialText.length()); + } + + final ActionMap actionMap = new ActionMap(); + actionMap.setParent(myTextField.getActionMap()); + actionMap.put(DefaultEditorKit.copyAction, new AbstractAction() { + public void actionPerformed(ActionEvent e) { + if (myTextField.getSelectedText() != null) { + actionMap.getParent().get(DefaultEditorKit.copyAction).actionPerformed(e); + return; + } + final Object chosenElement = getChosenElement(); + if (chosenElement instanceof PsiElement) { + CopyReferenceAction.doCopy((PsiElement)chosenElement, myProject); + } + } + }); + myTextField.setActionMap(actionMap); + + myTextFieldPanel.add(myTextField); + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + Font editorFont = new Font(scheme.getEditorFontName(), Font.PLAIN, scheme.getEditorFontSize()); + myTextField.setFont(editorFont); + + if (isCloseByFocusLost()) { + myTextField.addFocusListener(new FocusAdapter() { + public void focusLost(final FocusEvent e) { + myHideAlarm.addRequest(new Runnable() { + public void run() { + JBPopup popup = JBPopupFactory.getInstance().getChildFocusedPopup(e.getComponent()); + if (popup != null) { + popup.addListener(new JBPopupListener.Adapter() { + @Override + public void onClosed(LightweightWindowEvent event) { + if (event.isOk()) { + hideHint(); + } + } + }); + } + else { + hideHint(); + } + } + }, 200); + } + }); + } + + myCheckBox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + rebuildList(); + } + }); + myCheckBox.setFocusable(false); + + myTextField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(DocumentEvent e) { + clearPosponedOkAction(false); + rebuildList(); + } + }); + + myTextField.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (!myListScrollPane.isVisible()) { + return; + } + final int keyCode = e.getKeyCode(); + switch (keyCode) { + case KeyEvent.VK_DOWN: + ListScrollingUtil.moveDown(myList, e.getModifiersEx()); + break; + case KeyEvent.VK_UP: + ListScrollingUtil.moveUp(myList, e.getModifiersEx()); + break; + case KeyEvent.VK_PAGE_UP: + ListScrollingUtil.movePageUp(myList); + break; + case KeyEvent.VK_PAGE_DOWN: + ListScrollingUtil.movePageDown(myList); + break; + case KeyEvent.VK_ENTER: + if (myList.getSelectedValue() == EXTRA_ELEM) { + myMaximumListSizeLimit += MAXIMUM_LIST_SIZE_LIMIT; + rebuildList(myList.getSelectedIndex(), REBUILD_DELAY, null, ModalityState.current(), e); + e.consume(); + } + break; + } + } + }); + + myTextField.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + doClose(true); + } + }); + + myListModel = new DefaultListModel(); + myList = new JBList(myListModel); + myList.setFocusable(false); + myList.setSelectionMode(allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : + ListSelectionModel.SINGLE_SELECTION); + myList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (!myTextField.hasFocus()) { + myTextField.requestFocus(); + } + + if (e.getClickCount() == 2) { + if (myList.getSelectedValue() == EXTRA_ELEM) { + myMaximumListSizeLimit += MAXIMUM_LIST_SIZE_LIMIT; + rebuildList(myList.getSelectedIndex(), REBUILD_DELAY, null, ModalityState.current(), e); + e.consume(); + } + else { + doClose(true); + } + } + } + }); + myList.setCellRenderer(myModel.getListCellRenderer()); + myList.setFont(editorFont); + + myList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + choosenElementMightChange(); + updateDocumentation(); + } + }); + + myListScrollPane = ScrollPaneFactory.createScrollPane(myList); + myListScrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0)); + + myTextFieldPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); + + showTextFieldPanel(); + + if (modalityState != null) { + rebuildList(0, 0, null, modalityState, null); + } + } + + private void hideHint() { + if (!myTextFieldPanel.focusRequested()) { + doClose(false); + myTextFieldPanel.hideHint(); + } + } + + /** + * Default rebuild list. It uses {@link #REBUILD_DELAY} and current modality state. + */ + public void rebuildList() { + // TODO this method is public, because the chooser does not listed for the model. + rebuildList(0, REBUILD_DELAY, null, ModalityState.current(), null); + } + + private void updateDocumentation() { + final JBPopup hint = myTextFieldPanel.getHint(); + final Object element = getChosenElement(); + if (hint != null) { + if (element instanceof PsiElement) { + myTextFieldPanel.updateHint((PsiElement)element); + } + else if (element instanceof DataProvider) { + final Object o = ((DataProvider)element).getData(LangDataKeys.PSI_ELEMENT.getName()); + if (o instanceof PsiElement) { + myTextFieldPanel.updateHint((PsiElement)o); + } + } + } + } + + protected void doClose(final boolean ok) { + if (myDisposedFlag) return; + + if (postponeCloseWhenListReady(ok)) return; + + cancelListUpdater(); + close(ok); + + clearPosponedOkAction(ok); + } + + protected void cancelListUpdater() { + myListUpdater.cancelAll(); + } + + private boolean postponeCloseWhenListReady(boolean ok) { + if (!isToFixLostTyping()) return false; + + final String text = myTextField.getText(); + if (ok && !myListIsUpToDate && text != null && text.trim().length() > 0) { + myPosponedOkAction = new ActionCallback(); + IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(myPosponedOkAction); + return true; + } + + return false; + } + + protected boolean isToFixLostTyping() { + return Registry.is("actionSystem.fixLostTyping"); + } + + private synchronized void ensureNamesLoaded(boolean checkboxState) { + int index = checkboxState ? 1 : 0; + if (myNames[index] != null) return; + + Window window = (Window)SwingUtilities.getAncestorOfClass(Window.class, myTextField); + //LOG.assertTrue (myTextField != null); + //LOG.assertTrue (window != null); + Window ownerWindow = null; + if (window != null) { + window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + ownerWindow = window.getOwner(); + if (ownerWindow != null) { + ownerWindow.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + } + myNames[index] = myModel.getNames(checkboxState); + + if (window != null) { + window.setCursor(Cursor.getDefaultCursor()); + if (ownerWindow != null) { + ownerWindow.setCursor(Cursor.getDefaultCursor()); + } + } + } + + protected abstract boolean isCheckboxVisible(); + + protected abstract boolean isShowListForEmptyPattern(); + + protected abstract boolean isCloseByFocusLost(); + + protected void showTextFieldPanel() { + final JLayeredPane layeredPane = getLayeredPane(); + final Dimension preferredTextFieldPanelSize = myTextFieldPanel.getPreferredSize(); + final int x = (layeredPane.getWidth() - preferredTextFieldPanelSize.width) / 2; + final int paneHeight = layeredPane.getHeight(); + final int y = paneHeight / 3 - preferredTextFieldPanelSize.height / 2; + + VISIBLE_LIST_SIZE_LIMIT = Math.max + (10, (paneHeight - (y + preferredTextFieldPanelSize.height)) / (preferredTextFieldPanelSize.height / 2) - 1); + + ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myTextFieldPanel, myTextField); + builder.setCancelCallback(new Computable() { + @Override + public Boolean compute() { + myTextPopup = null; + close(false); + return Boolean.TRUE; + } + }).setFocusable(true).setRequestFocus(true).setForceHeavyweight(true).setModalContext(false).setCancelOnClickOutside(false); + + Point point = new Point(x, y); + SwingUtilities.convertPointToScreen(point, layeredPane); + Rectangle bounds = new Rectangle(point, new Dimension(preferredTextFieldPanelSize.width + 20, preferredTextFieldPanelSize.height)); + myTextPopup = builder.createPopup(); + myTextPopup.setSize(bounds.getSize()); + myTextPopup.setLocation(bounds.getLocation()); + + new MnemonicHelper().register(myTextFieldPanel); + myTextPopup.show(layeredPane); + } + + private JLayeredPane getLayeredPane() { + JLayeredPane layeredPane; + final Window window = WindowManager.getInstance().suggestParentWindow(myProject); + + Component parent = UIUtil.findUltimateParent(window); + + if (parent instanceof JFrame) { + layeredPane = ((JFrame)parent).getLayeredPane(); + } + else if (parent instanceof JDialog) { + layeredPane = ((JDialog)parent).getLayeredPane(); + } + else { + throw new IllegalStateException("cannot find parent window: project=" + myProject + + (myProject != null ? "; open=" + myProject.isOpen() : "") + + "; window=" + window); + } + return layeredPane; + } + + private final Object myRebuildMutex = new Object(); + + protected void rebuildList(final int pos, + final int delay, + final Runnable postRunnable, + final ModalityState modalityState, + final @Nullable ComponentEvent e) { + ApplicationManager.getApplication().assertIsDispatchThread(); + myListIsUpToDate = false; + myAlarm.cancelAllRequests(); + myListUpdater.cancelAll(); + + cancelCalcElementsThread(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final String text = myTextField.getText(); + if (!canShowListForEmptyPattern() && + (text == null || text.trim().length() == 0)) { + myListModel.clear(); + hideList(); + myCard.show(myCardContainer, CHECK_BOX_CARD); + return; + } + final Runnable request = new Runnable() { + public void run() { + final CalcElementsCallback callback = new CalcElementsCallback() { + public void run(final Set elements) { + synchronized (myRebuildMutex) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (myDisposedFlag) { + return; + } + + setElementsToList(pos, elements); + + myListIsUpToDate = true; + choosenElementMightChange(); + + if (postRunnable != null) { + postRunnable.run(); + } + } + } + }; + + cancelCalcElementsThread(); + + myCalcElementsThread = new CalcElementsThread(text, myCheckBox.isSelected(), callback, modalityState, postRunnable == null); + ApplicationManager.getApplication().executeOnPooledThread(myCalcElementsThread); + } + }; + + if (delay > 0) { + myAlarm.addRequest(request, delay, ModalityState.stateForComponent(myTextField)); + } + else { + request.run(); + } + } + }, modalityState); + } + + protected boolean isToBuildListOnPolledThread() { + return true; + } + + private boolean isShowListAfterCompletionKeyStroke() { + return myShowListAfterCompletionKeyStroke; + } + + private void cancelCalcElementsThread() { + if (myCalcElementsThread != null) { + myCalcElementsThread.cancel(); + myCalcElementsThread = null; + } + } + + private void setElementsToList(int pos, Set elements) { + myListUpdater.cancelAll(); + if (myDisposedFlag) return; + if (elements.isEmpty()) { + myListModel.clear(); + myTextField.setForeground(Color.red); + myListUpdater.cancelAll(); + hideList(); + clearPosponedOkAction(false); + return; + } + + Object[] oldElements = myListModel.toArray(); + Object[] newElements = elements.toArray(); + Diff.Change change = Diff.buildChanges(oldElements, newElements); + + if (change == null) return; // Nothing changed + + List commands = new ArrayList(); + int inserted = 0; + int deleted = 0; + while (change != null) { + if (change.deleted > 0) { + final int start = change.line0 + inserted - deleted; + commands.add(new RemoveCmd(start, start + change.deleted - 1)); + } + + if (change.inserted > 0) { + for (int i = 0; i < change.inserted; i++) { + commands.add(new InsertCmd(change.line0 + i + inserted - deleted, newElements[change.line1 + i])); + } + } + + deleted += change.deleted; + inserted += change.inserted; + change = change.link; + } + + myTextField.setForeground(UIUtil.getTextFieldForeground()); + if (!commands.isEmpty()) { + showList(); + myListUpdater.appendToModel(commands, pos); + } + else { + if (pos == 0) { + pos = detectBestStatisticalPosition(); + } + + ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1)); + myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize())); + showList(); + } + } + + private int detectBestStatisticalPosition() { + int best = 0; + int bestPosition = 0; + final int count = myListModel.getSize(); + + final String statContext = statisticsContext(); + for (int i = 0; i < count; i++) { + final Object modelElement = myListModel.getElementAt(i); + String text = EXTRA_ELEM.equals(modelElement) ? null : myModel.getFullName(modelElement); + if (text != null) { + int stats = StatisticsManager.getInstance().getUseCount(new StatisticsInfo(statContext, text)); + if (stats > best) { + best = stats; + bestPosition = i; + } + } + } + + return bestPosition; + } + + @NonNls + protected String statisticsContext() { + return "choose_by_name#" + myModel.getPromptText() + "#" + myCheckBox.isSelected() + "#" + myTextField.getText(); + } + + private String getQualifierPattern(String pattern) { + final String[] separators = myModel.getSeparators(); + int lastSeparatorOccurence = 0; + for (String separator : separators) { + lastSeparatorOccurence = Math.max(lastSeparatorOccurence, pattern.lastIndexOf(separator)); + } + return pattern.substring(0, lastSeparatorOccurence); + } + + public String getNamePattern(String pattern) { + final String[] separators = myModel.getSeparators(); + int lastSeparatorOccurence = 0; + for (String separator : separators) { + final int idx = pattern.lastIndexOf(separator); + lastSeparatorOccurence = Math.max(lastSeparatorOccurence, idx == -1 ? idx : idx + separator.length()); + } + + return pattern.substring(lastSeparatorOccurence); + } + + private interface Cmd { + void apply(); + } + + private class RemoveCmd implements Cmd { + private final int start; + private final int end; + + private RemoveCmd(final int start, final int end) { + this.start = start; + this.end = end; + } + + public void apply() { + myListModel.removeRange(start, end); + } + } + + private class InsertCmd implements Cmd { + private final int idx; + private final Object element; + + private InsertCmd(final int idx, final Object element) { + this.idx = idx; + this.element = element; + } + + public void apply() { + if (idx < myListModel.size()) { + myListModel.add(idx, element); + } + else { + myListModel.addElement(element); + } + } + } + + private class ListUpdater { + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + private static final int DELAY = 10; + private static final int MAX_BLOCKING_TIME = 30; + private final List myCommands = Collections.synchronizedList(new ArrayList()); + + public void cancelAll() { + myCommands.clear(); + myAlarm.cancelAllRequests(); + } + + public void appendToModel(final List commands, final int selectionPos) { + myAlarm.cancelAllRequests(); + myCommands.addAll(commands); + + if (myCommands.isEmpty() || myDisposedFlag) return; + myAlarm.addRequest(new Runnable() { + public void run() { + if (myDisposedFlag) { + return; + } + final long startTime = System.currentTimeMillis(); + while (!myCommands.isEmpty() && System.currentTimeMillis() - startTime < MAX_BLOCKING_TIME) { + final Cmd cmd = myCommands.remove(0); + cmd.apply(); + } + + myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize())); + if (!myListModel.isEmpty()) { + int pos = selectionPos == 0 ? detectBestStatisticalPosition() : selectionPos; + ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1)); + } + + if (!myCommands.isEmpty()) { + myAlarm.addRequest(this, DELAY); + } + else { + doPostponedOkIfNeeded(); + } + if (!myDisposedFlag) { + showList(); + } + } + }, DELAY); + } + + private void doPostponedOkIfNeeded() { + if (myPosponedOkAction != null) { + if (getChosenElement() != null) { + doClose(true); + clearPosponedOkAction(myDisposedFlag); + } + } + } + } + + private void clearPosponedOkAction(boolean success) { + if (myPosponedOkAction != null) { + if (success) { + myPosponedOkAction.setDone(); + } + else { + myPosponedOkAction.setRejected(); + } + } + + myPosponedOkAction = null; + } + + protected abstract void showList(); + + protected abstract void hideList(); + + protected abstract void close(boolean isOk); + + @Nullable + public Object getChosenElement() { + final List elements = getChosenElements(); + return elements != null && elements.size() == 1 ? elements.get(0) : null; + } + + protected List getChosenElements() { + if (myListIsUpToDate) { + List values = new ArrayList(Arrays.asList(myList.getSelectedValues())); + values.remove(EXTRA_ELEM); + return values; + } + + final String text = myTextField.getText(); + final boolean checkBoxState = myCheckBox.isSelected(); + //ensureNamesLoaded(checkBoxState); + final String[] names = checkBoxState ? myNames[1] : myNames[0]; + if (names == null) return Collections.emptyList(); + + Object uniqueElement = null; + + for (final String name : names) { + if (text.equalsIgnoreCase(name)) { + final Object[] elements = myModel.getElementsByName(name, checkBoxState, text); + if (elements.length > 1) return Collections.emptyList(); + if (elements.length == 0) continue; + if (uniqueElement != null) return Collections.emptyList(); + uniqueElement = elements[0]; + } + } + return uniqueElement == null ? Collections.emptyList() : Collections.singletonList(uniqueElement); + } + + protected void choosenElementMightChange() { + } + + protected final class MyTextField extends JTextField implements PopupOwner { + private final KeyStroke myCompletionKeyStroke; + private final KeyStroke forwardStroke; + private final KeyStroke backStroke; + + private boolean completionKeyStrokeHappened = false; + + private MyTextField() { + super(40); + enableEvents(AWTEvent.KEY_EVENT_MASK); + myCompletionKeyStroke = getShortcut(IdeActions.ACTION_CODE_COMPLETION); + forwardStroke = getShortcut(IdeActions.ACTION_GOTO_FORWARD); + backStroke = getShortcut(IdeActions.ACTION_GOTO_BACK); + + } + + private KeyStroke getShortcut(String actionCodeCompletion) { + final Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionCodeCompletion); + for (final Shortcut shortcut : shortcuts) { + if (shortcut instanceof KeyboardShortcut) { + return ((KeyboardShortcut)shortcut).getFirstKeyStroke(); + } + } + return null; + } + + protected void processKeyEvent(KeyEvent e) { + final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e); + + if (myCompletionKeyStroke != null && keyStroke.equals(myCompletionKeyStroke)) { + completionKeyStrokeHappened = true; + e.consume(); + final String pattern = myTextField.getText(); + final String oldText = myTextField.getText(); + final int oldPos = myList.getSelectedIndex(); + myHistory.add(Pair.create(oldText, oldPos)); + final Runnable postRunnable = new Runnable() { + public void run() { + fillInCommonPrefix(pattern); + } + }; + rebuildList(0, 0, postRunnable, ModalityState.current(), e); + return; + } + if (backStroke != null && keyStroke.equals(backStroke)) { + e.consume(); + if (!myHistory.isEmpty()) { + final String oldText = myTextField.getText(); + final int oldPos = myList.getSelectedIndex(); + final Pair last = myHistory.remove(myHistory.size() - 1); + myTextField.setText(last.first); + myFuture.add(Pair.create(oldText, oldPos)); + rebuildList(0, 0, null, ModalityState.current(), e); + } + return; + } + if (forwardStroke != null && keyStroke.equals(forwardStroke)) { + e.consume(); + if (!myFuture.isEmpty()) { + final String oldText = myTextField.getText(); + final int oldPos = myList.getSelectedIndex(); + final Pair next = myFuture.remove(myFuture.size() - 1); + myTextField.setText(next.first); + myHistory.add(Pair.create(oldText, oldPos)); + rebuildList(0, 0, null, ModalityState.current(), e); + } + return; + } + try { + super.processKeyEvent(e); + } + catch (NullPointerException e1) { + if (!Patches.SUN_BUG_6322854) { + throw e1; + } + } + } + + private void fillInCommonPrefix(final String pattern) { + final ArrayList list = new ArrayList(); + getNamesByPattern(myCheckBox.isSelected(), null, list, pattern); + + if (isComplexPattern(pattern)) return; //TODO: support '*' + final String oldText = myTextField.getText(); + final int oldPos = myList.getSelectedIndex(); + + String commonPrefix = null; + if (!list.isEmpty()) { + for (String name : list) { + final String string = name.toLowerCase(); + if (commonPrefix == null) { + commonPrefix = string; + } + else { + while (commonPrefix.length() > 0) { + if (string.startsWith(commonPrefix)) { + break; + } + commonPrefix = commonPrefix.substring(0, commonPrefix.length() - 1); + } + if (commonPrefix.length() == 0) break; + } + } + commonPrefix = list.get(0).substring(0, commonPrefix.length()); + for (int i = 1; i < list.size(); i++) { + final String string = list.get(i).substring(0, commonPrefix.length()); + if (!string.equals(commonPrefix)) { + commonPrefix = commonPrefix.toLowerCase(); + break; + } + } + } + if (commonPrefix == null) commonPrefix = ""; + final String newPattern = commonPrefix; + + myHistory.add(Pair.create(oldText, oldPos)); + myTextField.setText(newPattern); + myTextField.setCaretPosition(newPattern.length()); + + rebuildList(); + } + + private boolean isComplexPattern(final String pattern) { + if (pattern.indexOf('*') >= 0) return true; + for (String s : myModel.getSeparators()) { + if (pattern.contains(s)) return true; + } + + return false; + } + + @Nullable + public Point getBestPopupPosition() { + return new Point(myTextFieldPanel.getWidth(), getHeight()); + } + + protected void paintComponent(final Graphics g) { + UISettings.setupAntialiasing(g); + super.paintComponent(g); + } + + public boolean isCompletionKeyStroke() { + return completionKeyStrokeHappened; + } + } + + private static final String EXTRA_ELEM = "..."; + + private class CalcElementsThread implements Runnable { + private final String myPattern; + private boolean myCheckboxState; + private final CalcElementsCallback myCallback; + private final ModalityState myModalityState; + + private Set myElements = null; + + private volatile boolean myCancelled = false; + private final boolean myCanCancel; + + private CalcElementsThread(String pattern, + boolean checkboxState, + CalcElementsCallback callback, + ModalityState modalityState, + boolean canCancel) { + myPattern = pattern; + myCheckboxState = checkboxState; + myCallback = callback; + myModalityState = modalityState; + myCanCancel = canCancel; + } + + private final Alarm myShowCardAlarm = new Alarm(); + + public void run() { + showCard(SEARCHING_CARD, 200); + + final Set elements = new LinkedHashSet(); + Runnable action = new Runnable() { + public void run() { + try { + ensureNamesLoaded(myCheckboxState); + addElementsByPattern(elements, myPattern); + for (Object elem : elements) { + if (myCancelled) { + break; + } + if (elem instanceof PsiElement) { + final PsiElement psiElement = (PsiElement)elem; + psiElement.isWritable(); // That will cache writable flag in VirtualFile. Taking the action here makes it canceleable. + } + } + } + catch (ProcessCanceledException e) { + //OK + } + } + }; + ApplicationManager.getApplication().runReadAction(action); + + if (myCancelled) { + myShowCardAlarm.cancelAllRequests(); + return; + } + + final String cardToShow; + if (elements.isEmpty() && !myCheckboxState) { + myCheckboxState = true; + ApplicationManager.getApplication().runReadAction(action); + cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : NOT_FOUND_IN_PROJECT_CARD; + } + else { + cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : CHECK_BOX_CARD; + } + showCard(cardToShow, 0); + + myElements = elements; + + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + myCallback.run(myElements); + } + }, myModalityState); + } + + private void showCard(final String card, final int delay) { + myShowCardAlarm.cancelAllRequests(); + myShowCardAlarm.addRequest(new Runnable() { + public void run() { + myCard.show(myCardContainer, card); + } + }, delay, myModalityState); + } + + private void addElementsByPattern(Set elementsArray, String pattern) { + String namePattern = getNamePattern(pattern); + String qualifierPattern = getQualifierPattern(pattern); + + boolean empty = namePattern.length() == 0 || namePattern.equals("@"); // TODO[yole]: remove implicit dependency + if (empty && !canShowListForEmptyPattern()) return; + + List namesList = new ArrayList(); + getNamesByPattern(myCheckboxState, this, namesList, namePattern); + if (myCancelled) { + throw new ProcessCanceledException(); + } + // Here we sort using namePattern to have similar logic with empty qualified patten case + Collections.sort(namesList, new MatchesComparator(namePattern)); + + boolean overflow = false; + List sameNameElements = new SmartList(); + All: + for (String name : namesList) { + if (myCancelled) { + throw new ProcessCanceledException(); + } + final Object[] elements = myModel.getElementsByName(name, myCheckboxState, namePattern); + if (elements.length > 1) { + sameNameElements.clear(); + for (final Object element : elements) { + if (matchesQualifier(element, qualifierPattern)) { + sameNameElements.add(element); + } + } + sortByProximity(sameNameElements); + for (Object element : sameNameElements) { + elementsArray.add(element); + if (elementsArray.size() >= myMaximumListSizeLimit) { + overflow = true; + break All; + } + } + } + else if (elements.length == 1 && matchesQualifier(elements[0], qualifierPattern)) { + elementsArray.add(elements[0]); + if (elementsArray.size() >= myMaximumListSizeLimit) { + overflow = true; + break; + } + } + } + + if (overflow) { + elementsArray.add(EXTRA_ELEM); + } + } + + private void cancel() { + if (myCanCancel) { + myCancelled = true; + } + } + } + + private void sortByProximity(final List sameNameElements) { + Collections.sort(sameNameElements, new PathProximityComparator(myModel, myContext.get())); + } + + private List split(String s) { + List answer = new ArrayList(); + for (String token : StringUtil.tokenize(s, StringUtil.join(myModel.getSeparators(), ""))) { + if (token.length() > 0) { + answer.add(token); + } + } + + return answer.isEmpty() ? Collections.singletonList(s) : answer; + } + + private boolean matchesQualifier(final Object element, final String qualifierPattern) { + final String name = myModel.getFullName(element); + if (name == null) return false; + + final List suspects = split(name); + final List> patternsAndMatchers = + ContainerUtil.map2List(split(qualifierPattern), new Function>() { + public Pair fun(String s) { + final String pattern = getNamePattern(s); + final NameUtil.Matcher matcher = buildPatternMatcher(pattern); + + return new Pair(pattern, matcher); + } + }); + + int matchPosition = 0; + + try { + patterns: + for (Pair patternAndMatcher : patternsAndMatchers) { + final String pattern = patternAndMatcher.first; + final NameUtil.Matcher matcher = patternAndMatcher.second; + if (pattern.length() > 0) { + for (int j = matchPosition; j < suspects.size() - 1; j++) { + String suspect = suspects.get(j); + if (matches(pattern, matcher, suspect)) { + matchPosition = j + 1; + continue patterns; + } + } + + return false; + } + } + } + catch (Exception e) { + // Do nothing. No matches appears valid result for "bad" pattern + return false; + } + + return true; + } + + private void getNamesByPattern(final boolean checkboxState, + CalcElementsThread calcElementsThread, + final List list, + String pattern) throws ProcessCanceledException { + if (!canShowListForEmptyPattern()) { + LOG.assertTrue(pattern.length() > 0); + } + + if (pattern.startsWith("@")) { + pattern = pattern.substring(1); + } + + final String[] names = checkboxState ? myNames[1] : myNames[0]; + final NameUtil.Matcher matcher = buildPatternMatcher(pattern); + + try { + for (String name : names) { + if (calcElementsThread != null && calcElementsThread.myCancelled) { + break; + } + if (matches(pattern, matcher, name)) { + list.add(name); + } + } + } + catch (Exception e) { + // Do nothing. No matches appears valid result for "bad" pattern + } + } + + private boolean canShowListForEmptyPattern() { + return isShowListForEmptyPattern() || (isShowListAfterCompletionKeyStroke() && lastKeyStrokeIsCompletion()); + } + + protected boolean lastKeyStrokeIsCompletion() { + return myTextField.isCompletionKeyStroke(); + } + + private boolean matches(String pattern, NameUtil.Matcher matcher, String name) { + boolean matches = false; + if (name != null) { + if (myModel instanceof CustomMatcherModel) { + if (((CustomMatcherModel)myModel).matches(name, pattern)) { + matches = true; + } + } + else if (pattern.length() == 0 || matcher.matches(name)) { + matches = true; + } + } + return matches; + } + + private NameUtil.Matcher buildPatternMatcher(String pattern) { + return NameUtil.buildMatcher(pattern, 0, true, true, pattern.toLowerCase().equals(pattern)); + } + + private interface CalcElementsCallback { + void run(Set elements); + } + + private static class PathProximityComparator implements Comparator { + private final ChooseByNameModel myModel; + private final PsiProximityComparator myProximityComparator; + + private PathProximityComparator(final ChooseByNameModel model, @Nullable final PsiElement context) { + myModel = model; + myProximityComparator = new PsiProximityComparator(context); + } + + public int compare(final Object o1, final Object o2) { + int rc = myProximityComparator.compare(o1, o2); + if (rc != 0) return rc; + + return Comparing.compare(myModel.getFullName(o1), myModel.getFullName(o2)); + } + } + + private static class HintLabel extends JLabel { + private HintLabel(String text) { + super(text, RIGHT); + setForeground(Color.darkGray); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNamePopup.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNamePopup.java index a28f5ef5c644..a262d28b8be1 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNamePopup.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNamePopup.java @@ -1,292 +1,292 @@ -/* - * 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.ide.util.gotoByName; - -import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.ComponentPopupBuilder; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.DimensionService; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiElement; -import com.intellij.psi.statistics.StatisticsInfo; -import com.intellij.psi.statistics.StatisticsManager; -import com.intellij.ui.ScreenUtil; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.popup.AbstractPopup; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ChooseByNamePopup extends ChooseByNameBase implements ChooseByNamePopupComponent{ - private static final Key CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY = new Key("ChooseByNamePopup"); - private Component myOldFocusOwner = null; - private boolean myShowListForEmptyPattern = false; - - protected ChooseByNamePopup(final Project project, final ChooseByNameModel model, final ChooseByNamePopup oldPopup, - final PsiElement context, @Nullable final String predefinedText) { - super(project, model, oldPopup != null ? oldPopup.getEnteredText() : predefinedText, context); - if (oldPopup == null && predefinedText != null) { - setPreselectInitialText(true); - } - if (oldPopup != null) { //inherit old focus owner - myOldFocusOwner = oldPopup.myPreviouslyFocusedComponent; - } - } - - public String getEnteredText() { - return myTextField.getText(); - } - - protected void initUI(final Callback callback, final ModalityState modalityState, boolean allowMultipleSelection) { - super.initUI(callback, modalityState, allowMultipleSelection); - //LaterInvocator.enterModal(myTextFieldPanel); - if (myInitialText != null) { - rebuildList(0, 0, null, ModalityState.current(), null); - } - if (myOldFocusOwner != null){ - myPreviouslyFocusedComponent = myOldFocusOwner; - myOldFocusOwner = null; - } - } - - protected boolean isCheckboxVisible() { - return true; - } - - protected boolean isShowListForEmptyPattern(){ - return myShowListForEmptyPattern; - } - - public void setShowListForEmptyPattern(boolean showListForEmptyPattern) { - myShowListForEmptyPattern = showListForEmptyPattern; - } - - protected boolean isCloseByFocusLost(){ - return true; - } - - protected void showList() { - final JLayeredPane layeredPane = myTextField.getRootPane().getLayeredPane(); - - Rectangle bounds = new Rectangle(myTextFieldPanel.getLocationOnScreen(), myTextField.getSize()); - bounds.y += myTextFieldPanel.getHeight() + (SystemInfo.isMac ? 3 : 1); - - final Dimension preferredScrollPaneSize = myListScrollPane.getPreferredSize(); - preferredScrollPaneSize.width = Math.max(myTextFieldPanel.getWidth(), preferredScrollPaneSize.width); - - Rectangle prefferedBounds = new Rectangle(bounds.x, bounds.y, preferredScrollPaneSize.width, preferredScrollPaneSize.height); - ScreenUtil.fitToScreen(prefferedBounds); - - myListScrollPane.setVisible(true); - myListScrollPane.setBorder(null); - if (myDropdownPopup == null) { - ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myListScrollPane, myListScrollPane); - builder.setFocusable(false).setRequestFocus(false).setCancelKeyEnabled(false).setFocusOwners(new JComponent[] {myTextField}).setBelongsToGlobalPopupStack(false).setForceHeavyweight(true).setModalContext(false); - builder.setCancelCallback(new Computable() { - @Override - public Boolean compute() { - return Boolean.TRUE; - } - }); - myDropdownPopup = builder.createPopup(); - myDropdownPopup.setLocation(prefferedBounds.getLocation()); - myDropdownPopup.setSize(prefferedBounds.getSize()); - myDropdownPopup.show(layeredPane); - } else { - myDropdownPopup.setLocation(prefferedBounds.getLocation()); - myDropdownPopup.setSize(prefferedBounds.getSize()); - } - } - - protected void hideList() { - if (myDropdownPopup != null) { - myDropdownPopup.cancel(); - myDropdownPopup = null; - } - } - - protected void close(final boolean isOk) { - if (myDisposedFlag){ - return; - } - - if (isOk){ - myModel.saveInitialCheckBoxState(myCheckBox.isSelected()); - - final List chosenElements = getChosenElements(); - if (chosenElements != null) { - for (Object element : chosenElements) { - myActionListener.elementChosen(element); - String text = myModel.getFullName(element); - if (text != null) { - StatisticsManager.getInstance().incUseCount(new StatisticsInfo(statisticsContext(), text)); - } - } - } else { - return; - } - - if (!chosenElements.isEmpty()){ - final String enteredText = getEnteredText(); - if (enteredText.indexOf('*') >= 0) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.wildcards"); - } - else { - for (Object element : chosenElements) { - final String name = myModel.getElementName(element); - if (name != null) { - if (!StringUtil.startsWithIgnoreCase(name, enteredText)) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.camelprefix"); - break; - } - } - } - } - } - else{ - return; - } - } - - myDisposedFlag = true; - myAlarm.cancelAllRequests(); - myProject.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, null); - - //LaterInvocator.leaveModal(myTextFieldPanel); - - cleanupUI(); - myActionListener.onClose (); - } - - @Nullable - public static ChooseByNamePopup getActivePopup(@NotNull final Project project) { - return CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY.get(project); - } - - private void cleanupUI() { - if (myTextPopup != null) { - myTextPopup.cancel(); - myTextPopup = null; - } - - if (myDropdownPopup != null) { - myDropdownPopup.cancel(); - myDropdownPopup = null; - } - } - - public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context) { - return createPopup(project, model, context, null); - } - public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context, - @Nullable final String predefinedText) { - final ChooseByNamePopup oldPopup = project.getUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY); - if (oldPopup != null) { - oldPopup.close(false); - } - ChooseByNamePopup newPopup = new ChooseByNamePopup(project, model, oldPopup, context, predefinedText); - - project.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, newPopup); - return newPopup; - } - - private static final Pattern patternToDetectLinesAndColumns = Pattern.compile("(.*?)(?:\\:|@|,|#)(\\d+)?(?:(?:\\D)(\\d+)?)?"); - - public String getNamePattern(String pattern) { - if (pattern.indexOf(':') != -1 || - pattern.indexOf(',') != -1 || - pattern.indexOf(';') != -1 || - pattern.indexOf('#') != -1 || - pattern.indexOf('@') != -1) { // quick test if reg exp should be used - final Matcher matcher = patternToDetectLinesAndColumns.matcher(pattern); - if (matcher.matches()) { - pattern = matcher.group(1); - } - } - - return super.getNamePattern(pattern); - } - - public int getLinePosition() { - return getLineOrColumn(true); - } - - private int getLineOrColumn(final boolean line) { - final Matcher matcher = patternToDetectLinesAndColumns.matcher(getEnteredText()); - if (matcher.matches()) { - final int groupNumber = line ? 2:3; - try { - if(groupNumber <= matcher.groupCount()) { - final String group = matcher.group(groupNumber); - if (group != null) return Integer.parseInt(group) - 1; - } - if (!line && getLineOrColumn(true) != -1) return 0; - } - catch (NumberFormatException ignored) {} - } - - return -1; - } - - public int getColumnPosition() { - return getLineOrColumn(false); - } - - public void showItemPopup(final JBPopup hint) { - if (myDropdownPopup != null && myDropdownPopup.isVisible()) { - Dimension hintSize = null; - if (hint instanceof AbstractPopup) { - final String key = ((AbstractPopup)hint).getDimensionServiceKey(); - if (key != null) { - hintSize = DimensionService.getInstance().getSize(key); - } - } - - if (hintSize == null) { - hintSize = hint.getContent().getPreferredSize(); - } - - final Dimension size = myDropdownPopup.getSize(); - GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - final int width = gd.getDisplayMode().getWidth(); - Point p; - if (hintSize != null) { - final int dropDownX = myDropdownPopup.getLocationOnScreen().x; - if (dropDownX + size.width + 10 + hintSize.width < width || (dropDownX - hintSize.width - 10 < 0)) { - p = new Point(size.width + 5, 0); - } else { - p = new Point(-hintSize.width - 5, 0); - } - } else { - p = new Point(size.width + 5, 0); - } - - hint.show(new RelativePoint(myDropdownPopup.getContent(), p)); - } - } -} +/* + * 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.ide.util.gotoByName; + +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.ComponentPopupBuilder; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.DimensionService; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.statistics.StatisticsInfo; +import com.intellij.psi.statistics.StatisticsManager; +import com.intellij.ui.ScreenUtil; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.popup.AbstractPopup; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ChooseByNamePopup extends ChooseByNameBase implements ChooseByNamePopupComponent{ + private static final Key CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY = new Key("ChooseByNamePopup"); + private Component myOldFocusOwner = null; + private boolean myShowListForEmptyPattern = false; + + protected ChooseByNamePopup(final Project project, final ChooseByNameModel model, final ChooseByNamePopup oldPopup, + final PsiElement context, @Nullable final String predefinedText) { + super(project, model, oldPopup != null ? oldPopup.getEnteredText() : predefinedText, context); + if (oldPopup == null && predefinedText != null) { + setPreselectInitialText(true); + } + if (oldPopup != null) { //inherit old focus owner + myOldFocusOwner = oldPopup.myPreviouslyFocusedComponent; + } + } + + public String getEnteredText() { + return myTextField.getText(); + } + + protected void initUI(final Callback callback, final ModalityState modalityState, boolean allowMultipleSelection) { + super.initUI(callback, modalityState, allowMultipleSelection); + //LaterInvocator.enterModal(myTextFieldPanel); + if (myInitialText != null) { + rebuildList(0, 0, null, ModalityState.current(), null); + } + if (myOldFocusOwner != null){ + myPreviouslyFocusedComponent = myOldFocusOwner; + myOldFocusOwner = null; + } + } + + protected boolean isCheckboxVisible() { + return true; + } + + protected boolean isShowListForEmptyPattern(){ + return myShowListForEmptyPattern; + } + + public void setShowListForEmptyPattern(boolean showListForEmptyPattern) { + myShowListForEmptyPattern = showListForEmptyPattern; + } + + protected boolean isCloseByFocusLost(){ + return true; + } + + protected void showList() { + final JLayeredPane layeredPane = myTextField.getRootPane().getLayeredPane(); + + Rectangle bounds = new Rectangle(myTextFieldPanel.getLocationOnScreen(), myTextField.getSize()); + bounds.y += myTextFieldPanel.getHeight() + (SystemInfo.isMac ? 3 : 1); + + final Dimension preferredScrollPaneSize = myListScrollPane.getPreferredSize(); + preferredScrollPaneSize.width = Math.max(myTextFieldPanel.getWidth(), preferredScrollPaneSize.width); + + Rectangle prefferedBounds = new Rectangle(bounds.x, bounds.y, preferredScrollPaneSize.width, preferredScrollPaneSize.height); + ScreenUtil.fitToScreen(prefferedBounds); + + myListScrollPane.setVisible(true); + myListScrollPane.setBorder(null); + if (myDropdownPopup == null) { + ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myListScrollPane, myListScrollPane); + builder.setFocusable(false).setRequestFocus(false).setCancelKeyEnabled(false).setFocusOwners(new JComponent[] {myTextField}).setBelongsToGlobalPopupStack(false).setForceHeavyweight(true).setModalContext(false); + builder.setCancelCallback(new Computable() { + @Override + public Boolean compute() { + return Boolean.TRUE; + } + }); + myDropdownPopup = builder.createPopup(); + myDropdownPopup.setLocation(prefferedBounds.getLocation()); + myDropdownPopup.setSize(prefferedBounds.getSize()); + myDropdownPopup.show(layeredPane); + } else { + myDropdownPopup.setLocation(prefferedBounds.getLocation()); + myDropdownPopup.setSize(prefferedBounds.getSize()); + } + } + + protected void hideList() { + if (myDropdownPopup != null) { + myDropdownPopup.cancel(); + myDropdownPopup = null; + } + } + + protected void close(final boolean isOk) { + if (myDisposedFlag){ + return; + } + + if (isOk){ + myModel.saveInitialCheckBoxState(myCheckBox.isSelected()); + + final List chosenElements = getChosenElements(); + if (chosenElements != null) { + for (Object element : chosenElements) { + myActionListener.elementChosen(element); + String text = myModel.getFullName(element); + if (text != null) { + StatisticsManager.getInstance().incUseCount(new StatisticsInfo(statisticsContext(), text)); + } + } + } else { + return; + } + + if (!chosenElements.isEmpty()){ + final String enteredText = getEnteredText(); + if (enteredText.indexOf('*') >= 0) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.wildcards"); + } + else { + for (Object element : chosenElements) { + final String name = myModel.getElementName(element); + if (name != null) { + if (!StringUtil.startsWithIgnoreCase(name, enteredText)) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.camelprefix"); + break; + } + } + } + } + } + else{ + return; + } + } + + myDisposedFlag = true; + myAlarm.cancelAllRequests(); + myProject.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, null); + + //LaterInvocator.leaveModal(myTextFieldPanel); + + cleanupUI(); + myActionListener.onClose (); + } + + @Nullable + public static ChooseByNamePopup getActivePopup(@NotNull final Project project) { + return CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY.get(project); + } + + private void cleanupUI() { + if (myTextPopup != null) { + myTextPopup.cancel(); + myTextPopup = null; + } + + if (myDropdownPopup != null) { + myDropdownPopup.cancel(); + myDropdownPopup = null; + } + } + + public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context) { + return createPopup(project, model, context, null); + } + public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context, + @Nullable final String predefinedText) { + final ChooseByNamePopup oldPopup = project.getUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY); + if (oldPopup != null) { + oldPopup.close(false); + } + ChooseByNamePopup newPopup = new ChooseByNamePopup(project, model, oldPopup, context, predefinedText); + + project.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, newPopup); + return newPopup; + } + + private static final Pattern patternToDetectLinesAndColumns = Pattern.compile("(.*?)(?:\\:|@|,|#)(\\d+)?(?:(?:\\D)(\\d+)?)?"); + + public String getNamePattern(String pattern) { + if (pattern.indexOf(':') != -1 || + pattern.indexOf(',') != -1 || + pattern.indexOf(';') != -1 || + pattern.indexOf('#') != -1 || + pattern.indexOf('@') != -1) { // quick test if reg exp should be used + final Matcher matcher = patternToDetectLinesAndColumns.matcher(pattern); + if (matcher.matches()) { + pattern = matcher.group(1); + } + } + + return super.getNamePattern(pattern); + } + + public int getLinePosition() { + return getLineOrColumn(true); + } + + private int getLineOrColumn(final boolean line) { + final Matcher matcher = patternToDetectLinesAndColumns.matcher(getEnteredText()); + if (matcher.matches()) { + final int groupNumber = line ? 2:3; + try { + if(groupNumber <= matcher.groupCount()) { + final String group = matcher.group(groupNumber); + if (group != null) return Integer.parseInt(group) - 1; + } + if (!line && getLineOrColumn(true) != -1) return 0; + } + catch (NumberFormatException ignored) {} + } + + return -1; + } + + public int getColumnPosition() { + return getLineOrColumn(false); + } + + public void showItemPopup(final JBPopup hint) { + if (myDropdownPopup != null && myDropdownPopup.isVisible()) { + Dimension hintSize = null; + if (hint instanceof AbstractPopup) { + final String key = ((AbstractPopup)hint).getDimensionServiceKey(); + if (key != null) { + hintSize = DimensionService.getInstance().getSize(key); + } + } + + if (hintSize == null) { + hintSize = hint.getContent().getPreferredSize(); + } + + final Dimension size = myDropdownPopup.getSize(); + GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + final int width = gd.getDisplayMode().getWidth(); + Point p; + if (hintSize != null) { + final int dropDownX = myDropdownPopup.getLocationOnScreen().x; + if (dropDownX + size.width + 10 + hintSize.width < width || (dropDownX - hintSize.width - 10 < 0)) { + p = new Point(size.width + 5, 0); + } else { + p = new Point(-hintSize.width - 5, 0); + } + } else { + p = new Point(size.width + 5, 0); + } + + hint.show(new RelativePoint(myDropdownPopup.getContent(), p)); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java index a0b464e08e2f..c4cb198940af 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java @@ -1,786 +1,786 @@ -/* - * 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.injected.editor; - -import com.intellij.ide.DataManager; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.event.DocumentListener; -import com.intellij.openapi.editor.ex.*; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.editor.markup.MarkupModel; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiLanguageInjectionHost; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.impl.source.tree.injected.Place; -import com.intellij.util.Processor; -import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @author Alexey - */ -public class DocumentWindowImpl extends UserDataHolderBase implements Disposable, DocumentWindow, DocumentEx { - private static final Logger LOG = Logger.getInstance("com.intellij.openapi.editor.impl.injected.DocumentRangee"); - private final DocumentEx myDelegate; - private final boolean myOneLine; - private Place myShreds; - private final int myPrefixLineCount; - private final int mySuffixLineCount; - - public DocumentWindowImpl(@NotNull DocumentEx delegate, boolean oneLine, Place shreds) { - myDelegate = delegate; - myOneLine = oneLine; - myShreds = shreds; - myPrefixLineCount = Math.max(1, 1 + StringUtil.countNewLines(myShreds.get(0).prefix)); - mySuffixLineCount = Math.max(1, 1 + StringUtil.countNewLines(myShreds.get(shreds.size()- 1).suffix)); - } - - public int getLineCount() { - return 1 + StringUtil.countNewLines(getText()); - } - - public int getLineStartOffset(int line) { - //int oldso = oldso(line); - int newso = newso(line); - //if (newso != oldso) { - // int i = 0; - //} - - return newso; - } - - private int newso(int line) { - LOG.assertTrue(line >= 0, line); - String hostText = myDelegate.getText(); - - int curLine = 0; - int startOffset = 0; - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker hostRange = shred.getHostRangeMarker(); - if (!hostRange.isValid()) continue; - - startOffset += shred.prefix.length(); - curLine += StringUtil.getLineBreakCount(shred.prefix); - if (curLine >= line) return startOffset; - String text = hostText.substring(hostRange.getStartOffset(), hostRange.getEndOffset()); - - for (int i=text.indexOf('\n'), offsetInside = 0; i!=-1;i=text.substring(offsetInside).indexOf('\n')) { - curLine++; - offsetInside += i + 1; - if (curLine >= line) return startOffset + offsetInside; - } - - startOffset += text.length(); - - text = shred.suffix; - for (int i=text.indexOf('\n'), offsetInside = 0; i!=-1;i=text.substring(offsetInside).indexOf('\n')) { - curLine++; - offsetInside += i + 1; - if (curLine >= line) return startOffset + offsetInside; - } - startOffset += text.length(); - } - - return startOffset; - } - - private int oldso(int line) { - assert line >= 0 : line; - return new DocumentImpl(getText()).getLineStartOffset(line); - } - - public int getLineEndOffset(int line) { - LOG.assertTrue(line >= 0, line); - int startOffsetOfNextLine = getLineStartOffset(line + 1); - return startOffsetOfNextLine == 0 || getText().charAt(startOffsetOfNextLine - 1) != '\n' ? startOffsetOfNextLine : startOffsetOfNextLine - 1; - } - - public String getText() { - StringBuilder text = new StringBuilder(); - String hostText = myDelegate.getText(); - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker hostRange = shred.getHostRangeMarker(); - if (hostRange.isValid()) { - text.append(shred.prefix); - text.append(hostText, hostRange.getStartOffset(), hostRange.getEndOffset()); - text.append(shred.suffix); - } - } - return text.toString(); - } - - @NotNull - @Override - public String getText(@NotNull TextRange range) { - return range.substring(getText()); - } - - @NotNull - public CharSequence getCharsSequence() { - return getText(); - } - - @NotNull - public char[] getChars() { - return CharArrayUtil.fromSequence(getText()); - } - - public int getTextLength() { - int length = 0; - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker hostRange = shred.getHostRangeMarker(); - length += shred.prefix.length(); - length += hostRange.getEndOffset() - hostRange.getStartOffset(); - length += shred.suffix.length(); - } - return length; - } - - public int getLineNumber(int offset) { - int lineNumber = 0; - String hostText = myDelegate.getText(); - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - String prefix = shred.prefix; - String suffix = shred.suffix; - lineNumber += StringUtil.getLineBreakCount(prefix.substring(0, Math.min(offset, prefix.length()))); - if (offset < prefix.length()) { - return lineNumber; - } - offset -= prefix.length(); - - RangeMarker currentRange = shred.getHostRangeMarker(); - if (!currentRange.isValid()) continue; - int rangeLength = currentRange.getEndOffset() - currentRange.getStartOffset(); - String rangeText = hostText.substring(currentRange.getStartOffset(), currentRange.getEndOffset()); - - lineNumber += StringUtil.getLineBreakCount(rangeText.substring(0, Math.min(offset, rangeLength))); - if (offset < rangeLength) { - return lineNumber; - } - offset -= rangeLength; - - lineNumber += StringUtil.getLineBreakCount(suffix.substring(0, Math.min(offset, suffix.length()))); - if (offset < suffix.length()) { - return lineNumber; - } - - offset -= suffix.length(); - } - lineNumber = getLineCount() - 1; - return lineNumber < 0 ? 0 : lineNumber; - } - - public TextRange getHostRange(int hostOffset) { - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker currentRange = shred.getHostRangeMarker(); - TextRange textRange = InjectedLanguageUtil.toTextRange(currentRange); - if (textRange.grown(1).contains(hostOffset)) return textRange; - } - return null; - } - - public void insertString(final int offset, @NotNull CharSequence s) { - LOG.assertTrue(offset >= myShreds.get(0).prefix.length(), myShreds.get(0).prefix); - LOG.assertTrue(offset <= getTextLength() - myShreds.get(myShreds.size() - 1).suffix.length(), myShreds.get(myShreds.size() - 1).suffix); - if (isOneLine()) { - s = StringUtil.replace(s.toString(), "\n", ""); - } - myDelegate.insertString(injectedToHost(offset), s); - } - - public void deleteString(final int startOffset, final int endOffset) { - assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null; - assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null; - - List hostRangesToDelete = new ArrayList(myShreds.size()); - - int offset = startOffset; - int curRangeStart = 0; - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - curRangeStart += shred.prefix.length(); - if (offset < curRangeStart) offset = curRangeStart; - if (offset >= endOffset) break; - RangeMarker hostRange = shred.getHostRangeMarker(); - if (!hostRange.isValid()) continue; - int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset(); - TextRange range = TextRange.from(curRangeStart, hostRangeLength); - if (range.contains(offset)) { - TextRange rangeToDelete = new TextRange(offset, Math.min(range.getEndOffset(), endOffset)); - hostRangesToDelete.add(rangeToDelete.shiftRight(hostRange.getStartOffset() - curRangeStart)); - offset = rangeToDelete.getEndOffset(); - } - curRangeStart += hostRangeLength; - curRangeStart += shred.suffix.length(); - } - - int delta = 0; - for (TextRange hostRangeToDelete : hostRangesToDelete) { - myDelegate.deleteString(hostRangeToDelete.getStartOffset() + delta, hostRangeToDelete.getEndOffset() + delta); - delta -= hostRangeToDelete.getLength(); - } - } - - public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) { - if (isOneLine()) { - s = StringUtil.replace(s.toString(), "\n", ""); - } - - final CharSequence chars = getCharsSequence(); - CharSequence toDelete = chars.subSequence(startOffset, endOffset); - - int perfixLength = StringUtil.commonPrefixLength(s, toDelete); - int suffixLength = StringUtil.commonSuffixLength(toDelete.subSequence(perfixLength, toDelete.length()), s.subSequence(perfixLength, s.length())); - startOffset += perfixLength; - endOffset -= suffixLength; - s = s.subSequence(perfixLength, s.length() - suffixLength); - - doReplaceString(startOffset, endOffset, s); - } - - private void doReplaceString(int startOffset, int endOffset, CharSequence s) { - assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null; - assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null; - - List> hostRangesToModify = new ArrayList>(myShreds.size()); - - int offset = startOffset; - int curRangeStart = 0; - for (int i = 0; i < myShreds.size(); i++) { - PsiLanguageInjectionHost.Shred shred = myShreds.get(i); - curRangeStart += shred.prefix.length(); - if (offset < curRangeStart) offset = curRangeStart; - RangeMarker hostRange = shred.getHostRangeMarker(); - if (!hostRange.isValid()) continue; - int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset(); - TextRange range = TextRange.from(curRangeStart, hostRangeLength); - if (range.contains(offset) || range.getEndOffset() == offset/* in case of inserting at the end*/) { - TextRange rangeToModify = new TextRange(offset, Math.min(range.getEndOffset(), endOffset)); - TextRange hostRangeToModify = rangeToModify.shiftRight(hostRange.getStartOffset() - curRangeStart); - CharSequence toReplace = i == myShreds.size() - 1 || range.getEndOffset() + shred.suffix.length() >= endOffset - ? s : s.subSequence(0, Math.min(hostRangeToModify.getLength(), s.length())); - s = toReplace == s ? "" : s.subSequence(toReplace.length(), s.length()); - hostRangesToModify.add(Pair.create(hostRangeToModify, toReplace)); - offset = rangeToModify.getEndOffset(); - } - curRangeStart += hostRangeLength; - curRangeStart += shred.suffix.length(); - if (curRangeStart >= endOffset) break; - } - - int delta = 0; - for (Pair pair : hostRangesToModify) { - TextRange hostRange = pair.getFirst(); - CharSequence replace = pair.getSecond(); - - myDelegate.replaceString(hostRange.getStartOffset() + delta, hostRange.getEndOffset() + delta, replace); - delta -= hostRange.getLength() - replace.length(); - } - } - - public boolean isWritable() { - return myDelegate.isWritable(); - } - - public long getModificationStamp() { - return myDelegate.getModificationStamp(); - } - - public void fireReadOnlyModificationAttempt() { - myDelegate.fireReadOnlyModificationAttempt(); - } - - public void addDocumentListener(@NotNull final DocumentListener listener) { - myDelegate.addDocumentListener(listener); - } - - public void addDocumentListener(@NotNull DocumentListener listener, @NotNull Disposable parentDisposable) { - myDelegate.addDocumentListener(listener, parentDisposable); - } - - public void removeDocumentListener(@NotNull final DocumentListener listener) { - myDelegate.removeDocumentListener(listener); - } - - @NotNull - public RangeMarker createRangeMarker(final int startOffset, final int endOffset) { - TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); - RangeMarker hostMarker = myDelegate.createRangeMarker(hostRange); - return new RangeMarkerWindow(this, (RangeMarkerEx)hostMarker); - } - - @NotNull - public RangeMarker createRangeMarker(final int startOffset, final int endOffset, final boolean surviveOnExternalChange) { - if (!surviveOnExternalChange) { - return createRangeMarker(startOffset, endOffset); - } - TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); - //todo persistent? - RangeMarker hostMarker = myDelegate.createRangeMarker(hostRange.getStartOffset(), hostRange.getEndOffset(), surviveOnExternalChange); - return new RangeMarkerWindow(this, (RangeMarkerEx)hostMarker); - } - - @NotNull - public MarkupModel getMarkupModel() { - //noinspection deprecation - return new MarkupModelWindow((MarkupModelEx)myDelegate.getMarkupModel(), this); - } - - @NotNull - public MarkupModel getMarkupModel(final Project project) { - return new MarkupModelWindow((MarkupModelEx)myDelegate.getMarkupModel(project), this); - } - - public void addPropertyChangeListener(@NotNull final PropertyChangeListener listener) { - myDelegate.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(@NotNull final PropertyChangeListener listener) { - myDelegate.removePropertyChangeListener(listener); - } - - public void setReadOnly(final boolean isReadOnly) { - myDelegate.setReadOnly(isReadOnly); - } - - @NotNull - public RangeMarker createGuardedBlock(final int startOffset, final int endOffset) { - TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); - return myDelegate.createGuardedBlock(hostRange.getStartOffset(), hostRange.getEndOffset()); - } - - public void removeGuardedBlock(@NotNull final RangeMarker block) { - myDelegate.removeGuardedBlock(block); - } - - public RangeMarker getOffsetGuard(final int offset) { - return myDelegate.getOffsetGuard(injectedToHost(offset)); - } - - public RangeMarker getRangeGuard(final int startOffset, final int endOffset) { - TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); - - return myDelegate.getRangeGuard(hostRange.getStartOffset(), hostRange.getEndOffset()); - } - - public void startGuardedBlockChecking() { - myDelegate.startGuardedBlockChecking(); - } - - public void stopGuardedBlockChecking() { - myDelegate.stopGuardedBlockChecking(); - } - - public void setCyclicBufferSize(final int bufferSize) { - myDelegate.setCyclicBufferSize(bufferSize); - } - - public void setText(@NotNull CharSequence text) { - LOG.assertTrue(text.toString().startsWith(myShreds.get(0).prefix)); - LOG.assertTrue(text.toString().endsWith(myShreds.get(myShreds.size() - 1).suffix)); - if (isOneLine()) { - text = StringUtil.replace(text.toString(), "\n", ""); - } - String[] changes = calculateMinEditSequence(text.toString()); - assert changes.length == myShreds.size(); - for (int i = 0; i < changes.length; i++) { - String change = changes[i]; - if (change != null) { - RangeMarker hostRange = myShreds.get(i).getHostRangeMarker(); - myDelegate.replaceString(hostRange.getStartOffset(), hostRange.getEndOffset(), change); - } - } - } - - @NotNull - public RangeMarker[] getHostRanges() { - RangeMarker[] markers = new RangeMarker[myShreds.size()]; - for (int i = 0; i < myShreds.size(); i++) { - PsiLanguageInjectionHost.Shred shred = myShreds.get(i); - markers[i] = shred.getHostRangeMarker(); - } - return markers; - } - - @NotNull - public RangeMarker createRangeMarker(@NotNull final TextRange textRange) { - TextRange hostRange = injectedToHost(new ProperTextRange(textRange)); - RangeMarker hostMarker = myDelegate.createRangeMarker(hostRange); - return new RangeMarkerWindow(this, (RangeMarkerEx)hostMarker); - } - - public boolean stripTrailingSpaces(final boolean inChangedLinesOnly) { - return myDelegate.stripTrailingSpaces(inChangedLinesOnly); - } - - public void setStripTrailingSpacesEnabled(final boolean isEnabled) { - myDelegate.setStripTrailingSpacesEnabled(isEnabled); - } - - public int getLineSeparatorLength(final int line) { - return myDelegate.getLineSeparatorLength(injectedToHostLine(line)); - } - - @NotNull - public LineIterator createLineIterator() { - return myDelegate.createLineIterator(); - } - - public void setModificationStamp(final long modificationStamp) { - myDelegate.setModificationStamp(modificationStamp); - } - - public void addEditReadOnlyListener(@NotNull final EditReadOnlyListener listener) { - myDelegate.addEditReadOnlyListener(listener); - } - - public void removeEditReadOnlyListener(@NotNull final EditReadOnlyListener listener) { - myDelegate.removeEditReadOnlyListener(listener); - } - - public void replaceText(@NotNull final CharSequence chars, final long newModificationStamp) { - setText(chars); - myDelegate.setModificationStamp(newModificationStamp); - } - - public int getListenersCount() { - return myDelegate.getListenersCount(); - } - - public void suppressGuardedExceptions() { - myDelegate.suppressGuardedExceptions(); - } - - public void unSuppressGuardedExceptions() { - myDelegate.unSuppressGuardedExceptions(); - } - - public boolean isInEventsHandling() { - return myDelegate.isInEventsHandling(); - } - - public void clearLineModificationFlags() { - } - - public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { - return myDelegate.removeRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); - } - - public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { - myDelegate.addRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); - } - - public boolean isInBulkUpdate() { - return false; - } - - public void setInBulkUpdate(boolean value) { - } - - @NotNull - public DocumentEx getDelegate() { - return myDelegate; - } - - //todo use escaper? - public int hostToInjected(int hostOffset) { - if (hostOffset < myShreds.get(0).getHostRangeMarker().getStartOffset()) return myShreds.get(0).prefix.length(); - int offset = 0; - for (int i = 0; i < myShreds.size(); i++) { - offset += myShreds.get(i).prefix.length(); - RangeMarker currentRange = myShreds.get(i).getHostRangeMarker(); - RangeMarker nextRange = i == myShreds.size() - 1 ? null : myShreds.get(i + 1).getHostRangeMarker(); - if (nextRange == null || hostOffset < nextRange.getStartOffset()) { - if (hostOffset >= currentRange.getEndOffset()) hostOffset = currentRange.getEndOffset(); - return offset + hostOffset - currentRange.getStartOffset(); - } - offset += currentRange.getEndOffset() - currentRange.getStartOffset(); - offset += myShreds.get(i).suffix.length(); - } - return getTextLength() - myShreds.get(myShreds.size() - 1).suffix.length(); - } - - public int injectedToHost(int offset) { - int offsetInLeftFragment = injectedToHost(offset, true); - int offsetInRightFragment = injectedToHost(offset, false); - if (offsetInLeftFragment == offsetInRightFragment) return offsetInLeftFragment; - - // heuristics: return offset closest to caret - Editor editor = PlatformDataKeys.EDITOR.getData(DataManager.getInstance().getDataContext()); - if (editor instanceof EditorWindow) editor = ((EditorWindow)editor).getDelegate(); - if (editor != null) { - int caret = editor.getCaretModel().getOffset(); - return Math.abs(caret - offsetInLeftFragment) < Math.abs(caret - offsetInRightFragment) ? offsetInLeftFragment : offsetInRightFragment; - } - return offsetInLeftFragment; - } - - private int injectedToHost(int offset, boolean preferLeftFragment) { - if (offset < myShreds.get(0).prefix.length()) return myShreds.get(0).getHostRangeMarker().getStartOffset(); - int prevEnd = 0; - for (int i = 0; i < myShreds.size(); i++) { - RangeMarker currentRange = myShreds.get(i).getHostRangeMarker(); - offset -= myShreds.get(i).prefix.length(); - int length = currentRange.getEndOffset() - currentRange.getStartOffset(); - if (offset < 0) { - return preferLeftFragment ? prevEnd : currentRange.getStartOffset() - 1; - } - else if (offset == 0) { - return preferLeftFragment && i != 0 ? prevEnd : currentRange.getStartOffset(); - } - else if (offset < length || offset == length && preferLeftFragment) { - return currentRange.getStartOffset() + offset; - } - offset -= length; - offset -= myShreds.get(i).suffix.length(); - prevEnd = currentRange.getEndOffset(); - } - return myShreds.get(myShreds.size() - 1).getHostRangeMarker().getEndOffset(); - } - - @NotNull - public TextRange injectedToHost(@NotNull TextRange injected) { - ProperTextRange.assertProperRange(injected); - int start = injectedToHost(injected.getStartOffset(), false); - int end = injectedToHost(injected.getEndOffset(), true); - if (end < start) { - end = injectedToHost(injected.getEndOffset(), false); - } - return new ProperTextRange(start, end); - } - - public int injectedToHostLine(int line) { - if (line < myPrefixLineCount) { - return myDelegate.getLineNumber(myShreds.get(0).getHostRangeMarker().getStartOffset()); - } - int lineCount = getLineCount(); - if (line > lineCount - mySuffixLineCount) { - return lineCount; - } - int offset = getLineStartOffset(line); - int hostOffset = injectedToHost(offset); - - return myDelegate.getLineNumber(hostOffset); - } - - public boolean containsRange(int start, int end) { - if (end - start > myShreds.get(0).getHostRangeMarker().getEndOffset() - myShreds.get(0).getHostRangeMarker().getStartOffset()) return false; - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker hostRange = shred.getHostRangeMarker(); - if (!hostRange.isValid()) continue; - TextRange textRange = InjectedLanguageUtil.toTextRange(hostRange); - if (textRange.contains(new ProperTextRange(start, end))) return true; - } - return false; - } - - @Deprecated - @Nullable - public TextRange intersectWithEditable(@NotNull TextRange rangeToEdit) { - int offset = 0; - int startOffset = -1; - int endOffset = -1; - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker hostRange = shred.getHostRangeMarker(); - offset += shred.prefix.length(); - int length = hostRange.getEndOffset() - hostRange.getStartOffset(); - TextRange intersection = new ProperTextRange(offset, offset + length).intersection(rangeToEdit); - if (intersection != null) { - if (startOffset == -1) { - startOffset = intersection.getStartOffset(); - } - endOffset = intersection.getEndOffset(); - } - offset += length; - offset += shred.suffix.length(); - } - if (startOffset == -1) return null; - return new ProperTextRange(startOffset, endOffset); - } - - boolean intersects(DocumentWindowImpl documentWindow) { - int i = 0; - int j = 0; - while (i < myShreds.size() && j < documentWindow.myShreds.size()) { - RangeMarker range = myShreds.get(i).getHostRangeMarker(); - RangeMarker otherRange = documentWindow.myShreds.get(j).getHostRangeMarker(); - if (InjectedLanguageUtil.toTextRange(range).intersects(InjectedLanguageUtil.toTextRange(otherRange))) return true; - if (range.getEndOffset() > otherRange.getStartOffset()) i++; - else if (range.getStartOffset() < otherRange.getEndOffset()) j++; - else { - i++; - j++; - } - } - return false; - } - - // minimum sequence of text replacement operations for each host range - // result[i] == null means no change - // result[i] == "" means delete - // result[i] == string means replace - public String[] calculateMinEditSequence(String newText) { - String[] result = new String[myShreds.size()]; - String hostText = myDelegate.getText(); - calculateMinEditSequence(hostText, newText, result, 0, result.length - 1); - for (int i = 0; i < result.length; i++) { - String change = result[i]; - if (change == null) continue; - String prefix = myShreds.get(i).prefix; - String suffix = myShreds.get(i).suffix; - assert change.startsWith(prefix) : change + "/" + prefix; - assert change.endsWith(suffix) : change + "/" + suffix; - result[i] = StringUtil.trimEnd(StringUtil.trimStart(change, prefix), suffix); - } - return result; - } - - private String getRangeText(String hostText, int hostNum) { - PsiLanguageInjectionHost.Shred shred = myShreds.get(hostNum); - return shred.prefix + - hostText.substring(shred.getHostRangeMarker().getStartOffset(), shred.getHostRangeMarker().getEndOffset()) + - shred.suffix; - } - private void calculateMinEditSequence(String hostText, String newText, String[] result, int i, int j) { - String rangeText1 = getRangeText(hostText, i); - if (i == j) { - result[i] = rangeText1.equals(newText) ? null : newText; - return; - } - if (StringUtil.startsWith(newText, rangeText1)) { - result[i] = null; //no change - calculateMinEditSequence(hostText, newText.substring(rangeText1.length()), result, i+1, j); - return; - } - String rangeText2 = getRangeText(hostText, j); - if (StringUtil.endsWith(newText, rangeText2)) { - result[j] = null; //no change - calculateMinEditSequence(hostText, newText.substring(0, newText.length() - rangeText2.length()), result, i, j-1); - return; - } - if (i+1 == j) { - String suffix = myShreds.get(i).suffix; - String prefix = myShreds.get(j).prefix; - String separator = suffix + prefix; - if (separator.length() != 0) { - int sep = newText.indexOf(separator); - assert sep != -1; - result[i] = newText.substring(0, sep + suffix.length()); - result[j] = newText.substring(sep + suffix.length() + prefix.length(), newText.length()); - return; - } - String commonPrefix = StringUtil.commonPrefix(rangeText1, newText); - result[i] = commonPrefix; - result[j] = newText.substring(commonPrefix.length()); - return; - } - String middleText = getRangeText(hostText, i + 1); - int m = newText.indexOf(middleText); - if (m != -1) { - result[i] = newText.substring(0, m); - result[i+1] = null; - calculateMinEditSequence(hostText, newText.substring(m+middleText.length(), newText.length()), result, i+2, j); - return; - } - middleText = getRangeText(hostText, j - 1); - m = newText.lastIndexOf(middleText); - if (m != -1) { - result[j] = newText.substring(m+middleText.length()); - result[j-1] = null; - calculateMinEditSequence(hostText, newText.substring(0, m), result, i, j-2); - return; - } - result[i] = ""; - result[j] = ""; - calculateMinEditSequence(hostText, newText, result, i+1, j-1); - } - - public boolean areRangesEqual(@NotNull DocumentWindow otherd) { - DocumentWindowImpl window = (DocumentWindowImpl)otherd; - if (myShreds.size() != window.myShreds.size()) return false; - for (int i = 0; i < myShreds.size(); i++) { - PsiLanguageInjectionHost.Shred shred = myShreds.get(i); - PsiLanguageInjectionHost.Shred otherShred = window.myShreds.get(i); - if (!shred.prefix.equals(otherShred.prefix)) return false; - if (!shred.suffix.equals(otherShred.suffix)) return false; - - RangeMarker hostRange = shred.getHostRangeMarker(); - RangeMarker other = otherShred.getHostRangeMarker(); - if (hostRange.getStartOffset() != other.getStartOffset()) return false; - if (hostRange.getEndOffset() != other.getEndOffset()) return false; - } - return true; - } - - public boolean isValid() { - return myShreds.isValid(); - } - - public boolean equals(Object o) { - if (!(o instanceof DocumentWindowImpl)) return false; - DocumentWindowImpl window = (DocumentWindowImpl)o; - return myDelegate.equals(window.getDelegate()) && areRangesEqual(window); - } - - public int hashCode() { - return myShreds.get(0).getHostRangeMarker().getStartOffset(); - } - - public boolean isOneLine() { - return myOneLine; - } - - public void dispose() { - for (PsiLanguageInjectionHost.Shred shred : myShreds) { - RangeMarker rangeMarker = shred.getHostRangeMarker(); - rangeMarker.dispose(); - } - } - - public void setShreds(Place shreds) { - myShreds = shreds; - } - - @NotNull - public List getGuardedBlocks() { - return Collections.emptyList(); - } - - //todo convert injected RMs to host - public boolean processRangeMarkers(@NotNull Processor processor) { - return myDelegate.processRangeMarkers(processor); - } - - public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { - return myDelegate.processRangeMarkersOverlappingWith(start, end, processor); - } - - public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { - return myDelegate.processRangeMarkersOverlappingWith(offset, processor); - } -} +/* + * 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.injected.editor; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.editor.ex.*; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.editor.markup.MarkupModel; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiLanguageInjectionHost; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.impl.source.tree.injected.Place; +import com.intellij.util.Processor; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Alexey + */ +public class DocumentWindowImpl extends UserDataHolderBase implements Disposable, DocumentWindow, DocumentEx { + private static final Logger LOG = Logger.getInstance("com.intellij.openapi.editor.impl.injected.DocumentRangee"); + private final DocumentEx myDelegate; + private final boolean myOneLine; + private Place myShreds; + private final int myPrefixLineCount; + private final int mySuffixLineCount; + + public DocumentWindowImpl(@NotNull DocumentEx delegate, boolean oneLine, Place shreds) { + myDelegate = delegate; + myOneLine = oneLine; + myShreds = shreds; + myPrefixLineCount = Math.max(1, 1 + StringUtil.countNewLines(myShreds.get(0).prefix)); + mySuffixLineCount = Math.max(1, 1 + StringUtil.countNewLines(myShreds.get(shreds.size()- 1).suffix)); + } + + public int getLineCount() { + return 1 + StringUtil.countNewLines(getText()); + } + + public int getLineStartOffset(int line) { + //int oldso = oldso(line); + int newso = newso(line); + //if (newso != oldso) { + // int i = 0; + //} + + return newso; + } + + private int newso(int line) { + LOG.assertTrue(line >= 0, line); + String hostText = myDelegate.getText(); + + int curLine = 0; + int startOffset = 0; + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker hostRange = shred.getHostRangeMarker(); + if (!hostRange.isValid()) continue; + + startOffset += shred.prefix.length(); + curLine += StringUtil.getLineBreakCount(shred.prefix); + if (curLine >= line) return startOffset; + String text = hostText.substring(hostRange.getStartOffset(), hostRange.getEndOffset()); + + for (int i=text.indexOf('\n'), offsetInside = 0; i!=-1;i=text.substring(offsetInside).indexOf('\n')) { + curLine++; + offsetInside += i + 1; + if (curLine >= line) return startOffset + offsetInside; + } + + startOffset += text.length(); + + text = shred.suffix; + for (int i=text.indexOf('\n'), offsetInside = 0; i!=-1;i=text.substring(offsetInside).indexOf('\n')) { + curLine++; + offsetInside += i + 1; + if (curLine >= line) return startOffset + offsetInside; + } + startOffset += text.length(); + } + + return startOffset; + } + + private int oldso(int line) { + assert line >= 0 : line; + return new DocumentImpl(getText()).getLineStartOffset(line); + } + + public int getLineEndOffset(int line) { + LOG.assertTrue(line >= 0, line); + int startOffsetOfNextLine = getLineStartOffset(line + 1); + return startOffsetOfNextLine == 0 || getText().charAt(startOffsetOfNextLine - 1) != '\n' ? startOffsetOfNextLine : startOffsetOfNextLine - 1; + } + + public String getText() { + StringBuilder text = new StringBuilder(); + String hostText = myDelegate.getText(); + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker hostRange = shred.getHostRangeMarker(); + if (hostRange.isValid()) { + text.append(shred.prefix); + text.append(hostText, hostRange.getStartOffset(), hostRange.getEndOffset()); + text.append(shred.suffix); + } + } + return text.toString(); + } + + @NotNull + @Override + public String getText(@NotNull TextRange range) { + return range.substring(getText()); + } + + @NotNull + public CharSequence getCharsSequence() { + return getText(); + } + + @NotNull + public char[] getChars() { + return CharArrayUtil.fromSequence(getText()); + } + + public int getTextLength() { + int length = 0; + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker hostRange = shred.getHostRangeMarker(); + length += shred.prefix.length(); + length += hostRange.getEndOffset() - hostRange.getStartOffset(); + length += shred.suffix.length(); + } + return length; + } + + public int getLineNumber(int offset) { + int lineNumber = 0; + String hostText = myDelegate.getText(); + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + String prefix = shred.prefix; + String suffix = shred.suffix; + lineNumber += StringUtil.getLineBreakCount(prefix.substring(0, Math.min(offset, prefix.length()))); + if (offset < prefix.length()) { + return lineNumber; + } + offset -= prefix.length(); + + RangeMarker currentRange = shred.getHostRangeMarker(); + if (!currentRange.isValid()) continue; + int rangeLength = currentRange.getEndOffset() - currentRange.getStartOffset(); + String rangeText = hostText.substring(currentRange.getStartOffset(), currentRange.getEndOffset()); + + lineNumber += StringUtil.getLineBreakCount(rangeText.substring(0, Math.min(offset, rangeLength))); + if (offset < rangeLength) { + return lineNumber; + } + offset -= rangeLength; + + lineNumber += StringUtil.getLineBreakCount(suffix.substring(0, Math.min(offset, suffix.length()))); + if (offset < suffix.length()) { + return lineNumber; + } + + offset -= suffix.length(); + } + lineNumber = getLineCount() - 1; + return lineNumber < 0 ? 0 : lineNumber; + } + + public TextRange getHostRange(int hostOffset) { + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker currentRange = shred.getHostRangeMarker(); + TextRange textRange = InjectedLanguageUtil.toTextRange(currentRange); + if (textRange.grown(1).contains(hostOffset)) return textRange; + } + return null; + } + + public void insertString(final int offset, @NotNull CharSequence s) { + LOG.assertTrue(offset >= myShreds.get(0).prefix.length(), myShreds.get(0).prefix); + LOG.assertTrue(offset <= getTextLength() - myShreds.get(myShreds.size() - 1).suffix.length(), myShreds.get(myShreds.size() - 1).suffix); + if (isOneLine()) { + s = StringUtil.replace(s.toString(), "\n", ""); + } + myDelegate.insertString(injectedToHost(offset), s); + } + + public void deleteString(final int startOffset, final int endOffset) { + assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null; + assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null; + + List hostRangesToDelete = new ArrayList(myShreds.size()); + + int offset = startOffset; + int curRangeStart = 0; + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + curRangeStart += shred.prefix.length(); + if (offset < curRangeStart) offset = curRangeStart; + if (offset >= endOffset) break; + RangeMarker hostRange = shred.getHostRangeMarker(); + if (!hostRange.isValid()) continue; + int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset(); + TextRange range = TextRange.from(curRangeStart, hostRangeLength); + if (range.contains(offset)) { + TextRange rangeToDelete = new TextRange(offset, Math.min(range.getEndOffset(), endOffset)); + hostRangesToDelete.add(rangeToDelete.shiftRight(hostRange.getStartOffset() - curRangeStart)); + offset = rangeToDelete.getEndOffset(); + } + curRangeStart += hostRangeLength; + curRangeStart += shred.suffix.length(); + } + + int delta = 0; + for (TextRange hostRangeToDelete : hostRangesToDelete) { + myDelegate.deleteString(hostRangeToDelete.getStartOffset() + delta, hostRangeToDelete.getEndOffset() + delta); + delta -= hostRangeToDelete.getLength(); + } + } + + public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) { + if (isOneLine()) { + s = StringUtil.replace(s.toString(), "\n", ""); + } + + final CharSequence chars = getCharsSequence(); + CharSequence toDelete = chars.subSequence(startOffset, endOffset); + + int perfixLength = StringUtil.commonPrefixLength(s, toDelete); + int suffixLength = StringUtil.commonSuffixLength(toDelete.subSequence(perfixLength, toDelete.length()), s.subSequence(perfixLength, s.length())); + startOffset += perfixLength; + endOffset -= suffixLength; + s = s.subSequence(perfixLength, s.length() - suffixLength); + + doReplaceString(startOffset, endOffset, s); + } + + private void doReplaceString(int startOffset, int endOffset, CharSequence s) { + assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null; + assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null; + + List> hostRangesToModify = new ArrayList>(myShreds.size()); + + int offset = startOffset; + int curRangeStart = 0; + for (int i = 0; i < myShreds.size(); i++) { + PsiLanguageInjectionHost.Shred shred = myShreds.get(i); + curRangeStart += shred.prefix.length(); + if (offset < curRangeStart) offset = curRangeStart; + RangeMarker hostRange = shred.getHostRangeMarker(); + if (!hostRange.isValid()) continue; + int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset(); + TextRange range = TextRange.from(curRangeStart, hostRangeLength); + if (range.contains(offset) || range.getEndOffset() == offset/* in case of inserting at the end*/) { + TextRange rangeToModify = new TextRange(offset, Math.min(range.getEndOffset(), endOffset)); + TextRange hostRangeToModify = rangeToModify.shiftRight(hostRange.getStartOffset() - curRangeStart); + CharSequence toReplace = i == myShreds.size() - 1 || range.getEndOffset() + shred.suffix.length() >= endOffset + ? s : s.subSequence(0, Math.min(hostRangeToModify.getLength(), s.length())); + s = toReplace == s ? "" : s.subSequence(toReplace.length(), s.length()); + hostRangesToModify.add(Pair.create(hostRangeToModify, toReplace)); + offset = rangeToModify.getEndOffset(); + } + curRangeStart += hostRangeLength; + curRangeStart += shred.suffix.length(); + if (curRangeStart >= endOffset) break; + } + + int delta = 0; + for (Pair pair : hostRangesToModify) { + TextRange hostRange = pair.getFirst(); + CharSequence replace = pair.getSecond(); + + myDelegate.replaceString(hostRange.getStartOffset() + delta, hostRange.getEndOffset() + delta, replace); + delta -= hostRange.getLength() - replace.length(); + } + } + + public boolean isWritable() { + return myDelegate.isWritable(); + } + + public long getModificationStamp() { + return myDelegate.getModificationStamp(); + } + + public void fireReadOnlyModificationAttempt() { + myDelegate.fireReadOnlyModificationAttempt(); + } + + public void addDocumentListener(@NotNull final DocumentListener listener) { + myDelegate.addDocumentListener(listener); + } + + public void addDocumentListener(@NotNull DocumentListener listener, @NotNull Disposable parentDisposable) { + myDelegate.addDocumentListener(listener, parentDisposable); + } + + public void removeDocumentListener(@NotNull final DocumentListener listener) { + myDelegate.removeDocumentListener(listener); + } + + @NotNull + public RangeMarker createRangeMarker(final int startOffset, final int endOffset) { + TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); + RangeMarker hostMarker = myDelegate.createRangeMarker(hostRange); + return new RangeMarkerWindow(this, (RangeMarkerEx)hostMarker); + } + + @NotNull + public RangeMarker createRangeMarker(final int startOffset, final int endOffset, final boolean surviveOnExternalChange) { + if (!surviveOnExternalChange) { + return createRangeMarker(startOffset, endOffset); + } + TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); + //todo persistent? + RangeMarker hostMarker = myDelegate.createRangeMarker(hostRange.getStartOffset(), hostRange.getEndOffset(), surviveOnExternalChange); + return new RangeMarkerWindow(this, (RangeMarkerEx)hostMarker); + } + + @NotNull + public MarkupModel getMarkupModel() { + //noinspection deprecation + return new MarkupModelWindow((MarkupModelEx)myDelegate.getMarkupModel(), this); + } + + @NotNull + public MarkupModel getMarkupModel(final Project project) { + return new MarkupModelWindow((MarkupModelEx)myDelegate.getMarkupModel(project), this); + } + + public void addPropertyChangeListener(@NotNull final PropertyChangeListener listener) { + myDelegate.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(@NotNull final PropertyChangeListener listener) { + myDelegate.removePropertyChangeListener(listener); + } + + public void setReadOnly(final boolean isReadOnly) { + myDelegate.setReadOnly(isReadOnly); + } + + @NotNull + public RangeMarker createGuardedBlock(final int startOffset, final int endOffset) { + TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); + return myDelegate.createGuardedBlock(hostRange.getStartOffset(), hostRange.getEndOffset()); + } + + public void removeGuardedBlock(@NotNull final RangeMarker block) { + myDelegate.removeGuardedBlock(block); + } + + public RangeMarker getOffsetGuard(final int offset) { + return myDelegate.getOffsetGuard(injectedToHost(offset)); + } + + public RangeMarker getRangeGuard(final int startOffset, final int endOffset) { + TextRange hostRange = injectedToHost(new ProperTextRange(startOffset, endOffset)); + + return myDelegate.getRangeGuard(hostRange.getStartOffset(), hostRange.getEndOffset()); + } + + public void startGuardedBlockChecking() { + myDelegate.startGuardedBlockChecking(); + } + + public void stopGuardedBlockChecking() { + myDelegate.stopGuardedBlockChecking(); + } + + public void setCyclicBufferSize(final int bufferSize) { + myDelegate.setCyclicBufferSize(bufferSize); + } + + public void setText(@NotNull CharSequence text) { + LOG.assertTrue(text.toString().startsWith(myShreds.get(0).prefix)); + LOG.assertTrue(text.toString().endsWith(myShreds.get(myShreds.size() - 1).suffix)); + if (isOneLine()) { + text = StringUtil.replace(text.toString(), "\n", ""); + } + String[] changes = calculateMinEditSequence(text.toString()); + assert changes.length == myShreds.size(); + for (int i = 0; i < changes.length; i++) { + String change = changes[i]; + if (change != null) { + RangeMarker hostRange = myShreds.get(i).getHostRangeMarker(); + myDelegate.replaceString(hostRange.getStartOffset(), hostRange.getEndOffset(), change); + } + } + } + + @NotNull + public RangeMarker[] getHostRanges() { + RangeMarker[] markers = new RangeMarker[myShreds.size()]; + for (int i = 0; i < myShreds.size(); i++) { + PsiLanguageInjectionHost.Shred shred = myShreds.get(i); + markers[i] = shred.getHostRangeMarker(); + } + return markers; + } + + @NotNull + public RangeMarker createRangeMarker(@NotNull final TextRange textRange) { + TextRange hostRange = injectedToHost(new ProperTextRange(textRange)); + RangeMarker hostMarker = myDelegate.createRangeMarker(hostRange); + return new RangeMarkerWindow(this, (RangeMarkerEx)hostMarker); + } + + public boolean stripTrailingSpaces(final boolean inChangedLinesOnly) { + return myDelegate.stripTrailingSpaces(inChangedLinesOnly); + } + + public void setStripTrailingSpacesEnabled(final boolean isEnabled) { + myDelegate.setStripTrailingSpacesEnabled(isEnabled); + } + + public int getLineSeparatorLength(final int line) { + return myDelegate.getLineSeparatorLength(injectedToHostLine(line)); + } + + @NotNull + public LineIterator createLineIterator() { + return myDelegate.createLineIterator(); + } + + public void setModificationStamp(final long modificationStamp) { + myDelegate.setModificationStamp(modificationStamp); + } + + public void addEditReadOnlyListener(@NotNull final EditReadOnlyListener listener) { + myDelegate.addEditReadOnlyListener(listener); + } + + public void removeEditReadOnlyListener(@NotNull final EditReadOnlyListener listener) { + myDelegate.removeEditReadOnlyListener(listener); + } + + public void replaceText(@NotNull final CharSequence chars, final long newModificationStamp) { + setText(chars); + myDelegate.setModificationStamp(newModificationStamp); + } + + public int getListenersCount() { + return myDelegate.getListenersCount(); + } + + public void suppressGuardedExceptions() { + myDelegate.suppressGuardedExceptions(); + } + + public void unSuppressGuardedExceptions() { + myDelegate.unSuppressGuardedExceptions(); + } + + public boolean isInEventsHandling() { + return myDelegate.isInEventsHandling(); + } + + public void clearLineModificationFlags() { + } + + public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + return myDelegate.removeRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); + } + + public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + myDelegate.addRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); + } + + public boolean isInBulkUpdate() { + return false; + } + + public void setInBulkUpdate(boolean value) { + } + + @NotNull + public DocumentEx getDelegate() { + return myDelegate; + } + + //todo use escaper? + public int hostToInjected(int hostOffset) { + if (hostOffset < myShreds.get(0).getHostRangeMarker().getStartOffset()) return myShreds.get(0).prefix.length(); + int offset = 0; + for (int i = 0; i < myShreds.size(); i++) { + offset += myShreds.get(i).prefix.length(); + RangeMarker currentRange = myShreds.get(i).getHostRangeMarker(); + RangeMarker nextRange = i == myShreds.size() - 1 ? null : myShreds.get(i + 1).getHostRangeMarker(); + if (nextRange == null || hostOffset < nextRange.getStartOffset()) { + if (hostOffset >= currentRange.getEndOffset()) hostOffset = currentRange.getEndOffset(); + return offset + hostOffset - currentRange.getStartOffset(); + } + offset += currentRange.getEndOffset() - currentRange.getStartOffset(); + offset += myShreds.get(i).suffix.length(); + } + return getTextLength() - myShreds.get(myShreds.size() - 1).suffix.length(); + } + + public int injectedToHost(int offset) { + int offsetInLeftFragment = injectedToHost(offset, true); + int offsetInRightFragment = injectedToHost(offset, false); + if (offsetInLeftFragment == offsetInRightFragment) return offsetInLeftFragment; + + // heuristics: return offset closest to caret + Editor editor = PlatformDataKeys.EDITOR.getData(DataManager.getInstance().getDataContext()); + if (editor instanceof EditorWindow) editor = ((EditorWindow)editor).getDelegate(); + if (editor != null) { + int caret = editor.getCaretModel().getOffset(); + return Math.abs(caret - offsetInLeftFragment) < Math.abs(caret - offsetInRightFragment) ? offsetInLeftFragment : offsetInRightFragment; + } + return offsetInLeftFragment; + } + + private int injectedToHost(int offset, boolean preferLeftFragment) { + if (offset < myShreds.get(0).prefix.length()) return myShreds.get(0).getHostRangeMarker().getStartOffset(); + int prevEnd = 0; + for (int i = 0; i < myShreds.size(); i++) { + RangeMarker currentRange = myShreds.get(i).getHostRangeMarker(); + offset -= myShreds.get(i).prefix.length(); + int length = currentRange.getEndOffset() - currentRange.getStartOffset(); + if (offset < 0) { + return preferLeftFragment ? prevEnd : currentRange.getStartOffset() - 1; + } + else if (offset == 0) { + return preferLeftFragment && i != 0 ? prevEnd : currentRange.getStartOffset(); + } + else if (offset < length || offset == length && preferLeftFragment) { + return currentRange.getStartOffset() + offset; + } + offset -= length; + offset -= myShreds.get(i).suffix.length(); + prevEnd = currentRange.getEndOffset(); + } + return myShreds.get(myShreds.size() - 1).getHostRangeMarker().getEndOffset(); + } + + @NotNull + public TextRange injectedToHost(@NotNull TextRange injected) { + ProperTextRange.assertProperRange(injected); + int start = injectedToHost(injected.getStartOffset(), false); + int end = injectedToHost(injected.getEndOffset(), true); + if (end < start) { + end = injectedToHost(injected.getEndOffset(), false); + } + return new ProperTextRange(start, end); + } + + public int injectedToHostLine(int line) { + if (line < myPrefixLineCount) { + return myDelegate.getLineNumber(myShreds.get(0).getHostRangeMarker().getStartOffset()); + } + int lineCount = getLineCount(); + if (line > lineCount - mySuffixLineCount) { + return lineCount; + } + int offset = getLineStartOffset(line); + int hostOffset = injectedToHost(offset); + + return myDelegate.getLineNumber(hostOffset); + } + + public boolean containsRange(int start, int end) { + if (end - start > myShreds.get(0).getHostRangeMarker().getEndOffset() - myShreds.get(0).getHostRangeMarker().getStartOffset()) return false; + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker hostRange = shred.getHostRangeMarker(); + if (!hostRange.isValid()) continue; + TextRange textRange = InjectedLanguageUtil.toTextRange(hostRange); + if (textRange.contains(new ProperTextRange(start, end))) return true; + } + return false; + } + + @Deprecated + @Nullable + public TextRange intersectWithEditable(@NotNull TextRange rangeToEdit) { + int offset = 0; + int startOffset = -1; + int endOffset = -1; + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker hostRange = shred.getHostRangeMarker(); + offset += shred.prefix.length(); + int length = hostRange.getEndOffset() - hostRange.getStartOffset(); + TextRange intersection = new ProperTextRange(offset, offset + length).intersection(rangeToEdit); + if (intersection != null) { + if (startOffset == -1) { + startOffset = intersection.getStartOffset(); + } + endOffset = intersection.getEndOffset(); + } + offset += length; + offset += shred.suffix.length(); + } + if (startOffset == -1) return null; + return new ProperTextRange(startOffset, endOffset); + } + + boolean intersects(DocumentWindowImpl documentWindow) { + int i = 0; + int j = 0; + while (i < myShreds.size() && j < documentWindow.myShreds.size()) { + RangeMarker range = myShreds.get(i).getHostRangeMarker(); + RangeMarker otherRange = documentWindow.myShreds.get(j).getHostRangeMarker(); + if (InjectedLanguageUtil.toTextRange(range).intersects(InjectedLanguageUtil.toTextRange(otherRange))) return true; + if (range.getEndOffset() > otherRange.getStartOffset()) i++; + else if (range.getStartOffset() < otherRange.getEndOffset()) j++; + else { + i++; + j++; + } + } + return false; + } + + // minimum sequence of text replacement operations for each host range + // result[i] == null means no change + // result[i] == "" means delete + // result[i] == string means replace + public String[] calculateMinEditSequence(String newText) { + String[] result = new String[myShreds.size()]; + String hostText = myDelegate.getText(); + calculateMinEditSequence(hostText, newText, result, 0, result.length - 1); + for (int i = 0; i < result.length; i++) { + String change = result[i]; + if (change == null) continue; + String prefix = myShreds.get(i).prefix; + String suffix = myShreds.get(i).suffix; + assert change.startsWith(prefix) : change + "/" + prefix; + assert change.endsWith(suffix) : change + "/" + suffix; + result[i] = StringUtil.trimEnd(StringUtil.trimStart(change, prefix), suffix); + } + return result; + } + + private String getRangeText(String hostText, int hostNum) { + PsiLanguageInjectionHost.Shred shred = myShreds.get(hostNum); + return shred.prefix + + hostText.substring(shred.getHostRangeMarker().getStartOffset(), shred.getHostRangeMarker().getEndOffset()) + + shred.suffix; + } + private void calculateMinEditSequence(String hostText, String newText, String[] result, int i, int j) { + String rangeText1 = getRangeText(hostText, i); + if (i == j) { + result[i] = rangeText1.equals(newText) ? null : newText; + return; + } + if (StringUtil.startsWith(newText, rangeText1)) { + result[i] = null; //no change + calculateMinEditSequence(hostText, newText.substring(rangeText1.length()), result, i+1, j); + return; + } + String rangeText2 = getRangeText(hostText, j); + if (StringUtil.endsWith(newText, rangeText2)) { + result[j] = null; //no change + calculateMinEditSequence(hostText, newText.substring(0, newText.length() - rangeText2.length()), result, i, j-1); + return; + } + if (i+1 == j) { + String suffix = myShreds.get(i).suffix; + String prefix = myShreds.get(j).prefix; + String separator = suffix + prefix; + if (separator.length() != 0) { + int sep = newText.indexOf(separator); + assert sep != -1; + result[i] = newText.substring(0, sep + suffix.length()); + result[j] = newText.substring(sep + suffix.length() + prefix.length(), newText.length()); + return; + } + String commonPrefix = StringUtil.commonPrefix(rangeText1, newText); + result[i] = commonPrefix; + result[j] = newText.substring(commonPrefix.length()); + return; + } + String middleText = getRangeText(hostText, i + 1); + int m = newText.indexOf(middleText); + if (m != -1) { + result[i] = newText.substring(0, m); + result[i+1] = null; + calculateMinEditSequence(hostText, newText.substring(m+middleText.length(), newText.length()), result, i+2, j); + return; + } + middleText = getRangeText(hostText, j - 1); + m = newText.lastIndexOf(middleText); + if (m != -1) { + result[j] = newText.substring(m+middleText.length()); + result[j-1] = null; + calculateMinEditSequence(hostText, newText.substring(0, m), result, i, j-2); + return; + } + result[i] = ""; + result[j] = ""; + calculateMinEditSequence(hostText, newText, result, i+1, j-1); + } + + public boolean areRangesEqual(@NotNull DocumentWindow otherd) { + DocumentWindowImpl window = (DocumentWindowImpl)otherd; + if (myShreds.size() != window.myShreds.size()) return false; + for (int i = 0; i < myShreds.size(); i++) { + PsiLanguageInjectionHost.Shred shred = myShreds.get(i); + PsiLanguageInjectionHost.Shred otherShred = window.myShreds.get(i); + if (!shred.prefix.equals(otherShred.prefix)) return false; + if (!shred.suffix.equals(otherShred.suffix)) return false; + + RangeMarker hostRange = shred.getHostRangeMarker(); + RangeMarker other = otherShred.getHostRangeMarker(); + if (hostRange.getStartOffset() != other.getStartOffset()) return false; + if (hostRange.getEndOffset() != other.getEndOffset()) return false; + } + return true; + } + + public boolean isValid() { + return myShreds.isValid(); + } + + public boolean equals(Object o) { + if (!(o instanceof DocumentWindowImpl)) return false; + DocumentWindowImpl window = (DocumentWindowImpl)o; + return myDelegate.equals(window.getDelegate()) && areRangesEqual(window); + } + + public int hashCode() { + return myShreds.get(0).getHostRangeMarker().getStartOffset(); + } + + public boolean isOneLine() { + return myOneLine; + } + + public void dispose() { + for (PsiLanguageInjectionHost.Shred shred : myShreds) { + RangeMarker rangeMarker = shred.getHostRangeMarker(); + rangeMarker.dispose(); + } + } + + public void setShreds(Place shreds) { + myShreds = shreds; + } + + @NotNull + public List getGuardedBlocks() { + return Collections.emptyList(); + } + + //todo convert injected RMs to host + public boolean processRangeMarkers(@NotNull Processor processor) { + return myDelegate.processRangeMarkers(processor); + } + + public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { + return myDelegate.processRangeMarkersOverlappingWith(start, end, processor); + } + + public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { + return myDelegate.processRangeMarkersOverlappingWith(offset, processor); + } +} diff --git a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java index be788240b804..e0d4cf02f03e 100644 --- a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java +++ b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java @@ -1,947 +1,947 @@ -/* - * 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.internal.psiView; - -import com.intellij.ide.ui.ListCellRendererWrapper; -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageUtil; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.ComboBoxAction; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.ScrollType; -import com.intellij.openapi.editor.SelectionModel; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; -import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileTypes.*; -import com.intellij.openapi.fileTypes.impl.AbstractFileType; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.DimensionService; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiFileFactory; -import com.intellij.psi.PsiReference; -import com.intellij.psi.impl.source.resolve.FileContextUtil; -import com.intellij.psi.search.FilenameIndex; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.SortedComboBoxModel; -import com.intellij.ui.TitledBorderWithMnemonic; -import com.intellij.ui.TreeSpeedSearch; -import com.intellij.ui.components.JBList; -import com.intellij.ui.treeStructure.Tree; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.*; -import java.awt.*; -import java.awt.event.*; -import java.util.*; -import java.util.List; -import java.util.regex.Pattern; - -/** - * @author Konstantin Bulenkov - */ -public class PsiViewerDialog extends DialogWrapper implements DataProvider { - private final Project myProject; - - private final Tree myTree; - private final ViewerTreeBuilder myTreeBuilder; - - private final JList myRefs; - private static final String REFS_CACHE = "References Resolve Cache"; - - private EditorEx myEditor; - private String myLastParsedText = null; - - private JCheckBox myShowWhiteSpacesBox; - private JPanel myStructureTreePanel; - private JPanel myTextPanel; - private JPanel myPanel; - private JCheckBox myShowTreeNodesCheckBox; - private JComboBox myDialectsComboBox; - private JPanel myReferencesPanel; - private JPanel myButtonPanel; - private JSplitPane myTextSplit; - private JSplitPane myTreeSplit; - private JComboBox myExtensionComboBox; - private JLabel myExtensionLabel; - private final Presentation myPresentation = new Presentation(); - private final Map handlers = new HashMap(); - private DefaultActionGroup myGroup; - private Language[] myLanguageDialects; - private final Color SELECTION_BG_COLOR = Registry.getColor("psi.viewer.selection.color", new Color(255, 204, 204)); - - public static class ExtensionComparator implements Comparator { - private final String myOnTop; - - public ExtensionComparator(String onTop) { - myOnTop = onTop; - } - - @Override - public int compare(String o1, String o2) { - if (o1.equals(myOnTop)) return -1; - if (o2.equals(myOnTop)) return 1; - return o1.compareTo(o2); - } - } - - private static final Comparator DIALECTS_COMPARATOR = new Comparator() { - public int compare(final Language o1, final Language o2) { - if (o1 == null) return o2 == null ? 0 : -1; - if (o2 == null) return 1; - return o1.getID().compareTo(o2.getID()); - } - }; - private final EditorListener myEditorListener = new EditorListener(); - private int myLastParsedTextHashCode = 17; - private int myNewDocumentHashCode = 11; - - @Nullable - private static PsiElement findCommonParent(PsiElement start, PsiElement end) { - final TextRange range = end.getTextRange(); - while (start != null && !start.getTextRange().contains(range)) { - start = start.getParent(); - } - return start; - } - - public PsiViewerDialog(Project project, boolean modal) { - super(project, true); - setTitle("PSI Viewer"); - myProject = project; - myTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); - UIUtil.setLineStyleAngled(myTree); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(true); - myTree.updateUI(); - final TreeCellRenderer renderer = myTree.getCellRenderer(); - myTree.setCellRenderer(new TreeCellRenderer() { - @Override - public Component getTreeCellRendererComponent(JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - final Component c = renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); - if (value instanceof DefaultMutableTreeNode) { - final Object userObject = ((DefaultMutableTreeNode)value).getUserObject(); - if (userObject instanceof ViewerNodeDescriptor) { - final Object element = ((ViewerNodeDescriptor)userObject).getElement(); - if ((element instanceof PsiElement && FileContextUtil.getFileContext(((PsiElement)element).getContainingFile()) != null) - || element instanceof ViewerTreeStructure.Inject) { - final TextAttributes attr = - EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.INJECTED_LANGUAGE_FRAGMENT); - c.setBackground(attr.getBackgroundColor()); - - } - } - } - return c; - } - }); - ToolTipManager.sharedInstance().registerComponent(myTree); - TreeUtil.installActions(myTree); - new TreeSpeedSearch(myTree); - myTreeBuilder = new ViewerTreeBuilder(project, myTree); - - myTree.addTreeSelectionListener(new MyTreeSelectionListener()); - - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(scrollPane, BorderLayout.CENTER); - myStructureTreePanel.setLayout(new BorderLayout()); - myStructureTreePanel.add(panel, BorderLayout.CENTER); - - myRefs = new JBList(new DefaultListModel()); - JScrollPane refScrollPane = ScrollPaneFactory.createScrollPane(myRefs); - JPanel refPanel = new JPanel(new BorderLayout()); - refPanel.add(refScrollPane, BorderLayout.CENTER); - myReferencesPanel.setLayout(new BorderLayout()); - myReferencesPanel.add(refPanel, BorderLayout.CENTER); - final GoToListener listener = new GoToListener(); - myRefs.addKeyListener(listener); - myRefs.addMouseListener(listener); - myRefs.getSelectionModel().addListSelectionListener(listener); - myRefs.setCellRenderer(new DefaultListCellRenderer() { - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - final Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (resolve(index) == null) { - comp.setForeground(Color.red); - } - return comp; - } - }); - - setModal(modal); - setOKButtonText("&Build PSI Tree"); - init(); - } - - //private void initIcons() { - // final PsiFileFactory factory = PsiFileFactory.getInstance(myProject); - // final FileTypeManager typeManager = FileTypeManager.getInstance(); - // for (Object obj : handlers.values()) { - // if (obj instanceof LanguageFileType) { - // LanguageFileType type = (LanguageFileType)obj; - // if (!type.isBinary()) { - // for (String ext : getAllExtentions(type)) { - // final String name = "dummy." + ext; - // if (!typeManager.getFileTypeByFileName(name).isBinary()) { - // final Icon icon = factory.createFileFromText(name, " ").getIcon(0); - // - // if (icon != null) { - // EXT_ICONS.put(ext, icon); - // } - // } - // } - // } - // } - // } - //} - - protected String getDimensionServiceKey() { - return "#com.intellij.internal.psiView.PsiViewerDialog"; - } - - @Override - protected String getHelpId() { - return "reference.psi.viewer"; - } - - public JComponent getPreferredFocusedComponent() { - return myEditor.getContentComponent(); - } - - private void updatePresentation(Presentation p) { - myPresentation.setText(p.getText()); - myPresentation.setIcon(p.getIcon()); - } - - protected void init() { - initBorders(); - final List items = new ArrayList(); - final EditorFactory editorFactory = EditorFactory.getInstance(); - final Document document = editorFactory.createDocument(""); - myEditor = (EditorEx)editorFactory.createEditor(document, myProject); - myEditor.getSettings().setFoldingOutlineShown(false); - document.addDocumentListener(myEditorListener); - myEditor.getSelectionModel().addSelectionListener(myEditorListener); - myEditor.getCaretModel().addCaretListener(myEditorListener); - - for (PsiViewerExtension extension : Extensions.getExtensions(PsiViewerExtension.EP_NAME)) { - final Presentation p = new Presentation(extension.getName()); - p.setIcon(extension.getIcon()); - handlers.put(p.getText(), extension); - items.add(p); - } - - Set allFileTypes = new HashSet(); - Collections.addAll(allFileTypes, FileTypeManager.getInstance().getRegisteredFileTypes()); - for (Language language : Language.getRegisteredLanguages()) { - FileType fileType = language.getAssociatedFileType(); - if (fileType != null) { - allFileTypes.add(fileType); - } - } - for (FileType fileType : allFileTypes) { - if (fileType != StdFileTypes.GUI_DESIGNER_FORM && - fileType != StdFileTypes.IDEA_MODULE && - fileType != StdFileTypes.IDEA_PROJECT && - fileType != StdFileTypes.IDEA_WORKSPACE && - fileType != FileTypes.ARCHIVE && - fileType != FileTypes.UNKNOWN && - fileType != FileTypes.PLAIN_TEXT && - !(fileType instanceof AbstractFileType) && - !fileType.isBinary() && - !fileType.isReadOnly()) { - final Presentation p = new Presentation(fileType.getName() + " file"); - p.setIcon(fileType.getIcon()); - handlers.put(p.getText(), fileType); - items.add(p); - } - } - - final Presentation[] popupItems = items.toArray(new Presentation[items.size()]); - Arrays.sort(popupItems, new Comparator() { - public int compare(Presentation p1, Presentation p2) { - return p1.getText().toUpperCase().compareTo(p2.getText().toUpperCase()); - } - }); - - final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); - myShowWhiteSpacesBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - treeStructure.setShowWhiteSpaces(myShowWhiteSpacesBox.isSelected()); - myTreeBuilder.queueUpdate(); - } - }); - myShowTreeNodesCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - treeStructure.setShowTreeNodes(myShowTreeNodesCheckBox.isSelected()); - myTreeBuilder.queueUpdate(); - } - }); - myTextPanel.setLayout(new BorderLayout()); - myTextPanel.add(myEditor.getComponent(), BorderLayout.CENTER); - - myGroup = new DefaultActionGroup(); - for (final Presentation popupItem : popupItems) { - myGroup.add(new PopupItemAction(popupItem)); - } - - final PsiViewerSettings settings = PsiViewerSettings.getSettings(); - final String type = settings.type; - for (Presentation popupItem : popupItems) { - if (popupItem.getText().equals(type)) { - updatePresentation(popupItem); - break; - } - } - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - myEditor.getDocument().setText(settings.text); - myEditor.getSelectionModel().setSelection(0, settings.text.length()); - } - }); - - myShowWhiteSpacesBox.setSelected(settings.showWhiteSpaces); - treeStructure.setShowWhiteSpaces(settings.showWhiteSpaces); - myShowTreeNodesCheckBox.setSelected(settings.showTreeNodes); - treeStructure.setShowTreeNodes(settings.showTreeNodes); - - final ChoosePsiTypeButton typeButton = new ChoosePsiTypeButton(); - myButtonPanel.add(typeButton.createCustomComponent(myPresentation), BorderLayout.CENTER); - - updateDialectsCombo(); - updateExtentionsCombo(); - myDialectsComboBox.setRenderer(new ListCellRendererWrapper(myDialectsComboBox.getRenderer()) { - @Override - public void customize(final JList list, final Language value, final int index, final boolean selected, final boolean hasFocus) { - setText(value != null ? value.getDisplayName() : ""); - } - }); - - //if (EXT_ICONS == null) { - // initIcons(); - //} - //myExtensionComboBox.setRenderer(new DefaultListCellRenderer() { - // @Override - // public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - // if (value instanceof String) { - // return new JLabel(((String)value).toLowerCase()); - // } - // return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - // } - //}); - - if (myDialectsComboBox.isVisible()) { - for (int i = 0; i < myLanguageDialects.length; i++) { - if (settings.dialect.equals(myLanguageDialects[i].toString())) { - myDialectsComboBox.setSelectedIndex(i+1); - break; - } - } - } - - registerCustomKeyboardActions(); - final Dimension size = DimensionService.getInstance().getSize(getDimensionServiceKey(), myProject); - if (size == null) { - DimensionService.getInstance().setSize(getDimensionServiceKey(), new Dimension(600, 600)); - } - myTextSplit.setDividerLocation(settings.textDividerLocation); - myTreeSplit.setDividerLocation(settings.treeDividerLocation); - - updateEditor(); - super.init(); - } - - private void registerCustomKeyboardActions() { - final Component component = myButtonPanel.getComponents()[0]; - if (component instanceof JComponent) { - final Component button = ((JComponent)component).getComponents()[0]; - if (button instanceof JButton) { - final JButton jButton = (JButton)button; - final int mask = SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.ALT_DOWN_MASK; - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - jButton.doClick(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_P, mask)); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - focusEditor(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_T, mask)); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - focusTree(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - focusTree(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); - - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - focusTree(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - focusRefs(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_R, mask)); - - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (myRefs.isFocusOwner()) { - focusTree(); - } - else if (myTree.isFocusOwner()) { - focusRefs(); - } - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); - } - } - } - - private void registerKeyboardAction(ActionListener actionListener, KeyStroke keyStroke) { - getRootPane().registerKeyboardAction(actionListener, keyStroke, JComponent.WHEN_IN_FOCUSED_WINDOW); - } - - private void focusEditor() { - IdeFocusManager.getInstance(myProject).requestFocus(myEditor.getContentComponent(), true); - } - - private void focusTree() { - IdeFocusManager.getInstance(myProject).requestFocus(myTree, true); - } - - private void focusRefs() { - IdeFocusManager.getInstance(myProject).requestFocus(myRefs, true); - if (myRefs.getModel().getSize() > 0) { - if (myRefs.getSelectedIndex() == -1) { - myRefs.setSelectedIndex(0); - } - } - } - - private void initBorders() { - myTextPanel.setBorder(new TitledBorderWithMnemonic("&Text")); - myStructureTreePanel.setBorder(new TitledBorderWithMnemonic("PSI &Structure")); - myReferencesPanel.setBorder(new TitledBorderWithMnemonic("&References")); - } - - @Nullable - private PsiElement getPsiElement() { - final TreePath path = myTree.getSelectionPath(); - return path == null ? null : getPsiElement((DefaultMutableTreeNode)path.getLastPathComponent()); - } - - @Nullable - private static PsiElement getPsiElement(DefaultMutableTreeNode node) { - if (node.getUserObject() instanceof ViewerNodeDescriptor) { - ViewerNodeDescriptor descriptor = (ViewerNodeDescriptor)node.getUserObject(); - Object elementObject = descriptor.getElement(); - return elementObject instanceof PsiElement - ? (PsiElement)elementObject - : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; - } - return null; - } - - private void updateDialectsCombo() { - final SortedComboBoxModel model = new SortedComboBoxModel(DIALECTS_COMPARATOR); - final Object handler = getHandler(); - if (handler instanceof LanguageFileType) { - final Language baseLang = ((LanguageFileType)handler).getLanguage(); - myLanguageDialects = LanguageUtil.getLanguageDialects(baseLang); - Arrays.sort(myLanguageDialects, DIALECTS_COMPARATOR); - model.setAll(myLanguageDialects); - model.add(null); - } - myDialectsComboBox.setModel(model); - myDialectsComboBox.setVisible(model.getSize() > 1); - if (!myDialectsComboBox.isVisible()) { - myLanguageDialects = new Language[0]; - } - } - - private void updateExtentionsCombo() { - final Object handler = getHandler(); - - if (handler instanceof LanguageFileType) { - final List exts = getAllExtentions((LanguageFileType)handler); - if (exts.size() > 1) { - final ExtensionComparator comp = new ExtensionComparator(exts.get(0)); - Collections.sort(exts, comp); - final SortedComboBoxModel model = new SortedComboBoxModel(comp); - model.setAll(exts); - myExtensionComboBox.setModel(model); - myExtensionComboBox.setVisible(true); - myExtensionLabel.setVisible(true); - myExtensionComboBox.setSelectedIndex(0); - return; - } - } - myExtensionComboBox.setVisible(false); - myExtensionLabel.setVisible(false); - } - - private static final Pattern EXT_PATTERN = Pattern.compile("[a-z0-9]*"); - private static List getAllExtentions(LanguageFileType fileType) { - final List associations = FileTypeManager.getInstance().getAssociations(fileType); - final List exts = new ArrayList(); - exts.add(fileType.getDefaultExtension().toLowerCase()); - for (FileNameMatcher matcher : associations) { - final String presentableString = matcher.getPresentableString().toLowerCase(); - if (presentableString.startsWith("*.")) { - final String ext = presentableString.substring(2); - if (ext.length() > 0 && !exts.contains(ext) && EXT_PATTERN.matcher(ext).matches()) { - exts.add(ext); - } - } - } - return exts; - } - - - protected JComponent createCenterPanel() { - return myPanel; - } - - private Object getHandler() { - return handlers.get(myPresentation.getText()); - } - - protected void doOKAction() { - final String text = myEditor.getDocument().getText(); - myEditor.getSelectionModel().removeSelection(); - //if (text.trim().length() == 0) return; - - myLastParsedText = text; - myLastParsedTextHashCode = text.hashCode(); - myNewDocumentHashCode = myLastParsedTextHashCode; - PsiElement rootElement = null; - final Object handler = getHandler(); - - try { - if (handler instanceof PsiViewerExtension) { - final PsiViewerExtension ext = (PsiViewerExtension)handler; - rootElement = ext.createElement(myProject, text); - } - else if (handler instanceof FileType) { - final FileType type = (FileType)handler; - String ext = type.getDefaultExtension(); - if (myExtensionComboBox.isVisible()) { - ext = myExtensionComboBox.getSelectedItem().toString().toLowerCase(); - } - if (type instanceof LanguageFileType) { - final Language language = ((LanguageFileType)type).getLanguage(); - final Language dialect = (Language)myDialectsComboBox.getSelectedItem(); - rootElement = PsiFileFactory.getInstance(myProject) - .createFileFromText("Dummy." + ext, dialect == null ? language : dialect, text); - } - else { - rootElement = PsiFileFactory.getInstance(myProject).createFileFromText("Dummy." + ext, text); - } - } - focusTree(); - } - catch (IncorrectOperationException e1) { - rootElement = null; - Messages.showMessageDialog(myProject, e1.getMessage(), "Error", Messages.getErrorIcon()); - } - ViewerTreeStructure structure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); - structure.setRootPsiElement(rootElement); - - myTreeBuilder.queueUpdate(); - myTree.setRootVisible(true); - myTree.expandRow(0); - myTree.setRootVisible(false); - } - - public Object getData(@NonNls String dataId) { - if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { - String fqn = null; - if (myTree.hasFocus()) { - final TreePath path = myTree.getSelectionPath(); - if (path != null) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - if (!(node.getUserObject() instanceof ViewerNodeDescriptor)) return null; - ViewerNodeDescriptor descriptor = (ViewerNodeDescriptor)node.getUserObject(); - Object elementObject = descriptor.getElement(); - final PsiElement element = elementObject instanceof PsiElement - ? (PsiElement)elementObject - : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; - if (element != null) { - fqn = element.getClass().getName(); - } - } - } else if (myRefs.hasFocus()) { - final Object value = myRefs.getSelectedValue(); - if (value instanceof String) { - fqn = (String)value; - } - } - if (fqn != null) { - return getContainingFileForClass(fqn); - } - } - return null; - } - - private class MyTreeSelectionListener implements TreeSelectionListener { - private final TextAttributes myAttributes; - - public MyTreeSelectionListener() { - myAttributes = new TextAttributes(); - myAttributes.setBackgroundColor(SELECTION_BG_COLOR); - myAttributes.setForegroundColor(Color.white); - } - - public void valueChanged(TreeSelectionEvent e) { - if (!myEditor.getDocument().getText().equals(myLastParsedText)) return; - TreePath path = myTree.getSelectionPath(); - if (path == null) { - clearSelection(); - } - else { - clearSelection(); - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - if (!(node.getUserObject() instanceof ViewerNodeDescriptor)) return; - ViewerNodeDescriptor descriptor = (ViewerNodeDescriptor)node.getUserObject(); - Object elementObject = descriptor.getElement(); - final PsiElement element = elementObject instanceof PsiElement - ? (PsiElement)elementObject - : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; - if (element != null) { - final PsiElement psiElement = FileContextUtil.getFileContext(element.getContainingFile()); - final int textOffset = psiElement == null ? 0 : psiElement.getTextOffset(); - TextRange range = element.getTextRange(); - int start = range.getStartOffset() + textOffset; - int end = range.getEndOffset() + textOffset; - final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); - PsiElement rootPsiElement = treeStructure.getRootPsiElement(); - if (rootPsiElement != null) { - int baseOffset = rootPsiElement.getTextRange().getStartOffset(); - start -= baseOffset; - end -= baseOffset; - } - - final int textLength = myEditor.getDocument().getTextLength(); - if (end <= textLength) { - myEditor.getMarkupModel() - .addRangeHighlighter(start, end, HighlighterLayer.LAST, myAttributes, HighlighterTargetArea.EXACT_RANGE); - if (myTree.hasFocus()) { - myEditor.getCaretModel().moveToOffset(start); - myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); - } - else { - myEditor.getScrollingModel().scrollTo(myEditor.offsetToLogicalPosition(start), ScrollType.MAKE_VISIBLE); - } - } - updateReferences(element); - } - } - } - - public void updateReferences(PsiElement element) { - final DefaultListModel model = (DefaultListModel)myRefs.getModel(); - model.clear(); - final Object cache = myRefs.getClientProperty(REFS_CACHE); - if (cache instanceof Map) { - ((Map)cache).clear(); - } else { - myRefs.putClientProperty(REFS_CACHE, new HashMap()); - } - if (element != null) { - for (PsiReference reference : element.getReferences()) { - model.addElement(reference.getClass().getName()); - } - } - } - - private void clearSelection() { - myEditor.getMarkupModel().removeAllHighlighters(); - } - } - - public void doCancelAction() { - final PsiViewerSettings settings = PsiViewerSettings.getSettings(); - settings.type = myPresentation.getText(); - settings.text = myEditor.getDocument().getText(); - settings.showTreeNodes = myShowTreeNodesCheckBox.isSelected(); - settings.showWhiteSpaces = myShowWhiteSpacesBox.isSelected(); - final Object selectedDialect = myDialectsComboBox.getSelectedItem(); - settings.dialect = myDialectsComboBox.isVisible() && selectedDialect != null ? selectedDialect.toString() : ""; - settings.textDividerLocation = myTextSplit.getDividerLocation(); - settings.treeDividerLocation = myTreeSplit.getDividerLocation(); - super.doCancelAction(); - } - - public void dispose() { - Disposer.dispose(myTreeBuilder); - EditorFactory.getInstance().releaseEditor(myEditor); - - super.dispose(); - } - - @Nullable - private PsiElement resolve(int index) { - final PsiElement element = getPsiElement(); - if (element == null) return null; - Object o = myRefs.getClientProperty(REFS_CACHE); - if (o == null) { - myRefs.putClientProperty(REFS_CACHE, o = new HashMap()); - } - HashMap map = (HashMap)o; - Object cache = map.get(element); - if (cache == null) { - final PsiReference[] references = element.getReferences(); - cache = new PsiElement[references.length]; - for (int i = 0; i < references.length; i++) { - ((PsiElement[])cache)[i] = references[i].resolve(); - } - map.put(element, cache); - } - PsiElement[] elements = (PsiElement[])cache; - return index >= elements.length ? null : elements[index]; - } - - @Nullable - private PsiFile getContainingFileForClass(String fqn) { - String filename = fqn; - if (fqn.contains(".")) { - filename = fqn.substring(fqn.lastIndexOf('.') + 1); - } - if (filename.contains("$")) { - filename = filename.substring(0, filename.indexOf('$')); - } - filename += ".java"; - final PsiFile[] files = FilenameIndex.getFilesByName(myProject, filename, GlobalSearchScope.allScope(myProject)); - if (files != null && files.length > 0) { - return files[0]; - } - return null; - } - - - @Nullable - public static TreeNode findNodeWithObject(final Object object, final TreeModel model, final Object parent) { - for (int i = 0; i < model.getChildCount(parent); i++) { - final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) model.getChild(parent, i); - if (childNode.getUserObject().equals(object)) { - return childNode; - } else { - final TreeNode node = findNodeWithObject(object, model, childNode); - if (node != null) return node; - } - } - return null; - } - - private class ChoosePsiTypeButton extends ComboBoxAction { - protected int getMaxRows() { - return 15; - } - - protected int getMinWidth() { - return 150; - } - - protected int getMinHeight() { - return 200; - } - - @NotNull - protected DefaultActionGroup createPopupActionGroup(JComponent button) { - return myGroup; - } - } - - private class GoToListener implements KeyListener, MouseListener, ListSelectionListener { - private RangeHighlighter myHighlighter; - private final TextAttributes myAttributes = - new TextAttributes(Color.white, SELECTION_BG_COLOR, Color.red, EffectType.BOXED, Font.PLAIN); - - private void navigate() { - clearSelection(); - final Object value = myRefs.getSelectedValue(); - if (value instanceof String) { - final String fqn = (String)value; - final PsiFile file = getContainingFileForClass(fqn); - if (file != null) file.navigate(true); - } - } - - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - navigate(); - } - } - - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() > 1) { - navigate(); - } - } - - public void valueChanged(ListSelectionEvent e) { - clearSelection(); - updateDialectsCombo(); - updateExtentionsCombo(); - final int ind = myRefs.getSelectedIndex(); - final PsiElement element = getPsiElement(); - if (ind > -1 && element != null) { - final PsiReference[] references = element.getReferences(); - if (ind < references.length) { - final TextRange textRange = references[ind].getRangeInElement(); - TextRange range = element.getTextRange(); - int start = range.getStartOffset(); - int end = range.getEndOffset(); - final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); - PsiElement rootPsiElement = treeStructure.getRootPsiElement(); - if (rootPsiElement != null) { - int baseOffset = rootPsiElement.getTextRange().getStartOffset(); - start -= baseOffset; - end -= baseOffset; - } - - start += textRange.getStartOffset(); - end = start + textRange.getLength(); - myHighlighter = myEditor.getMarkupModel() - .addRangeHighlighter(start, end, HighlighterLayer.FIRST + 1, myAttributes, HighlighterTargetArea.EXACT_RANGE); - } - } - } - - public void clearSelection() { - if (myHighlighter != null && Arrays.asList(myEditor.getMarkupModel().getAllHighlighters()).contains(myHighlighter)) { - myEditor.getMarkupModel().removeHighlighter(myHighlighter); - myHighlighter = null; - } - } - - public void keyTyped(KeyEvent e) {} - public void keyReleased(KeyEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseReleased(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - } - - private class PopupItemAction extends AnAction implements DumbAware { - public PopupItemAction(Presentation p) { - super(p.getText(), p.getText(), p.getIcon()); - } - - public void actionPerformed(AnActionEvent e) { - updatePresentation(e.getPresentation()); - updateDialectsCombo(); - updateExtentionsCombo(); - updateEditor(); - } - } - - private FileType getFileType() { - Object handler = getHandler(); - return handler instanceof FileType ? (FileType)handler : - handler instanceof PsiViewerExtension ? ((PsiViewerExtension)handler).getDefaultFileType() : PlainTextFileType.INSTANCE; - } - - private void updateEditor() { - myEditor.setHighlighter(EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, getFileType())); - } - - private class EditorListener implements CaretListener, SelectionListener, DocumentListener { - public void caretPositionChanged(CaretEvent e) { - if (!available() || myEditor.getSelectionModel().hasSelection()) return; - final PsiFile psiFile = getPsiFile(); - if (psiFile == null) return; - final int offset = myEditor.getCaretModel().getOffset(); - final PsiElement element = PsiUtilBase.getElementAtOffset(psiFile, offset); - myTreeBuilder.select(element); - } - - public void selectionChanged(SelectionEvent e) { - if (!available() || !myEditor.getSelectionModel().hasSelection()) return; - final PsiFile psiFile = getPsiFile(); - if (psiFile == null) return; - final SelectionModel selection = myEditor.getSelectionModel(); - final int start = selection.getSelectionStart(); - final int end = selection.getSelectionEnd(); - final PsiElement element = findCommonParent(PsiUtilBase.getElementAtOffset(psiFile, start), PsiUtilBase.getElementAtOffset(psiFile, end)); - myTreeBuilder.select(element); - } - - private boolean available() { - return myLastParsedTextHashCode == myNewDocumentHashCode && myEditor.getContentComponent().hasFocus(); - } - - @Nullable - private PsiFile getPsiFile() { - final PsiElement root = ((ViewerTreeStructure)myTreeBuilder.getTreeStructure()).getRootPsiElement(); - return root instanceof PsiFile ? (PsiFile)root : null; - } - - public void beforeDocumentChange(DocumentEvent event) { - - } - public void documentChanged(DocumentEvent event) { - myNewDocumentHashCode = event.getDocument().getText().hashCode(); - } - } -} +/* + * 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.internal.psiView; + +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageUtil; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ComboBoxAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.*; +import com.intellij.openapi.fileTypes.impl.AbstractFileType; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.DimensionService; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.PsiReference; +import com.intellij.psi.impl.source.resolve.FileContextUtil; +import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SortedComboBoxModel; +import com.intellij.ui.TitledBorderWithMnemonic; +import com.intellij.ui.TreeSpeedSearch; +import com.intellij.ui.components.JBList; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @author Konstantin Bulenkov + */ +public class PsiViewerDialog extends DialogWrapper implements DataProvider { + private final Project myProject; + + private final Tree myTree; + private final ViewerTreeBuilder myTreeBuilder; + + private final JList myRefs; + private static final String REFS_CACHE = "References Resolve Cache"; + + private EditorEx myEditor; + private String myLastParsedText = null; + + private JCheckBox myShowWhiteSpacesBox; + private JPanel myStructureTreePanel; + private JPanel myTextPanel; + private JPanel myPanel; + private JCheckBox myShowTreeNodesCheckBox; + private JComboBox myDialectsComboBox; + private JPanel myReferencesPanel; + private JPanel myButtonPanel; + private JSplitPane myTextSplit; + private JSplitPane myTreeSplit; + private JComboBox myExtensionComboBox; + private JLabel myExtensionLabel; + private final Presentation myPresentation = new Presentation(); + private final Map handlers = new HashMap(); + private DefaultActionGroup myGroup; + private Language[] myLanguageDialects; + private final Color SELECTION_BG_COLOR = Registry.getColor("psi.viewer.selection.color", new Color(255, 204, 204)); + + public static class ExtensionComparator implements Comparator { + private final String myOnTop; + + public ExtensionComparator(String onTop) { + myOnTop = onTop; + } + + @Override + public int compare(String o1, String o2) { + if (o1.equals(myOnTop)) return -1; + if (o2.equals(myOnTop)) return 1; + return o1.compareTo(o2); + } + } + + private static final Comparator DIALECTS_COMPARATOR = new Comparator() { + public int compare(final Language o1, final Language o2) { + if (o1 == null) return o2 == null ? 0 : -1; + if (o2 == null) return 1; + return o1.getID().compareTo(o2.getID()); + } + }; + private final EditorListener myEditorListener = new EditorListener(); + private int myLastParsedTextHashCode = 17; + private int myNewDocumentHashCode = 11; + + @Nullable + private static PsiElement findCommonParent(PsiElement start, PsiElement end) { + final TextRange range = end.getTextRange(); + while (start != null && !start.getTextRange().contains(range)) { + start = start.getParent(); + } + return start; + } + + public PsiViewerDialog(Project project, boolean modal) { + super(project, true); + setTitle("PSI Viewer"); + myProject = project; + myTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); + UIUtil.setLineStyleAngled(myTree); + myTree.setRootVisible(false); + myTree.setShowsRootHandles(true); + myTree.updateUI(); + final TreeCellRenderer renderer = myTree.getCellRenderer(); + myTree.setCellRenderer(new TreeCellRenderer() { + @Override + public Component getTreeCellRendererComponent(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + final Component c = renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + if (value instanceof DefaultMutableTreeNode) { + final Object userObject = ((DefaultMutableTreeNode)value).getUserObject(); + if (userObject instanceof ViewerNodeDescriptor) { + final Object element = ((ViewerNodeDescriptor)userObject).getElement(); + if ((element instanceof PsiElement && FileContextUtil.getFileContext(((PsiElement)element).getContainingFile()) != null) + || element instanceof ViewerTreeStructure.Inject) { + final TextAttributes attr = + EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.INJECTED_LANGUAGE_FRAGMENT); + c.setBackground(attr.getBackgroundColor()); + + } + } + } + return c; + } + }); + ToolTipManager.sharedInstance().registerComponent(myTree); + TreeUtil.installActions(myTree); + new TreeSpeedSearch(myTree); + myTreeBuilder = new ViewerTreeBuilder(project, myTree); + + myTree.addTreeSelectionListener(new MyTreeSelectionListener()); + + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(scrollPane, BorderLayout.CENTER); + myStructureTreePanel.setLayout(new BorderLayout()); + myStructureTreePanel.add(panel, BorderLayout.CENTER); + + myRefs = new JBList(new DefaultListModel()); + JScrollPane refScrollPane = ScrollPaneFactory.createScrollPane(myRefs); + JPanel refPanel = new JPanel(new BorderLayout()); + refPanel.add(refScrollPane, BorderLayout.CENTER); + myReferencesPanel.setLayout(new BorderLayout()); + myReferencesPanel.add(refPanel, BorderLayout.CENTER); + final GoToListener listener = new GoToListener(); + myRefs.addKeyListener(listener); + myRefs.addMouseListener(listener); + myRefs.getSelectionModel().addListSelectionListener(listener); + myRefs.setCellRenderer(new DefaultListCellRenderer() { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + final Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (resolve(index) == null) { + comp.setForeground(Color.red); + } + return comp; + } + }); + + setModal(modal); + setOKButtonText("&Build PSI Tree"); + init(); + } + + //private void initIcons() { + // final PsiFileFactory factory = PsiFileFactory.getInstance(myProject); + // final FileTypeManager typeManager = FileTypeManager.getInstance(); + // for (Object obj : handlers.values()) { + // if (obj instanceof LanguageFileType) { + // LanguageFileType type = (LanguageFileType)obj; + // if (!type.isBinary()) { + // for (String ext : getAllExtentions(type)) { + // final String name = "dummy." + ext; + // if (!typeManager.getFileTypeByFileName(name).isBinary()) { + // final Icon icon = factory.createFileFromText(name, " ").getIcon(0); + // + // if (icon != null) { + // EXT_ICONS.put(ext, icon); + // } + // } + // } + // } + // } + // } + //} + + protected String getDimensionServiceKey() { + return "#com.intellij.internal.psiView.PsiViewerDialog"; + } + + @Override + protected String getHelpId() { + return "reference.psi.viewer"; + } + + public JComponent getPreferredFocusedComponent() { + return myEditor.getContentComponent(); + } + + private void updatePresentation(Presentation p) { + myPresentation.setText(p.getText()); + myPresentation.setIcon(p.getIcon()); + } + + protected void init() { + initBorders(); + final List items = new ArrayList(); + final EditorFactory editorFactory = EditorFactory.getInstance(); + final Document document = editorFactory.createDocument(""); + myEditor = (EditorEx)editorFactory.createEditor(document, myProject); + myEditor.getSettings().setFoldingOutlineShown(false); + document.addDocumentListener(myEditorListener); + myEditor.getSelectionModel().addSelectionListener(myEditorListener); + myEditor.getCaretModel().addCaretListener(myEditorListener); + + for (PsiViewerExtension extension : Extensions.getExtensions(PsiViewerExtension.EP_NAME)) { + final Presentation p = new Presentation(extension.getName()); + p.setIcon(extension.getIcon()); + handlers.put(p.getText(), extension); + items.add(p); + } + + Set allFileTypes = new HashSet(); + Collections.addAll(allFileTypes, FileTypeManager.getInstance().getRegisteredFileTypes()); + for (Language language : Language.getRegisteredLanguages()) { + FileType fileType = language.getAssociatedFileType(); + if (fileType != null) { + allFileTypes.add(fileType); + } + } + for (FileType fileType : allFileTypes) { + if (fileType != StdFileTypes.GUI_DESIGNER_FORM && + fileType != StdFileTypes.IDEA_MODULE && + fileType != StdFileTypes.IDEA_PROJECT && + fileType != StdFileTypes.IDEA_WORKSPACE && + fileType != FileTypes.ARCHIVE && + fileType != FileTypes.UNKNOWN && + fileType != FileTypes.PLAIN_TEXT && + !(fileType instanceof AbstractFileType) && + !fileType.isBinary() && + !fileType.isReadOnly()) { + final Presentation p = new Presentation(fileType.getName() + " file"); + p.setIcon(fileType.getIcon()); + handlers.put(p.getText(), fileType); + items.add(p); + } + } + + final Presentation[] popupItems = items.toArray(new Presentation[items.size()]); + Arrays.sort(popupItems, new Comparator() { + public int compare(Presentation p1, Presentation p2) { + return p1.getText().toUpperCase().compareTo(p2.getText().toUpperCase()); + } + }); + + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + myShowWhiteSpacesBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + treeStructure.setShowWhiteSpaces(myShowWhiteSpacesBox.isSelected()); + myTreeBuilder.queueUpdate(); + } + }); + myShowTreeNodesCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + treeStructure.setShowTreeNodes(myShowTreeNodesCheckBox.isSelected()); + myTreeBuilder.queueUpdate(); + } + }); + myTextPanel.setLayout(new BorderLayout()); + myTextPanel.add(myEditor.getComponent(), BorderLayout.CENTER); + + myGroup = new DefaultActionGroup(); + for (final Presentation popupItem : popupItems) { + myGroup.add(new PopupItemAction(popupItem)); + } + + final PsiViewerSettings settings = PsiViewerSettings.getSettings(); + final String type = settings.type; + for (Presentation popupItem : popupItems) { + if (popupItem.getText().equals(type)) { + updatePresentation(popupItem); + break; + } + } + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + myEditor.getDocument().setText(settings.text); + myEditor.getSelectionModel().setSelection(0, settings.text.length()); + } + }); + + myShowWhiteSpacesBox.setSelected(settings.showWhiteSpaces); + treeStructure.setShowWhiteSpaces(settings.showWhiteSpaces); + myShowTreeNodesCheckBox.setSelected(settings.showTreeNodes); + treeStructure.setShowTreeNodes(settings.showTreeNodes); + + final ChoosePsiTypeButton typeButton = new ChoosePsiTypeButton(); + myButtonPanel.add(typeButton.createCustomComponent(myPresentation), BorderLayout.CENTER); + + updateDialectsCombo(); + updateExtentionsCombo(); + myDialectsComboBox.setRenderer(new ListCellRendererWrapper(myDialectsComboBox.getRenderer()) { + @Override + public void customize(final JList list, final Language value, final int index, final boolean selected, final boolean hasFocus) { + setText(value != null ? value.getDisplayName() : ""); + } + }); + + //if (EXT_ICONS == null) { + // initIcons(); + //} + //myExtensionComboBox.setRenderer(new DefaultListCellRenderer() { + // @Override + // public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + // if (value instanceof String) { + // return new JLabel(((String)value).toLowerCase()); + // } + // return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + // } + //}); + + if (myDialectsComboBox.isVisible()) { + for (int i = 0; i < myLanguageDialects.length; i++) { + if (settings.dialect.equals(myLanguageDialects[i].toString())) { + myDialectsComboBox.setSelectedIndex(i+1); + break; + } + } + } + + registerCustomKeyboardActions(); + final Dimension size = DimensionService.getInstance().getSize(getDimensionServiceKey(), myProject); + if (size == null) { + DimensionService.getInstance().setSize(getDimensionServiceKey(), new Dimension(600, 600)); + } + myTextSplit.setDividerLocation(settings.textDividerLocation); + myTreeSplit.setDividerLocation(settings.treeDividerLocation); + + updateEditor(); + super.init(); + } + + private void registerCustomKeyboardActions() { + final Component component = myButtonPanel.getComponents()[0]; + if (component instanceof JComponent) { + final Component button = ((JComponent)component).getComponents()[0]; + if (button instanceof JButton) { + final JButton jButton = (JButton)button; + final int mask = SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.ALT_DOWN_MASK; + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + jButton.doClick(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_P, mask)); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + focusEditor(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_T, mask)); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + focusTree(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + focusTree(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); + + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + focusTree(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + focusRefs(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_R, mask)); + + registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (myRefs.isFocusOwner()) { + focusTree(); + } + else if (myTree.isFocusOwner()) { + focusRefs(); + } + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); + } + } + } + + private void registerKeyboardAction(ActionListener actionListener, KeyStroke keyStroke) { + getRootPane().registerKeyboardAction(actionListener, keyStroke, JComponent.WHEN_IN_FOCUSED_WINDOW); + } + + private void focusEditor() { + IdeFocusManager.getInstance(myProject).requestFocus(myEditor.getContentComponent(), true); + } + + private void focusTree() { + IdeFocusManager.getInstance(myProject).requestFocus(myTree, true); + } + + private void focusRefs() { + IdeFocusManager.getInstance(myProject).requestFocus(myRefs, true); + if (myRefs.getModel().getSize() > 0) { + if (myRefs.getSelectedIndex() == -1) { + myRefs.setSelectedIndex(0); + } + } + } + + private void initBorders() { + myTextPanel.setBorder(new TitledBorderWithMnemonic("&Text")); + myStructureTreePanel.setBorder(new TitledBorderWithMnemonic("PSI &Structure")); + myReferencesPanel.setBorder(new TitledBorderWithMnemonic("&References")); + } + + @Nullable + private PsiElement getPsiElement() { + final TreePath path = myTree.getSelectionPath(); + return path == null ? null : getPsiElement((DefaultMutableTreeNode)path.getLastPathComponent()); + } + + @Nullable + private static PsiElement getPsiElement(DefaultMutableTreeNode node) { + if (node.getUserObject() instanceof ViewerNodeDescriptor) { + ViewerNodeDescriptor descriptor = (ViewerNodeDescriptor)node.getUserObject(); + Object elementObject = descriptor.getElement(); + return elementObject instanceof PsiElement + ? (PsiElement)elementObject + : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; + } + return null; + } + + private void updateDialectsCombo() { + final SortedComboBoxModel model = new SortedComboBoxModel(DIALECTS_COMPARATOR); + final Object handler = getHandler(); + if (handler instanceof LanguageFileType) { + final Language baseLang = ((LanguageFileType)handler).getLanguage(); + myLanguageDialects = LanguageUtil.getLanguageDialects(baseLang); + Arrays.sort(myLanguageDialects, DIALECTS_COMPARATOR); + model.setAll(myLanguageDialects); + model.add(null); + } + myDialectsComboBox.setModel(model); + myDialectsComboBox.setVisible(model.getSize() > 1); + if (!myDialectsComboBox.isVisible()) { + myLanguageDialects = new Language[0]; + } + } + + private void updateExtentionsCombo() { + final Object handler = getHandler(); + + if (handler instanceof LanguageFileType) { + final List exts = getAllExtentions((LanguageFileType)handler); + if (exts.size() > 1) { + final ExtensionComparator comp = new ExtensionComparator(exts.get(0)); + Collections.sort(exts, comp); + final SortedComboBoxModel model = new SortedComboBoxModel(comp); + model.setAll(exts); + myExtensionComboBox.setModel(model); + myExtensionComboBox.setVisible(true); + myExtensionLabel.setVisible(true); + myExtensionComboBox.setSelectedIndex(0); + return; + } + } + myExtensionComboBox.setVisible(false); + myExtensionLabel.setVisible(false); + } + + private static final Pattern EXT_PATTERN = Pattern.compile("[a-z0-9]*"); + private static List getAllExtentions(LanguageFileType fileType) { + final List associations = FileTypeManager.getInstance().getAssociations(fileType); + final List exts = new ArrayList(); + exts.add(fileType.getDefaultExtension().toLowerCase()); + for (FileNameMatcher matcher : associations) { + final String presentableString = matcher.getPresentableString().toLowerCase(); + if (presentableString.startsWith("*.")) { + final String ext = presentableString.substring(2); + if (ext.length() > 0 && !exts.contains(ext) && EXT_PATTERN.matcher(ext).matches()) { + exts.add(ext); + } + } + } + return exts; + } + + + protected JComponent createCenterPanel() { + return myPanel; + } + + private Object getHandler() { + return handlers.get(myPresentation.getText()); + } + + protected void doOKAction() { + final String text = myEditor.getDocument().getText(); + myEditor.getSelectionModel().removeSelection(); + //if (text.trim().length() == 0) return; + + myLastParsedText = text; + myLastParsedTextHashCode = text.hashCode(); + myNewDocumentHashCode = myLastParsedTextHashCode; + PsiElement rootElement = null; + final Object handler = getHandler(); + + try { + if (handler instanceof PsiViewerExtension) { + final PsiViewerExtension ext = (PsiViewerExtension)handler; + rootElement = ext.createElement(myProject, text); + } + else if (handler instanceof FileType) { + final FileType type = (FileType)handler; + String ext = type.getDefaultExtension(); + if (myExtensionComboBox.isVisible()) { + ext = myExtensionComboBox.getSelectedItem().toString().toLowerCase(); + } + if (type instanceof LanguageFileType) { + final Language language = ((LanguageFileType)type).getLanguage(); + final Language dialect = (Language)myDialectsComboBox.getSelectedItem(); + rootElement = PsiFileFactory.getInstance(myProject) + .createFileFromText("Dummy." + ext, dialect == null ? language : dialect, text); + } + else { + rootElement = PsiFileFactory.getInstance(myProject).createFileFromText("Dummy." + ext, text); + } + } + focusTree(); + } + catch (IncorrectOperationException e1) { + rootElement = null; + Messages.showMessageDialog(myProject, e1.getMessage(), "Error", Messages.getErrorIcon()); + } + ViewerTreeStructure structure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + structure.setRootPsiElement(rootElement); + + myTreeBuilder.queueUpdate(); + myTree.setRootVisible(true); + myTree.expandRow(0); + myTree.setRootVisible(false); + } + + public Object getData(@NonNls String dataId) { + if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { + String fqn = null; + if (myTree.hasFocus()) { + final TreePath path = myTree.getSelectionPath(); + if (path != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); + if (!(node.getUserObject() instanceof ViewerNodeDescriptor)) return null; + ViewerNodeDescriptor descriptor = (ViewerNodeDescriptor)node.getUserObject(); + Object elementObject = descriptor.getElement(); + final PsiElement element = elementObject instanceof PsiElement + ? (PsiElement)elementObject + : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; + if (element != null) { + fqn = element.getClass().getName(); + } + } + } else if (myRefs.hasFocus()) { + final Object value = myRefs.getSelectedValue(); + if (value instanceof String) { + fqn = (String)value; + } + } + if (fqn != null) { + return getContainingFileForClass(fqn); + } + } + return null; + } + + private class MyTreeSelectionListener implements TreeSelectionListener { + private final TextAttributes myAttributes; + + public MyTreeSelectionListener() { + myAttributes = new TextAttributes(); + myAttributes.setBackgroundColor(SELECTION_BG_COLOR); + myAttributes.setForegroundColor(Color.white); + } + + public void valueChanged(TreeSelectionEvent e) { + if (!myEditor.getDocument().getText().equals(myLastParsedText)) return; + TreePath path = myTree.getSelectionPath(); + if (path == null) { + clearSelection(); + } + else { + clearSelection(); + DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); + if (!(node.getUserObject() instanceof ViewerNodeDescriptor)) return; + ViewerNodeDescriptor descriptor = (ViewerNodeDescriptor)node.getUserObject(); + Object elementObject = descriptor.getElement(); + final PsiElement element = elementObject instanceof PsiElement + ? (PsiElement)elementObject + : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; + if (element != null) { + final PsiElement psiElement = FileContextUtil.getFileContext(element.getContainingFile()); + final int textOffset = psiElement == null ? 0 : psiElement.getTextOffset(); + TextRange range = element.getTextRange(); + int start = range.getStartOffset() + textOffset; + int end = range.getEndOffset() + textOffset; + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + PsiElement rootPsiElement = treeStructure.getRootPsiElement(); + if (rootPsiElement != null) { + int baseOffset = rootPsiElement.getTextRange().getStartOffset(); + start -= baseOffset; + end -= baseOffset; + } + + final int textLength = myEditor.getDocument().getTextLength(); + if (end <= textLength) { + myEditor.getMarkupModel() + .addRangeHighlighter(start, end, HighlighterLayer.LAST, myAttributes, HighlighterTargetArea.EXACT_RANGE); + if (myTree.hasFocus()) { + myEditor.getCaretModel().moveToOffset(start); + myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + } + else { + myEditor.getScrollingModel().scrollTo(myEditor.offsetToLogicalPosition(start), ScrollType.MAKE_VISIBLE); + } + } + updateReferences(element); + } + } + } + + public void updateReferences(PsiElement element) { + final DefaultListModel model = (DefaultListModel)myRefs.getModel(); + model.clear(); + final Object cache = myRefs.getClientProperty(REFS_CACHE); + if (cache instanceof Map) { + ((Map)cache).clear(); + } else { + myRefs.putClientProperty(REFS_CACHE, new HashMap()); + } + if (element != null) { + for (PsiReference reference : element.getReferences()) { + model.addElement(reference.getClass().getName()); + } + } + } + + private void clearSelection() { + myEditor.getMarkupModel().removeAllHighlighters(); + } + } + + public void doCancelAction() { + final PsiViewerSettings settings = PsiViewerSettings.getSettings(); + settings.type = myPresentation.getText(); + settings.text = myEditor.getDocument().getText(); + settings.showTreeNodes = myShowTreeNodesCheckBox.isSelected(); + settings.showWhiteSpaces = myShowWhiteSpacesBox.isSelected(); + final Object selectedDialect = myDialectsComboBox.getSelectedItem(); + settings.dialect = myDialectsComboBox.isVisible() && selectedDialect != null ? selectedDialect.toString() : ""; + settings.textDividerLocation = myTextSplit.getDividerLocation(); + settings.treeDividerLocation = myTreeSplit.getDividerLocation(); + super.doCancelAction(); + } + + public void dispose() { + Disposer.dispose(myTreeBuilder); + EditorFactory.getInstance().releaseEditor(myEditor); + + super.dispose(); + } + + @Nullable + private PsiElement resolve(int index) { + final PsiElement element = getPsiElement(); + if (element == null) return null; + Object o = myRefs.getClientProperty(REFS_CACHE); + if (o == null) { + myRefs.putClientProperty(REFS_CACHE, o = new HashMap()); + } + HashMap map = (HashMap)o; + Object cache = map.get(element); + if (cache == null) { + final PsiReference[] references = element.getReferences(); + cache = new PsiElement[references.length]; + for (int i = 0; i < references.length; i++) { + ((PsiElement[])cache)[i] = references[i].resolve(); + } + map.put(element, cache); + } + PsiElement[] elements = (PsiElement[])cache; + return index >= elements.length ? null : elements[index]; + } + + @Nullable + private PsiFile getContainingFileForClass(String fqn) { + String filename = fqn; + if (fqn.contains(".")) { + filename = fqn.substring(fqn.lastIndexOf('.') + 1); + } + if (filename.contains("$")) { + filename = filename.substring(0, filename.indexOf('$')); + } + filename += ".java"; + final PsiFile[] files = FilenameIndex.getFilesByName(myProject, filename, GlobalSearchScope.allScope(myProject)); + if (files != null && files.length > 0) { + return files[0]; + } + return null; + } + + + @Nullable + public static TreeNode findNodeWithObject(final Object object, final TreeModel model, final Object parent) { + for (int i = 0; i < model.getChildCount(parent); i++) { + final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) model.getChild(parent, i); + if (childNode.getUserObject().equals(object)) { + return childNode; + } else { + final TreeNode node = findNodeWithObject(object, model, childNode); + if (node != null) return node; + } + } + return null; + } + + private class ChoosePsiTypeButton extends ComboBoxAction { + protected int getMaxRows() { + return 15; + } + + protected int getMinWidth() { + return 150; + } + + protected int getMinHeight() { + return 200; + } + + @NotNull + protected DefaultActionGroup createPopupActionGroup(JComponent button) { + return myGroup; + } + } + + private class GoToListener implements KeyListener, MouseListener, ListSelectionListener { + private RangeHighlighter myHighlighter; + private final TextAttributes myAttributes = + new TextAttributes(Color.white, SELECTION_BG_COLOR, Color.red, EffectType.BOXED, Font.PLAIN); + + private void navigate() { + clearSelection(); + final Object value = myRefs.getSelectedValue(); + if (value instanceof String) { + final String fqn = (String)value; + final PsiFile file = getContainingFileForClass(fqn); + if (file != null) file.navigate(true); + } + } + + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + navigate(); + } + } + + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() > 1) { + navigate(); + } + } + + public void valueChanged(ListSelectionEvent e) { + clearSelection(); + updateDialectsCombo(); + updateExtentionsCombo(); + final int ind = myRefs.getSelectedIndex(); + final PsiElement element = getPsiElement(); + if (ind > -1 && element != null) { + final PsiReference[] references = element.getReferences(); + if (ind < references.length) { + final TextRange textRange = references[ind].getRangeInElement(); + TextRange range = element.getTextRange(); + int start = range.getStartOffset(); + int end = range.getEndOffset(); + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + PsiElement rootPsiElement = treeStructure.getRootPsiElement(); + if (rootPsiElement != null) { + int baseOffset = rootPsiElement.getTextRange().getStartOffset(); + start -= baseOffset; + end -= baseOffset; + } + + start += textRange.getStartOffset(); + end = start + textRange.getLength(); + myHighlighter = myEditor.getMarkupModel() + .addRangeHighlighter(start, end, HighlighterLayer.FIRST + 1, myAttributes, HighlighterTargetArea.EXACT_RANGE); + } + } + } + + public void clearSelection() { + if (myHighlighter != null && Arrays.asList(myEditor.getMarkupModel().getAllHighlighters()).contains(myHighlighter)) { + myEditor.getMarkupModel().removeHighlighter(myHighlighter); + myHighlighter = null; + } + } + + public void keyTyped(KeyEvent e) {} + public void keyReleased(KeyEvent e) {} + public void mousePressed(MouseEvent e) {} + public void mouseReleased(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + } + + private class PopupItemAction extends AnAction implements DumbAware { + public PopupItemAction(Presentation p) { + super(p.getText(), p.getText(), p.getIcon()); + } + + public void actionPerformed(AnActionEvent e) { + updatePresentation(e.getPresentation()); + updateDialectsCombo(); + updateExtentionsCombo(); + updateEditor(); + } + } + + private FileType getFileType() { + Object handler = getHandler(); + return handler instanceof FileType ? (FileType)handler : + handler instanceof PsiViewerExtension ? ((PsiViewerExtension)handler).getDefaultFileType() : PlainTextFileType.INSTANCE; + } + + private void updateEditor() { + myEditor.setHighlighter(EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, getFileType())); + } + + private class EditorListener implements CaretListener, SelectionListener, DocumentListener { + public void caretPositionChanged(CaretEvent e) { + if (!available() || myEditor.getSelectionModel().hasSelection()) return; + final PsiFile psiFile = getPsiFile(); + if (psiFile == null) return; + final int offset = myEditor.getCaretModel().getOffset(); + final PsiElement element = PsiUtilBase.getElementAtOffset(psiFile, offset); + myTreeBuilder.select(element); + } + + public void selectionChanged(SelectionEvent e) { + if (!available() || !myEditor.getSelectionModel().hasSelection()) return; + final PsiFile psiFile = getPsiFile(); + if (psiFile == null) return; + final SelectionModel selection = myEditor.getSelectionModel(); + final int start = selection.getSelectionStart(); + final int end = selection.getSelectionEnd(); + final PsiElement element = findCommonParent(PsiUtilBase.getElementAtOffset(psiFile, start), PsiUtilBase.getElementAtOffset(psiFile, end)); + myTreeBuilder.select(element); + } + + private boolean available() { + return myLastParsedTextHashCode == myNewDocumentHashCode && myEditor.getContentComponent().hasFocus(); + } + + @Nullable + private PsiFile getPsiFile() { + final PsiElement root = ((ViewerTreeStructure)myTreeBuilder.getTreeStructure()).getRootPsiElement(); + return root instanceof PsiFile ? (PsiFile)root : null; + } + + public void beforeDocumentChange(DocumentEvent event) { + + } + public void documentChanged(DocumentEvent event) { + myNewDocumentHashCode = event.getDocument().getText().hashCode(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java index e5f19b88186a..f7260d85a6c6 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java @@ -1,352 +1,352 @@ -/* - * 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.roots.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.pointers.VirtualFilePointer; -import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; -import com.intellij.util.ArrayUtil; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -/** - * @author dsl - */ -public class ContentEntryImpl extends RootModelComponentBase implements ContentEntry, ClonableContentEntry, Comparable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.SimpleContentEntryImpl"); - private final VirtualFilePointer myRoot; - @NonNls public static final String ELEMENT_NAME = "content"; - private final LinkedHashSet mySourceFolders = new LinkedHashSet(); - private final TreeSet myExcludeFolders = new TreeSet(ContentFolderComparator.INSTANCE); - private final TreeSet myExcludedOutputFolders = new TreeSet(ContentFolderComparator.INSTANCE); - @NonNls public static final String URL_ATTRIBUTE = "url"; - - ContentEntryImpl(VirtualFile file, RootModelImpl m) { - this(file.getUrl(), m); - } - - ContentEntryImpl(String url, RootModelImpl m) { - super(m); - myRoot = VirtualFilePointerManager.getInstance().create(url, this, m.myVirtualFilePointerListener); - } - - ContentEntryImpl(Element e, RootModelImpl m) throws InvalidDataException { - this(getUrlFrom(e), m); - initSourceFolders(e); - initExcludeFolders(e); - } - - private static String getUrlFrom(Element e) throws InvalidDataException { - LOG.assertTrue(ELEMENT_NAME.equals(e.getName())); - - String url = e.getAttributeValue(URL_ATTRIBUTE); - if (url == null) throw new InvalidDataException(); - return url; - } - - private void initSourceFolders(Element e) throws InvalidDataException { - mySourceFolders.clear(); - for (Object child : e.getChildren(SourceFolderImpl.ELEMENT_NAME)) { - mySourceFolders.add(new SourceFolderImpl((Element)child, this)); - } - } - - private void initExcludeFolders(Element e) throws InvalidDataException { - myExcludeFolders.clear(); - for (Object child : e.getChildren(ExcludeFolderImpl.ELEMENT_NAME)) { - myExcludeFolders.add(new ExcludeFolderImpl((Element)child, this)); - } - } - - public VirtualFile getFile() { - //assert !isDisposed(); - final VirtualFile file = myRoot.getFile(); - return file == null || !file.isDirectory() ? null : file; - } - - @NotNull - public String getUrl() { - return myRoot.getUrl(); - } - - public SourceFolder[] getSourceFolders() { - return mySourceFolders.toArray(new SourceFolder[mySourceFolders.size()]); - } - - public VirtualFile[] getSourceFolderFiles() { - assert !isDisposed(); - final SourceFolder[] sourceFolders = getSourceFolders(); - ArrayList result = new ArrayList(sourceFolders.length); - for (SourceFolder sourceFolder : sourceFolders) { - final VirtualFile file = sourceFolder.getFile(); - if (file != null) { - result.add(file); - } - } - return VfsUtil.toVirtualFileArray(result); - } - - public ExcludeFolder[] getExcludeFolders() { - //assert !isDisposed(); - final ArrayList result = new ArrayList(myExcludeFolders); - for (DirectoryIndexExcludePolicy excludePolicy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, getRootModel().getProject())) { - final VirtualFilePointer[] files = excludePolicy.getExcludeRootsForModule(getRootModel()); - for (VirtualFilePointer file : files) { - addExcludeForOutputPath(file, result); - } - } - if (getRootModel().isExcludeExplodedDirectory()) { - addExcludeForOutputPath(getRootModel().myExplodedDirectoryPointer, result); - } - return result.toArray(new ExcludeFolder[result.size()]); - } - - private void addExcludeForOutputPath(final VirtualFilePointer outputPath, ArrayList result) { - if (outputPath == null) return; - final VirtualFile outputPathFile = outputPath.getFile(); - final VirtualFile file = myRoot.getFile(); - if (outputPathFile != null && file != null /* TODO: ??? && VfsUtil.isAncestor(file, outputPathFile, false) */) { - result.add(new ExcludedOutputFolderImpl(this, outputPath)); - } - } - - public VirtualFile[] getExcludeFolderFiles() { - assert !isDisposed(); - final ExcludeFolder[] excludeFolders = getExcludeFolders(); - ArrayList result = new ArrayList(); - for (ExcludeFolder excludeFolder : excludeFolders) { - final VirtualFile file = excludeFolder.getFile(); - if (file != null) { - result.add(file); - } - } - return VfsUtil.toVirtualFileArray(result); - } - - public SourceFolder addSourceFolder(@NotNull VirtualFile file, boolean isTestSource) { - assertCanAddFolder(file); - return addSourceFolder(new SourceFolderImpl(file, isTestSource, this)); - } - - public SourceFolder addSourceFolder(@NotNull VirtualFile file, boolean isTestSource, @NotNull String packagePrefix) { - assertCanAddFolder(file); - return addSourceFolder(new SourceFolderImpl(file, isTestSource, packagePrefix, this)); - } - - public SourceFolder addSourceFolder(@NotNull String url, boolean isTestSource) { - assertFolderUnderMe(url); - return addSourceFolder(new SourceFolderImpl(url, isTestSource, this)); - } - - private SourceFolder addSourceFolder(SourceFolderImpl f) { - mySourceFolders.add(f); - return f; - } - - public void removeSourceFolder(@NotNull SourceFolder sourceFolder) { - assert !isDisposed(); - assertCanRemoveFrom(sourceFolder, mySourceFolders); - mySourceFolders.remove(sourceFolder); - } - - public ExcludeFolder addExcludeFolder(@NotNull VirtualFile file) { - assert !isDisposed(); - assertCanAddFolder(file); - return addExcludeFolder(new ExcludeFolderImpl(file, this)); - } - - public ExcludeFolder addExcludeFolder(@NotNull String url) { - assert !isDisposed(); - assertCanAddFolder(url); - return addExcludeFolder(new ExcludeFolderImpl(url, this)); - } - - private void assertCanAddFolder(VirtualFile file) { - assertCanAddFolder(file.getUrl()); - } - - private void assertCanAddFolder(String url) { - getRootModel().assertWritable(); - assertFolderUnderMe(url); - } - - public void removeExcludeFolder(@NotNull ExcludeFolder excludeFolder) { - assert !isDisposed(); - assertCanRemoveFrom(excludeFolder, myExcludeFolders); - myExcludeFolders.remove(excludeFolder); - } - - private ExcludeFolder addExcludeFolder(ExcludeFolder f) { - myExcludeFolders.add(f); - return f; - } - - private void assertCanRemoveFrom(T f, Set ff) { - getRootModel().assertWritable(); - LOG.assertTrue(ff.contains(f)); - } - - private void assertFolderUnderMe(String url) { - final String rootUrl = getUrl(); - try { - if (!FileUtil.isAncestor(new File(rootUrl), new File(url), false)) { - LOG.error("The file " + url + " is not under content entry root " + rootUrl); - } - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - public boolean isSynthetic() { - return false; - } - - public ContentEntry cloneEntry(RootModelImpl rootModel) { - assert !isDisposed(); - ContentEntryImpl cloned = new ContentEntryImpl(myRoot.getUrl(), rootModel); - for (final SourceFolder sourceFolder : mySourceFolders) { - if (sourceFolder instanceof ClonableContentFolder) { - ContentFolder folder = ((ClonableContentFolder)sourceFolder).cloneFolder(cloned); - cloned.mySourceFolders.add((SourceFolder)folder); - } - } - - for (final ExcludeFolder excludeFolder : myExcludeFolders) { - if (excludeFolder instanceof ClonableContentFolder) { - ContentFolder folder = ((ClonableContentFolder)excludeFolder).cloneFolder(cloned); - cloned.myExcludeFolders.add((ExcludeFolder)folder); - } - } - - for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) { - if (excludedOutputFolder instanceof ClonableContentFolder) { - ContentFolder folder = ((ClonableContentFolder)excludedOutputFolder).cloneFolder(cloned); - cloned.myExcludedOutputFolders.add((ExcludedOutputFolder)folder); - } - } - - return cloned; - } - - public void dispose() { - super.dispose(); - for (final Object mySourceFolder : mySourceFolders) { - ContentFolder contentFolder = (ContentFolder)mySourceFolder; - Disposer.dispose((Disposable)contentFolder); - } - for (final ExcludeFolder excludeFolder : myExcludeFolders) { - Disposer.dispose((Disposable)excludeFolder); - } - } - - @Override - protected void projectOpened() { - super.projectOpened(); - for (SourceFolder sourceFolder : mySourceFolders) { - ((RootModelComponentBase)sourceFolder).projectOpened(); - } - for (ExcludeFolder excludeFolder : myExcludeFolders) { - ((RootModelComponentBase)excludeFolder).projectOpened(); - } - for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) { - ((RootModelComponentBase)excludedOutputFolder).projectOpened(); - } - } - - @Override - protected void projectClosed() { - super.projectClosed(); - for (SourceFolder sourceFolder : mySourceFolders) { - ((RootModelComponentBase)sourceFolder).projectClosed(); - } - for (ExcludeFolder excludeFolder : myExcludeFolders) { - ((RootModelComponentBase)excludeFolder).projectClosed(); - } - for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) { - ((RootModelComponentBase)excludedOutputFolder).projectClosed(); - } - } - - public void writeExternal(Element element) throws WriteExternalException { - assert !isDisposed(); - LOG.assertTrue(ELEMENT_NAME.equals(element.getName())); - element.setAttribute(URL_ATTRIBUTE, myRoot.getUrl()); - for (final SourceFolder sourceFolder : mySourceFolders) { - if (sourceFolder instanceof SourceFolderImpl) { - final Element subElement = new Element(SourceFolderImpl.ELEMENT_NAME); - ((SourceFolderImpl)sourceFolder).writeExternal(subElement); - element.addContent(subElement); - } - } - - for (final ExcludeFolder excludeFolder : myExcludeFolders) { - if (excludeFolder instanceof ExcludeFolderImpl) { - final Element subElement = new Element(ExcludeFolderImpl.ELEMENT_NAME); - ((ExcludeFolderImpl)excludeFolder).writeExternal(subElement); - element.addContent(subElement); - } - } - } - - //private static final class ContentFolderComparator implements Comparator { - // public static final ContentFolderComparator INSTANCE = new ContentFolderComparator(); - // - // public int compare(ContentFolder o1, ContentFolder o2) { - // if (o1 instanceof ContentFolderBaseImpl && o2 instanceof ContentFolderBaseImpl) { - // ((ContentFolderBaseImpl)o1).compareTo((ContentFolderBaseImpl)o2); - // } - // int i = o1.getUrl().compareTo(o2.getUrl()); - // if (i != 0) return i; - // return System.identityHashCode(o1) - System.identityHashCode(o2); - // } - //} - private static final class ContentFolderComparator implements Comparator { - public static final ContentFolderComparator INSTANCE = new ContentFolderComparator(); - - public int compare(ContentFolder o1, ContentFolder o2) { - return o1.getUrl().compareTo(o2.getUrl()); - } - } - - public int compareTo(ContentEntryImpl other) { - int i = getUrl().compareTo(other.getUrl()); - if (i != 0) return i; - i = ArrayUtil.lexicographicCompare(getSourceFolders(), other.getSourceFolders()); - if (i != 0) return i; - i = ArrayUtil.lexicographicCompare(getExcludeFolders(), other.getExcludeFolders()); - if (i != 0) return i; - - ExcludedOutputFolder[] excludedOutputFolders = myExcludedOutputFolders.toArray(new ExcludedOutputFolder[myExcludedOutputFolders.size()]); - ExcludedOutputFolder[] otherExcludedOutputFolders = other.myExcludedOutputFolders.toArray(new ExcludedOutputFolder[other.myExcludedOutputFolders.size()]); - return ArrayUtil.lexicographicCompare(excludedOutputFolders, otherExcludedOutputFolders); - } -} +/* + * 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.roots.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; +import com.intellij.util.ArrayUtil; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author dsl + */ +public class ContentEntryImpl extends RootModelComponentBase implements ContentEntry, ClonableContentEntry, Comparable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.SimpleContentEntryImpl"); + private final VirtualFilePointer myRoot; + @NonNls public static final String ELEMENT_NAME = "content"; + private final LinkedHashSet mySourceFolders = new LinkedHashSet(); + private final TreeSet myExcludeFolders = new TreeSet(ContentFolderComparator.INSTANCE); + private final TreeSet myExcludedOutputFolders = new TreeSet(ContentFolderComparator.INSTANCE); + @NonNls public static final String URL_ATTRIBUTE = "url"; + + ContentEntryImpl(VirtualFile file, RootModelImpl m) { + this(file.getUrl(), m); + } + + ContentEntryImpl(String url, RootModelImpl m) { + super(m); + myRoot = VirtualFilePointerManager.getInstance().create(url, this, m.myVirtualFilePointerListener); + } + + ContentEntryImpl(Element e, RootModelImpl m) throws InvalidDataException { + this(getUrlFrom(e), m); + initSourceFolders(e); + initExcludeFolders(e); + } + + private static String getUrlFrom(Element e) throws InvalidDataException { + LOG.assertTrue(ELEMENT_NAME.equals(e.getName())); + + String url = e.getAttributeValue(URL_ATTRIBUTE); + if (url == null) throw new InvalidDataException(); + return url; + } + + private void initSourceFolders(Element e) throws InvalidDataException { + mySourceFolders.clear(); + for (Object child : e.getChildren(SourceFolderImpl.ELEMENT_NAME)) { + mySourceFolders.add(new SourceFolderImpl((Element)child, this)); + } + } + + private void initExcludeFolders(Element e) throws InvalidDataException { + myExcludeFolders.clear(); + for (Object child : e.getChildren(ExcludeFolderImpl.ELEMENT_NAME)) { + myExcludeFolders.add(new ExcludeFolderImpl((Element)child, this)); + } + } + + public VirtualFile getFile() { + //assert !isDisposed(); + final VirtualFile file = myRoot.getFile(); + return file == null || !file.isDirectory() ? null : file; + } + + @NotNull + public String getUrl() { + return myRoot.getUrl(); + } + + public SourceFolder[] getSourceFolders() { + return mySourceFolders.toArray(new SourceFolder[mySourceFolders.size()]); + } + + public VirtualFile[] getSourceFolderFiles() { + assert !isDisposed(); + final SourceFolder[] sourceFolders = getSourceFolders(); + ArrayList result = new ArrayList(sourceFolders.length); + for (SourceFolder sourceFolder : sourceFolders) { + final VirtualFile file = sourceFolder.getFile(); + if (file != null) { + result.add(file); + } + } + return VfsUtil.toVirtualFileArray(result); + } + + public ExcludeFolder[] getExcludeFolders() { + //assert !isDisposed(); + final ArrayList result = new ArrayList(myExcludeFolders); + for (DirectoryIndexExcludePolicy excludePolicy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, getRootModel().getProject())) { + final VirtualFilePointer[] files = excludePolicy.getExcludeRootsForModule(getRootModel()); + for (VirtualFilePointer file : files) { + addExcludeForOutputPath(file, result); + } + } + if (getRootModel().isExcludeExplodedDirectory()) { + addExcludeForOutputPath(getRootModel().myExplodedDirectoryPointer, result); + } + return result.toArray(new ExcludeFolder[result.size()]); + } + + private void addExcludeForOutputPath(final VirtualFilePointer outputPath, ArrayList result) { + if (outputPath == null) return; + final VirtualFile outputPathFile = outputPath.getFile(); + final VirtualFile file = myRoot.getFile(); + if (outputPathFile != null && file != null /* TODO: ??? && VfsUtil.isAncestor(file, outputPathFile, false) */) { + result.add(new ExcludedOutputFolderImpl(this, outputPath)); + } + } + + public VirtualFile[] getExcludeFolderFiles() { + assert !isDisposed(); + final ExcludeFolder[] excludeFolders = getExcludeFolders(); + ArrayList result = new ArrayList(); + for (ExcludeFolder excludeFolder : excludeFolders) { + final VirtualFile file = excludeFolder.getFile(); + if (file != null) { + result.add(file); + } + } + return VfsUtil.toVirtualFileArray(result); + } + + public SourceFolder addSourceFolder(@NotNull VirtualFile file, boolean isTestSource) { + assertCanAddFolder(file); + return addSourceFolder(new SourceFolderImpl(file, isTestSource, this)); + } + + public SourceFolder addSourceFolder(@NotNull VirtualFile file, boolean isTestSource, @NotNull String packagePrefix) { + assertCanAddFolder(file); + return addSourceFolder(new SourceFolderImpl(file, isTestSource, packagePrefix, this)); + } + + public SourceFolder addSourceFolder(@NotNull String url, boolean isTestSource) { + assertFolderUnderMe(url); + return addSourceFolder(new SourceFolderImpl(url, isTestSource, this)); + } + + private SourceFolder addSourceFolder(SourceFolderImpl f) { + mySourceFolders.add(f); + return f; + } + + public void removeSourceFolder(@NotNull SourceFolder sourceFolder) { + assert !isDisposed(); + assertCanRemoveFrom(sourceFolder, mySourceFolders); + mySourceFolders.remove(sourceFolder); + } + + public ExcludeFolder addExcludeFolder(@NotNull VirtualFile file) { + assert !isDisposed(); + assertCanAddFolder(file); + return addExcludeFolder(new ExcludeFolderImpl(file, this)); + } + + public ExcludeFolder addExcludeFolder(@NotNull String url) { + assert !isDisposed(); + assertCanAddFolder(url); + return addExcludeFolder(new ExcludeFolderImpl(url, this)); + } + + private void assertCanAddFolder(VirtualFile file) { + assertCanAddFolder(file.getUrl()); + } + + private void assertCanAddFolder(String url) { + getRootModel().assertWritable(); + assertFolderUnderMe(url); + } + + public void removeExcludeFolder(@NotNull ExcludeFolder excludeFolder) { + assert !isDisposed(); + assertCanRemoveFrom(excludeFolder, myExcludeFolders); + myExcludeFolders.remove(excludeFolder); + } + + private ExcludeFolder addExcludeFolder(ExcludeFolder f) { + myExcludeFolders.add(f); + return f; + } + + private void assertCanRemoveFrom(T f, Set ff) { + getRootModel().assertWritable(); + LOG.assertTrue(ff.contains(f)); + } + + private void assertFolderUnderMe(String url) { + final String rootUrl = getUrl(); + try { + if (!FileUtil.isAncestor(new File(rootUrl), new File(url), false)) { + LOG.error("The file " + url + " is not under content entry root " + rootUrl); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public boolean isSynthetic() { + return false; + } + + public ContentEntry cloneEntry(RootModelImpl rootModel) { + assert !isDisposed(); + ContentEntryImpl cloned = new ContentEntryImpl(myRoot.getUrl(), rootModel); + for (final SourceFolder sourceFolder : mySourceFolders) { + if (sourceFolder instanceof ClonableContentFolder) { + ContentFolder folder = ((ClonableContentFolder)sourceFolder).cloneFolder(cloned); + cloned.mySourceFolders.add((SourceFolder)folder); + } + } + + for (final ExcludeFolder excludeFolder : myExcludeFolders) { + if (excludeFolder instanceof ClonableContentFolder) { + ContentFolder folder = ((ClonableContentFolder)excludeFolder).cloneFolder(cloned); + cloned.myExcludeFolders.add((ExcludeFolder)folder); + } + } + + for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) { + if (excludedOutputFolder instanceof ClonableContentFolder) { + ContentFolder folder = ((ClonableContentFolder)excludedOutputFolder).cloneFolder(cloned); + cloned.myExcludedOutputFolders.add((ExcludedOutputFolder)folder); + } + } + + return cloned; + } + + public void dispose() { + super.dispose(); + for (final Object mySourceFolder : mySourceFolders) { + ContentFolder contentFolder = (ContentFolder)mySourceFolder; + Disposer.dispose((Disposable)contentFolder); + } + for (final ExcludeFolder excludeFolder : myExcludeFolders) { + Disposer.dispose((Disposable)excludeFolder); + } + } + + @Override + protected void projectOpened() { + super.projectOpened(); + for (SourceFolder sourceFolder : mySourceFolders) { + ((RootModelComponentBase)sourceFolder).projectOpened(); + } + for (ExcludeFolder excludeFolder : myExcludeFolders) { + ((RootModelComponentBase)excludeFolder).projectOpened(); + } + for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) { + ((RootModelComponentBase)excludedOutputFolder).projectOpened(); + } + } + + @Override + protected void projectClosed() { + super.projectClosed(); + for (SourceFolder sourceFolder : mySourceFolders) { + ((RootModelComponentBase)sourceFolder).projectClosed(); + } + for (ExcludeFolder excludeFolder : myExcludeFolders) { + ((RootModelComponentBase)excludeFolder).projectClosed(); + } + for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) { + ((RootModelComponentBase)excludedOutputFolder).projectClosed(); + } + } + + public void writeExternal(Element element) throws WriteExternalException { + assert !isDisposed(); + LOG.assertTrue(ELEMENT_NAME.equals(element.getName())); + element.setAttribute(URL_ATTRIBUTE, myRoot.getUrl()); + for (final SourceFolder sourceFolder : mySourceFolders) { + if (sourceFolder instanceof SourceFolderImpl) { + final Element subElement = new Element(SourceFolderImpl.ELEMENT_NAME); + ((SourceFolderImpl)sourceFolder).writeExternal(subElement); + element.addContent(subElement); + } + } + + for (final ExcludeFolder excludeFolder : myExcludeFolders) { + if (excludeFolder instanceof ExcludeFolderImpl) { + final Element subElement = new Element(ExcludeFolderImpl.ELEMENT_NAME); + ((ExcludeFolderImpl)excludeFolder).writeExternal(subElement); + element.addContent(subElement); + } + } + } + + //private static final class ContentFolderComparator implements Comparator { + // public static final ContentFolderComparator INSTANCE = new ContentFolderComparator(); + // + // public int compare(ContentFolder o1, ContentFolder o2) { + // if (o1 instanceof ContentFolderBaseImpl && o2 instanceof ContentFolderBaseImpl) { + // ((ContentFolderBaseImpl)o1).compareTo((ContentFolderBaseImpl)o2); + // } + // int i = o1.getUrl().compareTo(o2.getUrl()); + // if (i != 0) return i; + // return System.identityHashCode(o1) - System.identityHashCode(o2); + // } + //} + private static final class ContentFolderComparator implements Comparator { + public static final ContentFolderComparator INSTANCE = new ContentFolderComparator(); + + public int compare(ContentFolder o1, ContentFolder o2) { + return o1.getUrl().compareTo(o2.getUrl()); + } + } + + public int compareTo(ContentEntryImpl other) { + int i = getUrl().compareTo(other.getUrl()); + if (i != 0) return i; + i = ArrayUtil.lexicographicCompare(getSourceFolders(), other.getSourceFolders()); + if (i != 0) return i; + i = ArrayUtil.lexicographicCompare(getExcludeFolders(), other.getExcludeFolders()); + if (i != 0) return i; + + ExcludedOutputFolder[] excludedOutputFolders = myExcludedOutputFolders.toArray(new ExcludedOutputFolder[myExcludedOutputFolders.size()]); + ExcludedOutputFolder[] otherExcludedOutputFolders = other.myExcludedOutputFolders.toArray(new ExcludedOutputFolder[other.myExcludedOutputFolders.size()]); + return ArrayUtil.lexicographicCompare(excludedOutputFolders, otherExcludedOutputFolders); + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java index be5f23170e20..1ce9ea5865cb 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java @@ -1,845 +1,845 @@ -/* - * 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.roots.impl; - -import com.intellij.AppTopics; -import com.intellij.ProjectTopics; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileTypes.FileTypeEvent; -import com.intellij.openapi.fileTypes.FileTypeListener; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.util.*; -import com.intellij.util.containers.HashMap; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.THashMap; -import gnu.trove.THashSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.util.*; - -public class DirectoryIndexImpl extends DirectoryIndex implements ProjectComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.DirectoryIndexImpl"); - - private final Project myProject; - - private boolean myInitialized = false; - private boolean myDisposed = false; - private volatile IndexState myState; - - private final DirectoryIndexExcludePolicy[] myExcludePolicies; - private final MessageBusConnection myConnection; - - public DirectoryIndexImpl(Project project, StartupManager startupManager) { - myProject = project; - myConnection = project.getMessageBus().connect(project); - - startupManager.registerPreStartupActivity(new Runnable() { - public void run() { - initialize(); - } - }); - myExcludePolicies = Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, myProject); - myState = new IndexState(); - } - - @NotNull - public String getComponentName() { - return "DirectoryIndex"; - } - - public void initComponent() { - } - - public void disposeComponent() { - myDisposed = true; - } - - public void projectOpened() { - } - - public void projectClosed() { - } - - @TestOnly - public void checkConsistency() { - doCheckConsistency(false); - doCheckConsistency(true); - } - - @TestOnly - private void doCheckConsistency(boolean reverseAllSets) { - assert myInitialized; - assert !myDisposed; - - IndexState oldState = myState; - myState = myState.copy(); - - myState.doInitialize(reverseAllSets); - - Set keySet = myState.myDirToInfoMap.keySet(); - assert keySet.size() == oldState.myDirToInfoMap.keySet().size(); - for (VirtualFile file : keySet) { - DirectoryInfo info1 = myState.myDirToInfoMap.get(file); - DirectoryInfo info2 = oldState.myDirToInfoMap.get(file); - assert info1.equals(info2); - } - - assert myState.myPackageNameToDirsMap.keySet().size() == oldState.myPackageNameToDirsMap.keySet().size(); - for (Map.Entry> entry : myState.myPackageNameToDirsMap.entrySet()) { - String packageName = entry.getKey(); - List dirs = entry.getValue(); - List dirs1 = oldState.myPackageNameToDirsMap.get(packageName); - - HashSet set1 = new HashSet(); - set1.addAll(dirs); - HashSet set2 = new HashSet(); - set2.addAll(dirs1); - assert set1.equals(set2); - } - } - - public boolean isInitialized() { - return myInitialized; - } - - public void initialize() { - if (myInitialized) { - LOG.error("Directory index is already initialized."); - return; - } - - if (myDisposed) { - LOG.error("Directory index is already disposed for this project"); - return; - } - - subscribeToFileChanges(); - - myInitialized = true; - doInitialize(); - } - - private void subscribeToFileChanges() { - myConnection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() { - public void beforeFileTypesChanged(FileTypeEvent event) { - } - - public void fileTypesChanged(FileTypeEvent event) { - doInitialize(); - } - }); - - myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { - public void beforeRootsChange(ModuleRootEvent event) { - } - - public void rootsChanged(ModuleRootEvent event) { - doInitialize(); - } - }); - - myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new MyVirtualFileListener())); - } - - private void doInitialize() { - IndexState newState = new IndexState(); - newState.doInitialize(false); - myState = newState; - } - - private boolean isExcludeRootForModule(Module module, VirtualFile excludeRoot) { - for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { - if (policy.isExcludeRootForModule(module, excludeRoot)) return true; - } - return false; - } - - private static ContentEntry[] getContentEntries(Module module) { - return ModuleRootManager.getInstance(module).getContentEntries(); - } - - private static OrderEntry[] getOrderEntries(Module module) { - return ModuleRootManager.getInstance(module).getOrderEntries(); - } - - private static boolean isIgnored(@NotNull VirtualFile f) { - return FileTypeManager.getInstance().isFileIgnored(f.getName()); - } - - public DirectoryInfo getInfoForDirectory(VirtualFile dir) { - checkAvailability(); - dispatchPendingEvents(); - - return myState.myDirToInfoMap.get(dir); - } - - @Override - public boolean isProjectExcludeRoot(VirtualFile dir) { - checkAvailability(); - return myState.myProjectExcludeRoots.contains(dir); - } - - private final PackageSink mySink = new PackageSink(); - - private static final Condition IS_VALID = new Condition() { - public boolean value(final VirtualFile virtualFile) { - return virtualFile.isValid(); - } - }; - - private class PackageSink extends QueryFactory>> { - private PackageSink() { - registerExecutor(new QueryExecutor>>() { - public boolean execute(@NotNull final Pair> stateAndDirs, - @NotNull final Processor consumer) { - for (VirtualFile dir : stateAndDirs.second) { - DirectoryInfo info = stateAndDirs.first.myDirToInfoMap.get(dir); - assert info != null; - - if (!info.isInLibrarySource || info.libraryClassRoot != null) { - if (!consumer.process(dir)) return false; - } - } - return true; - } - }); - } - - public Query search(@NotNull String packageName, boolean includeLibrarySources) { - checkAvailability(); - dispatchPendingEvents(); - - IndexState state = myState; - List allDirs = state.myPackageNameToDirsMap.get(packageName); - if (allDirs == null) allDirs = Collections.emptyList(); - - Query query = includeLibrarySources ? new CollectionQuery(allDirs) - : createQuery(Pair.create(state, allDirs)); - return new FilteredQuery(query, IS_VALID); - } - } - - @NotNull - public Query getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources) { - return mySink.search(packageName, includeLibrarySources); - } - - @Override - public String getPackageName(VirtualFile dir) { - checkAvailability(); - return myState.myDirToPackageName.get(dir); - } - - private void dispatchPendingEvents() { - myConnection.deliverImmediately(); - } - - private void checkAvailability() { - if (!myInitialized) { - LOG.error("Directory index is not initialized yet for " + myProject); - } - - if (myDisposed) { - LOG.error("Directory index is already disposed for " + myProject); - } - } - - @Nullable - private static String getPackageNameForSubdir(String parentPackageName, String subdirName) { - if (parentPackageName == null) return null; - return parentPackageName.length() > 0 ? parentPackageName + "." + subdirName : subdirName; - } - - private class MyVirtualFileListener extends VirtualFileAdapter { - private final Key> FILES_TO_RELEASE_KEY = Key.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY"); - - public void fileCreated(VirtualFileEvent event) { - VirtualFile file = event.getFile(); - - if (!file.isDirectory()) return; - - VirtualFile parent = file.getParent(); - if (parent == null) return; - - IndexState state = myState.copy(); - - DirectoryInfo parentInfo = state.myDirToInfoMap.get(parent); - - // fill info for all nested roots - for (Module eachModule : ModuleManager.getInstance(myProject).getModules()) { - for (ContentEntry eachRoot : getContentEntries(eachModule)) { - if (parentInfo != null && eachRoot == parentInfo.contentRoot) continue; - - if (FileUtil.startsWith(eachRoot.getUrl(), file.getUrl())) { - String rel = FileUtil.getRelativePath(file.getUrl(), eachRoot.getUrl(), '/'); - if (rel != null) { - VirtualFile f = file.findFileByRelativePath(rel); - if (f != null) { - state.fillMapWithModuleContent(f, eachModule, f); - } - } - } - } - } - - if (parentInfo == null) return; - - Module module = parentInfo.module; - - for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { - if (policy.isExcludeRoot(file)) return; - } - - state.fillMapWithModuleContent(file, module, parentInfo.contentRoot); - - String parentPackage = state.myDirToPackageName.get(parent); - - if (module != null) { - if (parentInfo.isInModuleSource) { - String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); - state.fillMapWithModuleSource(file, module, newDirPackageName, parentInfo.sourceRoot, parentInfo.isTestSource); - } - } - - if (parentInfo.libraryClassRoot != null) { - String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); - state.fillMapWithLibraryClasses(file, newDirPackageName, parentInfo.libraryClassRoot); - } - - if (parentInfo.isInLibrarySource) { - String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); - state.fillMapWithLibrarySources(file, newDirPackageName, parentInfo.sourceRoot); - } - - if (!parentInfo.getOrderEntries().isEmpty()) { - state.fillMapWithOrderEntries(file, parentInfo.getOrderEntries(), null, null, null, parentInfo, null); - } - myState = state; - } - - public void beforeFileDeletion(VirtualFileEvent event) { - VirtualFile file = event.getFile(); - if (!file.isDirectory()) return; - if (!myState.myDirToInfoMap.containsKey(file)) return; - - final IndexState state = myState.copy(); - - ArrayList list = new ArrayList(); - state.addDirsRecursively(list, file); - file.putUserData(FILES_TO_RELEASE_KEY, list); - myState = state; - } - - public void fileDeleted(VirtualFileEvent event) { - VirtualFile file = event.getFile(); - List list = file.getUserData(FILES_TO_RELEASE_KEY); - if (list == null) return; - - IndexState copy = null; - for (VirtualFile dir : list) { - if (myState.myDirToInfoMap.containsKey(dir)) { - if (copy == null) copy = myState.copy(); - - copy.myDirToInfoMap.remove(dir); - copy.setPackageName(dir, null); - } - } - - if (copy != null) { - myState = copy; - } - } - - public void fileMoved(VirtualFileMoveEvent event) { - VirtualFile file = event.getFile(); - if (file.isDirectory()) { - doInitialize(); - } - } - - public void propertyChanged(VirtualFilePropertyEvent event) { - if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { - VirtualFile file = event.getFile(); - if (file.isDirectory()) { - doInitialize(); - } - } - } - } - - private class IndexState { - final Map> myExcludeRootsMap = new THashMap>(); - final Set myProjectExcludeRoots = new THashSet(); - final Map myDirToInfoMap = new THashMap(); - final Map> myPackageNameToDirsMap = new THashMap>(); - final Map myDirToPackageName = new THashMap(); - - public IndexState() { - } - - DirectoryInfo getOrCreateDirInfo(VirtualFile dir) { - DirectoryInfo info = myDirToInfoMap.get(dir); - if (info == null) { - info = new DirectoryInfo(); - myDirToInfoMap.put(dir, info); - } - return info; - } - - private void fillMapWithModuleContent(VirtualFile dir, Module module, VirtualFile contentRoot) { - if (isExcluded(contentRoot, dir)) return; - if (isIgnored(dir)) return; - - DirectoryInfo info = getOrCreateDirInfo(dir); - - if (info.module != null) { // module contents overlap - DirectoryInfo parentInfo = myDirToInfoMap.get(dir.getParent()); - if (parentInfo == null || !info.module.equals(parentInfo.module)) return; // content of another module is below this module's content - } - - VirtualFile[] children = dir.getChildren(); - for (VirtualFile child : children) { - if (child.isDirectory()) { - fillMapWithModuleContent(child, module, contentRoot); - } - } - - // important to change module AFTER processing children - to handle overlapping modules - info.module = module; - info.contentRoot = contentRoot; - } - - private boolean isExcluded(VirtualFile root, VirtualFile dir) { - Set excludes = myExcludeRootsMap.get(root); - return excludes != null && excludes.contains(dir.getUrl()); - } - - private void initModuleContents(Module module, boolean reverseAllSets, ProgressIndicator progress) { - progress.checkCanceled(); - progress.setText2(ProjectBundle.message("project.index.processing.module.content.progress", module.getName())); - - ModuleRootManager rootManager = ModuleRootManager.getInstance(module); - VirtualFile[] contentRoots = rootManager.getContentRoots(); - if (reverseAllSets) { - contentRoots = ArrayUtil.reverseArray(contentRoots); - } - - for (final VirtualFile contentRoot : contentRoots) { - fillMapWithModuleContent(contentRoot, module, contentRoot); - } - } - - private void initModuleSources(Module module, boolean reverseAllSets, ProgressIndicator progress) { - progress.checkCanceled(); - progress.setText2(ProjectBundle.message("project.index.processing.module.sources.progress", module.getName())); - - ContentEntry[] contentEntries = getContentEntries(module); - - if (reverseAllSets) { - contentEntries = ArrayUtil.reverseArray(contentEntries); - } - - for (ContentEntry contentEntry : contentEntries) { - SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); - if (reverseAllSets) { - sourceFolders = ArrayUtil.reverseArray(sourceFolders); - } - for (SourceFolder sourceFolder : sourceFolders) { - VirtualFile dir = sourceFolder.getFile(); - if (dir != null) { - fillMapWithModuleSource(dir, module, sourceFolder.getPackagePrefix(), dir, sourceFolder.isTestSource()); - } - } - } - } - - private void fillMapWithModuleSource(VirtualFile dir, Module module, String packageName, VirtualFile sourceRoot, boolean isTestSource) { - DirectoryInfo info = myDirToInfoMap.get(dir); - if (info == null) return; - if (!module.equals(info.module)) return; - - if (info.isInModuleSource) { // module sources overlap - String definedPackage = myDirToPackageName.get(dir); - if (definedPackage != null && definedPackage.length() == 0) return; // another source root starts here - } - - info.isInModuleSource = true; - info.isTestSource = isTestSource; - info.sourceRoot = sourceRoot; - setPackageName(dir, packageName); - - VirtualFile[] children = dir.getChildren(); - for (VirtualFile child : children) { - if (child.isDirectory()) { - String childPackageName = getPackageNameForSubdir(packageName, child.getName()); - fillMapWithModuleSource(child, module, childPackageName, sourceRoot, isTestSource); - } - } - } - - private void initLibrarySources(Module module, ProgressIndicator progress) { - progress.checkCanceled(); - progress.setText2(ProjectBundle.message("project.index.processing.library.sources.progress", module.getName())); - - for (OrderEntry orderEntry : getOrderEntries(module)) { - boolean isLibrary = orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry; - if (isLibrary) { - VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); - for (final VirtualFile sourceRoot : sourceRoots) { - fillMapWithLibrarySources(sourceRoot, "", sourceRoot); - } - } - } - } - - private void fillMapWithLibrarySources(VirtualFile dir, String packageName, VirtualFile sourceRoot) { - if (isIgnored(dir)) return; - - DirectoryInfo info = getOrCreateDirInfo(dir); - - if (info.isInLibrarySource) { // library sources overlap - String definedPackage = myDirToPackageName.get(dir); - if (definedPackage != null && definedPackage.length() == 0) return; // another library source root starts here - } - - info.isInModuleSource = false; - info.isInLibrarySource = true; - info.sourceRoot = sourceRoot; - setPackageName(dir, packageName); - - VirtualFile[] children = dir.getChildren(); - for (VirtualFile child : children) { - if (child.isDirectory()) { - String childPackageName = getPackageNameForSubdir(packageName, child.getName()); - fillMapWithLibrarySources(child, childPackageName, sourceRoot); - } - } - } - - private void initLibraryClasses(Module module, ProgressIndicator progress) { - progress.checkCanceled(); - progress.setText2(ProjectBundle.message("project.index.processing.library.classes.progress", module.getName())); - - for (OrderEntry orderEntry : getOrderEntries(module)) { - boolean isLibrary = orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry; - if (isLibrary) { - VirtualFile[] classRoots = orderEntry.getFiles(OrderRootType.CLASSES); - for (final VirtualFile classRoot : classRoots) { - fillMapWithLibraryClasses(classRoot, "", classRoot); - } - } - } - } - - private void fillMapWithLibraryClasses(VirtualFile dir, String packageName, VirtualFile classRoot) { - if (isIgnored(dir)) return; - - DirectoryInfo info = getOrCreateDirInfo(dir); - - if (info.libraryClassRoot != null) { // library classes overlap - String definedPackage = myDirToPackageName.get(dir); - if (definedPackage != null && definedPackage.length() == 0) return; // another library root starts here - } - - info.libraryClassRoot = classRoot; - - if (!info.isInModuleSource && !info.isInLibrarySource) { - setPackageName(dir, packageName); - } - - VirtualFile[] children = dir.getChildren(); - for (VirtualFile child : children) { - if (child.isDirectory()) { - String childPackageName = getPackageNameForSubdir(packageName, child.getName()); - fillMapWithLibraryClasses(child, childPackageName, classRoot); - } - } - } - - private void initOrderEntries(Module module) { - Map> depEntries = new HashMap>(); - Map> libClassRootEntries = new HashMap>(); - Map> libSourceRootEntries = new HashMap>(); - - for (OrderEntry orderEntry : getOrderEntries(module)) { - if (orderEntry instanceof ModuleOrderEntry) { - final Module depModule = ((ModuleOrderEntry)orderEntry).getModule(); - if (depModule != null) { - VirtualFile[] importedClassRoots = - OrderEnumerator.orderEntries(depModule).exportedOnly().recursively().classes().usingCache().getRoots(); - for (VirtualFile importedClassRoot : importedClassRoots) { - addEntryToMap(importedClassRoot, orderEntry, depEntries); - } - } - VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); - for (VirtualFile sourceRoot : sourceRoots) { - addEntryToMap(sourceRoot, orderEntry, depEntries); - } - } - else if (orderEntry instanceof ModuleSourceOrderEntry) { - List oneEntryList = Arrays.asList(orderEntry); - Module entryModule = orderEntry.getOwnerModule(); - - VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); - for (VirtualFile sourceRoot : sourceRoots) { - fillMapWithOrderEntries(sourceRoot, oneEntryList, entryModule, null, null, null, null); - } - } - else if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { - VirtualFile[] classRoots = orderEntry.getFiles(OrderRootType.CLASSES); - for (VirtualFile classRoot : classRoots) { - addEntryToMap(classRoot, orderEntry, libClassRootEntries); - } - VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); - for (VirtualFile sourceRoot : sourceRoots) { - addEntryToMap(sourceRoot, orderEntry, libSourceRootEntries); - } - } - } - - for (Map.Entry> mapEntry : depEntries.entrySet()) { - final VirtualFile vRoot = mapEntry.getKey(); - final List entries = mapEntry.getValue(); - fillMapWithOrderEntries(vRoot, entries, null, null, null, null, null); - } - - for (Map.Entry> mapEntry : libClassRootEntries.entrySet()) { - final VirtualFile vRoot = mapEntry.getKey(); - final List entries = mapEntry.getValue(); - fillMapWithOrderEntries(vRoot, entries, null, vRoot, null, null, null); - } - - for (Map.Entry> mapEntry : libSourceRootEntries.entrySet()) { - final VirtualFile vRoot = mapEntry.getKey(); - final List entries = mapEntry.getValue(); - fillMapWithOrderEntries(vRoot, entries, null, null, vRoot, null, null); - } - } - - private void setPackageName(VirtualFile dir, String newPackageName) { - assert dir != null; - - String oldPackageName = myDirToPackageName.get(dir); - if (oldPackageName != null) { - List oldPackageDirs = myPackageNameToDirsMap.get(oldPackageName); - final boolean removed = oldPackageDirs.remove(dir); - assert removed; - - if (oldPackageDirs.size() == 0) { - myPackageNameToDirsMap.remove(oldPackageName); - } - } - - if (newPackageName != null) { - List newPackageDirs = myPackageNameToDirsMap.get(newPackageName); - if (newPackageDirs == null) { - newPackageDirs = new SmartList(); - myPackageNameToDirsMap.put(newPackageName, newPackageDirs); - } - newPackageDirs.add(dir); - - myDirToPackageName.put(dir, newPackageName); - } - else { - myDirToPackageName.remove(dir); - } - } - - private void addEntryToMap(final VirtualFile vRoot, final OrderEntry entry, final Map> map) { - List list = map.get(vRoot); - if (list == null) { - list = new ArrayList(); - map.put(vRoot, list); - } - list.add(entry); - } - - private void fillMapWithOrderEntries(VirtualFile dir, - List orderEntries, - Module module, - VirtualFile libraryClassRoot, - VirtualFile librarySourceRoot, - DirectoryInfo parentInfo, - final List oldParentEntries) { - if (isIgnored(dir)) return; - - DirectoryInfo info = myDirToInfoMap.get(dir); // do not create it here! - if (info == null) return; - - if (module != null) { - if (info.module != module) return; - if (!info.isInModuleSource) return; - } - else if (libraryClassRoot != null) { - if (info.libraryClassRoot != libraryClassRoot) return; - if (info.isInModuleSource) return; - } - else if (librarySourceRoot != null) { - if (!info.isInLibrarySource) return; - if (info.sourceRoot != librarySourceRoot) return; - if (info.libraryClassRoot != null) return; - } - - final List oldEntries = info.getOrderEntries(); - info.addOrderEntries(orderEntries, parentInfo, oldParentEntries); - - final VirtualFile[] children = dir.getChildren(); - for (VirtualFile child : children) { - if (child.isDirectory()) { - fillMapWithOrderEntries(child, orderEntries, module, libraryClassRoot, librarySourceRoot, info, oldEntries); - } - } - } - - private void doInitialize(boolean reverseAllSets/* for testing order independence*/) { - ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); - if (progress == null) progress = new EmptyProgressIndicator(); - - progress.pushState(); - - progress.checkCanceled(); - progress.setText(ProjectBundle.message("project.index.scanning.files.progress")); - - Module[] modules = ModuleManager.getInstance(myProject).getModules(); - if (reverseAllSets) modules = ArrayUtil.reverseArray(modules); - - initExcludedDirMap(modules, progress); - - for (Module module : modules) { - initModuleContents(module, reverseAllSets, progress); - } - // Important! Because module's contents may overlap, - // first modules should be marked and only after that sources markup - // should be added. (src markup depends on module markup) - for (Module module : modules) { - initModuleSources(module, reverseAllSets, progress); - initLibrarySources(module, progress); - initLibraryClasses(module, progress); - } - - progress.checkCanceled(); - progress.setText2(""); - - for (Module module : modules) { - initOrderEntries(module); - } - - progress.popState(); - } - - private void initExcludedDirMap(Module[] modules, ProgressIndicator progress) { - progress.checkCanceled(); - progress.setText2(ProjectBundle.message("project.index.building.exclude.roots.progress")); - - // exclude roots should be merged to prevent including excluded dirs of an inner module into the outer - // exclude root should exclude from its content root and all outer content roots - - for (Module module : modules) { - for (ContentEntry contentEntry : getContentEntries(module)) { - VirtualFile contentRoot = contentEntry.getFile(); - if (contentRoot == null) continue; - - ExcludeFolder[] excludeRoots = contentEntry.getExcludeFolders(); - for (ExcludeFolder excludeRoot : excludeRoots) { - // Output paths should be excluded (if marked as such) regardless if they're under corresponding module's content root - if (excludeRoot.getFile() != null) { - if (!FileUtil.startsWith(contentRoot.getUrl(), excludeRoot.getUrl())) { - if (isExcludeRootForModule(module, excludeRoot.getFile())) { - putForFileAndAllAncestors(myExcludeRootsMap, excludeRoot.getFile(), excludeRoot.getUrl()); - } - } - } - - putForFileAndAllAncestors(myExcludeRootsMap, contentRoot, excludeRoot.getUrl()); - } - } - } - - for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { - for (VirtualFile file : policy.getExcludeRootsForProject()) { - putForFileAndAllAncestors(myExcludeRootsMap, file, file.getUrl()); - myProjectExcludeRoots.add(file); - } - } - } - - private void putForFileAndAllAncestors(Map> map, VirtualFile file, String value) { - while (true) { - Set set = map.get(file); - if (set == null) { - set = new HashSet(); - map.put(file, set); - } - set.add(value); - - file = file.getParent(); - if (file == null) break; - } - } - - private void addDirsRecursively(ArrayList list, VirtualFile dir) { - if (!myDirToInfoMap.containsKey(dir) || !(dir instanceof NewVirtualFile)) return; - - list.add(dir); - - for (VirtualFile child : ((NewVirtualFile)dir).getCachedChildren()) { - if (child.isDirectory()) { - addDirsRecursively(list, child); - } - } - } - - public IndexState copy() { - IndexState copy = new IndexState(); - - for (Map.Entry> entry : myExcludeRootsMap.entrySet()) { - copy.myExcludeRootsMap.put(entry.getKey(), new HashSet(entry.getValue())); - } - - copy.myProjectExcludeRoots.addAll(myProjectExcludeRoots); - copy.myDirToInfoMap.putAll(myDirToInfoMap); - - for (Map.Entry> entry : myPackageNameToDirsMap.entrySet()) { - copy.myPackageNameToDirsMap.put(entry.getKey(), new SmartList(entry.getValue())); - } - - copy.myDirToPackageName.putAll(myDirToPackageName); - - return copy; - } - } -} +/* + * 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.roots.impl; + +import com.intellij.AppTopics; +import com.intellij.ProjectTopics; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileTypeEvent; +import com.intellij.openapi.fileTypes.FileTypeListener; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.util.*; +import com.intellij.util.containers.HashMap; +import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.*; + +public class DirectoryIndexImpl extends DirectoryIndex implements ProjectComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.DirectoryIndexImpl"); + + private final Project myProject; + + private boolean myInitialized = false; + private boolean myDisposed = false; + private volatile IndexState myState; + + private final DirectoryIndexExcludePolicy[] myExcludePolicies; + private final MessageBusConnection myConnection; + + public DirectoryIndexImpl(Project project, StartupManager startupManager) { + myProject = project; + myConnection = project.getMessageBus().connect(project); + + startupManager.registerPreStartupActivity(new Runnable() { + public void run() { + initialize(); + } + }); + myExcludePolicies = Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, myProject); + myState = new IndexState(); + } + + @NotNull + public String getComponentName() { + return "DirectoryIndex"; + } + + public void initComponent() { + } + + public void disposeComponent() { + myDisposed = true; + } + + public void projectOpened() { + } + + public void projectClosed() { + } + + @TestOnly + public void checkConsistency() { + doCheckConsistency(false); + doCheckConsistency(true); + } + + @TestOnly + private void doCheckConsistency(boolean reverseAllSets) { + assert myInitialized; + assert !myDisposed; + + IndexState oldState = myState; + myState = myState.copy(); + + myState.doInitialize(reverseAllSets); + + Set keySet = myState.myDirToInfoMap.keySet(); + assert keySet.size() == oldState.myDirToInfoMap.keySet().size(); + for (VirtualFile file : keySet) { + DirectoryInfo info1 = myState.myDirToInfoMap.get(file); + DirectoryInfo info2 = oldState.myDirToInfoMap.get(file); + assert info1.equals(info2); + } + + assert myState.myPackageNameToDirsMap.keySet().size() == oldState.myPackageNameToDirsMap.keySet().size(); + for (Map.Entry> entry : myState.myPackageNameToDirsMap.entrySet()) { + String packageName = entry.getKey(); + List dirs = entry.getValue(); + List dirs1 = oldState.myPackageNameToDirsMap.get(packageName); + + HashSet set1 = new HashSet(); + set1.addAll(dirs); + HashSet set2 = new HashSet(); + set2.addAll(dirs1); + assert set1.equals(set2); + } + } + + public boolean isInitialized() { + return myInitialized; + } + + public void initialize() { + if (myInitialized) { + LOG.error("Directory index is already initialized."); + return; + } + + if (myDisposed) { + LOG.error("Directory index is already disposed for this project"); + return; + } + + subscribeToFileChanges(); + + myInitialized = true; + doInitialize(); + } + + private void subscribeToFileChanges() { + myConnection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() { + public void beforeFileTypesChanged(FileTypeEvent event) { + } + + public void fileTypesChanged(FileTypeEvent event) { + doInitialize(); + } + }); + + myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + public void beforeRootsChange(ModuleRootEvent event) { + } + + public void rootsChanged(ModuleRootEvent event) { + doInitialize(); + } + }); + + myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new MyVirtualFileListener())); + } + + private void doInitialize() { + IndexState newState = new IndexState(); + newState.doInitialize(false); + myState = newState; + } + + private boolean isExcludeRootForModule(Module module, VirtualFile excludeRoot) { + for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { + if (policy.isExcludeRootForModule(module, excludeRoot)) return true; + } + return false; + } + + private static ContentEntry[] getContentEntries(Module module) { + return ModuleRootManager.getInstance(module).getContentEntries(); + } + + private static OrderEntry[] getOrderEntries(Module module) { + return ModuleRootManager.getInstance(module).getOrderEntries(); + } + + private static boolean isIgnored(@NotNull VirtualFile f) { + return FileTypeManager.getInstance().isFileIgnored(f.getName()); + } + + public DirectoryInfo getInfoForDirectory(VirtualFile dir) { + checkAvailability(); + dispatchPendingEvents(); + + return myState.myDirToInfoMap.get(dir); + } + + @Override + public boolean isProjectExcludeRoot(VirtualFile dir) { + checkAvailability(); + return myState.myProjectExcludeRoots.contains(dir); + } + + private final PackageSink mySink = new PackageSink(); + + private static final Condition IS_VALID = new Condition() { + public boolean value(final VirtualFile virtualFile) { + return virtualFile.isValid(); + } + }; + + private class PackageSink extends QueryFactory>> { + private PackageSink() { + registerExecutor(new QueryExecutor>>() { + public boolean execute(@NotNull final Pair> stateAndDirs, + @NotNull final Processor consumer) { + for (VirtualFile dir : stateAndDirs.second) { + DirectoryInfo info = stateAndDirs.first.myDirToInfoMap.get(dir); + assert info != null; + + if (!info.isInLibrarySource || info.libraryClassRoot != null) { + if (!consumer.process(dir)) return false; + } + } + return true; + } + }); + } + + public Query search(@NotNull String packageName, boolean includeLibrarySources) { + checkAvailability(); + dispatchPendingEvents(); + + IndexState state = myState; + List allDirs = state.myPackageNameToDirsMap.get(packageName); + if (allDirs == null) allDirs = Collections.emptyList(); + + Query query = includeLibrarySources ? new CollectionQuery(allDirs) + : createQuery(Pair.create(state, allDirs)); + return new FilteredQuery(query, IS_VALID); + } + } + + @NotNull + public Query getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources) { + return mySink.search(packageName, includeLibrarySources); + } + + @Override + public String getPackageName(VirtualFile dir) { + checkAvailability(); + return myState.myDirToPackageName.get(dir); + } + + private void dispatchPendingEvents() { + myConnection.deliverImmediately(); + } + + private void checkAvailability() { + if (!myInitialized) { + LOG.error("Directory index is not initialized yet for " + myProject); + } + + if (myDisposed) { + LOG.error("Directory index is already disposed for " + myProject); + } + } + + @Nullable + private static String getPackageNameForSubdir(String parentPackageName, String subdirName) { + if (parentPackageName == null) return null; + return parentPackageName.length() > 0 ? parentPackageName + "." + subdirName : subdirName; + } + + private class MyVirtualFileListener extends VirtualFileAdapter { + private final Key> FILES_TO_RELEASE_KEY = Key.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY"); + + public void fileCreated(VirtualFileEvent event) { + VirtualFile file = event.getFile(); + + if (!file.isDirectory()) return; + + VirtualFile parent = file.getParent(); + if (parent == null) return; + + IndexState state = myState.copy(); + + DirectoryInfo parentInfo = state.myDirToInfoMap.get(parent); + + // fill info for all nested roots + for (Module eachModule : ModuleManager.getInstance(myProject).getModules()) { + for (ContentEntry eachRoot : getContentEntries(eachModule)) { + if (parentInfo != null && eachRoot == parentInfo.contentRoot) continue; + + if (FileUtil.startsWith(eachRoot.getUrl(), file.getUrl())) { + String rel = FileUtil.getRelativePath(file.getUrl(), eachRoot.getUrl(), '/'); + if (rel != null) { + VirtualFile f = file.findFileByRelativePath(rel); + if (f != null) { + state.fillMapWithModuleContent(f, eachModule, f); + } + } + } + } + } + + if (parentInfo == null) return; + + Module module = parentInfo.module; + + for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { + if (policy.isExcludeRoot(file)) return; + } + + state.fillMapWithModuleContent(file, module, parentInfo.contentRoot); + + String parentPackage = state.myDirToPackageName.get(parent); + + if (module != null) { + if (parentInfo.isInModuleSource) { + String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); + state.fillMapWithModuleSource(file, module, newDirPackageName, parentInfo.sourceRoot, parentInfo.isTestSource); + } + } + + if (parentInfo.libraryClassRoot != null) { + String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); + state.fillMapWithLibraryClasses(file, newDirPackageName, parentInfo.libraryClassRoot); + } + + if (parentInfo.isInLibrarySource) { + String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); + state.fillMapWithLibrarySources(file, newDirPackageName, parentInfo.sourceRoot); + } + + if (!parentInfo.getOrderEntries().isEmpty()) { + state.fillMapWithOrderEntries(file, parentInfo.getOrderEntries(), null, null, null, parentInfo, null); + } + myState = state; + } + + public void beforeFileDeletion(VirtualFileEvent event) { + VirtualFile file = event.getFile(); + if (!file.isDirectory()) return; + if (!myState.myDirToInfoMap.containsKey(file)) return; + + final IndexState state = myState.copy(); + + ArrayList list = new ArrayList(); + state.addDirsRecursively(list, file); + file.putUserData(FILES_TO_RELEASE_KEY, list); + myState = state; + } + + public void fileDeleted(VirtualFileEvent event) { + VirtualFile file = event.getFile(); + List list = file.getUserData(FILES_TO_RELEASE_KEY); + if (list == null) return; + + IndexState copy = null; + for (VirtualFile dir : list) { + if (myState.myDirToInfoMap.containsKey(dir)) { + if (copy == null) copy = myState.copy(); + + copy.myDirToInfoMap.remove(dir); + copy.setPackageName(dir, null); + } + } + + if (copy != null) { + myState = copy; + } + } + + public void fileMoved(VirtualFileMoveEvent event) { + VirtualFile file = event.getFile(); + if (file.isDirectory()) { + doInitialize(); + } + } + + public void propertyChanged(VirtualFilePropertyEvent event) { + if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { + VirtualFile file = event.getFile(); + if (file.isDirectory()) { + doInitialize(); + } + } + } + } + + private class IndexState { + final Map> myExcludeRootsMap = new THashMap>(); + final Set myProjectExcludeRoots = new THashSet(); + final Map myDirToInfoMap = new THashMap(); + final Map> myPackageNameToDirsMap = new THashMap>(); + final Map myDirToPackageName = new THashMap(); + + public IndexState() { + } + + DirectoryInfo getOrCreateDirInfo(VirtualFile dir) { + DirectoryInfo info = myDirToInfoMap.get(dir); + if (info == null) { + info = new DirectoryInfo(); + myDirToInfoMap.put(dir, info); + } + return info; + } + + private void fillMapWithModuleContent(VirtualFile dir, Module module, VirtualFile contentRoot) { + if (isExcluded(contentRoot, dir)) return; + if (isIgnored(dir)) return; + + DirectoryInfo info = getOrCreateDirInfo(dir); + + if (info.module != null) { // module contents overlap + DirectoryInfo parentInfo = myDirToInfoMap.get(dir.getParent()); + if (parentInfo == null || !info.module.equals(parentInfo.module)) return; // content of another module is below this module's content + } + + VirtualFile[] children = dir.getChildren(); + for (VirtualFile child : children) { + if (child.isDirectory()) { + fillMapWithModuleContent(child, module, contentRoot); + } + } + + // important to change module AFTER processing children - to handle overlapping modules + info.module = module; + info.contentRoot = contentRoot; + } + + private boolean isExcluded(VirtualFile root, VirtualFile dir) { + Set excludes = myExcludeRootsMap.get(root); + return excludes != null && excludes.contains(dir.getUrl()); + } + + private void initModuleContents(Module module, boolean reverseAllSets, ProgressIndicator progress) { + progress.checkCanceled(); + progress.setText2(ProjectBundle.message("project.index.processing.module.content.progress", module.getName())); + + ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + VirtualFile[] contentRoots = rootManager.getContentRoots(); + if (reverseAllSets) { + contentRoots = ArrayUtil.reverseArray(contentRoots); + } + + for (final VirtualFile contentRoot : contentRoots) { + fillMapWithModuleContent(contentRoot, module, contentRoot); + } + } + + private void initModuleSources(Module module, boolean reverseAllSets, ProgressIndicator progress) { + progress.checkCanceled(); + progress.setText2(ProjectBundle.message("project.index.processing.module.sources.progress", module.getName())); + + ContentEntry[] contentEntries = getContentEntries(module); + + if (reverseAllSets) { + contentEntries = ArrayUtil.reverseArray(contentEntries); + } + + for (ContentEntry contentEntry : contentEntries) { + SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); + if (reverseAllSets) { + sourceFolders = ArrayUtil.reverseArray(sourceFolders); + } + for (SourceFolder sourceFolder : sourceFolders) { + VirtualFile dir = sourceFolder.getFile(); + if (dir != null) { + fillMapWithModuleSource(dir, module, sourceFolder.getPackagePrefix(), dir, sourceFolder.isTestSource()); + } + } + } + } + + private void fillMapWithModuleSource(VirtualFile dir, Module module, String packageName, VirtualFile sourceRoot, boolean isTestSource) { + DirectoryInfo info = myDirToInfoMap.get(dir); + if (info == null) return; + if (!module.equals(info.module)) return; + + if (info.isInModuleSource) { // module sources overlap + String definedPackage = myDirToPackageName.get(dir); + if (definedPackage != null && definedPackage.length() == 0) return; // another source root starts here + } + + info.isInModuleSource = true; + info.isTestSource = isTestSource; + info.sourceRoot = sourceRoot; + setPackageName(dir, packageName); + + VirtualFile[] children = dir.getChildren(); + for (VirtualFile child : children) { + if (child.isDirectory()) { + String childPackageName = getPackageNameForSubdir(packageName, child.getName()); + fillMapWithModuleSource(child, module, childPackageName, sourceRoot, isTestSource); + } + } + } + + private void initLibrarySources(Module module, ProgressIndicator progress) { + progress.checkCanceled(); + progress.setText2(ProjectBundle.message("project.index.processing.library.sources.progress", module.getName())); + + for (OrderEntry orderEntry : getOrderEntries(module)) { + boolean isLibrary = orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry; + if (isLibrary) { + VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); + for (final VirtualFile sourceRoot : sourceRoots) { + fillMapWithLibrarySources(sourceRoot, "", sourceRoot); + } + } + } + } + + private void fillMapWithLibrarySources(VirtualFile dir, String packageName, VirtualFile sourceRoot) { + if (isIgnored(dir)) return; + + DirectoryInfo info = getOrCreateDirInfo(dir); + + if (info.isInLibrarySource) { // library sources overlap + String definedPackage = myDirToPackageName.get(dir); + if (definedPackage != null && definedPackage.length() == 0) return; // another library source root starts here + } + + info.isInModuleSource = false; + info.isInLibrarySource = true; + info.sourceRoot = sourceRoot; + setPackageName(dir, packageName); + + VirtualFile[] children = dir.getChildren(); + for (VirtualFile child : children) { + if (child.isDirectory()) { + String childPackageName = getPackageNameForSubdir(packageName, child.getName()); + fillMapWithLibrarySources(child, childPackageName, sourceRoot); + } + } + } + + private void initLibraryClasses(Module module, ProgressIndicator progress) { + progress.checkCanceled(); + progress.setText2(ProjectBundle.message("project.index.processing.library.classes.progress", module.getName())); + + for (OrderEntry orderEntry : getOrderEntries(module)) { + boolean isLibrary = orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry; + if (isLibrary) { + VirtualFile[] classRoots = orderEntry.getFiles(OrderRootType.CLASSES); + for (final VirtualFile classRoot : classRoots) { + fillMapWithLibraryClasses(classRoot, "", classRoot); + } + } + } + } + + private void fillMapWithLibraryClasses(VirtualFile dir, String packageName, VirtualFile classRoot) { + if (isIgnored(dir)) return; + + DirectoryInfo info = getOrCreateDirInfo(dir); + + if (info.libraryClassRoot != null) { // library classes overlap + String definedPackage = myDirToPackageName.get(dir); + if (definedPackage != null && definedPackage.length() == 0) return; // another library root starts here + } + + info.libraryClassRoot = classRoot; + + if (!info.isInModuleSource && !info.isInLibrarySource) { + setPackageName(dir, packageName); + } + + VirtualFile[] children = dir.getChildren(); + for (VirtualFile child : children) { + if (child.isDirectory()) { + String childPackageName = getPackageNameForSubdir(packageName, child.getName()); + fillMapWithLibraryClasses(child, childPackageName, classRoot); + } + } + } + + private void initOrderEntries(Module module) { + Map> depEntries = new HashMap>(); + Map> libClassRootEntries = new HashMap>(); + Map> libSourceRootEntries = new HashMap>(); + + for (OrderEntry orderEntry : getOrderEntries(module)) { + if (orderEntry instanceof ModuleOrderEntry) { + final Module depModule = ((ModuleOrderEntry)orderEntry).getModule(); + if (depModule != null) { + VirtualFile[] importedClassRoots = + OrderEnumerator.orderEntries(depModule).exportedOnly().recursively().classes().usingCache().getRoots(); + for (VirtualFile importedClassRoot : importedClassRoots) { + addEntryToMap(importedClassRoot, orderEntry, depEntries); + } + } + VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); + for (VirtualFile sourceRoot : sourceRoots) { + addEntryToMap(sourceRoot, orderEntry, depEntries); + } + } + else if (orderEntry instanceof ModuleSourceOrderEntry) { + List oneEntryList = Arrays.asList(orderEntry); + Module entryModule = orderEntry.getOwnerModule(); + + VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); + for (VirtualFile sourceRoot : sourceRoots) { + fillMapWithOrderEntries(sourceRoot, oneEntryList, entryModule, null, null, null, null); + } + } + else if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { + VirtualFile[] classRoots = orderEntry.getFiles(OrderRootType.CLASSES); + for (VirtualFile classRoot : classRoots) { + addEntryToMap(classRoot, orderEntry, libClassRootEntries); + } + VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES); + for (VirtualFile sourceRoot : sourceRoots) { + addEntryToMap(sourceRoot, orderEntry, libSourceRootEntries); + } + } + } + + for (Map.Entry> mapEntry : depEntries.entrySet()) { + final VirtualFile vRoot = mapEntry.getKey(); + final List entries = mapEntry.getValue(); + fillMapWithOrderEntries(vRoot, entries, null, null, null, null, null); + } + + for (Map.Entry> mapEntry : libClassRootEntries.entrySet()) { + final VirtualFile vRoot = mapEntry.getKey(); + final List entries = mapEntry.getValue(); + fillMapWithOrderEntries(vRoot, entries, null, vRoot, null, null, null); + } + + for (Map.Entry> mapEntry : libSourceRootEntries.entrySet()) { + final VirtualFile vRoot = mapEntry.getKey(); + final List entries = mapEntry.getValue(); + fillMapWithOrderEntries(vRoot, entries, null, null, vRoot, null, null); + } + } + + private void setPackageName(VirtualFile dir, String newPackageName) { + assert dir != null; + + String oldPackageName = myDirToPackageName.get(dir); + if (oldPackageName != null) { + List oldPackageDirs = myPackageNameToDirsMap.get(oldPackageName); + final boolean removed = oldPackageDirs.remove(dir); + assert removed; + + if (oldPackageDirs.size() == 0) { + myPackageNameToDirsMap.remove(oldPackageName); + } + } + + if (newPackageName != null) { + List newPackageDirs = myPackageNameToDirsMap.get(newPackageName); + if (newPackageDirs == null) { + newPackageDirs = new SmartList(); + myPackageNameToDirsMap.put(newPackageName, newPackageDirs); + } + newPackageDirs.add(dir); + + myDirToPackageName.put(dir, newPackageName); + } + else { + myDirToPackageName.remove(dir); + } + } + + private void addEntryToMap(final VirtualFile vRoot, final OrderEntry entry, final Map> map) { + List list = map.get(vRoot); + if (list == null) { + list = new ArrayList(); + map.put(vRoot, list); + } + list.add(entry); + } + + private void fillMapWithOrderEntries(VirtualFile dir, + List orderEntries, + Module module, + VirtualFile libraryClassRoot, + VirtualFile librarySourceRoot, + DirectoryInfo parentInfo, + final List oldParentEntries) { + if (isIgnored(dir)) return; + + DirectoryInfo info = myDirToInfoMap.get(dir); // do not create it here! + if (info == null) return; + + if (module != null) { + if (info.module != module) return; + if (!info.isInModuleSource) return; + } + else if (libraryClassRoot != null) { + if (info.libraryClassRoot != libraryClassRoot) return; + if (info.isInModuleSource) return; + } + else if (librarySourceRoot != null) { + if (!info.isInLibrarySource) return; + if (info.sourceRoot != librarySourceRoot) return; + if (info.libraryClassRoot != null) return; + } + + final List oldEntries = info.getOrderEntries(); + info.addOrderEntries(orderEntries, parentInfo, oldParentEntries); + + final VirtualFile[] children = dir.getChildren(); + for (VirtualFile child : children) { + if (child.isDirectory()) { + fillMapWithOrderEntries(child, orderEntries, module, libraryClassRoot, librarySourceRoot, info, oldEntries); + } + } + } + + private void doInitialize(boolean reverseAllSets/* for testing order independence*/) { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress == null) progress = new EmptyProgressIndicator(); + + progress.pushState(); + + progress.checkCanceled(); + progress.setText(ProjectBundle.message("project.index.scanning.files.progress")); + + Module[] modules = ModuleManager.getInstance(myProject).getModules(); + if (reverseAllSets) modules = ArrayUtil.reverseArray(modules); + + initExcludedDirMap(modules, progress); + + for (Module module : modules) { + initModuleContents(module, reverseAllSets, progress); + } + // Important! Because module's contents may overlap, + // first modules should be marked and only after that sources markup + // should be added. (src markup depends on module markup) + for (Module module : modules) { + initModuleSources(module, reverseAllSets, progress); + initLibrarySources(module, progress); + initLibraryClasses(module, progress); + } + + progress.checkCanceled(); + progress.setText2(""); + + for (Module module : modules) { + initOrderEntries(module); + } + + progress.popState(); + } + + private void initExcludedDirMap(Module[] modules, ProgressIndicator progress) { + progress.checkCanceled(); + progress.setText2(ProjectBundle.message("project.index.building.exclude.roots.progress")); + + // exclude roots should be merged to prevent including excluded dirs of an inner module into the outer + // exclude root should exclude from its content root and all outer content roots + + for (Module module : modules) { + for (ContentEntry contentEntry : getContentEntries(module)) { + VirtualFile contentRoot = contentEntry.getFile(); + if (contentRoot == null) continue; + + ExcludeFolder[] excludeRoots = contentEntry.getExcludeFolders(); + for (ExcludeFolder excludeRoot : excludeRoots) { + // Output paths should be excluded (if marked as such) regardless if they're under corresponding module's content root + if (excludeRoot.getFile() != null) { + if (!FileUtil.startsWith(contentRoot.getUrl(), excludeRoot.getUrl())) { + if (isExcludeRootForModule(module, excludeRoot.getFile())) { + putForFileAndAllAncestors(myExcludeRootsMap, excludeRoot.getFile(), excludeRoot.getUrl()); + } + } + } + + putForFileAndAllAncestors(myExcludeRootsMap, contentRoot, excludeRoot.getUrl()); + } + } + } + + for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { + for (VirtualFile file : policy.getExcludeRootsForProject()) { + putForFileAndAllAncestors(myExcludeRootsMap, file, file.getUrl()); + myProjectExcludeRoots.add(file); + } + } + } + + private void putForFileAndAllAncestors(Map> map, VirtualFile file, String value) { + while (true) { + Set set = map.get(file); + if (set == null) { + set = new HashSet(); + map.put(file, set); + } + set.add(value); + + file = file.getParent(); + if (file == null) break; + } + } + + private void addDirsRecursively(ArrayList list, VirtualFile dir) { + if (!myDirToInfoMap.containsKey(dir) || !(dir instanceof NewVirtualFile)) return; + + list.add(dir); + + for (VirtualFile child : ((NewVirtualFile)dir).getCachedChildren()) { + if (child.isDirectory()) { + addDirsRecursively(list, child); + } + } + } + + public IndexState copy() { + IndexState copy = new IndexState(); + + for (Map.Entry> entry : myExcludeRootsMap.entrySet()) { + copy.myExcludeRootsMap.put(entry.getKey(), new HashSet(entry.getValue())); + } + + copy.myProjectExcludeRoots.addAll(myProjectExcludeRoots); + copy.myDirToInfoMap.putAll(myDirToInfoMap); + + for (Map.Entry> entry : myPackageNameToDirsMap.entrySet()) { + copy.myPackageNameToDirsMap.put(entry.getKey(), new SmartList(entry.getValue())); + } + + copy.myDirToPackageName.putAll(myDirToPackageName); + + return copy; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryEx.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryEx.java index da79292cd10b..54ac21a3ffd3 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryEx.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryEx.java @@ -1,46 +1,46 @@ -/* - * 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.roots.impl.libraries; - -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.impl.RootModelImpl; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryProperties; -import com.intellij.openapi.roots.libraries.LibraryType; - -import java.util.List; - -/** - * @author dsl - */ -public interface LibraryEx extends Library { - Library cloneLibrary(RootModelImpl rootModel); - - List getInvalidRootUrls(OrderRootType type); - - boolean isDisposed(); - - LibraryType getType(); - - LibraryProperties getProperties(); - - interface ModifiableModelEx extends ModifiableModel { - void setProperties(LibraryProperties properties); - - LibraryProperties getProperties(); - } -} +/* + * 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.roots.impl.libraries; + +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.impl.RootModelImpl; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryProperties; +import com.intellij.openapi.roots.libraries.LibraryType; + +import java.util.List; + +/** + * @author dsl + */ +public interface LibraryEx extends Library { + Library cloneLibrary(RootModelImpl rootModel); + + List getInvalidRootUrls(OrderRootType type); + + boolean isDisposed(); + + LibraryType getType(); + + LibraryProperties getProperties(); + + interface ModifiableModelEx extends ModifiableModel { + void setProperties(LibraryProperties properties); + + LibraryProperties getProperties(); + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java index 5b96bbacae00..5af2d67121e0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java @@ -1,639 +1,639 @@ -/* - * 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.roots.impl.libraries; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.RootProvider; -import com.intellij.openapi.roots.impl.RootModelImpl; -import com.intellij.openapi.roots.impl.RootProviderBaseImpl; -import com.intellij.openapi.roots.libraries.*; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.events.*; -import com.intellij.openapi.vfs.pointers.VirtualFilePointer; -import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer; -import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ReflectionUtil; -import com.intellij.util.SmartList; -import com.intellij.util.containers.HashMap; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters; -import com.intellij.util.xmlb.XmlSerializer; -import gnu.trove.THashSet; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author dsl - */ -public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.impl.LibraryImpl"); - @NonNls public static final String LIBRARY_NAME_ATTR = "name"; - @NonNls private static final String LIBRARY_TYPE_ATTR = "type"; - @NonNls private static final String ROOT_PATH_ELEMENT = "root"; - @NonNls public static final String ELEMENT = "library"; - @NonNls private static final String JAR_DIRECTORY_ELEMENT = "jarDirectory"; - @NonNls private static final String URL_ATTR = "url"; - @NonNls private static final String RECURSIVE_ATTR = "recursive"; - @NonNls private static final String PROPERTIES_ELEMENT = "properties"; - private static final SkipDefaultValuesSerializationFilters SERIALIZATION_FILTERS = new SkipDefaultValuesSerializationFilters(); - private String myName; - private final LibraryTable myLibraryTable; - private final Map myRoots; - private final Map myJarDirectories = new HashMap(); - private final List myWatchRequests = new ArrayList(); - private final LibraryImpl mySource; - private LibraryType myType; - private LibraryProperties myProperties; - - private final MyRootProviderImpl myRootProvider = new MyRootProviderImpl(); - private final ModifiableRootModel myRootModel; - private MessageBusConnection myBusConnection = null; - private boolean myDisposed; - private final Disposable myPointersDisposable = Disposer.newDisposable(); - - LibraryImpl(LibraryTable table, Element element, ModifiableRootModel rootModel) throws InvalidDataException { - myLibraryTable = table; - myRootModel = rootModel; - mySource = null; - readName(element); - readProperties(element); - readJarDirectories(element); - //init roots depends on my hashcode, hashcode depends on jardirectories and name - myRoots = initRoots(); - readRoots(element); - updateWatchedRoots(); - } - - LibraryImpl(String name, final @Nullable LibraryType type, LibraryTable table, ModifiableRootModel rootModel) { - myName = name; - myLibraryTable = table; - myRootModel = rootModel; - myType = type; - if (type != null) { - myProperties = type.createDefaultProperties(); - } - myRoots = initRoots(); - mySource = null; - } - - private LibraryImpl(LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) { - assert !from.isDisposed(); - myRootModel = rootModel; - myName = from.myName; - myType = from.myType; - if (from.myType != null && from.myProperties != null) { - myProperties = myType.createDefaultProperties(); - //noinspection unchecked - myProperties.loadState(from.myProperties.getState()); - } - myRoots = initRoots(); - mySource = newSource; - myLibraryTable = from.myLibraryTable; - for (OrderRootType rootType : OrderRootType.getAllTypes()) { - final VirtualFilePointerContainer thisContainer = myRoots.get(rootType); - final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType); - thisContainer.addAll(thatContainer); - } - myJarDirectories.putAll(from.myJarDirectories); - } - - public void dispose() { - assert !isDisposed(); - if (!myWatchRequests.isEmpty()) { - LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests); - myWatchRequests.clear(); - } - if (myBusConnection != null) { - myBusConnection.disconnect(); - myBusConnection = null; - } - myDisposed = true; - } - - public boolean isDisposed() { - return myDisposed; - } - - public String getName() { - return myName; - } - - @NotNull - public String[] getUrls(@NotNull OrderRootType rootType) { - assert !isDisposed(); - final VirtualFilePointerContainer result = myRoots.get(rootType); - return result.getUrls(); - } - - @NotNull - public VirtualFile[] getFiles(@NotNull OrderRootType rootType) { - assert !isDisposed(); - final List expanded = new ArrayList(); - for (VirtualFile file : myRoots.get(rootType).getFiles()) { - if (file.isDirectory()) { - final Boolean expandRecursively = myJarDirectories.get(file.getUrl()); - if (expandRecursively != null) { - collectJarFiles(file, expanded, expandRecursively.booleanValue()); - continue; - } - } - expanded.add(file); - } - return VfsUtil.toVirtualFileArray(expanded); - } - - public static void collectJarFiles(final VirtualFile dir, final List container, final boolean recursively) { - for (VirtualFile child : dir.getChildren()) { - final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(child); - if (jarRoot != null) { - container.add(jarRoot); - } - else { - if (recursively && child.isDirectory()) { - collectJarFiles(child, container, recursively); - } - } - } - } - - public void setName(String name) { - LOG.assertTrue(isWritable()); - myName = name; - } - - /* you have to commit modifiable model or dispose it by yourself! */ - @NotNull - public ModifiableModel getModifiableModel() { - assert !isDisposed(); - return new LibraryImpl(this, this, myRootModel); - } - - public Library cloneLibrary(RootModelImpl rootModel) { - LOG.assertTrue(myLibraryTable == null); - final LibraryImpl clone = new LibraryImpl(this, null, rootModel); - clone.updateWatchedRoots(); - return clone; - } - - public List getInvalidRootUrls(OrderRootType type) { - final List pointers = myRoots.get(type).getList(); - List invalidPaths = null; - for (VirtualFilePointer pointer : pointers) { - if (!pointer.isValid()) { - if (invalidPaths == null) { - invalidPaths = new SmartList(); - } - invalidPaths.add(pointer.getUrl()); - } - } - return invalidPaths != null ? invalidPaths : Collections.emptyList(); - } - - @Override - public void setProperties(LibraryProperties properties) { - LOG.assertTrue(isWritable()); - myProperties = properties; - } - - @NotNull - public RootProvider getRootProvider() { - return myRootProvider; - } - - private Map initRoots() { - Disposer.register(this, myPointersDisposable); - - Map result = new HashMap(5); - - for (OrderRootType rootType : OrderRootType.getAllTypes()) { - result.put(rootType, VirtualFilePointerManager.getInstance().createContainer(myPointersDisposable)); - } - result.put(OrderRootType.COMPILATION_CLASSES, result.get(OrderRootType.CLASSES)); - result.put(OrderRootType.PRODUCTION_COMPILATION_CLASSES, result.get(OrderRootType.CLASSES)); - result.put(OrderRootType.CLASSES_AND_OUTPUT, result.get(OrderRootType.CLASSES)); - - return result; - } - - public void readExternal(Element element) throws InvalidDataException { - readName(element); - readProperties(element); - readRoots(element); - readJarDirectories(element); - updateWatchedRoots(); - } - - private void readProperties(Element element) { - final String typeId = element.getAttributeValue(LIBRARY_TYPE_ATTR); - if (typeId == null) return; - - myType = LibraryTypeRegistry.getInstance().findTypeById(typeId); - if (myType == null) return; - - myProperties = myType.createDefaultProperties(); - final Element propertiesElement = element.getChild(PROPERTIES_ELEMENT); - if (propertiesElement != null) { - final Class stateClass = ReflectionUtil.getRawType(ReflectionUtil.resolveVariableInHierarchy(PersistentStateComponent.class.getTypeParameters()[0], myProperties.getClass())); - //noinspection unchecked - myProperties.loadState(XmlSerializer.deserialize(propertiesElement, stateClass)); - } - } - - private void readName(Element element) { - myName = element.getAttributeValue(LIBRARY_NAME_ATTR); - } - - private void readRoots(Element element) throws InvalidDataException { - for (OrderRootType rootType : OrderRootType.getAllTypes()) { - final Element rootChild = element.getChild(rootType.name()); - if (rootChild == null) { - continue; - } - VirtualFilePointerContainer roots = myRoots.get(rootType); - roots.readExternal(rootChild, ROOT_PATH_ELEMENT); - } - } - - private void readJarDirectories(Element element) { - myJarDirectories.clear(); - final List jarDirs = element.getChildren(JAR_DIRECTORY_ELEMENT); - for (Object item : jarDirs) { - final Element jarDir = (Element)item; - final String url = jarDir.getAttributeValue(URL_ATTR); - final String recursive = jarDir.getAttributeValue(RECURSIVE_ATTR); - if (url != null) { - myJarDirectories.put(url, Boolean.valueOf(Boolean.parseBoolean(recursive))); - } - } - } - - - public void writeExternal(Element rootElement) { - LOG.assertTrue(!isDisposed(), "Already disposed!"); - - Element element = new Element(ELEMENT); - if (myName != null) { - element.setAttribute(LIBRARY_NAME_ATTR, myName); - } - if (myType != null) { - element.setAttribute(LIBRARY_TYPE_ATTR, myType.getKind().getKindId()); - final Object state = myProperties.getState(); - if (state != null) { - final Element propertiesElement = XmlSerializer.serialize(state, SERIALIZATION_FILTERS); - if (propertiesElement != null) { - element.addContent(propertiesElement.setName(PROPERTIES_ELEMENT)); - } - } - } - for (OrderRootType rootType : OrderRootType.getSortedRootTypes()) { - final VirtualFilePointerContainer roots = myRoots.get(rootType); - if (roots.size() == 0 && rootType.skipWriteIfEmpty()) continue; //compatibility iml/ipr - final Element rootTypeElement = new Element(rootType.name()); - roots.writeExternal(rootTypeElement, ROOT_PATH_ELEMENT); - element.addContent(rootTypeElement); - } - List urls = new ArrayList(myJarDirectories.keySet()); - Collections.sort(urls, new Comparator() { - public int compare(final String url1, final String url2) { - return url1.compareToIgnoreCase(url2); - } - }); - for (String url : urls) { - final Element jarDirElement = new Element(JAR_DIRECTORY_ELEMENT); - jarDirElement.setAttribute(URL_ATTR, url); - jarDirElement.setAttribute(RECURSIVE_ATTR, myJarDirectories.get(url).toString()); - element.addContent(jarDirElement); - } - rootElement.addContent(element); - } - - private boolean isWritable() { - return mySource != null; - } - - @Override - public LibraryType getType() { - return myType; - } - - @Override - public LibraryProperties getProperties() { - return myProperties; - } - - public void addRoot(@NotNull String url, @NotNull OrderRootType rootType) { - LOG.assertTrue(isWritable()); - assert !isDisposed(); - - final VirtualFilePointerContainer container = myRoots.get(rootType); - container.add(url); - } - - public void addRoot(@NotNull VirtualFile file, @NotNull OrderRootType rootType) { - LOG.assertTrue(isWritable()); - assert !isDisposed(); - - final VirtualFilePointerContainer container = myRoots.get(rootType); - container.add(file); - } - - public void addJarDirectory(@NotNull final String url, final boolean recursive) { - assert !isDisposed(); - LOG.assertTrue(isWritable()); - final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES); - container.add(url); - myJarDirectories.put(url, Boolean.valueOf(recursive)); - } - - public void addJarDirectory(@NotNull final VirtualFile file, final boolean recursive) { - assert !isDisposed(); - LOG.assertTrue(isWritable()); - final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES); - container.add(file); - myJarDirectories.put(file.getUrl(), Boolean.valueOf(recursive)); - } - - public boolean isJarDirectory(@NotNull final String url) { - return myJarDirectories.containsKey(url); - } - - public boolean isValid(@NotNull final String url, @NotNull final OrderRootType rootType) { - final VirtualFilePointerContainer container = myRoots.get(rootType); - final VirtualFilePointer fp = container.findByUrl(url); - return fp != null && fp.isValid(); - } - - public boolean removeRoot(@NotNull String url, @NotNull OrderRootType rootType) { - assert !isDisposed(); - LOG.assertTrue(isWritable()); - final VirtualFilePointerContainer container = myRoots.get(rootType); - final VirtualFilePointer byUrl = container.findByUrl(url); - if (byUrl != null) { - container.remove(byUrl); - myJarDirectories.remove(url); - return true; - } - return false; - } - - public void moveRootUp(@NotNull String url, @NotNull OrderRootType rootType) { - assert !isDisposed(); - LOG.assertTrue(isWritable()); - final VirtualFilePointerContainer container = myRoots.get(rootType); - container.moveUp(url); - } - - public void moveRootDown(@NotNull String url, @NotNull OrderRootType rootType) { - assert !isDisposed(); - LOG.assertTrue(isWritable()); - final VirtualFilePointerContainer container = myRoots.get(rootType); - container.moveDown(url); - } - - public boolean isChanged() { - return !mySource.equals(this); - } - - private boolean areRootsChanged(final LibraryImpl that) { - return !that.equals(this); - //final OrderRootType[] allTypes = OrderRootType.getAllTypes(); - //for (OrderRootType type : allTypes) { - // final String[] urls = getUrls(type); - // final String[] thatUrls = that.getUrls(type); - // if (urls.length != thatUrls.length) { - // return true; - // } - // for (int idx = 0; idx < urls.length; idx++) { - // final String url = urls[idx]; - // final String thatUrl = thatUrls[idx]; - // if (!Comparing.equal(url, thatUrl)) { - // return true; - // } - // final Boolean jarDirRecursive = myJarDirectories.get(url); - // final Boolean sourceJarDirRecursive = that.myJarDirectories.get(thatUrl); - // if (jarDirRecursive == null ? sourceJarDirRecursive != null : !jarDirRecursive.equals(sourceJarDirRecursive)) { - // return true; - // } - // } - //} - //return false; - } - - public Library getSource() { - return mySource; - } - - public void commit() { - assert !isDisposed(); - mySource.commit(this); - Disposer.dispose(this); - } - - private void commit(@NotNull LibraryImpl fromModel) { - if (myLibraryTable != null) { - ApplicationManager.getApplication().assertWriteAccessAllowed(); - } - if (!Comparing.equal(fromModel.myName, myName)) { - myName = fromModel.myName; - if (myLibraryTable instanceof LibraryTableBase) { - ((LibraryTableBase)myLibraryTable).fireLibraryRenamed(this); - } - } - myProperties = fromModel.myProperties; - if (areRootsChanged(fromModel)) { - disposeMyPointers(); - copyRootsFrom(fromModel); - myJarDirectories.clear(); - myJarDirectories.putAll(fromModel.myJarDirectories); - updateWatchedRoots(); - myRootProvider.fireRootSetChanged(); - } - } - - private void copyRootsFrom(LibraryImpl fromModel) { - myRoots.clear(); - for (Map.Entry entry : fromModel.myRoots.entrySet()) { - OrderRootType rootType = entry.getKey(); - VirtualFilePointerContainer container = entry.getValue(); - VirtualFilePointerContainer clone = container.clone(myPointersDisposable); - myRoots.put(rootType, clone); - } - } - - private void disposeMyPointers() { - for (VirtualFilePointerContainer container : new THashSet(myRoots.values())) { - container.killAll(); - } - Disposer.dispose(myPointersDisposable); - Disposer.register(this, myPointersDisposable); - } - - private void updateWatchedRoots() { - final LocalFileSystem fs = LocalFileSystem.getInstance(); - if (!myWatchRequests.isEmpty()) { - fs.removeWatchedRoots(myWatchRequests); - myWatchRequests.clear(); - } - if (!myJarDirectories.isEmpty()) { - final VirtualFileManager fm = VirtualFileManager.getInstance(); - for (Map.Entry entry : myJarDirectories.entrySet()) { - String url = entry.getKey(); - if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) { - final boolean watchRecursively = entry.getValue().booleanValue(); - final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively); - myWatchRequests.add(request); - } - } - if (myBusConnection == null) { - myBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); - myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { - public void before(final List events) { - } - - public void after(final List events) { - boolean changesDetected = false; - for (VFileEvent event : events) { - if (event instanceof VFileCopyEvent) { - final VFileCopyEvent copyEvent = (VFileCopyEvent)event; - if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) || - isUnderJarDirectory(copyEvent.getFile().getUrl())) { - changesDetected = true; - break; - } - } - else if (event instanceof VFileMoveEvent) { - final VFileMoveEvent moveEvent = (VFileMoveEvent)event; - - final VirtualFile file = moveEvent.getFile(); - if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) { - changesDetected = true; - break; - } - } - else if (event instanceof VFileDeleteEvent) { - final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; - if (isUnderJarDirectory(deleteEvent.getFile().getUrl())) { - changesDetected = true; - break; - } - } - else if (event instanceof VFileCreateEvent) { - final VFileCreateEvent createEvent = (VFileCreateEvent)event; - if (isUnderJarDirectory(createEvent.getParent().getUrl() + "/" + createEvent.getChildName())) { - changesDetected = true; - break; - } - } - } - - if (changesDetected) { - myRootProvider.fireRootSetChanged(); - } - } - - private boolean isUnderJarDirectory(String url) { - for (String rootUrl : myJarDirectories.keySet()) { - if (FileUtil.startsWith(url, rootUrl)) { - return true; - } - } - return false; - } - }); - } - } - else { - final MessageBusConnection connection = myBusConnection; - if (connection != null) { - myBusConnection = null; - connection.disconnect(); - } - } - } - - private class MyRootProviderImpl extends RootProviderBaseImpl { - @NotNull - public String[] getUrls(@NotNull OrderRootType rootType) { - Set originalUrls = new LinkedHashSet(Arrays.asList(LibraryImpl.this.getUrls(rootType))); - for (VirtualFile file : getFiles(rootType)) { // Add those expanded with jar directories. - originalUrls.add(file.getUrl()); - } - return ArrayUtil.toStringArray(originalUrls); - } - - @NotNull - public VirtualFile[] getFiles(@NotNull final OrderRootType rootType) { - return LibraryImpl.this.getFiles(rootType); - } - - public void fireRootSetChanged() { - super.fireRootSetChanged(); - } - } - - public LibraryTable getTable() { - return myLibraryTable; - } - - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final LibraryImpl library = (LibraryImpl)o; - - if (!myJarDirectories.equals(library.myJarDirectories)) return false; - if (myName != null ? !myName.equals(library.myName) : library.myName != null) return false; - if (myRoots != null ? !myRoots.equals(library.myRoots) : library.myRoots != null) return false; - if (myType != null ? !myType.equals(library.myType) : library.myType != null) return false; - if (myProperties != null ? !myProperties.equals(library.myProperties) : library.myProperties != null) return false; - - return true; - } - - public int hashCode() { - int result = myName != null ? myName.hashCode() : 0; - result = 31 * result + (myRoots != null ? myRoots.hashCode() : 0); - result = 31 * result + (myJarDirectories != null ? myJarDirectories.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "Library: name:" + myName + "; jars:" + myJarDirectories.keySet() + "; roots:" + myRoots.values(); - } - - @Nullable("will return non-null value only for module level libraries") - public Module getModule() { - return myRootModel == null ? null : myRootModel.getModule(); - } -} +/* + * 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.roots.impl.libraries; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.RootProvider; +import com.intellij.openapi.roots.impl.RootModelImpl; +import com.intellij.openapi.roots.impl.RootProviderBaseImpl; +import com.intellij.openapi.roots.libraries.*; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.*; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ReflectionUtil; +import com.intellij.util.SmartList; +import com.intellij.util.containers.HashMap; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters; +import com.intellij.util.xmlb.XmlSerializer; +import gnu.trove.THashSet; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author dsl + */ +public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.impl.LibraryImpl"); + @NonNls public static final String LIBRARY_NAME_ATTR = "name"; + @NonNls private static final String LIBRARY_TYPE_ATTR = "type"; + @NonNls private static final String ROOT_PATH_ELEMENT = "root"; + @NonNls public static final String ELEMENT = "library"; + @NonNls private static final String JAR_DIRECTORY_ELEMENT = "jarDirectory"; + @NonNls private static final String URL_ATTR = "url"; + @NonNls private static final String RECURSIVE_ATTR = "recursive"; + @NonNls private static final String PROPERTIES_ELEMENT = "properties"; + private static final SkipDefaultValuesSerializationFilters SERIALIZATION_FILTERS = new SkipDefaultValuesSerializationFilters(); + private String myName; + private final LibraryTable myLibraryTable; + private final Map myRoots; + private final Map myJarDirectories = new HashMap(); + private final List myWatchRequests = new ArrayList(); + private final LibraryImpl mySource; + private LibraryType myType; + private LibraryProperties myProperties; + + private final MyRootProviderImpl myRootProvider = new MyRootProviderImpl(); + private final ModifiableRootModel myRootModel; + private MessageBusConnection myBusConnection = null; + private boolean myDisposed; + private final Disposable myPointersDisposable = Disposer.newDisposable(); + + LibraryImpl(LibraryTable table, Element element, ModifiableRootModel rootModel) throws InvalidDataException { + myLibraryTable = table; + myRootModel = rootModel; + mySource = null; + readName(element); + readProperties(element); + readJarDirectories(element); + //init roots depends on my hashcode, hashcode depends on jardirectories and name + myRoots = initRoots(); + readRoots(element); + updateWatchedRoots(); + } + + LibraryImpl(String name, final @Nullable LibraryType type, LibraryTable table, ModifiableRootModel rootModel) { + myName = name; + myLibraryTable = table; + myRootModel = rootModel; + myType = type; + if (type != null) { + myProperties = type.createDefaultProperties(); + } + myRoots = initRoots(); + mySource = null; + } + + private LibraryImpl(LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) { + assert !from.isDisposed(); + myRootModel = rootModel; + myName = from.myName; + myType = from.myType; + if (from.myType != null && from.myProperties != null) { + myProperties = myType.createDefaultProperties(); + //noinspection unchecked + myProperties.loadState(from.myProperties.getState()); + } + myRoots = initRoots(); + mySource = newSource; + myLibraryTable = from.myLibraryTable; + for (OrderRootType rootType : OrderRootType.getAllTypes()) { + final VirtualFilePointerContainer thisContainer = myRoots.get(rootType); + final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType); + thisContainer.addAll(thatContainer); + } + myJarDirectories.putAll(from.myJarDirectories); + } + + public void dispose() { + assert !isDisposed(); + if (!myWatchRequests.isEmpty()) { + LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests); + myWatchRequests.clear(); + } + if (myBusConnection != null) { + myBusConnection.disconnect(); + myBusConnection = null; + } + myDisposed = true; + } + + public boolean isDisposed() { + return myDisposed; + } + + public String getName() { + return myName; + } + + @NotNull + public String[] getUrls(@NotNull OrderRootType rootType) { + assert !isDisposed(); + final VirtualFilePointerContainer result = myRoots.get(rootType); + return result.getUrls(); + } + + @NotNull + public VirtualFile[] getFiles(@NotNull OrderRootType rootType) { + assert !isDisposed(); + final List expanded = new ArrayList(); + for (VirtualFile file : myRoots.get(rootType).getFiles()) { + if (file.isDirectory()) { + final Boolean expandRecursively = myJarDirectories.get(file.getUrl()); + if (expandRecursively != null) { + collectJarFiles(file, expanded, expandRecursively.booleanValue()); + continue; + } + } + expanded.add(file); + } + return VfsUtil.toVirtualFileArray(expanded); + } + + public static void collectJarFiles(final VirtualFile dir, final List container, final boolean recursively) { + for (VirtualFile child : dir.getChildren()) { + final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(child); + if (jarRoot != null) { + container.add(jarRoot); + } + else { + if (recursively && child.isDirectory()) { + collectJarFiles(child, container, recursively); + } + } + } + } + + public void setName(String name) { + LOG.assertTrue(isWritable()); + myName = name; + } + + /* you have to commit modifiable model or dispose it by yourself! */ + @NotNull + public ModifiableModel getModifiableModel() { + assert !isDisposed(); + return new LibraryImpl(this, this, myRootModel); + } + + public Library cloneLibrary(RootModelImpl rootModel) { + LOG.assertTrue(myLibraryTable == null); + final LibraryImpl clone = new LibraryImpl(this, null, rootModel); + clone.updateWatchedRoots(); + return clone; + } + + public List getInvalidRootUrls(OrderRootType type) { + final List pointers = myRoots.get(type).getList(); + List invalidPaths = null; + for (VirtualFilePointer pointer : pointers) { + if (!pointer.isValid()) { + if (invalidPaths == null) { + invalidPaths = new SmartList(); + } + invalidPaths.add(pointer.getUrl()); + } + } + return invalidPaths != null ? invalidPaths : Collections.emptyList(); + } + + @Override + public void setProperties(LibraryProperties properties) { + LOG.assertTrue(isWritable()); + myProperties = properties; + } + + @NotNull + public RootProvider getRootProvider() { + return myRootProvider; + } + + private Map initRoots() { + Disposer.register(this, myPointersDisposable); + + Map result = new HashMap(5); + + for (OrderRootType rootType : OrderRootType.getAllTypes()) { + result.put(rootType, VirtualFilePointerManager.getInstance().createContainer(myPointersDisposable)); + } + result.put(OrderRootType.COMPILATION_CLASSES, result.get(OrderRootType.CLASSES)); + result.put(OrderRootType.PRODUCTION_COMPILATION_CLASSES, result.get(OrderRootType.CLASSES)); + result.put(OrderRootType.CLASSES_AND_OUTPUT, result.get(OrderRootType.CLASSES)); + + return result; + } + + public void readExternal(Element element) throws InvalidDataException { + readName(element); + readProperties(element); + readRoots(element); + readJarDirectories(element); + updateWatchedRoots(); + } + + private void readProperties(Element element) { + final String typeId = element.getAttributeValue(LIBRARY_TYPE_ATTR); + if (typeId == null) return; + + myType = LibraryTypeRegistry.getInstance().findTypeById(typeId); + if (myType == null) return; + + myProperties = myType.createDefaultProperties(); + final Element propertiesElement = element.getChild(PROPERTIES_ELEMENT); + if (propertiesElement != null) { + final Class stateClass = ReflectionUtil.getRawType(ReflectionUtil.resolveVariableInHierarchy(PersistentStateComponent.class.getTypeParameters()[0], myProperties.getClass())); + //noinspection unchecked + myProperties.loadState(XmlSerializer.deserialize(propertiesElement, stateClass)); + } + } + + private void readName(Element element) { + myName = element.getAttributeValue(LIBRARY_NAME_ATTR); + } + + private void readRoots(Element element) throws InvalidDataException { + for (OrderRootType rootType : OrderRootType.getAllTypes()) { + final Element rootChild = element.getChild(rootType.name()); + if (rootChild == null) { + continue; + } + VirtualFilePointerContainer roots = myRoots.get(rootType); + roots.readExternal(rootChild, ROOT_PATH_ELEMENT); + } + } + + private void readJarDirectories(Element element) { + myJarDirectories.clear(); + final List jarDirs = element.getChildren(JAR_DIRECTORY_ELEMENT); + for (Object item : jarDirs) { + final Element jarDir = (Element)item; + final String url = jarDir.getAttributeValue(URL_ATTR); + final String recursive = jarDir.getAttributeValue(RECURSIVE_ATTR); + if (url != null) { + myJarDirectories.put(url, Boolean.valueOf(Boolean.parseBoolean(recursive))); + } + } + } + + + public void writeExternal(Element rootElement) { + LOG.assertTrue(!isDisposed(), "Already disposed!"); + + Element element = new Element(ELEMENT); + if (myName != null) { + element.setAttribute(LIBRARY_NAME_ATTR, myName); + } + if (myType != null) { + element.setAttribute(LIBRARY_TYPE_ATTR, myType.getKind().getKindId()); + final Object state = myProperties.getState(); + if (state != null) { + final Element propertiesElement = XmlSerializer.serialize(state, SERIALIZATION_FILTERS); + if (propertiesElement != null) { + element.addContent(propertiesElement.setName(PROPERTIES_ELEMENT)); + } + } + } + for (OrderRootType rootType : OrderRootType.getSortedRootTypes()) { + final VirtualFilePointerContainer roots = myRoots.get(rootType); + if (roots.size() == 0 && rootType.skipWriteIfEmpty()) continue; //compatibility iml/ipr + final Element rootTypeElement = new Element(rootType.name()); + roots.writeExternal(rootTypeElement, ROOT_PATH_ELEMENT); + element.addContent(rootTypeElement); + } + List urls = new ArrayList(myJarDirectories.keySet()); + Collections.sort(urls, new Comparator() { + public int compare(final String url1, final String url2) { + return url1.compareToIgnoreCase(url2); + } + }); + for (String url : urls) { + final Element jarDirElement = new Element(JAR_DIRECTORY_ELEMENT); + jarDirElement.setAttribute(URL_ATTR, url); + jarDirElement.setAttribute(RECURSIVE_ATTR, myJarDirectories.get(url).toString()); + element.addContent(jarDirElement); + } + rootElement.addContent(element); + } + + private boolean isWritable() { + return mySource != null; + } + + @Override + public LibraryType getType() { + return myType; + } + + @Override + public LibraryProperties getProperties() { + return myProperties; + } + + public void addRoot(@NotNull String url, @NotNull OrderRootType rootType) { + LOG.assertTrue(isWritable()); + assert !isDisposed(); + + final VirtualFilePointerContainer container = myRoots.get(rootType); + container.add(url); + } + + public void addRoot(@NotNull VirtualFile file, @NotNull OrderRootType rootType) { + LOG.assertTrue(isWritable()); + assert !isDisposed(); + + final VirtualFilePointerContainer container = myRoots.get(rootType); + container.add(file); + } + + public void addJarDirectory(@NotNull final String url, final boolean recursive) { + assert !isDisposed(); + LOG.assertTrue(isWritable()); + final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES); + container.add(url); + myJarDirectories.put(url, Boolean.valueOf(recursive)); + } + + public void addJarDirectory(@NotNull final VirtualFile file, final boolean recursive) { + assert !isDisposed(); + LOG.assertTrue(isWritable()); + final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES); + container.add(file); + myJarDirectories.put(file.getUrl(), Boolean.valueOf(recursive)); + } + + public boolean isJarDirectory(@NotNull final String url) { + return myJarDirectories.containsKey(url); + } + + public boolean isValid(@NotNull final String url, @NotNull final OrderRootType rootType) { + final VirtualFilePointerContainer container = myRoots.get(rootType); + final VirtualFilePointer fp = container.findByUrl(url); + return fp != null && fp.isValid(); + } + + public boolean removeRoot(@NotNull String url, @NotNull OrderRootType rootType) { + assert !isDisposed(); + LOG.assertTrue(isWritable()); + final VirtualFilePointerContainer container = myRoots.get(rootType); + final VirtualFilePointer byUrl = container.findByUrl(url); + if (byUrl != null) { + container.remove(byUrl); + myJarDirectories.remove(url); + return true; + } + return false; + } + + public void moveRootUp(@NotNull String url, @NotNull OrderRootType rootType) { + assert !isDisposed(); + LOG.assertTrue(isWritable()); + final VirtualFilePointerContainer container = myRoots.get(rootType); + container.moveUp(url); + } + + public void moveRootDown(@NotNull String url, @NotNull OrderRootType rootType) { + assert !isDisposed(); + LOG.assertTrue(isWritable()); + final VirtualFilePointerContainer container = myRoots.get(rootType); + container.moveDown(url); + } + + public boolean isChanged() { + return !mySource.equals(this); + } + + private boolean areRootsChanged(final LibraryImpl that) { + return !that.equals(this); + //final OrderRootType[] allTypes = OrderRootType.getAllTypes(); + //for (OrderRootType type : allTypes) { + // final String[] urls = getUrls(type); + // final String[] thatUrls = that.getUrls(type); + // if (urls.length != thatUrls.length) { + // return true; + // } + // for (int idx = 0; idx < urls.length; idx++) { + // final String url = urls[idx]; + // final String thatUrl = thatUrls[idx]; + // if (!Comparing.equal(url, thatUrl)) { + // return true; + // } + // final Boolean jarDirRecursive = myJarDirectories.get(url); + // final Boolean sourceJarDirRecursive = that.myJarDirectories.get(thatUrl); + // if (jarDirRecursive == null ? sourceJarDirRecursive != null : !jarDirRecursive.equals(sourceJarDirRecursive)) { + // return true; + // } + // } + //} + //return false; + } + + public Library getSource() { + return mySource; + } + + public void commit() { + assert !isDisposed(); + mySource.commit(this); + Disposer.dispose(this); + } + + private void commit(@NotNull LibraryImpl fromModel) { + if (myLibraryTable != null) { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + } + if (!Comparing.equal(fromModel.myName, myName)) { + myName = fromModel.myName; + if (myLibraryTable instanceof LibraryTableBase) { + ((LibraryTableBase)myLibraryTable).fireLibraryRenamed(this); + } + } + myProperties = fromModel.myProperties; + if (areRootsChanged(fromModel)) { + disposeMyPointers(); + copyRootsFrom(fromModel); + myJarDirectories.clear(); + myJarDirectories.putAll(fromModel.myJarDirectories); + updateWatchedRoots(); + myRootProvider.fireRootSetChanged(); + } + } + + private void copyRootsFrom(LibraryImpl fromModel) { + myRoots.clear(); + for (Map.Entry entry : fromModel.myRoots.entrySet()) { + OrderRootType rootType = entry.getKey(); + VirtualFilePointerContainer container = entry.getValue(); + VirtualFilePointerContainer clone = container.clone(myPointersDisposable); + myRoots.put(rootType, clone); + } + } + + private void disposeMyPointers() { + for (VirtualFilePointerContainer container : new THashSet(myRoots.values())) { + container.killAll(); + } + Disposer.dispose(myPointersDisposable); + Disposer.register(this, myPointersDisposable); + } + + private void updateWatchedRoots() { + final LocalFileSystem fs = LocalFileSystem.getInstance(); + if (!myWatchRequests.isEmpty()) { + fs.removeWatchedRoots(myWatchRequests); + myWatchRequests.clear(); + } + if (!myJarDirectories.isEmpty()) { + final VirtualFileManager fm = VirtualFileManager.getInstance(); + for (Map.Entry entry : myJarDirectories.entrySet()) { + String url = entry.getKey(); + if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) { + final boolean watchRecursively = entry.getValue().booleanValue(); + final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively); + myWatchRequests.add(request); + } + } + if (myBusConnection == null) { + myBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); + myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + public void before(final List events) { + } + + public void after(final List events) { + boolean changesDetected = false; + for (VFileEvent event : events) { + if (event instanceof VFileCopyEvent) { + final VFileCopyEvent copyEvent = (VFileCopyEvent)event; + if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) || + isUnderJarDirectory(copyEvent.getFile().getUrl())) { + changesDetected = true; + break; + } + } + else if (event instanceof VFileMoveEvent) { + final VFileMoveEvent moveEvent = (VFileMoveEvent)event; + + final VirtualFile file = moveEvent.getFile(); + if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) { + changesDetected = true; + break; + } + } + else if (event instanceof VFileDeleteEvent) { + final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; + if (isUnderJarDirectory(deleteEvent.getFile().getUrl())) { + changesDetected = true; + break; + } + } + else if (event instanceof VFileCreateEvent) { + final VFileCreateEvent createEvent = (VFileCreateEvent)event; + if (isUnderJarDirectory(createEvent.getParent().getUrl() + "/" + createEvent.getChildName())) { + changesDetected = true; + break; + } + } + } + + if (changesDetected) { + myRootProvider.fireRootSetChanged(); + } + } + + private boolean isUnderJarDirectory(String url) { + for (String rootUrl : myJarDirectories.keySet()) { + if (FileUtil.startsWith(url, rootUrl)) { + return true; + } + } + return false; + } + }); + } + } + else { + final MessageBusConnection connection = myBusConnection; + if (connection != null) { + myBusConnection = null; + connection.disconnect(); + } + } + } + + private class MyRootProviderImpl extends RootProviderBaseImpl { + @NotNull + public String[] getUrls(@NotNull OrderRootType rootType) { + Set originalUrls = new LinkedHashSet(Arrays.asList(LibraryImpl.this.getUrls(rootType))); + for (VirtualFile file : getFiles(rootType)) { // Add those expanded with jar directories. + originalUrls.add(file.getUrl()); + } + return ArrayUtil.toStringArray(originalUrls); + } + + @NotNull + public VirtualFile[] getFiles(@NotNull final OrderRootType rootType) { + return LibraryImpl.this.getFiles(rootType); + } + + public void fireRootSetChanged() { + super.fireRootSetChanged(); + } + } + + public LibraryTable getTable() { + return myLibraryTable; + } + + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final LibraryImpl library = (LibraryImpl)o; + + if (!myJarDirectories.equals(library.myJarDirectories)) return false; + if (myName != null ? !myName.equals(library.myName) : library.myName != null) return false; + if (myRoots != null ? !myRoots.equals(library.myRoots) : library.myRoots != null) return false; + if (myType != null ? !myType.equals(library.myType) : library.myType != null) return false; + if (myProperties != null ? !myProperties.equals(library.myProperties) : library.myProperties != null) return false; + + return true; + } + + public int hashCode() { + int result = myName != null ? myName.hashCode() : 0; + result = 31 * result + (myRoots != null ? myRoots.hashCode() : 0); + result = 31 * result + (myJarDirectories != null ? myJarDirectories.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Library: name:" + myName + "; jars:" + myJarDirectories.keySet() + "; roots:" + myRoots.values(); + } + + @Nullable("will return non-null value only for module level libraries") + public Module getModule() { + return myRootModel == null ? null : myRootModel.getModule(); + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryManager.java b/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryManager.java index 4255a44d2b8a..921c06a29345 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryManager.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryManager.java @@ -1,221 +1,221 @@ -/* - * 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.roots.libraries.scripting; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.ex.ProjectRootManagerEx; -import com.intellij.openapi.roots.impl.libraries.LibraryTableBase; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; -import com.intellij.openapi.roots.libraries.LibraryType; -import com.intellij.openapi.util.EmptyRunnable; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.Nullable; - -/** - * @author Rustam Vishnyakov - */ -public class ScriptingLibraryManager { - - public enum LibraryLevel {GLOBAL, PROJECT} - - private Project myProject; - private LibraryLevel myLibLevel = LibraryLevel.PROJECT; - private ScriptingLibraryTable myLibTable; - private LibraryType myLibraryType; - - public ScriptingLibraryManager(Project project, LibraryType libraryType) { - this(LibraryLevel.GLOBAL, project, libraryType); - } - - public ScriptingLibraryManager(LibraryLevel libLevel, Project project, LibraryType libraryType) { - myProject = project; - myLibLevel = libLevel; - myLibraryType = libraryType; - } - - public ScriptingLibraryTable getScriptingLibraryTable() { - ensureModel(); - return myLibTable; - } - - public void commitChanges() { - if (myLibTable != null) { - LibraryTable libTable = getLibraryTable(); - if (libTable != null) { - LibraryTable.ModifiableModel libTableModel = libTable.getModifiableModel(); - updateLibraries(libTableModel, OrderRootType.SOURCES); - updateLibraries(libTableModel, OrderRootType.CLASSES); - libTableModel.commit(); - } - myLibTable = null; - } - updateOpenProjects(); - } - - private static void updateOpenProjects() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - for (Project project : ProjectManager.getInstance().getOpenProjects()) { - ProjectRootManagerEx.getInstanceEx(project).makeRootsChange(EmptyRunnable.getInstance(), false, true); - } - } - }); - } - - private void updateLibraries(LibraryTable.ModifiableModel libTableModel, OrderRootType rootType) { - for (Library library : libTableModel.getLibraries()) { - ScriptingLibraryTable.LibraryModel scriptingLibModel = myLibTable.getLibraryByName(library.getName()); - if (scriptingLibModel == null) { - libTableModel.removeLibrary(library); - } - else { - Library.ModifiableModel libModel = library.getModifiableModel(); - for (VirtualFile libRoot : libModel.getFiles(rootType)) { - libModel.removeRoot(libRoot.getUrl(), rootType); - } - for (VirtualFile newRoot : scriptingLibModel.getFiles(rootType)) { - libModel.addRoot(newRoot, rootType); - } - libModel.commit(); - } - } - for (ScriptingLibraryTable.LibraryModel scriptingLibModel : myLibTable.getLibraries()) { - Library library = libTableModel.getLibraryByName(scriptingLibModel.getName()); - if (library == null && libTableModel instanceof LibraryTableBase.ModifiableModelEx) { - library = ((LibraryTableBase.ModifiableModelEx)libTableModel).createLibrary(scriptingLibModel.getName(), myLibraryType); - Library.ModifiableModel libModel = library.getModifiableModel(); - for (VirtualFile newRoot : scriptingLibModel.getFiles(rootType)) { - libModel.addRoot(newRoot, rootType); - } - libModel.commit(); - } - } - } - - public void reset() { - myLibTable = null; - } - - @Nullable - public ScriptingLibraryTable.LibraryModel createLibrary(String name, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { - if (ensureModel()) { - return myLibTable.createLibrary(name, sourceFiles, compactFiles); - } - return null; - } - - @Nullable - public Library createSourceLibrary(String libName, String sourceUrl, LibraryLevel libraryLevel) { - LibraryTable libraryTable = getLibraryTable(myProject, libraryLevel); - if (libraryTable == null) return null; - LibraryTable.ModifiableModel libTableModel = libraryTable.getModifiableModel(); - if (libTableModel instanceof LibraryTableBase.ModifiableModelEx) { - Library library = ((LibraryTableBase.ModifiableModelEx)libTableModel).createLibrary(libName, myLibraryType); - if (library != null) { - Library.ModifiableModel libModel = library.getModifiableModel(); - libModel.addRoot(sourceUrl, OrderRootType.SOURCES); - libModel.commit(); - libTableModel.commit(); - return library; - } - } - return null; - } - - public void removeLibrary(ScriptingLibraryTable.LibraryModel library) { - if (ensureModel()) { - myLibTable.removeLibrary(library); - } - } - - public void updateLibrary(String oldName, String name, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { - if (ensureModel()) { - ScriptingLibraryTable.LibraryModel libModel = myLibTable.getLibraryByName(oldName); - if (libModel != null) { - libModel.setName(name); - libModel.setSourceFiles(sourceFiles); - libModel.setCompactFiles(compactFiles); - myLibTable.invalidateCache(); - } - } - } - - @Nullable - public ScriptingLibraryTable.LibraryModel[] getLibraries() { - if (ensureModel()) { - return myLibTable.getLibraries(); - } - return null; - } - - @Nullable - public ScriptingLibraryTable.LibraryModel getLibraryByName(String name) { - if (ensureModel()) { - return myLibTable.getLibraryByName(name); - } - return null; - } - - public boolean ensureModel() { - if (myLibTable == null) { - LibraryTable libTable = getLibraryTable(); - if (libTable != null) { - myLibTable = new ScriptingLibraryTable(libTable, myLibraryType); - return true; - } - return false; - } - return true; - } - - @Nullable - public LibraryTable getLibraryTable() { - return getLibraryTable(myProject, myLibLevel); - } - - @Nullable - public static LibraryTable getLibraryTable(Project project, LibraryLevel libraryLevel) { - String libLevel = null; - switch (libraryLevel) { - case PROJECT: - libLevel = LibraryTablesRegistrar.PROJECT_LEVEL; - break; - case GLOBAL: - libLevel = LibraryTablesRegistrar.APPLICATION_LEVEL; - break; - } - if (libLevel != null) { - return LibraryTablesRegistrar.getInstance().getLibraryTableByLevel(libLevel, project); - } - return null; - } - - public Project getProject() { - return myProject; - } - - public boolean isCompactFile(VirtualFile file) { - ensureModel(); - assert myLibTable != null; - return myLibTable.isCompactFile(file); - } - -} +/* + * 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.roots.libraries.scripting; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.roots.impl.libraries.LibraryTableBase; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.roots.libraries.LibraryType; +import com.intellij.openapi.util.EmptyRunnable; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +/** + * @author Rustam Vishnyakov + */ +public class ScriptingLibraryManager { + + public enum LibraryLevel {GLOBAL, PROJECT} + + private Project myProject; + private LibraryLevel myLibLevel = LibraryLevel.PROJECT; + private ScriptingLibraryTable myLibTable; + private LibraryType myLibraryType; + + public ScriptingLibraryManager(Project project, LibraryType libraryType) { + this(LibraryLevel.GLOBAL, project, libraryType); + } + + public ScriptingLibraryManager(LibraryLevel libLevel, Project project, LibraryType libraryType) { + myProject = project; + myLibLevel = libLevel; + myLibraryType = libraryType; + } + + public ScriptingLibraryTable getScriptingLibraryTable() { + ensureModel(); + return myLibTable; + } + + public void commitChanges() { + if (myLibTable != null) { + LibraryTable libTable = getLibraryTable(); + if (libTable != null) { + LibraryTable.ModifiableModel libTableModel = libTable.getModifiableModel(); + updateLibraries(libTableModel, OrderRootType.SOURCES); + updateLibraries(libTableModel, OrderRootType.CLASSES); + libTableModel.commit(); + } + myLibTable = null; + } + updateOpenProjects(); + } + + private static void updateOpenProjects() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + ProjectRootManagerEx.getInstanceEx(project).makeRootsChange(EmptyRunnable.getInstance(), false, true); + } + } + }); + } + + private void updateLibraries(LibraryTable.ModifiableModel libTableModel, OrderRootType rootType) { + for (Library library : libTableModel.getLibraries()) { + ScriptingLibraryTable.LibraryModel scriptingLibModel = myLibTable.getLibraryByName(library.getName()); + if (scriptingLibModel == null) { + libTableModel.removeLibrary(library); + } + else { + Library.ModifiableModel libModel = library.getModifiableModel(); + for (VirtualFile libRoot : libModel.getFiles(rootType)) { + libModel.removeRoot(libRoot.getUrl(), rootType); + } + for (VirtualFile newRoot : scriptingLibModel.getFiles(rootType)) { + libModel.addRoot(newRoot, rootType); + } + libModel.commit(); + } + } + for (ScriptingLibraryTable.LibraryModel scriptingLibModel : myLibTable.getLibraries()) { + Library library = libTableModel.getLibraryByName(scriptingLibModel.getName()); + if (library == null && libTableModel instanceof LibraryTableBase.ModifiableModelEx) { + library = ((LibraryTableBase.ModifiableModelEx)libTableModel).createLibrary(scriptingLibModel.getName(), myLibraryType); + Library.ModifiableModel libModel = library.getModifiableModel(); + for (VirtualFile newRoot : scriptingLibModel.getFiles(rootType)) { + libModel.addRoot(newRoot, rootType); + } + libModel.commit(); + } + } + } + + public void reset() { + myLibTable = null; + } + + @Nullable + public ScriptingLibraryTable.LibraryModel createLibrary(String name, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { + if (ensureModel()) { + return myLibTable.createLibrary(name, sourceFiles, compactFiles); + } + return null; + } + + @Nullable + public Library createSourceLibrary(String libName, String sourceUrl, LibraryLevel libraryLevel) { + LibraryTable libraryTable = getLibraryTable(myProject, libraryLevel); + if (libraryTable == null) return null; + LibraryTable.ModifiableModel libTableModel = libraryTable.getModifiableModel(); + if (libTableModel instanceof LibraryTableBase.ModifiableModelEx) { + Library library = ((LibraryTableBase.ModifiableModelEx)libTableModel).createLibrary(libName, myLibraryType); + if (library != null) { + Library.ModifiableModel libModel = library.getModifiableModel(); + libModel.addRoot(sourceUrl, OrderRootType.SOURCES); + libModel.commit(); + libTableModel.commit(); + return library; + } + } + return null; + } + + public void removeLibrary(ScriptingLibraryTable.LibraryModel library) { + if (ensureModel()) { + myLibTable.removeLibrary(library); + } + } + + public void updateLibrary(String oldName, String name, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { + if (ensureModel()) { + ScriptingLibraryTable.LibraryModel libModel = myLibTable.getLibraryByName(oldName); + if (libModel != null) { + libModel.setName(name); + libModel.setSourceFiles(sourceFiles); + libModel.setCompactFiles(compactFiles); + myLibTable.invalidateCache(); + } + } + } + + @Nullable + public ScriptingLibraryTable.LibraryModel[] getLibraries() { + if (ensureModel()) { + return myLibTable.getLibraries(); + } + return null; + } + + @Nullable + public ScriptingLibraryTable.LibraryModel getLibraryByName(String name) { + if (ensureModel()) { + return myLibTable.getLibraryByName(name); + } + return null; + } + + public boolean ensureModel() { + if (myLibTable == null) { + LibraryTable libTable = getLibraryTable(); + if (libTable != null) { + myLibTable = new ScriptingLibraryTable(libTable, myLibraryType); + return true; + } + return false; + } + return true; + } + + @Nullable + public LibraryTable getLibraryTable() { + return getLibraryTable(myProject, myLibLevel); + } + + @Nullable + public static LibraryTable getLibraryTable(Project project, LibraryLevel libraryLevel) { + String libLevel = null; + switch (libraryLevel) { + case PROJECT: + libLevel = LibraryTablesRegistrar.PROJECT_LEVEL; + break; + case GLOBAL: + libLevel = LibraryTablesRegistrar.APPLICATION_LEVEL; + break; + } + if (libLevel != null) { + return LibraryTablesRegistrar.getInstance().getLibraryTableByLevel(libLevel, project); + } + return null; + } + + public Project getProject() { + return myProject; + } + + public boolean isCompactFile(VirtualFile file) { + ensureModel(); + assert myLibTable != null; + return myLibTable.isCompactFile(file); + } + +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryTable.java b/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryTable.java index 3c40bc4356e4..a28616d2d60b 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryTable.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/libraries/scripting/ScriptingLibraryTable.java @@ -1,168 +1,168 @@ -/* - * 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.roots.libraries.scripting; - -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.impl.libraries.LibraryEx; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.libraries.LibraryType; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.HashSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * @author Rustam Vishnyakov - */ -public class ScriptingLibraryTable { - - private final static OrderRootType SOURCE_ROOT_TYPE = OrderRootType.SOURCES; - private final static OrderRootType COMPACT_ROOT_TYPE = OrderRootType.CLASSES; - - private ArrayList myLibraryModels = new ArrayList(); - private HashSet myCompactFilesCache; - - public ScriptingLibraryTable(@NotNull LibraryTable libraryTable, LibraryType libraryType) { - for (Library library : libraryTable.getLibraries()) { - if (library instanceof LibraryEx) { - LibraryType libType = ((LibraryEx)library).getType(); - if (libType != null && libType.equals(libraryType)) { - LibraryModel libModel = new LibraryModel(library.getName()); - libModel.setSourceFiles(library.getFiles(SOURCE_ROOT_TYPE)); - libModel.setCompactFiles(library.getFiles(COMPACT_ROOT_TYPE)); - myLibraryModels.add(libModel); - } - } - } - } - - public boolean isCompactFile(VirtualFile file) { - if (myCompactFilesCache == null) { - myCompactFilesCache = new HashSet(); - for (LibraryModel libraryModel : myLibraryModels) { - myCompactFilesCache.addAll(Arrays.asList(libraryModel.getCompactFiles())); - } - } - return myCompactFilesCache.contains(file); - } - - public void invalidateCache() { - myCompactFilesCache = null; - } - - @Nullable - public LibraryModel getLibraryByName(@NotNull String libName) { - for (LibraryModel libraryModel : myLibraryModels) { - if (libName.equals(libraryModel.getName())) return libraryModel; - } - return null; - } - - public void removeLibrary(LibraryModel libraryModel) { - myLibraryModels.remove(libraryModel); - invalidateCache(); - } - - public LibraryModel createLibrary(String libName) { - LibraryModel libModel = new LibraryModel(libName); - myLibraryModels.add(libModel); - invalidateCache(); - return libModel; - } - - public LibraryModel createLibrary(String libName, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { - LibraryModel libModel = new LibraryModel(libName, sourceFiles, compactFiles); - myLibraryModels.add(libModel); - invalidateCache(); - return libModel; - } - - public LibraryModel[] getLibraries() { - return myLibraryModels.toArray(new LibraryModel[myLibraryModels.size()]); - } - - public int getLibCount() { - return myLibraryModels.size(); - } - - @Nullable - public LibraryModel getLibraryAt(int index) { - if (index < 0 || index > myLibraryModels.size() - 1) return null; - return myLibraryModels.get(index); - } - - public static class LibraryModel { - private String myName; - private ArrayList mySourceFiles = new ArrayList(); - private ArrayList myCompactFiles = new ArrayList(); - - public LibraryModel(String name, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { - this(name); - mySourceFiles.addAll(Arrays.asList(sourceFiles)); - myCompactFiles.addAll(Arrays.asList(compactFiles)); - } - - public LibraryModel(String name) { - myName = name; - } - - public void setName(String name) { - myName = name; - } - - public void setSourceFiles(VirtualFile[] files) { - mySourceFiles.clear(); - mySourceFiles.addAll(Arrays.asList(files)); - } - - public void setCompactFiles(VirtualFile[] files) { - myCompactFiles.clear(); - myCompactFiles.addAll(Arrays.asList(files)); - } - - public VirtualFile[] getSourceFiles() { - return mySourceFiles.toArray(new VirtualFile[mySourceFiles.size()]); - } - - public VirtualFile[] getCompactFiles() { - return myCompactFiles.toArray(new VirtualFile[myCompactFiles.size()]); - } - - @NotNull - public VirtualFile[] getFiles(OrderRootType rootType) { - if (rootType == COMPACT_ROOT_TYPE) { - return getCompactFiles(); - } - return getSourceFiles(); - } - - public String getName() { - return myName; - } - - public boolean containsFile(VirtualFile file) { - return mySourceFiles.contains(file) || myCompactFiles.contains(file); - } - - public boolean isEmpty() { - return mySourceFiles.isEmpty() && myCompactFiles.isEmpty(); - } - } -} +/* + * 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.roots.libraries.scripting; + +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.impl.libraries.LibraryEx; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.libraries.LibraryType; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * @author Rustam Vishnyakov + */ +public class ScriptingLibraryTable { + + private final static OrderRootType SOURCE_ROOT_TYPE = OrderRootType.SOURCES; + private final static OrderRootType COMPACT_ROOT_TYPE = OrderRootType.CLASSES; + + private ArrayList myLibraryModels = new ArrayList(); + private HashSet myCompactFilesCache; + + public ScriptingLibraryTable(@NotNull LibraryTable libraryTable, LibraryType libraryType) { + for (Library library : libraryTable.getLibraries()) { + if (library instanceof LibraryEx) { + LibraryType libType = ((LibraryEx)library).getType(); + if (libType != null && libType.equals(libraryType)) { + LibraryModel libModel = new LibraryModel(library.getName()); + libModel.setSourceFiles(library.getFiles(SOURCE_ROOT_TYPE)); + libModel.setCompactFiles(library.getFiles(COMPACT_ROOT_TYPE)); + myLibraryModels.add(libModel); + } + } + } + } + + public boolean isCompactFile(VirtualFile file) { + if (myCompactFilesCache == null) { + myCompactFilesCache = new HashSet(); + for (LibraryModel libraryModel : myLibraryModels) { + myCompactFilesCache.addAll(Arrays.asList(libraryModel.getCompactFiles())); + } + } + return myCompactFilesCache.contains(file); + } + + public void invalidateCache() { + myCompactFilesCache = null; + } + + @Nullable + public LibraryModel getLibraryByName(@NotNull String libName) { + for (LibraryModel libraryModel : myLibraryModels) { + if (libName.equals(libraryModel.getName())) return libraryModel; + } + return null; + } + + public void removeLibrary(LibraryModel libraryModel) { + myLibraryModels.remove(libraryModel); + invalidateCache(); + } + + public LibraryModel createLibrary(String libName) { + LibraryModel libModel = new LibraryModel(libName); + myLibraryModels.add(libModel); + invalidateCache(); + return libModel; + } + + public LibraryModel createLibrary(String libName, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { + LibraryModel libModel = new LibraryModel(libName, sourceFiles, compactFiles); + myLibraryModels.add(libModel); + invalidateCache(); + return libModel; + } + + public LibraryModel[] getLibraries() { + return myLibraryModels.toArray(new LibraryModel[myLibraryModels.size()]); + } + + public int getLibCount() { + return myLibraryModels.size(); + } + + @Nullable + public LibraryModel getLibraryAt(int index) { + if (index < 0 || index > myLibraryModels.size() - 1) return null; + return myLibraryModels.get(index); + } + + public static class LibraryModel { + private String myName; + private ArrayList mySourceFiles = new ArrayList(); + private ArrayList myCompactFiles = new ArrayList(); + + public LibraryModel(String name, VirtualFile[] sourceFiles, VirtualFile[] compactFiles) { + this(name); + mySourceFiles.addAll(Arrays.asList(sourceFiles)); + myCompactFiles.addAll(Arrays.asList(compactFiles)); + } + + public LibraryModel(String name) { + myName = name; + } + + public void setName(String name) { + myName = name; + } + + public void setSourceFiles(VirtualFile[] files) { + mySourceFiles.clear(); + mySourceFiles.addAll(Arrays.asList(files)); + } + + public void setCompactFiles(VirtualFile[] files) { + myCompactFiles.clear(); + myCompactFiles.addAll(Arrays.asList(files)); + } + + public VirtualFile[] getSourceFiles() { + return mySourceFiles.toArray(new VirtualFile[mySourceFiles.size()]); + } + + public VirtualFile[] getCompactFiles() { + return myCompactFiles.toArray(new VirtualFile[myCompactFiles.size()]); + } + + @NotNull + public VirtualFile[] getFiles(OrderRootType rootType) { + if (rootType == COMPACT_ROOT_TYPE) { + return getCompactFiles(); + } + return getSourceFiles(); + } + + public String getName() { + return myName; + } + + public boolean containsFile(VirtualFile file) { + return mySourceFiles.contains(file) || myCompactFiles.contains(file); + } + + public boolean isEmpty() { + return mySourceFiles.isEmpty() && myCompactFiles.isEmpty(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/wm/impl/status/TogglePopupHintsPanel.java b/platform/lang-impl/src/com/intellij/openapi/wm/impl/status/TogglePopupHintsPanel.java index 77cc83fb5920..bf66899e9661 100644 --- a/platform/lang-impl/src/com/intellij/openapi/wm/impl/status/TogglePopupHintsPanel.java +++ b/platform/lang-impl/src/com/intellij/openapi/wm/impl/status/TogglePopupHintsPanel.java @@ -1,180 +1,180 @@ -/* - * 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.wm.impl.status; - -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.codeInsight.daemon.impl.HectorComponent; -import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil; -import com.intellij.ide.DataManager; -import com.intellij.ide.PowerSaveMode; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.ui.UIBundle; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.util.Consumer; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseEvent; - -public class TogglePopupHintsPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.IconPresentation { - private static final Icon INSPECTIONS_ICON = IconLoader.getIcon("/ide/hectorOn.png"); - private static final Icon INSPECTIONS_OFF_ICON = IconLoader.getIcon("/ide/hectorOff.png"); - private static final Icon SYNTAX_ONLY_ICON = IconLoader.getIcon("/ide/hectorSyntax.png"); - private static final Icon EMPTY_ICON = IconLoader.getIcon("/ide/hectorNo.png"); - - private Icon myCurrentIcon; - private String myToolTipText; - - public TogglePopupHintsPanel(@NotNull final Project project) { - super(project); - myCurrentIcon = EMPTY_ICON; - final MessageBusConnection connection = project.getMessageBus().connect(); - connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { - @Override - public void selectionChanged(FileEditorManagerEvent event) { - updateStatus(); - } - - @Override - public void fileOpened(FileEditorManager source, VirtualFile file) { - updateStatus(); - } - }); - connection.subscribe(PowerSaveMode.TOPIC, new PowerSaveMode.Listener() { - @Override - public void powerSaveStateChanged() { - updateStatus(); - } - }); - } - - @Override - public StatusBarWidget copy() { - return new TogglePopupHintsPanel(getProject()); - } - - @NotNull - public Icon getIcon() { - return myCurrentIcon; - } - - public String getTooltipText() { - return myToolTipText; - } - - public Consumer getClickConsumer() { - return new Consumer() { - public void consume(final MouseEvent e) { - Point point = new Point(0, 0); - final PsiFile file = getCurrentFile(); - if (file != null) { - if (!DaemonCodeAnalyzer.getInstance(file.getProject()).isHighlightingAvailable(file)) return; - final HectorComponent component = new HectorComponent(file); - final Dimension dimension = component.getPreferredSize(); - point = new Point(point.x - dimension.width, point.y - dimension.height); - component.showComponent(new RelativePoint(e.getComponent(), point)); - } - } - }; - } - - @NotNull - public String ID() { - return "InspectionProfile"; - } - - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return this; - } - - public String updateStatusBar(final Editor selected, final JComponent componentSelected) { - //updateStatus(); - //String text = componentSelected == null ? null : componentSelected.getToolTipText(); - //setCursor(Cursor.getPredefinedCursor(text == null ? Cursor.DEFAULT_CURSOR : Cursor.HAND_CURSOR)); - return ""; - } - - public void clear() { - myCurrentIcon = EMPTY_ICON; - myToolTipText = null; - myStatusBar.updateWidget(ID()); - } - - public void updateStatus() { - updateStatus(getCurrentFile()); - } - - private void updateStatus(PsiFile file) { - if (isStateChangeable(file)) { - if (PowerSaveMode.isEnabled()) { - myCurrentIcon = EMPTY_ICON; - myToolTipText = "Code analysis is disabled in power save mode. "; - } - else if (HighlightLevelUtil.shouldInspect(file)) { - myCurrentIcon = INSPECTIONS_ICON; - myToolTipText = "Current inspection profile: " + - InspectionProjectProfileManager.getInstance(file.getProject()).getInspectionProfile().getName() + - ". "; - } - else if (HighlightLevelUtil.shouldHighlight(file)) { - myCurrentIcon = SYNTAX_ONLY_ICON; - myToolTipText = "Highlighting level is: Syntax. "; - } - else { - myCurrentIcon = INSPECTIONS_OFF_ICON; - myToolTipText = "Inspections are off. "; - } - myToolTipText += UIBundle.message("popup.hints.panel.click.to.configure.highlighting.tooltip.text"); - } else { - myCurrentIcon = EMPTY_ICON; - myToolTipText = null; - } - - myStatusBar.updateWidget(ID()); - } - - private static boolean isStateChangeable(PsiFile file) { - return file != null && DaemonCodeAnalyzer.getInstance(file.getProject()).isHighlightingAvailable(file); - } - - @Nullable - private PsiFile getCurrentFile() { - VirtualFile virtualFile = getSelectedFile(); - if (virtualFile != null && virtualFile.isValid()){ - return PsiManager.getInstance(getProject()).findFile(virtualFile); - } - return null; - } - -} +/* + * 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.wm.impl.status; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.daemon.impl.HectorComponent; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil; +import com.intellij.ide.DataManager; +import com.intellij.ide.PowerSaveMode; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.ui.UIBundle; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.Consumer; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; + +public class TogglePopupHintsPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.IconPresentation { + private static final Icon INSPECTIONS_ICON = IconLoader.getIcon("/ide/hectorOn.png"); + private static final Icon INSPECTIONS_OFF_ICON = IconLoader.getIcon("/ide/hectorOff.png"); + private static final Icon SYNTAX_ONLY_ICON = IconLoader.getIcon("/ide/hectorSyntax.png"); + private static final Icon EMPTY_ICON = IconLoader.getIcon("/ide/hectorNo.png"); + + private Icon myCurrentIcon; + private String myToolTipText; + + public TogglePopupHintsPanel(@NotNull final Project project) { + super(project); + myCurrentIcon = EMPTY_ICON; + final MessageBusConnection connection = project.getMessageBus().connect(); + connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { + @Override + public void selectionChanged(FileEditorManagerEvent event) { + updateStatus(); + } + + @Override + public void fileOpened(FileEditorManager source, VirtualFile file) { + updateStatus(); + } + }); + connection.subscribe(PowerSaveMode.TOPIC, new PowerSaveMode.Listener() { + @Override + public void powerSaveStateChanged() { + updateStatus(); + } + }); + } + + @Override + public StatusBarWidget copy() { + return new TogglePopupHintsPanel(getProject()); + } + + @NotNull + public Icon getIcon() { + return myCurrentIcon; + } + + public String getTooltipText() { + return myToolTipText; + } + + public Consumer getClickConsumer() { + return new Consumer() { + public void consume(final MouseEvent e) { + Point point = new Point(0, 0); + final PsiFile file = getCurrentFile(); + if (file != null) { + if (!DaemonCodeAnalyzer.getInstance(file.getProject()).isHighlightingAvailable(file)) return; + final HectorComponent component = new HectorComponent(file); + final Dimension dimension = component.getPreferredSize(); + point = new Point(point.x - dimension.width, point.y - dimension.height); + component.showComponent(new RelativePoint(e.getComponent(), point)); + } + } + }; + } + + @NotNull + public String ID() { + return "InspectionProfile"; + } + + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return this; + } + + public String updateStatusBar(final Editor selected, final JComponent componentSelected) { + //updateStatus(); + //String text = componentSelected == null ? null : componentSelected.getToolTipText(); + //setCursor(Cursor.getPredefinedCursor(text == null ? Cursor.DEFAULT_CURSOR : Cursor.HAND_CURSOR)); + return ""; + } + + public void clear() { + myCurrentIcon = EMPTY_ICON; + myToolTipText = null; + myStatusBar.updateWidget(ID()); + } + + public void updateStatus() { + updateStatus(getCurrentFile()); + } + + private void updateStatus(PsiFile file) { + if (isStateChangeable(file)) { + if (PowerSaveMode.isEnabled()) { + myCurrentIcon = EMPTY_ICON; + myToolTipText = "Code analysis is disabled in power save mode. "; + } + else if (HighlightLevelUtil.shouldInspect(file)) { + myCurrentIcon = INSPECTIONS_ICON; + myToolTipText = "Current inspection profile: " + + InspectionProjectProfileManager.getInstance(file.getProject()).getInspectionProfile().getName() + + ". "; + } + else if (HighlightLevelUtil.shouldHighlight(file)) { + myCurrentIcon = SYNTAX_ONLY_ICON; + myToolTipText = "Highlighting level is: Syntax. "; + } + else { + myCurrentIcon = INSPECTIONS_OFF_ICON; + myToolTipText = "Inspections are off. "; + } + myToolTipText += UIBundle.message("popup.hints.panel.click.to.configure.highlighting.tooltip.text"); + } else { + myCurrentIcon = EMPTY_ICON; + myToolTipText = null; + } + + myStatusBar.updateWidget(ID()); + } + + private static boolean isStateChangeable(PsiFile file) { + return file != null && DaemonCodeAnalyzer.getInstance(file.getProject()).isHighlightingAvailable(file); + } + + @Nullable + private PsiFile getCurrentFile() { + VirtualFile virtualFile = getSelectedFile(); + if (virtualFile != null && virtualFile.isValid()){ + return PsiManager.getInstance(getProject()).findFile(virtualFile); + } + return null; + } + +} diff --git a/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java b/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java index d36a16ef683c..c18aed8e50af 100644 --- a/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java +++ b/platform/lang-impl/src/com/intellij/packageDependencies/DefaultScopesProvider.java @@ -1,102 +1,102 @@ -/* - * 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.packageDependencies; - -import com.intellij.ide.IdeBundle; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.problems.WolfTheProblemSolver; -import com.intellij.psi.PsiFile; -import com.intellij.psi.search.scope.NonProjectFilesScope; -import com.intellij.psi.search.scope.ProjectFilesScope; -import com.intellij.psi.search.scope.TestsScope; -import com.intellij.psi.search.scope.packageSet.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * @author anna - * @author Konstantin Bulenkov - */ -public class DefaultScopesProvider implements CustomScopesProvider { - private final NamedScope myProblemsScope; - private final Project myProject; - private final List myScopes; - - public static DefaultScopesProvider getInstance(Project project) { - return Extensions.findExtension(CUSTOM_SCOPES_PROVIDER, project, DefaultScopesProvider.class); - } - - public DefaultScopesProvider(Project project) { - myProject = project; - final NamedScope projectScope = new ProjectFilesScope(); - final NamedScope projectTestScope = new TestsScope(); - final NamedScope nonProjectScope = new NonProjectFilesScope(); - final String text = FilePatternPackageSet.SCOPE_FILE + ":*//*"; - myProblemsScope = new NamedScope(IdeBundle.message("predefined.scope.problems.name"), new AbstractPackageSet(text) { - public boolean contains(PsiFile file, NamedScopesHolder holder) { - return file.getProject() == myProject - && WolfTheProblemSolver.getInstance(myProject).isProblemFile(file.getVirtualFile()); - } - }); - myScopes = Arrays.asList(projectScope, getProblemsScope(), getAllScope(), projectTestScope, nonProjectScope); - } - - @NotNull - public List getCustomScopes() { - return myScopes; - } - - @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) - private static class AllScopeHolder { - private static final String TEXT = FilePatternPackageSet.SCOPE_FILE + ":*//*"; - private static final NamedScope ALL = new NamedScope("All", new AbstractPackageSet(TEXT, 0) { - public boolean contains(final PsiFile file, final NamedScopesHolder scopesHolder) { - return true; - } - }); - } - - public static NamedScope getAllScope() { - return AllScopeHolder.ALL; - } - - public NamedScope getProblemsScope() { - return myProblemsScope; - } - - public List getAllCustomScopes() { - final List scopes = new ArrayList(); - for (CustomScopesProvider provider : Extensions.getExtensions(CUSTOM_SCOPES_PROVIDER, myProject)) { - scopes.addAll(provider.getCustomScopes()); - } - return scopes; - } - - @Nullable - public NamedScope findCustomScope(String name) { - for (NamedScope scope : getAllCustomScopes()) { - if (name.equals(scope.getName())) { - return scope; - } - } - return null; - } -} +/* + * 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.packageDependencies; + +import com.intellij.ide.IdeBundle; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.problems.WolfTheProblemSolver; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.scope.NonProjectFilesScope; +import com.intellij.psi.search.scope.ProjectFilesScope; +import com.intellij.psi.search.scope.TestsScope; +import com.intellij.psi.search.scope.packageSet.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author anna + * @author Konstantin Bulenkov + */ +public class DefaultScopesProvider implements CustomScopesProvider { + private final NamedScope myProblemsScope; + private final Project myProject; + private final List myScopes; + + public static DefaultScopesProvider getInstance(Project project) { + return Extensions.findExtension(CUSTOM_SCOPES_PROVIDER, project, DefaultScopesProvider.class); + } + + public DefaultScopesProvider(Project project) { + myProject = project; + final NamedScope projectScope = new ProjectFilesScope(); + final NamedScope projectTestScope = new TestsScope(); + final NamedScope nonProjectScope = new NonProjectFilesScope(); + final String text = FilePatternPackageSet.SCOPE_FILE + ":*//*"; + myProblemsScope = new NamedScope(IdeBundle.message("predefined.scope.problems.name"), new AbstractPackageSet(text) { + public boolean contains(PsiFile file, NamedScopesHolder holder) { + return file.getProject() == myProject + && WolfTheProblemSolver.getInstance(myProject).isProblemFile(file.getVirtualFile()); + } + }); + myScopes = Arrays.asList(projectScope, getProblemsScope(), getAllScope(), projectTestScope, nonProjectScope); + } + + @NotNull + public List getCustomScopes() { + return myScopes; + } + + @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) + private static class AllScopeHolder { + private static final String TEXT = FilePatternPackageSet.SCOPE_FILE + ":*//*"; + private static final NamedScope ALL = new NamedScope("All", new AbstractPackageSet(TEXT, 0) { + public boolean contains(final PsiFile file, final NamedScopesHolder scopesHolder) { + return true; + } + }); + } + + public static NamedScope getAllScope() { + return AllScopeHolder.ALL; + } + + public NamedScope getProblemsScope() { + return myProblemsScope; + } + + public List getAllCustomScopes() { + final List scopes = new ArrayList(); + for (CustomScopesProvider provider : Extensions.getExtensions(CUSTOM_SCOPES_PROVIDER, myProject)) { + scopes.addAll(provider.getCustomScopes()); + } + return scopes; + } + + @Nullable + public NamedScope findCustomScope(String name) { + for (NamedScope scope : getAllCustomScopes()) { + if (name.equals(scope.getName())) { + return scope; + } + } + return null; + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java index 1d1786b88a2a..e4f212c4ab54 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java @@ -1,395 +1,395 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: Anna.Kozlova - * Date: 31-Jul-2006 - * Time: 17:44:39 - */ -package com.intellij.profile.codeInspection.ui; - -import com.intellij.codeInsight.daemon.impl.HighlightInfoType; -import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; -import com.intellij.codeInspection.ModifiableModel; -import com.intellij.codeInspection.ex.InspectionProfileImpl; -import com.intellij.codeInspection.ex.InspectionToolRegistrar; -import com.intellij.ide.ui.ListCellRendererWrapper; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.colors.CodeInsightColors; -import com.intellij.openapi.editor.colors.TextAttributesKey; -import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.options.BaseConfigurable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.profile.Profile; -import com.intellij.profile.codeInspection.InspectionProfileManager; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; -import com.intellij.util.SystemProperties; -import com.intellij.util.containers.HashMap; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.IOException; -import java.util.*; - -public abstract class InspectionToolsConfigurable extends BaseConfigurable implements ErrorsConfigurable, SearchableConfigurable { - private final CardLayout myLayout = new CardLayout(); - private JPanel myPanel; - - public static final String ID = "Errors"; - public static final String DISPLAY_NAME = "Inspections"; - - protected JComboBox myProfiles; - private final Map myPanels = new HashMap(); - - private JPanel myWholePanel; - private JButton myAddButton; - private JButton myDeleteButton; - private JButton myImportButton; - private JButton myExportButton; - private JCheckBox myShareProfileCheckBox; - private JButton myCopyButton; - - private final ArrayList myDeletedProfiles = new ArrayList(); - protected final InspectionProfileManager myProfileManager; - protected final InspectionProjectProfileManager myProjectProfileManager; - private static final Logger LOG = Logger.getInstance("#" + InspectionToolsConfigurable.class.getName()); - - - public InspectionToolsConfigurable(InspectionProjectProfileManager projectProfileManager, InspectionProfileManager profileManager) { - myAddButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final ModifiableModel model = SingleInspectionProfilePanel.createNewProfile(-1, getSelectedObject(), myWholePanel, ""); - if (model != null) { - addProfile((InspectionProfileImpl)model); - myDeletedProfiles.remove(model.getName()); - myDeleteButton.setEnabled(true); - } - } - }); - - myDeleteButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final InspectionProfileImpl selectedProfile = (InspectionProfileImpl)myProfiles.getSelectedItem(); - ((DefaultComboBoxModel)myProfiles.getModel()).removeElement(selectedProfile); - myDeletedProfiles.add(selectedProfile.getName()); - myDeleteButton.setEnabled(myProfiles.getModel().getSize() > 1); - } - }); - - myImportButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false){ - @Override - public boolean isFileSelectable(VirtualFile file) { - return file.getFileType().equals(StdFileTypes.XML); - } - }; - descriptor.setDescription("Choose profile file"); - final VirtualFile[] files = FileChooser.chooseFiles(myWholePanel, descriptor); - if (files.length == 0) return; - InspectionProfileImpl profile = - new InspectionProfileImpl("TempProfile", InspectionToolRegistrar.getInstance(), myProfileManager); - try { - Element rootElement = JDOMUtil.loadDocument(VfsUtil.virtualToIoFile(files[0])).getRootElement(); - if (Comparing.strEqual(rootElement.getName(), "component")) {//import right from .idea/inspectProfiles/xxx.xml - rootElement = (Element)rootElement.getChildren().get(0); - } - final Set levels = new HashSet(); - for (Object o : rootElement.getChildren("inspection_tool")) { - final Element inspectElement = (Element)o; - levels.add(inspectElement.getAttributeValue("level")); - for (Object s : inspectElement.getChildren("scope")) { - levels.add(((Element)s).getAttributeValue("level")); - } - } - for (Iterator iterator = levels.iterator(); iterator.hasNext();) { - String level = iterator.next(); - if (myProfileManager.getOwnSeverityRegistrar().getSeverity(level) != null) { - iterator.remove(); - } - } - if (!levels.isEmpty()) { - if (Messages.showYesNoDialog(myWholePanel, "Undefined severities detected: " + - StringUtil.join(levels, ", ") + - ". Do you want to create them?", "Warning", Messages.getWarningIcon()) == - DialogWrapper.OK_EXIT_CODE) { - for (String level : levels) { - final TextAttributes textAttributes = CodeInsightColors.WARNINGS_ATTRIBUTES.getDefaultAttributes(); - HighlightInfoType.HighlightInfoTypeImpl info - = new HighlightInfoType.HighlightInfoTypeImpl(new HighlightSeverity(level, 50), TextAttributesKey.createTextAttributesKey(level)); - myProfileManager.getOwnSeverityRegistrar() - .registerSeverity(new SeverityRegistrar.SeverityBasedTextAttributes(textAttributes.clone(), info), - textAttributes.getErrorStripeColor()); - } - } - } - profile.readExternal(rootElement); - profile.setLocal(true); - profile.initInspectionTools(); - if (myPanels.get(profile.getName()) != null) { - if (Messages.showOkCancelDialog(myWholePanel, "Profile with name \'" + profile.getName() + "\' already exists. Do you want to overwrite it?", "Warning", Messages.getInformationIcon()) != DialogWrapper.OK_EXIT_CODE) return; - } - addProfile((InspectionProfileImpl)profile.getModifiableModel()); - myDeletedProfiles.remove(profile.getName()); - myDeleteButton.setEnabled(true); - } - catch (InvalidDataException e1) { - LOG.error(e1); - } - catch (JDOMException e1) { - LOG.error(e1); - } - catch (IOException e1) { - LOG.error(e1); - } - } - }); - - myExportButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); - descriptor.setDescription("Choose directory to store profile file"); - final VirtualFile[] files = FileChooser.chooseFiles(myWholePanel, descriptor); - if (files.length == 0) return; - final Element element = new Element("inspections"); - try { - final SingleInspectionProfilePanel panel = getSelectedPanel(); - LOG.assertTrue(panel != null); - final InspectionProfileImpl profile = (InspectionProfileImpl)panel.getSelectedProfile(); - profile.writeExternal(element); - final String filePath = files[0].getPath() + File.separator + FileUtil.sanitizeFileName(profile.getName()) + ".xml"; - if (new File(filePath).isFile()) { - if (Messages.showOkCancelDialog(myWholePanel, "File \'" + filePath + "\' already exist. Do you want to override it?", "Warning", Messages.getQuestionIcon()) != DialogWrapper.OK_EXIT_CODE) return; - } - JDOMUtil.writeDocument(new Document(element), filePath, SystemProperties.getLineSeparator()); - } - catch (WriteExternalException e1) { - LOG.error(e1); - } - catch (IOException e1) { - LOG.error(e1); - } - } - }); - - myCopyButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final InspectionProfileImpl model = (InspectionProfileImpl)SingleInspectionProfilePanel.createNewProfile(0, getSelectedObject(), myWholePanel, ""); - if (model != null) { - addProfile((InspectionProfileImpl)model.getModifiableModel()); - myDeletedProfiles.remove(model.getName()); - myDeleteButton.setEnabled(true); - } - } - }); - - myProjectProfileManager = projectProfileManager; - myProfileManager = profileManager; - } - - private void addProfile(InspectionProfileImpl model) { - final String modelName = model.getName(); - final SingleInspectionProfilePanel panel = new SingleInspectionProfilePanel(myProjectProfileManager, modelName, model); - panel.reset(); - myPanel.add(modelName, panel); - if (!myPanels.containsKey(modelName)) { - ((DefaultComboBoxModel)myProfiles.getModel()).addElement(model); - } - myPanels.put(modelName, panel); - myProfiles.setSelectedItem(model); - } - - public String getDisplayName() { - return DISPLAY_NAME; - } - - public Icon getIcon() { - return IconLoader.getIcon("/general/configurableErrorHighlighting.png"); - } - - public String getHelpTopic() { - return "preferences.inspections"; - } - - public String getId() { - return ID; - } - - public Runnable enableSearch(final String option) { - return new Runnable(){ - public void run() { - SingleInspectionProfilePanel panel = getSelectedPanel(); - if (panel != null) { - panel.filterTree(option); - } - } - }; - } - - public JComponent createComponent() { - myProfiles.setRenderer(new ListCellRendererWrapper(myProfiles.getRenderer()) { - @Override - public void customize(final JList list, final Profile value, final int index, final boolean selected, final boolean hasFocus) { - final String profileName = value.getName(); - setText(profileName); - final SingleInspectionProfilePanel panel = myPanels.get(profileName); - setIcon(panel != null && panel.isProfileShared() ? Profile.PROJECT_PROFILE : Profile.LOCAL_PROFILE); - } - }); - myProfiles.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final InspectionProfileImpl profile = (InspectionProfileImpl)myProfiles.getSelectedItem(); - myDeleteButton.setEnabled(myProfiles.getModel().getSize() > 1); - myLayout.show(myPanel, profile.getName()); - SingleInspectionProfilePanel panel = getSelectedPanel(); - if (panel != null) { - myShareProfileCheckBox.setSelected(panel.isProfileShared()); - } - } - }); - myShareProfileCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final SingleInspectionProfilePanel panel = getSelectedPanel(); - LOG.assertTrue(panel != null, "No settings panel for: " + getSelectedObject()); - panel.setProfileShared(myShareProfileCheckBox.isSelected()); - myProfiles.repaint(); - } - }); - - myPanel.setLayout(myLayout); - return myWholePanel; - } - - protected abstract InspectionProfileImpl getCurrentProfile(); - - public boolean isModified() { - for (SingleInspectionProfilePanel panel : myPanels.values()) { - if (panel.isModified()) return true; - } - if (getProfiles().size() != myPanels.size()) return true; - return !myDeletedProfiles.isEmpty(); - } - - public void apply() throws ConfigurationException { - for (final Iterator it = myPanels.keySet().iterator(); it.hasNext();) { - final String name = it.next(); - if (myDeletedProfiles.remove(name)) { - deleteProfile(name); - it.remove(); - } else { - myPanels.get(name).apply(); - } - } - } - - protected void deleteProfile(String name) { - if (myProfileManager.getProfile(name, false) != null) { - myProfileManager.deleteProfile(name); - } - if (myProjectProfileManager.getProfile(name, false) != null) { - myProjectProfileManager.deleteProfile(name); - } - } - - public void reset() { - myDeletedProfiles.clear(); - myPanels.clear(); - final DefaultComboBoxModel model = new DefaultComboBoxModel(); - myProfiles.setModel(model); - for (Profile profile : getProfiles()) { - model.addElement(profile); - final String profileName = profile.getName(); - final SingleInspectionProfilePanel panel = new SingleInspectionProfilePanel(myProjectProfileManager, profileName, ((InspectionProfileImpl)profile).getModifiableModel()); - myPanels.put(profileName, panel); - panel.reset(); - myPanel.add(profileName, panel); - } - final InspectionProfileImpl inspectionProfile = getCurrentProfile(); - myProfiles.setSelectedItem(inspectionProfile); - myLayout.show(myPanel, inspectionProfile.getName()); - myDeleteButton.setEnabled(getProfiles().size() > 1 && inspectionProfile.getProfileManager() == myProfileManager); - SingleInspectionProfilePanel panel = getSelectedPanel(); - if (panel != null) { - myShareProfileCheckBox.setSelected(panel.isProfileShared()); - } - } - - protected Collection getProfiles() { - final Collection result = new ArrayList(); - result.addAll(myProfileManager.getProfiles()); - result.addAll(myProjectProfileManager.getProfiles()); - return result; - } - - public void disposeUIResources() { - for (SingleInspectionProfilePanel panel : myPanels.values()) { - panel.disposeUI(); - } - myPanels.clear(); - } - - public void selectProfile(String name) { - for (int i = 0; i < myProfiles.getItemCount(); i++) { - if (Comparing.strEqual(((InspectionProfileImpl)myProfiles.getItemAt(i)).getName(), name)) { - myProfiles.setSelectedIndex(i); - break; - } - } - } - - public void selectInspectionTool(String selectedToolShortName) { - final SingleInspectionProfilePanel panel = getSelectedPanel(); - LOG.assertTrue(panel != null, "No settings panel for: " + getSelectedObject()); - panel.selectInspectionTool(selectedToolShortName); - } - - protected SingleInspectionProfilePanel getSelectedPanel() { - final InspectionProfileImpl inspectionProfile = getSelectedObject(); - assert inspectionProfile != null; - return myPanels.get(inspectionProfile.getName()); - } - - public InspectionProfileImpl getSelectedObject() { - return (InspectionProfileImpl)myProfiles.getSelectedItem(); - } - - @Override - public JComponent getPreferredFocusedComponent() { - final InspectionProfileImpl inspectionProfile = getSelectedObject(); - assert inspectionProfile != null; - return myPanels.get(inspectionProfile.getName()).getTree(); - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: Anna.Kozlova + * Date: 31-Jul-2006 + * Time: 17:44:39 + */ +package com.intellij.profile.codeInspection.ui; + +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; +import com.intellij.codeInspection.ModifiableModel; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.codeInspection.ex.InspectionToolRegistrar; +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.colors.CodeInsightColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.options.BaseConfigurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.profile.Profile; +import com.intellij.profile.codeInspection.InspectionProfileManager; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.util.SystemProperties; +import com.intellij.util.containers.HashMap; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.util.*; + +public abstract class InspectionToolsConfigurable extends BaseConfigurable implements ErrorsConfigurable, SearchableConfigurable { + private final CardLayout myLayout = new CardLayout(); + private JPanel myPanel; + + public static final String ID = "Errors"; + public static final String DISPLAY_NAME = "Inspections"; + + protected JComboBox myProfiles; + private final Map myPanels = new HashMap(); + + private JPanel myWholePanel; + private JButton myAddButton; + private JButton myDeleteButton; + private JButton myImportButton; + private JButton myExportButton; + private JCheckBox myShareProfileCheckBox; + private JButton myCopyButton; + + private final ArrayList myDeletedProfiles = new ArrayList(); + protected final InspectionProfileManager myProfileManager; + protected final InspectionProjectProfileManager myProjectProfileManager; + private static final Logger LOG = Logger.getInstance("#" + InspectionToolsConfigurable.class.getName()); + + + public InspectionToolsConfigurable(InspectionProjectProfileManager projectProfileManager, InspectionProfileManager profileManager) { + myAddButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final ModifiableModel model = SingleInspectionProfilePanel.createNewProfile(-1, getSelectedObject(), myWholePanel, ""); + if (model != null) { + addProfile((InspectionProfileImpl)model); + myDeletedProfiles.remove(model.getName()); + myDeleteButton.setEnabled(true); + } + } + }); + + myDeleteButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final InspectionProfileImpl selectedProfile = (InspectionProfileImpl)myProfiles.getSelectedItem(); + ((DefaultComboBoxModel)myProfiles.getModel()).removeElement(selectedProfile); + myDeletedProfiles.add(selectedProfile.getName()); + myDeleteButton.setEnabled(myProfiles.getModel().getSize() > 1); + } + }); + + myImportButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false){ + @Override + public boolean isFileSelectable(VirtualFile file) { + return file.getFileType().equals(StdFileTypes.XML); + } + }; + descriptor.setDescription("Choose profile file"); + final VirtualFile[] files = FileChooser.chooseFiles(myWholePanel, descriptor); + if (files.length == 0) return; + InspectionProfileImpl profile = + new InspectionProfileImpl("TempProfile", InspectionToolRegistrar.getInstance(), myProfileManager); + try { + Element rootElement = JDOMUtil.loadDocument(VfsUtil.virtualToIoFile(files[0])).getRootElement(); + if (Comparing.strEqual(rootElement.getName(), "component")) {//import right from .idea/inspectProfiles/xxx.xml + rootElement = (Element)rootElement.getChildren().get(0); + } + final Set levels = new HashSet(); + for (Object o : rootElement.getChildren("inspection_tool")) { + final Element inspectElement = (Element)o; + levels.add(inspectElement.getAttributeValue("level")); + for (Object s : inspectElement.getChildren("scope")) { + levels.add(((Element)s).getAttributeValue("level")); + } + } + for (Iterator iterator = levels.iterator(); iterator.hasNext();) { + String level = iterator.next(); + if (myProfileManager.getOwnSeverityRegistrar().getSeverity(level) != null) { + iterator.remove(); + } + } + if (!levels.isEmpty()) { + if (Messages.showYesNoDialog(myWholePanel, "Undefined severities detected: " + + StringUtil.join(levels, ", ") + + ". Do you want to create them?", "Warning", Messages.getWarningIcon()) == + DialogWrapper.OK_EXIT_CODE) { + for (String level : levels) { + final TextAttributes textAttributes = CodeInsightColors.WARNINGS_ATTRIBUTES.getDefaultAttributes(); + HighlightInfoType.HighlightInfoTypeImpl info + = new HighlightInfoType.HighlightInfoTypeImpl(new HighlightSeverity(level, 50), TextAttributesKey.createTextAttributesKey(level)); + myProfileManager.getOwnSeverityRegistrar() + .registerSeverity(new SeverityRegistrar.SeverityBasedTextAttributes(textAttributes.clone(), info), + textAttributes.getErrorStripeColor()); + } + } + } + profile.readExternal(rootElement); + profile.setLocal(true); + profile.initInspectionTools(); + if (myPanels.get(profile.getName()) != null) { + if (Messages.showOkCancelDialog(myWholePanel, "Profile with name \'" + profile.getName() + "\' already exists. Do you want to overwrite it?", "Warning", Messages.getInformationIcon()) != DialogWrapper.OK_EXIT_CODE) return; + } + addProfile((InspectionProfileImpl)profile.getModifiableModel()); + myDeletedProfiles.remove(profile.getName()); + myDeleteButton.setEnabled(true); + } + catch (InvalidDataException e1) { + LOG.error(e1); + } + catch (JDOMException e1) { + LOG.error(e1); + } + catch (IOException e1) { + LOG.error(e1); + } + } + }); + + myExportButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + descriptor.setDescription("Choose directory to store profile file"); + final VirtualFile[] files = FileChooser.chooseFiles(myWholePanel, descriptor); + if (files.length == 0) return; + final Element element = new Element("inspections"); + try { + final SingleInspectionProfilePanel panel = getSelectedPanel(); + LOG.assertTrue(panel != null); + final InspectionProfileImpl profile = (InspectionProfileImpl)panel.getSelectedProfile(); + profile.writeExternal(element); + final String filePath = files[0].getPath() + File.separator + FileUtil.sanitizeFileName(profile.getName()) + ".xml"; + if (new File(filePath).isFile()) { + if (Messages.showOkCancelDialog(myWholePanel, "File \'" + filePath + "\' already exist. Do you want to override it?", "Warning", Messages.getQuestionIcon()) != DialogWrapper.OK_EXIT_CODE) return; + } + JDOMUtil.writeDocument(new Document(element), filePath, SystemProperties.getLineSeparator()); + } + catch (WriteExternalException e1) { + LOG.error(e1); + } + catch (IOException e1) { + LOG.error(e1); + } + } + }); + + myCopyButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final InspectionProfileImpl model = (InspectionProfileImpl)SingleInspectionProfilePanel.createNewProfile(0, getSelectedObject(), myWholePanel, ""); + if (model != null) { + addProfile((InspectionProfileImpl)model.getModifiableModel()); + myDeletedProfiles.remove(model.getName()); + myDeleteButton.setEnabled(true); + } + } + }); + + myProjectProfileManager = projectProfileManager; + myProfileManager = profileManager; + } + + private void addProfile(InspectionProfileImpl model) { + final String modelName = model.getName(); + final SingleInspectionProfilePanel panel = new SingleInspectionProfilePanel(myProjectProfileManager, modelName, model); + panel.reset(); + myPanel.add(modelName, panel); + if (!myPanels.containsKey(modelName)) { + ((DefaultComboBoxModel)myProfiles.getModel()).addElement(model); + } + myPanels.put(modelName, panel); + myProfiles.setSelectedItem(model); + } + + public String getDisplayName() { + return DISPLAY_NAME; + } + + public Icon getIcon() { + return IconLoader.getIcon("/general/configurableErrorHighlighting.png"); + } + + public String getHelpTopic() { + return "preferences.inspections"; + } + + public String getId() { + return ID; + } + + public Runnable enableSearch(final String option) { + return new Runnable(){ + public void run() { + SingleInspectionProfilePanel panel = getSelectedPanel(); + if (panel != null) { + panel.filterTree(option); + } + } + }; + } + + public JComponent createComponent() { + myProfiles.setRenderer(new ListCellRendererWrapper(myProfiles.getRenderer()) { + @Override + public void customize(final JList list, final Profile value, final int index, final boolean selected, final boolean hasFocus) { + final String profileName = value.getName(); + setText(profileName); + final SingleInspectionProfilePanel panel = myPanels.get(profileName); + setIcon(panel != null && panel.isProfileShared() ? Profile.PROJECT_PROFILE : Profile.LOCAL_PROFILE); + } + }); + myProfiles.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final InspectionProfileImpl profile = (InspectionProfileImpl)myProfiles.getSelectedItem(); + myDeleteButton.setEnabled(myProfiles.getModel().getSize() > 1); + myLayout.show(myPanel, profile.getName()); + SingleInspectionProfilePanel panel = getSelectedPanel(); + if (panel != null) { + myShareProfileCheckBox.setSelected(panel.isProfileShared()); + } + } + }); + myShareProfileCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final SingleInspectionProfilePanel panel = getSelectedPanel(); + LOG.assertTrue(panel != null, "No settings panel for: " + getSelectedObject()); + panel.setProfileShared(myShareProfileCheckBox.isSelected()); + myProfiles.repaint(); + } + }); + + myPanel.setLayout(myLayout); + return myWholePanel; + } + + protected abstract InspectionProfileImpl getCurrentProfile(); + + public boolean isModified() { + for (SingleInspectionProfilePanel panel : myPanels.values()) { + if (panel.isModified()) return true; + } + if (getProfiles().size() != myPanels.size()) return true; + return !myDeletedProfiles.isEmpty(); + } + + public void apply() throws ConfigurationException { + for (final Iterator it = myPanels.keySet().iterator(); it.hasNext();) { + final String name = it.next(); + if (myDeletedProfiles.remove(name)) { + deleteProfile(name); + it.remove(); + } else { + myPanels.get(name).apply(); + } + } + } + + protected void deleteProfile(String name) { + if (myProfileManager.getProfile(name, false) != null) { + myProfileManager.deleteProfile(name); + } + if (myProjectProfileManager.getProfile(name, false) != null) { + myProjectProfileManager.deleteProfile(name); + } + } + + public void reset() { + myDeletedProfiles.clear(); + myPanels.clear(); + final DefaultComboBoxModel model = new DefaultComboBoxModel(); + myProfiles.setModel(model); + for (Profile profile : getProfiles()) { + model.addElement(profile); + final String profileName = profile.getName(); + final SingleInspectionProfilePanel panel = new SingleInspectionProfilePanel(myProjectProfileManager, profileName, ((InspectionProfileImpl)profile).getModifiableModel()); + myPanels.put(profileName, panel); + panel.reset(); + myPanel.add(profileName, panel); + } + final InspectionProfileImpl inspectionProfile = getCurrentProfile(); + myProfiles.setSelectedItem(inspectionProfile); + myLayout.show(myPanel, inspectionProfile.getName()); + myDeleteButton.setEnabled(getProfiles().size() > 1 && inspectionProfile.getProfileManager() == myProfileManager); + SingleInspectionProfilePanel panel = getSelectedPanel(); + if (panel != null) { + myShareProfileCheckBox.setSelected(panel.isProfileShared()); + } + } + + protected Collection getProfiles() { + final Collection result = new ArrayList(); + result.addAll(myProfileManager.getProfiles()); + result.addAll(myProjectProfileManager.getProfiles()); + return result; + } + + public void disposeUIResources() { + for (SingleInspectionProfilePanel panel : myPanels.values()) { + panel.disposeUI(); + } + myPanels.clear(); + } + + public void selectProfile(String name) { + for (int i = 0; i < myProfiles.getItemCount(); i++) { + if (Comparing.strEqual(((InspectionProfileImpl)myProfiles.getItemAt(i)).getName(), name)) { + myProfiles.setSelectedIndex(i); + break; + } + } + } + + public void selectInspectionTool(String selectedToolShortName) { + final SingleInspectionProfilePanel panel = getSelectedPanel(); + LOG.assertTrue(panel != null, "No settings panel for: " + getSelectedObject()); + panel.selectInspectionTool(selectedToolShortName); + } + + protected SingleInspectionProfilePanel getSelectedPanel() { + final InspectionProfileImpl inspectionProfile = getSelectedObject(); + assert inspectionProfile != null; + return myPanels.get(inspectionProfile.getName()); + } + + public InspectionProfileImpl getSelectedObject() { + return (InspectionProfileImpl)myProfiles.getSelectedItem(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + final InspectionProfileImpl inspectionProfile = getSelectedObject(); + assert inspectionProfile != null; + return myPanels.get(inspectionProfile.getName()).getTree(); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java b/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java index 1d48a5289380..1fb72c4108dc 100644 --- a/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java +++ b/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java @@ -1,295 +1,295 @@ -/* - * 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; - -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.NullableComputable; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.impl.light.LightElement; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.PsiFileWithStubSupport; -import com.intellij.psi.stubs.StubBase; -import com.intellij.psi.stubs.StubElement; -import com.intellij.psi.stubs.StubTree; -import com.intellij.psi.tree.IStubFileElementType; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Set; - -/** - * @author db - */ -public abstract class PsiAnchor { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.PsiAnchor"); - @Nullable - public abstract PsiElement retrieve(); - public abstract PsiFile getFile(); - public abstract int getStartOffset(); - public abstract int getEndOffset(); - - public static PsiAnchor create(@NotNull final PsiElement element) { - LOG.assertTrue(element.isValid()); - - if (element instanceof PsiFile) { - return new HardReference(element); - } - - PsiFile file = element.getContainingFile(); - if (file == null) { - return new HardReference(element); - } - - if (element instanceof StubBasedPsiElement && element.isPhysical() && (element instanceof PsiCompiledElement || ((PsiFileImpl)file).getContentElementType() instanceof IStubFileElementType)) { - final StubBasedPsiElement elt = (StubBasedPsiElement)element; - if (elt.getStub() != null || elt.getElementType().shouldCreateStub(element.getNode())) { - int index = calcStubIndex((StubBasedPsiElement)element); - if (index != -1) return new StubIndexReference(file, index); - } - } - - TextRange textRange = element.getTextRange(); - if (textRange == null || element instanceof LightElement) { - return new HardReference(element); - } - - Language lang = null; - final FileViewProvider viewProvider = file.getViewProvider(); - final Set languages = viewProvider.getLanguages(); - for (Language l : languages) { - if (PsiTreeUtil.isAncestor(viewProvider.getPsi(l), element, false)) { - lang = l; - break; - } - } - - if (lang == null) lang = element.getLanguage(); - return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang); - } - - private static int calcStubIndex(StubBasedPsiElement psi) { - if (psi instanceof PsiFile) { - return 0; - } - - final StubElement liveStub = psi.getStub(); - if (liveStub != null) { - return ((StubBase)liveStub).id; - } - - PsiFileImpl file = (PsiFileImpl)psi.getContainingFile(); - final StubTree stubTree = file.calcStubTree(); - for (StubElement stb : stubTree.getPlainList()) { - if (stb.getPsi() == psi) { - return ((StubBase)stb).id; - } - } - - return -1; // it is possible via custom stub builder intentionally not producing stubs for stubbed elements - } - - - private static class TreeRangeReference extends PsiAnchor { - private final PsiFile myFile; - private final Language myLanguage; - private final int myStartOffset; - private final int myEndOffset; - private final Class myClass; - - private TreeRangeReference(final PsiFile file, final int startOffset, final int endOffset, final Class aClass, final Language language) { - myFile = file; - myStartOffset = startOffset; - myEndOffset = endOffset; - myClass = aClass; - myLanguage = language; - } - - @Nullable - public PsiElement retrieve() { - PsiElement element = myFile.getViewProvider().findElementAt(myStartOffset, myLanguage); - if (element == null) return null; - - while (!element.getClass().equals(myClass) || - element.getTextRange().getStartOffset() != myStartOffset || - element.getTextRange().getEndOffset() != myEndOffset) { - element = element.getParent(); - if (element == null || element.getTextRange() == null) return null; - } - - return element; - } - - public PsiFile getFile() { - return myFile; - } - - public int getStartOffset() { - return myStartOffset; - } - - public int getEndOffset() { - return myEndOffset; - } - - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof TreeRangeReference)) return false; - - final TreeRangeReference that = (TreeRangeReference)o; - - if (myEndOffset != that.myEndOffset) return false; - if (myStartOffset != that.myStartOffset) return false; - if (myClass != null ? !myClass.equals(that.myClass) : that.myClass != null) return false; - if (myFile != null ? !myFile.equals(that.myFile) : that.myFile != null) return false; - - return true; - } - - public int hashCode() { - int result = myClass != null ? myClass.getName().hashCode() : 0; - result = 31 * result + myStartOffset; //todo - result = 31 * result + myEndOffset; - if (myFile != null) { - result = 31 * result + myFile.getName().hashCode(); - } - - return result; - } - } - - private static class HardReference extends PsiAnchor { - private final PsiElement myElement; - - private HardReference(final PsiElement element) { - myElement = element; - } - - public PsiElement retrieve() { - return myElement; - } - - public PsiFile getFile() { - return myElement.getContainingFile(); - } - - public int getStartOffset() { - return myElement.getTextRange().getStartOffset(); - } - - public int getEndOffset() { - return myElement.getTextRange().getEndOffset(); - } - - - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof HardReference)) return false; - - final HardReference that = (HardReference)o; - - return myElement.equals(that.myElement); - } - - public int hashCode() { - return myElement.hashCode(); - } - } - - public static class StubIndexReference extends PsiAnchor { - private final VirtualFile myVirtualFile; - private final Project myProject; - private final int myIndex; - - public StubIndexReference(@NotNull PsiFile file, final int index) { - myVirtualFile = file.getVirtualFile(); - myProject = file.getProject(); - myIndex = index; - } - - public PsiFile getFile() { - if (myProject.isDisposed() || !myVirtualFile.isValid()) return null; - return PsiManager.getInstance(myProject).findFile(myVirtualFile); - } - - public PsiElement retrieve() { - return ApplicationManager.getApplication().runReadAction(new NullableComputable() { - public PsiElement compute() { - PsiFileWithStubSupport fileImpl = (PsiFileWithStubSupport)getFile(); - if (fileImpl == null) return null; - StubTree tree = fileImpl.getStubTree(); - - boolean foreign = tree == null; - if (foreign) { - if (fileImpl instanceof PsiFileImpl) { - tree = ((PsiFileImpl)fileImpl).calcStubTree(); - } - else { - return null; - } - } - - StubElement stub = tree.getPlainList().get(myIndex); - - if (foreign) { - final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi(); - if (cachedPsi != null) return cachedPsi; - - final ASTNode ast = fileImpl.findTreeForStub(tree, stub); - return ast != null ? ast.getPsi() : null; - } - else { - return stub != null ? stub.getPsi() : null; - } - } - }); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof StubIndexReference)) return false; - - final StubIndexReference that = (StubIndexReference)o; - - return myIndex == that.myIndex && myVirtualFile.equals(that.myVirtualFile); - } - - @Override - public int hashCode() { - return 31 * myVirtualFile.hashCode() + myIndex; - } - - public int getStartOffset() { - final PsiElement resolved = retrieve(); - if (resolved == null) throw new PsiInvalidElementAccessException(null); - return resolved.getTextRange().getStartOffset(); - } - - public int getEndOffset() { - final PsiElement resolved = retrieve(); - if (resolved == null) throw new PsiInvalidElementAccessException(null); - return resolved.getTextRange().getEndOffset(); - } - } -} - +/* + * 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; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.NullableComputable; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.impl.light.LightElement; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.PsiFileWithStubSupport; +import com.intellij.psi.stubs.StubBase; +import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.stubs.StubTree; +import com.intellij.psi.tree.IStubFileElementType; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * @author db + */ +public abstract class PsiAnchor { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.PsiAnchor"); + @Nullable + public abstract PsiElement retrieve(); + public abstract PsiFile getFile(); + public abstract int getStartOffset(); + public abstract int getEndOffset(); + + public static PsiAnchor create(@NotNull final PsiElement element) { + LOG.assertTrue(element.isValid()); + + if (element instanceof PsiFile) { + return new HardReference(element); + } + + PsiFile file = element.getContainingFile(); + if (file == null) { + return new HardReference(element); + } + + if (element instanceof StubBasedPsiElement && element.isPhysical() && (element instanceof PsiCompiledElement || ((PsiFileImpl)file).getContentElementType() instanceof IStubFileElementType)) { + final StubBasedPsiElement elt = (StubBasedPsiElement)element; + if (elt.getStub() != null || elt.getElementType().shouldCreateStub(element.getNode())) { + int index = calcStubIndex((StubBasedPsiElement)element); + if (index != -1) return new StubIndexReference(file, index); + } + } + + TextRange textRange = element.getTextRange(); + if (textRange == null || element instanceof LightElement) { + return new HardReference(element); + } + + Language lang = null; + final FileViewProvider viewProvider = file.getViewProvider(); + final Set languages = viewProvider.getLanguages(); + for (Language l : languages) { + if (PsiTreeUtil.isAncestor(viewProvider.getPsi(l), element, false)) { + lang = l; + break; + } + } + + if (lang == null) lang = element.getLanguage(); + return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang); + } + + private static int calcStubIndex(StubBasedPsiElement psi) { + if (psi instanceof PsiFile) { + return 0; + } + + final StubElement liveStub = psi.getStub(); + if (liveStub != null) { + return ((StubBase)liveStub).id; + } + + PsiFileImpl file = (PsiFileImpl)psi.getContainingFile(); + final StubTree stubTree = file.calcStubTree(); + for (StubElement stb : stubTree.getPlainList()) { + if (stb.getPsi() == psi) { + return ((StubBase)stb).id; + } + } + + return -1; // it is possible via custom stub builder intentionally not producing stubs for stubbed elements + } + + + private static class TreeRangeReference extends PsiAnchor { + private final PsiFile myFile; + private final Language myLanguage; + private final int myStartOffset; + private final int myEndOffset; + private final Class myClass; + + private TreeRangeReference(final PsiFile file, final int startOffset, final int endOffset, final Class aClass, final Language language) { + myFile = file; + myStartOffset = startOffset; + myEndOffset = endOffset; + myClass = aClass; + myLanguage = language; + } + + @Nullable + public PsiElement retrieve() { + PsiElement element = myFile.getViewProvider().findElementAt(myStartOffset, myLanguage); + if (element == null) return null; + + while (!element.getClass().equals(myClass) || + element.getTextRange().getStartOffset() != myStartOffset || + element.getTextRange().getEndOffset() != myEndOffset) { + element = element.getParent(); + if (element == null || element.getTextRange() == null) return null; + } + + return element; + } + + public PsiFile getFile() { + return myFile; + } + + public int getStartOffset() { + return myStartOffset; + } + + public int getEndOffset() { + return myEndOffset; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TreeRangeReference)) return false; + + final TreeRangeReference that = (TreeRangeReference)o; + + if (myEndOffset != that.myEndOffset) return false; + if (myStartOffset != that.myStartOffset) return false; + if (myClass != null ? !myClass.equals(that.myClass) : that.myClass != null) return false; + if (myFile != null ? !myFile.equals(that.myFile) : that.myFile != null) return false; + + return true; + } + + public int hashCode() { + int result = myClass != null ? myClass.getName().hashCode() : 0; + result = 31 * result + myStartOffset; //todo + result = 31 * result + myEndOffset; + if (myFile != null) { + result = 31 * result + myFile.getName().hashCode(); + } + + return result; + } + } + + private static class HardReference extends PsiAnchor { + private final PsiElement myElement; + + private HardReference(final PsiElement element) { + myElement = element; + } + + public PsiElement retrieve() { + return myElement; + } + + public PsiFile getFile() { + return myElement.getContainingFile(); + } + + public int getStartOffset() { + return myElement.getTextRange().getStartOffset(); + } + + public int getEndOffset() { + return myElement.getTextRange().getEndOffset(); + } + + + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof HardReference)) return false; + + final HardReference that = (HardReference)o; + + return myElement.equals(that.myElement); + } + + public int hashCode() { + return myElement.hashCode(); + } + } + + public static class StubIndexReference extends PsiAnchor { + private final VirtualFile myVirtualFile; + private final Project myProject; + private final int myIndex; + + public StubIndexReference(@NotNull PsiFile file, final int index) { + myVirtualFile = file.getVirtualFile(); + myProject = file.getProject(); + myIndex = index; + } + + public PsiFile getFile() { + if (myProject.isDisposed() || !myVirtualFile.isValid()) return null; + return PsiManager.getInstance(myProject).findFile(myVirtualFile); + } + + public PsiElement retrieve() { + return ApplicationManager.getApplication().runReadAction(new NullableComputable() { + public PsiElement compute() { + PsiFileWithStubSupport fileImpl = (PsiFileWithStubSupport)getFile(); + if (fileImpl == null) return null; + StubTree tree = fileImpl.getStubTree(); + + boolean foreign = tree == null; + if (foreign) { + if (fileImpl instanceof PsiFileImpl) { + tree = ((PsiFileImpl)fileImpl).calcStubTree(); + } + else { + return null; + } + } + + StubElement stub = tree.getPlainList().get(myIndex); + + if (foreign) { + final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi(); + if (cachedPsi != null) return cachedPsi; + + final ASTNode ast = fileImpl.findTreeForStub(tree, stub); + return ast != null ? ast.getPsi() : null; + } + else { + return stub != null ? stub.getPsi() : null; + } + } + }); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof StubIndexReference)) return false; + + final StubIndexReference that = (StubIndexReference)o; + + return myIndex == that.myIndex && myVirtualFile.equals(that.myVirtualFile); + } + + @Override + public int hashCode() { + return 31 * myVirtualFile.hashCode() + myIndex; + } + + public int getStartOffset() { + final PsiElement resolved = retrieve(); + if (resolved == null) throw new PsiInvalidElementAccessException(null); + return resolved.getTextRange().getStartOffset(); + } + + public int getEndOffset() { + final PsiElement resolved = retrieve(); + if (resolved == null) throw new PsiInvalidElementAccessException(null); + return resolved.getTextRange().getEndOffset(); + } + } +} + diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiCachedValuesFactory.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiCachedValuesFactory.java index 404169780c1c..00924cb57fcb 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiCachedValuesFactory.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiCachedValuesFactory.java @@ -1,69 +1,69 @@ -/* - * 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.impl; - -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiManager; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.ParameterizedCachedValueProvider; -import com.intellij.psi.util.ParameterizedCachedValue; -import com.intellij.psi.util.CachedValue; -import com.intellij.util.*; -import org.jetbrains.annotations.NotNull; - -/** - * @author Dmitry Avdeev - */ -public class PsiCachedValuesFactory implements CachedValuesFactory { - - private final Project myProject; - private final PsiManager myManager; - - public PsiCachedValuesFactory(PsiManager manager) { - myManager = manager; - myProject = manager.getProject(); - } - - public CachedValue createCachedValue(@NotNull CachedValueProvider provider, boolean trackValue) { - return trackValue ? new PsiCachedValueImpl(myManager, provider) { - @Override - protected Object[] getDependencies(CachedValueProvider.Result result) { - return getDependenciesPlusValue(result); - } - } : new PsiCachedValueImpl(myManager, provider); - } - - public ParameterizedCachedValue createParameterizedCachedValue(@NotNull ParameterizedCachedValueProvider provider, - boolean trackValue) { - return trackValue ? new PsiParameterizedCachedValue(myManager, provider) { - @Override - public boolean isFromMyProject(Project project) { - return myProject == project; - } - - @Override - protected Object[] getDependencies(CachedValueProvider.Result tResult) { - return getDependenciesPlusValue(tResult); - } - } : new PsiParameterizedCachedValue(myManager, provider) { - @Override - public boolean isFromMyProject(Project project) { - return myProject == project; - } - }; - } -} +/* + * 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.impl; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiManager; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.ParameterizedCachedValueProvider; +import com.intellij.psi.util.ParameterizedCachedValue; +import com.intellij.psi.util.CachedValue; +import com.intellij.util.*; +import org.jetbrains.annotations.NotNull; + +/** + * @author Dmitry Avdeev + */ +public class PsiCachedValuesFactory implements CachedValuesFactory { + + private final Project myProject; + private final PsiManager myManager; + + public PsiCachedValuesFactory(PsiManager manager) { + myManager = manager; + myProject = manager.getProject(); + } + + public CachedValue createCachedValue(@NotNull CachedValueProvider provider, boolean trackValue) { + return trackValue ? new PsiCachedValueImpl(myManager, provider) { + @Override + protected Object[] getDependencies(CachedValueProvider.Result result) { + return getDependenciesPlusValue(result); + } + } : new PsiCachedValueImpl(myManager, provider); + } + + public ParameterizedCachedValue createParameterizedCachedValue(@NotNull ParameterizedCachedValueProvider provider, + boolean trackValue) { + return trackValue ? new PsiParameterizedCachedValue(myManager, provider) { + @Override + public boolean isFromMyProject(Project project) { + return myProject == project; + } + + @Override + protected Object[] getDependencies(CachedValueProvider.Result tResult) { + return getDependenciesPlusValue(tResult); + } + } : new PsiParameterizedCachedValue(myManager, provider) { + @Override + public boolean isFromMyProject(Project project) { + return myProject == project; + } + }; + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/IndexCacheManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/IndexCacheManagerImpl.java index 2ab338fc3844..b381d41b5dae 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/IndexCacheManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/IndexCacheManagerImpl.java @@ -1,197 +1,197 @@ -/* - * 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.impl.cache.impl; - -import com.intellij.ide.caches.CacheUpdater; -import com.intellij.injected.editor.VirtualFileWindow; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ReadActionProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.impl.cache.CacheManager; -import com.intellij.psi.impl.cache.impl.id.IdIndex; -import com.intellij.psi.impl.cache.impl.id.IdIndexEntry; -import com.intellij.psi.impl.cache.impl.todo.TodoIndex; -import com.intellij.psi.impl.cache.impl.todo.TodoIndexEntry; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.IndexPattern; -import com.intellij.psi.search.IndexPatternProvider; -import com.intellij.util.CommonProcessors; -import com.intellij.util.Processor; -import com.intellij.util.indexing.FileBasedIndex; -import gnu.trove.THashSet; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/** - * @author Eugene Zhuravlev - * Date: Jan 16, 2008 - */ -public class IndexCacheManagerImpl implements CacheManager{ - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.cache.impl.IndexCacheManagerImpl"); - private final Project myProject; - private final PsiManager myPsiManager; - - public IndexCacheManagerImpl(PsiManager psiManager) { - myPsiManager = psiManager; - myProject = psiManager.getProject(); - } - - public void initialize() { - } - - public void dispose() { - } - - @NotNull - public CacheUpdater[] getCacheUpdaters() { - return new CacheUpdater[0]; // do not expose own updaters - } - - @NotNull - public PsiFile[] getFilesWithWord(@NotNull final String word, final short occurenceMask, @NotNull final GlobalSearchScope scope, final boolean caseSensitively) { - CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(); - processFilesWithWord(processor, word, occurenceMask, scope, caseSensitively); - return processor.getResults().isEmpty() ? PsiFile.EMPTY_ARRAY : processor.toArray(PsiFile.EMPTY_ARRAY); - } - - public static boolean shouldBeFound(VirtualFile virtualFile, ProjectFileIndex index) { - return (index.isInContent(virtualFile) || index.isInLibrarySource(virtualFile)) && !virtualFile.getFileType().isBinary(); - } - - public boolean processFilesWithWord(@NotNull final Processor psiFileProcessor, @NotNull final String word, final short occurrenceMask, @NotNull final GlobalSearchScope scope, final boolean caseSensitively) { - final Set vFiles = new THashSet(); - final GlobalSearchScope projectScope = GlobalSearchScope.allScope(myProject); - try { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - FileBasedIndex.getInstance().processValues(IdIndex.NAME, new IdIndexEntry(word, caseSensitively), null, new FileBasedIndex.ValueProcessor() { - public boolean process(final VirtualFile file, final Integer value) { - ProgressManager.checkCanceled(); - final int mask = value.intValue(); - if ((mask & occurrenceMask) != 0) { - vFiles.add(file); - } - return true; - } - }, projectScope); - } - }); - } - catch (IndexNotReadyException e) { - throw new ProcessCanceledException(); - } - - if (vFiles.isEmpty()) return true; - - final ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex(); - - final Processor virtualFileProcessor = new ReadActionProcessor() { - @Override - public boolean processInReadAction(VirtualFile virtualFile) { - LOG.assertTrue(virtualFile.isValid()); - if (virtualFile.isValid() && scope.contains(virtualFile) && shouldBeFound(virtualFile, index)) { - final PsiFile psiFile = myPsiManager.findFile(virtualFile); - return psiFile == null || psiFileProcessor.process(psiFile); - } - return true; - } - }; - - - // IMPORTANT!!! - // Since implementation of virtualFileProcessor.process() may call indices directly or indirectly, - // we cannot call it inside FileBasedIndex.processValues() method - // If we do, deadlocks are possible (IDEADEV-42137). So first we obtain files with the word specified, - // and then process them not holding indices' read lock. - for (VirtualFile vFile : vFiles) { - ProgressManager.checkCanceled(); - if (!virtualFileProcessor.process(vFile)) { - return false; - } - } - return true; - } - - @NotNull - public PsiFile[] getFilesWithTodoItems() { - final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); - final Set allFiles = new HashSet(); - final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); - for (IndexPattern indexPattern : CacheUtil.getIndexPatterns()) { - final Collection files = fileBasedIndex.getContainingFiles( - TodoIndex.NAME, - new TodoIndexEntry(indexPattern.getPatternString(), indexPattern.isCaseSensitive()), GlobalSearchScope.allScope(myProject)); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (VirtualFile file : files) { - if (projectFileIndex.isInContent(file)) { - final PsiFile psiFile = myPsiManager.findFile(file); - if (psiFile != null) { - allFiles.add(psiFile); - } - } - } - } - }); - } - return allFiles.isEmpty() ? PsiFile.EMPTY_ARRAY : allFiles.toArray(new PsiFile[allFiles.size()]); - } - - public int getTodoCount(@NotNull final VirtualFile file, final IndexPatternProvider patternProvider) { - if (file instanceof VirtualFileWindow) return -1; - final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); - int count = 0; - for (IndexPattern indexPattern : patternProvider.getIndexPatterns()) { - count += fetchCount(fileBasedIndex, file, indexPattern); - } - return count; - } - - public int getTodoCount(@NotNull final VirtualFile file, final IndexPattern pattern) { - if (file instanceof VirtualFileWindow) return -1; - return fetchCount(FileBasedIndex.getInstance(), file, pattern); - } - - private int fetchCount(final FileBasedIndex fileBasedIndex, final VirtualFile file, final IndexPattern indexPattern) { - final int[] count = {0}; - fileBasedIndex.processValues( - TodoIndex.NAME, new TodoIndexEntry(indexPattern.getPatternString(), indexPattern.isCaseSensitive()), file, - new FileBasedIndex.ValueProcessor() { - public boolean process(final VirtualFile file, final Integer value) { - count[0] += value.intValue(); - return true; - } - }, GlobalSearchScope.fileScope(myProject, file)); - return count[0]; - } - - public void addOrInvalidateFile(@NotNull final VirtualFile file) { - // empty - } -} +/* + * 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.impl.cache.impl; + +import com.intellij.ide.caches.CacheUpdater; +import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadActionProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.cache.CacheManager; +import com.intellij.psi.impl.cache.impl.id.IdIndex; +import com.intellij.psi.impl.cache.impl.id.IdIndexEntry; +import com.intellij.psi.impl.cache.impl.todo.TodoIndex; +import com.intellij.psi.impl.cache.impl.todo.TodoIndexEntry; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.IndexPattern; +import com.intellij.psi.search.IndexPatternProvider; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Processor; +import com.intellij.util.indexing.FileBasedIndex; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Eugene Zhuravlev + * Date: Jan 16, 2008 + */ +public class IndexCacheManagerImpl implements CacheManager{ + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.cache.impl.IndexCacheManagerImpl"); + private final Project myProject; + private final PsiManager myPsiManager; + + public IndexCacheManagerImpl(PsiManager psiManager) { + myPsiManager = psiManager; + myProject = psiManager.getProject(); + } + + public void initialize() { + } + + public void dispose() { + } + + @NotNull + public CacheUpdater[] getCacheUpdaters() { + return new CacheUpdater[0]; // do not expose own updaters + } + + @NotNull + public PsiFile[] getFilesWithWord(@NotNull final String word, final short occurenceMask, @NotNull final GlobalSearchScope scope, final boolean caseSensitively) { + CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(); + processFilesWithWord(processor, word, occurenceMask, scope, caseSensitively); + return processor.getResults().isEmpty() ? PsiFile.EMPTY_ARRAY : processor.toArray(PsiFile.EMPTY_ARRAY); + } + + public static boolean shouldBeFound(VirtualFile virtualFile, ProjectFileIndex index) { + return (index.isInContent(virtualFile) || index.isInLibrarySource(virtualFile)) && !virtualFile.getFileType().isBinary(); + } + + public boolean processFilesWithWord(@NotNull final Processor psiFileProcessor, @NotNull final String word, final short occurrenceMask, @NotNull final GlobalSearchScope scope, final boolean caseSensitively) { + final Set vFiles = new THashSet(); + final GlobalSearchScope projectScope = GlobalSearchScope.allScope(myProject); + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + FileBasedIndex.getInstance().processValues(IdIndex.NAME, new IdIndexEntry(word, caseSensitively), null, new FileBasedIndex.ValueProcessor() { + public boolean process(final VirtualFile file, final Integer value) { + ProgressManager.checkCanceled(); + final int mask = value.intValue(); + if ((mask & occurrenceMask) != 0) { + vFiles.add(file); + } + return true; + } + }, projectScope); + } + }); + } + catch (IndexNotReadyException e) { + throw new ProcessCanceledException(); + } + + if (vFiles.isEmpty()) return true; + + final ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex(); + + final Processor virtualFileProcessor = new ReadActionProcessor() { + @Override + public boolean processInReadAction(VirtualFile virtualFile) { + LOG.assertTrue(virtualFile.isValid()); + if (virtualFile.isValid() && scope.contains(virtualFile) && shouldBeFound(virtualFile, index)) { + final PsiFile psiFile = myPsiManager.findFile(virtualFile); + return psiFile == null || psiFileProcessor.process(psiFile); + } + return true; + } + }; + + + // IMPORTANT!!! + // Since implementation of virtualFileProcessor.process() may call indices directly or indirectly, + // we cannot call it inside FileBasedIndex.processValues() method + // If we do, deadlocks are possible (IDEADEV-42137). So first we obtain files with the word specified, + // and then process them not holding indices' read lock. + for (VirtualFile vFile : vFiles) { + ProgressManager.checkCanceled(); + if (!virtualFileProcessor.process(vFile)) { + return false; + } + } + return true; + } + + @NotNull + public PsiFile[] getFilesWithTodoItems() { + final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); + final Set allFiles = new HashSet(); + final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); + for (IndexPattern indexPattern : CacheUtil.getIndexPatterns()) { + final Collection files = fileBasedIndex.getContainingFiles( + TodoIndex.NAME, + new TodoIndexEntry(indexPattern.getPatternString(), indexPattern.isCaseSensitive()), GlobalSearchScope.allScope(myProject)); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (VirtualFile file : files) { + if (projectFileIndex.isInContent(file)) { + final PsiFile psiFile = myPsiManager.findFile(file); + if (psiFile != null) { + allFiles.add(psiFile); + } + } + } + } + }); + } + return allFiles.isEmpty() ? PsiFile.EMPTY_ARRAY : allFiles.toArray(new PsiFile[allFiles.size()]); + } + + public int getTodoCount(@NotNull final VirtualFile file, final IndexPatternProvider patternProvider) { + if (file instanceof VirtualFileWindow) return -1; + final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); + int count = 0; + for (IndexPattern indexPattern : patternProvider.getIndexPatterns()) { + count += fetchCount(fileBasedIndex, file, indexPattern); + } + return count; + } + + public int getTodoCount(@NotNull final VirtualFile file, final IndexPattern pattern) { + if (file instanceof VirtualFileWindow) return -1; + return fetchCount(FileBasedIndex.getInstance(), file, pattern); + } + + private int fetchCount(final FileBasedIndex fileBasedIndex, final VirtualFile file, final IndexPattern indexPattern) { + final int[] count = {0}; + fileBasedIndex.processValues( + TodoIndex.NAME, new TodoIndexEntry(indexPattern.getPatternString(), indexPattern.isCaseSensitive()), file, + new FileBasedIndex.ValueProcessor() { + public boolean process(final VirtualFile file, final Integer value) { + count[0] += value.intValue(); + return true; + } + }, GlobalSearchScope.fileScope(myProject, file)); + return count[0]; + } + + public void addOrInvalidateFile(@NotNull final VirtualFile file) { + // empty + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java index 79350d31aafb..ed82047b391b 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java @@ -1,1174 +1,1174 @@ -/* - * 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.impl.file.impl; - -import com.intellij.AppTopics; -import com.intellij.ProjectTopics; -import com.intellij.extapi.psi.PsiFileBase; -import com.intellij.injected.editor.DocumentWindow; -import com.intellij.injected.editor.VirtualFileWindow; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageParserDefinitions; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; -import com.intellij.openapi.fileTypes.*; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ex.ProjectEx; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.roots.ex.ProjectRootManagerEx; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; -import com.intellij.psi.*; -import com.intellij.psi.impl.PsiFileEx; -import com.intellij.psi.impl.PsiManagerImpl; -import com.intellij.psi.impl.PsiTreeChangeEventImpl; -import com.intellij.psi.impl.file.PsiDirectoryFactory; -import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.resolve.FileContextUtil; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.SearchScope; -import com.intellij.util.ConcurrencyUtil; -import com.intellij.util.FileContentUtil; -import com.intellij.util.containers.ConcurrentFactoryMap; -import com.intellij.util.containers.ConcurrentHashMap; -import com.intellij.util.containers.ConcurrentWeakValueHashMap; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.THashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.io.IOException; -import java.io.Writer; -import java.util.*; -import java.util.concurrent.ConcurrentMap; - -public class FileManagerImpl implements FileManager { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.file.impl.FileManagerImpl"); - - private final PsiManagerImpl myManager; - - private final FileTypeManager myFileTypeManager; - - private final ProjectRootManager myProjectRootManager; - private ProjectFileIndex myProjectFileIndex = null; - - private final ConcurrentMap myVFileToPsiDirMap = new ConcurrentHashMap(); - private final ConcurrentWeakValueHashMap myVFileToViewProviderMap = new ConcurrentWeakValueHashMap(); - - private final Map myDefaultUseScopesCache = new ConcurrentFactoryMap() { - @Override - protected GlobalSearchScope create(VirtualFile key) { - GlobalSearchScope scope = getInherentResolveScope(key); - for (ResolveScopeEnlarger enlarger : ResolveScopeEnlarger.EP_NAME.getExtensions()) { - final SearchScope extra = enlarger.getAdditionalResolveScope(key, myManager.getProject()); - if (extra != null) { - scope = scope.union(extra); - } - } - - return scope; - } - }; - - private boolean myInitialized = false; - private boolean myDisposed = false; - - private final FileDocumentManager myFileDocumentManager; - private final MessageBusConnection myConnection; - - public FileManagerImpl(PsiManagerImpl manager, - FileTypeManager fileTypeManager, - FileDocumentManager fileDocumentManager, - ProjectRootManager projectRootManager) { - myFileTypeManager = fileTypeManager; - myManager = manager; - myConnection = manager.getProject().getMessageBus().connect(); - - myFileDocumentManager = fileDocumentManager; - myProjectRootManager = projectRootManager; - - myConnection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() { - - public void enteredDumbMode() { - recalcAllViewProviders(); - } - - public void exitDumbMode() { - recalcAllViewProviders(); - } - }); - - manager.registerRunnableToRunOnChange(new Runnable() { - @Override - public void run() { - myDefaultUseScopesCache.clear(); - } - }); - } - - public void processQueue() { - myVFileToViewProviderMap.processQueue(); - } - - @TestOnly - public ConcurrentWeakValueHashMap getVFileToViewProviderMap() { - return myVFileToViewProviderMap; - } - - private void recalcAllViewProviders() { - handleFileTypesChange(new FileTypesChanged() { - protected void updateMaps() { - for (final FileViewProvider provider : myVFileToViewProviderMap.values()) { - if (!provider.getVirtualFile().isValid()) { - continue; - } - - for (Language language : provider.getLanguages()) { - final PsiFile psi = provider.getPsi(language); - if (psi instanceof PsiFileImpl) { - ((PsiFileImpl)psi).clearCaches(); - } - } - } - removeInvalidFilesAndDirs(false); - } - }); - } - - public void dispose() { - if (myInitialized) { - myConnection.disconnect(); - } - myDisposed = true; - } - - public void cleanupForNextTest() { - myVFileToViewProviderMap.clear(); - myVFileToPsiDirMap.clear(); - processQueue(); - } - - @NotNull - public FileViewProvider findViewProvider(@NotNull final VirtualFile file) { - FileViewProvider viewProvider = getFromInjected(file); - if (viewProvider != null) return viewProvider; - viewProvider = myVFileToViewProviderMap.get(file); - if(viewProvider == null) { - viewProvider = ConcurrencyUtil.cacheOrGet(myVFileToViewProviderMap, file, createFileViewProvider(file, true)); - } - return viewProvider; - } - - public FileViewProvider findCachedViewProvider(@NotNull final VirtualFile file) { - FileViewProvider viewProvider = getFromInjected(file); - if (viewProvider != null) return viewProvider; - return myVFileToViewProviderMap.get(file); - } - - private FileViewProvider getFromInjected(VirtualFile file) { - if (file instanceof VirtualFileWindow) { - DocumentWindow document = ((VirtualFileWindow)file).getDocumentWindow(); - PsiFile psiFile = PsiDocumentManager.getInstance(myManager.getProject()).getCachedPsiFile(document); - if (psiFile == null) return null; - return psiFile.getViewProvider(); - } - return null; - } - - public void setViewProvider(@NotNull final VirtualFile virtualFile, final FileViewProvider fileViewProvider) { - if (!(virtualFile instanceof VirtualFileWindow)) { - if (fileViewProvider == null) { - myVFileToViewProviderMap.remove(virtualFile); - } - else { - myVFileToViewProviderMap.put(virtualFile, fileViewProvider); - } - } - } - - @NotNull - public FileViewProvider createFileViewProvider(@NotNull final VirtualFile file, boolean physical) { - Language language = getLanguage(file); - final FileViewProviderFactory factory = language == null - ? FileTypeFileViewProviders.INSTANCE.forFileType(file.getFileType()) - : LanguageFileViewProviders.INSTANCE.forLanguage(language); - FileViewProvider viewProvider = factory == null ? null : factory.createFileViewProvider(file, language, myManager, physical); - - return viewProvider == null ? new SingleRootFileViewProvider(myManager, file, physical) : viewProvider; - } - - @Nullable - private Language getLanguage(final VirtualFile file) { - final FileType fileType = file.getFileType(); - Project project = myManager.getProject(); - if (fileType instanceof LanguageFileType) { - return LanguageSubstitutors.INSTANCE.substituteLanguage(((LanguageFileType)fileType).getLanguage(), file, project); - } - // Define language for binary file - final ContentBasedClassFileProcessor[] processors = Extensions.getExtensions(ContentBasedClassFileProcessor.EP_NAME); - for (ContentBasedClassFileProcessor processor : processors) { - Language language = processor.obtainLanguageForFile(file); - if (language != null) { - return language; - } - } - - return null; - } - - public void runStartupActivity() { - LOG.assertTrue(!myInitialized); - myDisposed = false; - myInitialized = true; - - myProjectFileIndex = myProjectRootManager.getFileIndex(); - - myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new MyVirtualFileListener())); - - myConnection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() { - public void beforeFileTypesChanged(FileTypeEvent event) {} - - public void fileTypesChanged(FileTypeEvent e) { - handleFileTypesChange(new FileTypesChanged() { - protected void updateMaps() { - removeInvalidFilesAndDirs(true); - } - }); - } - }); - - myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener()); - myConnection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new MyFileDocumentManagerAdapter()); - } - - private abstract class FileTypesChanged implements Runnable { - protected abstract void updateMaps(); - - public void run() { - PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager); - event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES); - myManager.beforePropertyChange(event); - - updateMaps(); - - event = new PsiTreeChangeEventImpl(myManager); - event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES); - myManager.propertyChanged(event); - } - } - - private boolean myProcessingFileTypesChange = false; - private void handleFileTypesChange(final FileTypesChanged runnable) { - if (myProcessingFileTypesChange) return; - myProcessingFileTypesChange = true; - try { - ApplicationManager.getApplication().runWriteAction(runnable); - } - finally { - myProcessingFileTypesChange = false; - } - } - - private void dispatchPendingEvents() { - if (!myInitialized) { - LOG.error("Project is not yet initialized"); - } - if (myDisposed) { - LOG.error("Project is already disposed"); - } - - myConnection.deliverImmediately(); - } - - @TestOnly - public void checkConsistency() { - HashMap fileToViewProvider = new HashMap(myVFileToViewProviderMap); - myVFileToViewProviderMap.clear(); - for (VirtualFile vFile : fileToViewProvider.keySet()) { - final FileViewProvider fileViewProvider = fileToViewProvider.get(vFile); - - LOG.assertTrue(vFile.isValid()); - PsiFile psiFile1 = findFile(vFile); - if (psiFile1 != null && fileViewProvider != null && fileViewProvider.isPhysical()) { // might get collected - PsiFile psi = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage()); - assert psi != null : fileViewProvider +"; "+fileViewProvider.getBaseLanguage()+"; "+psiFile1; - assert psiFile1.getClass().equals(psi.getClass()) : psiFile1 +"; "+psi + "; "+psiFile1.getClass() +"; "+psi.getClass(); - } - } - - HashMap fileToPsiDirMap = new HashMap(myVFileToPsiDirMap); - myVFileToPsiDirMap.clear(); - - for (VirtualFile vFile : fileToPsiDirMap.keySet()) { - LOG.assertTrue(vFile.isValid()); - PsiDirectory psiDir1 = findDirectory(vFile); - LOG.assertTrue(psiDir1 != null); - - VirtualFile parent = vFile.getParent(); - if (parent != null) { - LOG.assertTrue(myVFileToPsiDirMap.containsKey(parent)); - } - } - } - - @Nullable - public PsiFile findFile(@NotNull VirtualFile vFile) { - if (vFile.isDirectory()) return null; - final ProjectEx project = (ProjectEx)myManager.getProject(); - if (project.isDefault()) return null; - - ApplicationManager.getApplication().assertReadAccessAllowed(); - if (!vFile.isValid()) { - LOG.error("Invalid file: " + vFile); - return null; - } - - dispatchPendingEvents(); - final FileViewProvider viewProvider = findViewProvider(vFile); - return viewProvider.getPsi(viewProvider.getBaseLanguage()); - } - - @Nullable - public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) { - ApplicationManager.getApplication().assertReadAccessAllowed(); - LOG.assertTrue(vFile.isValid()); - LOG.assertTrue(!myDisposed); - if (!myInitialized) return null; - - dispatchPendingEvents(); - - return getCachedPsiFileInner(vFile); - } - - @NotNull - public GlobalSearchScope getResolveScope(@NotNull PsiElement element) { - ProgressManager.checkCanceled(); - - VirtualFile vFile; - final Project project = myManager.getProject(); - final PsiFile contextFile; - if (element instanceof PsiDirectory) { - vFile = ((PsiDirectory)element).getVirtualFile(); - contextFile = null; - } - else { - final PsiFile containingFile = element.getContainingFile(); - if (containingFile instanceof PsiCodeFragment) { - final GlobalSearchScope forcedScope = ((PsiCodeFragment)containingFile).getForcedResolveScope(); - if (forcedScope != null) { - return forcedScope; - } - final PsiElement context = containingFile.getContext(); - if (context == null) { - return GlobalSearchScope.allScope(project); - } - return getResolveScope(context); - } - - contextFile = containingFile != null ? FileContextUtil.getContextFile(containingFile) : null; - if (contextFile == null) { - return GlobalSearchScope.allScope(project); - } - else if (contextFile instanceof FileResolveScopeProvider) { - return ((FileResolveScopeProvider) contextFile).getFileResolveScope(); - } - vFile = contextFile.getOriginalFile().getVirtualFile(); - } - if (vFile == null || contextFile == null) { - return GlobalSearchScope.allScope(project); - } - - return getDefaultResolveScope(project, contextFile, vFile); - } - - - public GlobalSearchScope getDefaultResolveScope(final VirtualFile vFile) { - final Project project = myManager.getProject(); - final PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile); - assert psiFile != null; - return getDefaultResolveScope(project, psiFile, vFile); - } - - private GlobalSearchScope getDefaultResolveScope(@NotNull final Project project, @NotNull PsiFile psiFile, @NotNull final VirtualFile vFile) { - return myDefaultUseScopesCache.get(vFile); - } - - private GlobalSearchScope getInherentResolveScope(VirtualFile vFile) { - ProjectFileIndex projectFileIndex = myProjectRootManager.getFileIndex(); - Module module = projectFileIndex.getModuleForFile(vFile); - if (module != null) { - boolean includeTests = projectFileIndex.isInTestSourceContent(vFile) || - !projectFileIndex.isContentJavaSourceFile(vFile); - return GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, includeTests); - } - else { - // resolve references in libraries in context of all modules which contain it - List modulesLibraryUsedIn = new ArrayList(); - List orderEntries = projectFileIndex.getOrderEntriesForFile(vFile); - - for (OrderEntry entry : orderEntries) { - ProgressManager.checkCanceled(); - - if (entry instanceof JdkOrderEntry) { - return ((ProjectRootManagerEx)myProjectRootManager).getScopeForJdk((JdkOrderEntry)entry); - } - - if (entry instanceof LibraryOrderEntry || entry instanceof ModuleOrderEntry) { - modulesLibraryUsedIn.add(entry.getOwnerModule()); - } - } - - return ((ProjectRootManagerEx)myProjectRootManager).getScopeForLibraryUsedIn(modulesLibraryUsedIn); - } - } - - @NotNull - public GlobalSearchScope getUseScope(@NotNull PsiElement element) { - VirtualFile vFile; - final GlobalSearchScope allScope = GlobalSearchScope.allScope(myManager.getProject()); - if (element instanceof PsiDirectory) { - vFile = ((PsiDirectory)element).getVirtualFile(); - } - else { - final PsiFile containingFile = element.getContainingFile(); - if (containingFile == null) return allScope; - final VirtualFile virtualFile = containingFile.getVirtualFile(); - if (virtualFile == null) return allScope; - vFile = virtualFile.getParent(); - } - - if (vFile == null) return allScope; - ProjectFileIndex projectFileIndex = myProjectRootManager.getFileIndex(); - Module module = projectFileIndex.getModuleForFile(vFile); - if (module != null) { - boolean isTest = projectFileIndex.isInTestSourceContent(vFile); - return isTest - ? GlobalSearchScope.moduleTestsWithDependentsScope(module) - : GlobalSearchScope.moduleWithDependentsScope(module); - } - else { - final PsiFile f = element.getContainingFile(); - final VirtualFile vf = f == null ? null : f.getVirtualFile(); - - return f == null || vf == null || vf.isDirectory() || allScope.contains(vf) - ? allScope : GlobalSearchScope.fileScope(f).uniteWith(allScope); - } - } - - - @Nullable - private PsiFile createFileCopyWithNewName(VirtualFile vFile, String name) { - // TODO[ik] remove this. Event handling and generation must be in view providers mechanism since we - // need to track changes in _all_ psi views (e.g. namespace changes in XML) - final FileTypeManager instance = FileTypeManager.getInstance(); - if(instance.isFileIgnored(name)) return null; - final FileType fileTypeByFileName = instance.getFileTypeByFileName(name); - final Document document = FileDocumentManager.getInstance().getDocument(vFile); - return PsiFileFactory.getInstance(myManager.getProject()).createFileFromText(name, fileTypeByFileName, - document != null ? document.getCharsSequence() : "", vFile.getModificationStamp(), - true, false); - } - - @Nullable - public PsiDirectory findDirectory(@NotNull VirtualFile vFile) { - LOG.assertTrue(myInitialized, "Access to psi files should be performed only after startup activity"); - LOG.assertTrue(!myDisposed, "Access to psi files should not be performed after disposal"); - - ApplicationManager.getApplication().assertReadAccessAllowed(); - if (!vFile.isValid()) { - LOG.error("File is not valid:" + vFile.getName()); - } - - if (!vFile.isDirectory()) return null; - dispatchPendingEvents(); - - return findDirectoryImpl(vFile); - } - - private PsiDirectory findDirectoryImpl(final VirtualFile vFile) { - PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); - if (psiDir != null) return psiDir; - - if (myProjectRootManager.getFileIndex().isIgnored(vFile)) return null; - - VirtualFile parent = vFile.getParent(); - if (parent != null) { //? - findDirectoryImpl(parent);// need to cache parent directory - used for firing events - } - - psiDir = PsiDirectoryFactory.getInstance(myManager.getProject()).createDirectory(vFile); - return ConcurrencyUtil.cacheOrGet(myVFileToPsiDirMap, vFile, psiDir); - } - - - @Nullable - private PsiFile getCachedPsiFileInner(VirtualFile file) { - final FileViewProvider fileViewProvider = myVFileToViewProviderMap.get(file); - return fileViewProvider instanceof SingleRootFileViewProvider - ? ((SingleRootFileViewProvider)fileViewProvider).getCachedPsi(fileViewProvider.getBaseLanguage()) : null; - } - - public List getAllCachedFiles() { - List files = new ArrayList(); - for (FileViewProvider provider : myVFileToViewProviderMap.values()) { - if (provider instanceof SingleRootFileViewProvider) { - files.add(((SingleRootFileViewProvider)provider).getCachedPsi(provider.getBaseLanguage())); - } - } - return files; - } - - private void removeInvalidFilesAndDirs(boolean useFind) { - Map fileToPsiDirMap = new THashMap(myVFileToPsiDirMap); - if (useFind) { - myVFileToPsiDirMap.clear(); - } - for (Iterator iterator = fileToPsiDirMap.keySet().iterator(); iterator.hasNext();) { - VirtualFile vFile = iterator.next(); - if (!vFile.isValid()) { - iterator.remove(); - } - else { - PsiDirectory psiDir = findDirectory(vFile); - if (psiDir == null) { - iterator.remove(); - } - } - } - myVFileToPsiDirMap.clear(); - myVFileToPsiDirMap.putAll(fileToPsiDirMap); - - // note: important to update directories map first - findFile uses findDirectory! - Map fileToPsiFileMap = new THashMap(myVFileToViewProviderMap); - if (useFind) { - myVFileToViewProviderMap.clear(); - } - for (Iterator iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) { - VirtualFile vFile = iterator.next(); - - if (!vFile.isValid()) { - iterator.remove(); - continue; - } - - if (useFind) { - FileViewProvider view = fileToPsiFileMap.get(vFile); - if (view == null) { // soft ref. collected - iterator.remove(); - continue; - } - PsiFile psiFile1 = findFile(vFile); - if (psiFile1 == null) { - iterator.remove(); - continue; - } - - PsiFile psi = view.getPsi(view.getBaseLanguage()); - if (psi == null || !psiFile1.getClass().equals(psi.getClass()) || - psiFile1.getViewProvider().getBaseLanguage() != view.getBaseLanguage() // e.g. JSP <-> JSPX - ) { - iterator.remove(); - } - else if (psi instanceof PsiFileImpl) { - ((PsiFileImpl)psi).clearCaches(); - } - } - } - myVFileToViewProviderMap.clear(); - myVFileToViewProviderMap.putAll(fileToPsiFileMap); - } - - public void reloadFromDisk(@NotNull PsiFile file) { - reloadFromDisk(file, false); - } - - private void reloadFromDisk(PsiFile file, boolean ignoreDocument) { - VirtualFile vFile = file.getVirtualFile(); - assert vFile != null; - - if (!(file instanceof PsiBinaryFile)) { - FileDocumentManager fileDocumentManager = myFileDocumentManager; - Document document = fileDocumentManager.getCachedDocument(vFile); - if (document != null && !ignoreDocument){ - fileDocumentManager.reloadFromDisk(document); - } - else{ - PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager); - event.setParent(file); - event.setFile(file); - if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) { - event.setOffset(0); - event.setOldLength(file.getTextLength()); - } - myManager.beforeChildrenChange(event); - - if (file instanceof PsiFileEx) { - ((PsiFileEx)file).onContentReload(); - } - - myManager.childrenChanged(event); - } - } - } - - private class MyVirtualFileListener extends VirtualFileAdapter { - public void contentsChanged(final VirtualFileEvent event) { - // handled by FileDocumentManagerListener - } - - public void fileCreated(VirtualFileEvent event) { - final VirtualFile vFile = event.getFile(); - - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - VirtualFile parent = vFile.getParent(); - PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); - if (parentDir == null) return; // do not notifyListeners event if parent directory was never accessed via PSI - - if (!vFile.isDirectory()) { - PsiFile psiFile = findFile(vFile); - if (psiFile != null) { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - myManager.beforeChildAddition(treeEvent); - treeEvent.setChild(psiFile); - myManager.childAdded(treeEvent); - } - } - else { - PsiDirectory psiDir = findDirectory(vFile); - if (psiDir != null) { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - myManager.beforeChildAddition(treeEvent); - treeEvent.setChild(psiDir); - myManager.childAdded(treeEvent); - } - } - } - } - ); - } - - public void beforeFileDeletion(VirtualFileEvent event) { - final VirtualFile vFile = event.getFile(); - - VirtualFile parent = vFile.getParent(); - final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); - if (parentDir == null) return; // do not notify listeners if parent directory was never accessed via PSI - - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - if (!vFile.isDirectory()) { - PsiFile psiFile = getCachedPsiFile(vFile); - if (psiFile != null) { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - treeEvent.setChild(psiFile); - myManager.beforeChildRemoval(treeEvent); - } - } - else { - PsiDirectory psiDir = findDirectory(vFile); - if (psiDir != null) { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - treeEvent.setChild(psiDir); - myManager.beforeChildRemoval(treeEvent); - } - } - } - } - ); - } - - public void fileDeleted(final VirtualFileEvent event) { - final VirtualFile vFile = event.getFile(); - - VirtualFile parent = event.getParent(); - final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); - - final PsiFile psiFile = getCachedPsiFileInner(vFile); - if (psiFile != null) { - myVFileToViewProviderMap.remove(vFile); - - if (parentDir != null) { - ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() { - public void run() { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - treeEvent.setChild(psiFile); - myManager.childRemoved(treeEvent); - } - }); - } - } - else { - final PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); - if (psiDir != null) { - removeInvalidFilesAndDirs(false); - - if (parentDir != null) { - ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() { - public void run() { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - treeEvent.setChild(psiDir); - myManager.childRemoved(treeEvent); - } - }); - } - } - } - } - - public void beforePropertyChange(final VirtualFilePropertyEvent event) { - final VirtualFile vFile = event.getFile(); - final String propertyName = event.getPropertyName(); - - VirtualFile parent = vFile.getParent(); - final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); - if (parentDir == null) return; // do not notifyListeners event if parent directory was never accessed via PSI - - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - - if (VirtualFile.PROP_NAME.equals(propertyName)) { - final String newName = (String)event.getNewValue(); - - if (vFile.isDirectory()) { - PsiDirectory psiDir = findDirectory(vFile); - if (psiDir != null) { - if (!myFileTypeManager.isFileIgnored(newName)) { - treeEvent.setChild(psiDir); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_DIRECTORY_NAME); - treeEvent.setOldValue(vFile.getName()); - treeEvent.setNewValue(newName); - myManager.beforePropertyChange(treeEvent); - } - else { - treeEvent.setChild(psiDir); - myManager.beforeChildRemoval(treeEvent); - } - } - else { - if (!isExcludeRoot(vFile) && !myFileTypeManager.isFileIgnored(newName)) { - myManager.beforeChildAddition(treeEvent); - } - } - } - else { - final FileViewProvider viewProvider = findViewProvider(vFile); - PsiFile psiFile = viewProvider.getPsi(viewProvider.getBaseLanguage()); - PsiFile psiFile1 = createFileCopyWithNewName(vFile, newName); - - if (psiFile != null) { - if (psiFile1 == null) { - treeEvent.setChild(psiFile); - myManager.beforeChildRemoval(treeEvent); - } - else if (!psiFile1.getClass().equals(psiFile.getClass())) { - treeEvent.setOldChild(psiFile); - myManager.beforeChildReplacement(treeEvent); - } - else { - treeEvent.setChild(psiFile); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_FILE_NAME); - treeEvent.setOldValue(vFile.getName()); - treeEvent.setNewValue(newName); - myManager.beforePropertyChange(treeEvent); - } - } - else { - if (psiFile1 != null) { - myManager.beforeChildAddition(treeEvent); - } - } - } - } - else if (VirtualFile.PROP_WRITABLE.equals(propertyName)) { - PsiFile psiFile = getCachedPsiFileInner(vFile); - if (psiFile == null) return; - - treeEvent.setElement(psiFile); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_WRITABLE); - treeEvent.setOldValue(event.getOldValue()); - treeEvent.setNewValue(event.getNewValue()); - myManager.beforePropertyChange(treeEvent); - } - } - } - ); - } - - private boolean isExcludeRoot(VirtualFile file) { - VirtualFile parent = file.getParent(); - if (parent == null) return false; - - Module module = myProjectRootManager.getFileIndex().getModuleForFile(parent); - if (module == null) return false; - VirtualFile[] excludeRoots = ModuleRootManager.getInstance(module).getExcludeRoots(); - for (VirtualFile root : excludeRoots) { - if (root.equals(file)) return true; - } - return false; - } - - public void propertyChanged(final VirtualFilePropertyEvent event) { - final String propertyName = event.getPropertyName(); - final VirtualFile vFile = event.getFile(); - - final FileViewProvider oldFileViewProvider = findViewProvider(vFile); - final PsiFile oldPsiFile; - if (oldFileViewProvider instanceof SingleRootFileViewProvider) { - oldPsiFile = ((SingleRootFileViewProvider)oldFileViewProvider).getCachedPsi(oldFileViewProvider.getBaseLanguage()); - } - else { - oldPsiFile = null; - } - - VirtualFile parent = vFile.getParent(); - final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); - if (parentDir == null) { - boolean fire = VirtualFile.PROP_NAME.equals(propertyName) && vFile.isDirectory(); - if (fire) { - PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); - fire = psiDir != null; - } - if (!fire) return; // do not fire event if parent directory was never accessed via PSI - } - - if (oldPsiFile != null && oldPsiFile.isPhysical()) { - SmartPointerManagerImpl.fastenBelts(oldPsiFile); - } - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setParent(parentDir); - - if (VirtualFile.PROP_NAME.equals(propertyName)) { - if (vFile.isDirectory()) { - PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); - if (psiDir != null) { - if (myFileTypeManager.isFileIgnored(vFile.getName())) { - removeFilesAndDirsRecursively(vFile); - - treeEvent.setChild(psiDir); - myManager.childRemoved(treeEvent); - } - else { - treeEvent.setElement(psiDir); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_DIRECTORY_NAME); - treeEvent.setOldValue(event.getOldValue()); - treeEvent.setNewValue(event.getNewValue()); - myManager.propertyChanged(treeEvent); - } - } - else { - PsiDirectory psiDir1 = findDirectory(vFile); - if (psiDir1 != null) { - treeEvent.setChild(psiDir1); - myManager.childAdded(treeEvent); - } - } - } - else { - final FileViewProvider fileViewProvider = createFileViewProvider(vFile, true); - final PsiFile newPsiFile = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage()); - if(oldPsiFile != null) { - if (newPsiFile == null) { - myVFileToViewProviderMap.remove(vFile); - - treeEvent.setChild(oldPsiFile); - myManager.childRemoved(treeEvent); - } - else if (!newPsiFile.getClass().equals(oldPsiFile.getClass()) || - newPsiFile.getFileType() != myFileTypeManager.getFileTypeByFileName((String)event.getOldValue()) || - languageDialectChanged(newPsiFile, (String)event.getOldValue()) || - !oldFileViewProvider.getLanguages().equals(fileViewProvider.getLanguages()) || - FileContentUtil.FORCE_RELOAD_REQUESTOR.equals(event.getRequestor()) - ) { - myVFileToViewProviderMap.put(vFile, fileViewProvider); - - treeEvent.setOldChild(oldPsiFile); - treeEvent.setNewChild(newPsiFile); - myManager.childReplaced(treeEvent); - } - else { - treeEvent.setElement(oldPsiFile); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_FILE_NAME); - treeEvent.setOldValue(event.getOldValue()); - treeEvent.setNewValue(event.getNewValue()); - myManager.propertyChanged(treeEvent); - } - } - else if (newPsiFile != null) { - myVFileToViewProviderMap.put(vFile, fileViewProvider); - treeEvent.setChild(newPsiFile); - myManager.childAdded(treeEvent); - } - } - } - else if (VirtualFile.PROP_WRITABLE.equals(propertyName)) { - if (oldPsiFile == null) return; - - treeEvent.setElement(oldPsiFile); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_WRITABLE); - treeEvent.setOldValue(event.getOldValue()); - treeEvent.setNewValue(event.getNewValue()); - myManager.propertyChanged(treeEvent); - } - } - } - ); - } - - public void beforeFileMovement(VirtualFileMoveEvent event) { - final VirtualFile vFile = event.getFile(); - - final PsiDirectory oldParentDir = findDirectory(event.getOldParent()); - final PsiDirectory newParentDir = findDirectory(event.getNewParent()); - if (oldParentDir == null && newParentDir == null) return; - if (myFileTypeManager.isFileIgnored(vFile.getName())) return; - - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - - boolean isExcluded = vFile.isDirectory() && myProjectFileIndex.isIgnored(vFile); - if (oldParentDir != null && !isExcluded) { - if (newParentDir != null) { - treeEvent.setOldParent(oldParentDir); - treeEvent.setNewParent(newParentDir); - if (vFile.isDirectory()) { - PsiDirectory psiDir = findDirectory(vFile); - treeEvent.setChild(psiDir); - } - else { - PsiFile psiFile = findFile(vFile); - treeEvent.setChild(psiFile); - } - myManager.beforeChildMovement(treeEvent); - } - else { - treeEvent.setParent(oldParentDir); - if (vFile.isDirectory()) { - PsiDirectory psiDir = findDirectory(vFile); - treeEvent.setChild(psiDir); - } - else { - PsiFile psiFile = findFile(vFile); - treeEvent.setChild(psiFile); - } - myManager.beforeChildRemoval(treeEvent); - } - } - else { - LOG.assertTrue(newParentDir != null); // checked above - treeEvent.setParent(newParentDir); - myManager.beforeChildAddition(treeEvent); - } - } - } - ); - } - - public void fileMoved(VirtualFileMoveEvent event) { - final VirtualFile vFile = event.getFile(); - - final PsiDirectory oldParentDir = findDirectory(event.getOldParent()); - final PsiDirectory newParentDir = findDirectory(event.getNewParent()); - if (oldParentDir == null && newParentDir == null) return; - - final PsiElement oldElement = vFile.isDirectory() ? myVFileToPsiDirMap.get(vFile) : getCachedPsiFileInner(vFile); - removeInvalidFilesAndDirs(true); - final FileViewProvider viewProvider = findViewProvider(vFile); - final PsiElement newElement; - final FileViewProvider newViewProvider; - if (!vFile.isDirectory()){ - newViewProvider = createFileViewProvider(vFile, true); - newElement = newViewProvider.getPsi(viewProvider.getBaseLanguage()); - } - else { - newElement = findDirectory(vFile); - newViewProvider = null; - } - - if (oldElement == null && newElement == null) return; - - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - if (oldElement == null) { - myVFileToViewProviderMap.put(vFile, newViewProvider); - treeEvent.setParent(newParentDir); - treeEvent.setChild(newElement); - myManager.childAdded(treeEvent); - } - else { - if (newElement == null) { - myVFileToViewProviderMap.remove(vFile); - treeEvent.setParent(oldParentDir); - treeEvent.setChild(oldElement); - myManager.childRemoved(treeEvent); - } - else { - if (oldElement.getClass().equals(newElement.getClass())) { - treeEvent.setOldParent(oldParentDir); - treeEvent.setNewParent(newParentDir); - treeEvent.setChild(newElement); - myManager.childMoved(treeEvent); - } - else { - myVFileToViewProviderMap.put(vFile, newViewProvider); - PsiTreeChangeEventImpl treeRemoveEvent = new PsiTreeChangeEventImpl(myManager); - treeRemoveEvent.setParent(oldParentDir); - treeRemoveEvent.setChild(oldElement); - myManager.childRemoved(treeRemoveEvent); - PsiTreeChangeEventImpl treeAddEvent = new PsiTreeChangeEventImpl(myManager); - treeAddEvent.setParent(newParentDir); - treeAddEvent.setChild(newElement); - myManager.childAdded(treeAddEvent); - } - } - } - } - } - ); - } - - private void removeFilesAndDirsRecursively(VirtualFile vFile) { - if (vFile.isDirectory()) { - myVFileToPsiDirMap.remove(vFile); - - VirtualFile[] children = vFile.getChildren(); - for (VirtualFile child : children) { - removeFilesAndDirsRecursively(child); - } - } - else myVFileToViewProviderMap.remove(vFile); - } - } - - // When file is renamed so that extension changes then language dialect might change and thus psiFile should be invalidated - // We could detect it right now with checks of parser definition equivalence - // The file name under passed psi file is "new" but parser def is from old name - private static boolean languageDialectChanged(final PsiFile newPsiFile, String oldFileName) { - return newPsiFile instanceof PsiFileBase - && LanguageParserDefinitions.INSTANCE.forLanguage(newPsiFile.getLanguage()).getClass() == ((PsiFileBase)newPsiFile).getParserDefinition().getClass() - && !FileUtil.getExtension(newPsiFile.getName()).equals(FileUtil.getExtension(oldFileName)); - } - - private class MyModuleRootListener implements ModuleRootListener { - private VirtualFile[] myOldContentRoots = null; - private volatile int depthCounter = 0; - public void beforeRootsChange(final ModuleRootEvent event) { - if (!myInitialized) return; - if (event.isCausedByFileTypesChange()) return; - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - depthCounter++; - if (depthCounter > 1) return; - - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_ROOTS); - final VirtualFile[] contentRoots = myProjectRootManager.getContentRoots(); - LOG.assertTrue(myOldContentRoots == null); - myOldContentRoots = contentRoots; - treeEvent.setOldValue(contentRoots); - myManager.beforePropertyChange(treeEvent); - } - } - ); - } - - public void rootsChanged(final ModuleRootEvent event) { - dispatchPendingEvents(); - - if (!myInitialized) return; - if (event.isCausedByFileTypesChange()) return; - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - depthCounter--; - assert depthCounter >= 0 : depthCounter; - if (depthCounter > 0) return; - - removeInvalidFilesAndDirs(true); - - PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_ROOTS); - final VirtualFile[] contentRoots = myProjectRootManager.getContentRoots(); - treeEvent.setNewValue(contentRoots); - LOG.assertTrue(myOldContentRoots != null); - treeEvent.setOldValue(myOldContentRoots); - myOldContentRoots = null; - myManager.propertyChanged(treeEvent); - } - } - ); - } - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - public void dumpFilesWithContentLoaded(Writer out) throws IOException { - out.write("Files with content loaded cached in FileManagerImpl:\n"); - Set vFiles = myVFileToViewProviderMap.keySet(); - for (VirtualFile fileCacheEntry : vFiles) { - final FileViewProvider view = myVFileToViewProviderMap.get(fileCacheEntry); - PsiFile psiFile = view.getPsi(view.getBaseLanguage()); - if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) { - out.write(fileCacheEntry.getPresentableUrl()); - out.write("\n"); - } - } - } - - private class MyFileDocumentManagerAdapter extends FileDocumentManagerAdapter { - public void fileWithNoDocumentChanged(VirtualFile file) { - final PsiFile psiFile = getCachedPsiFileInner(file); - if (psiFile != null) { - ApplicationManager.getApplication().runWriteAction( - new ExternalChangeAction() { - public void run() { - reloadFromDisk(psiFile, true); // important to ignore document which might appear already! - } - } - ); - } - } - } -} +/* + * 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.impl.file.impl; + +import com.intellij.AppTopics; +import com.intellij.ProjectTopics; +import com.intellij.extapi.psi.PsiFileBase; +import com.intellij.injected.editor.DocumentWindow; +import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageParserDefinitions; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; +import com.intellij.openapi.fileTypes.*; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ex.ProjectEx; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; +import com.intellij.psi.*; +import com.intellij.psi.impl.PsiFileEx; +import com.intellij.psi.impl.PsiManagerImpl; +import com.intellij.psi.impl.PsiTreeChangeEventImpl; +import com.intellij.psi.impl.file.PsiDirectoryFactory; +import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.resolve.FileContextUtil; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.SearchScope; +import com.intellij.util.ConcurrencyUtil; +import com.intellij.util.FileContentUtil; +import com.intellij.util.containers.ConcurrentFactoryMap; +import com.intellij.util.containers.ConcurrentHashMap; +import com.intellij.util.containers.ConcurrentWeakValueHashMap; +import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.io.IOException; +import java.io.Writer; +import java.util.*; +import java.util.concurrent.ConcurrentMap; + +public class FileManagerImpl implements FileManager { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.file.impl.FileManagerImpl"); + + private final PsiManagerImpl myManager; + + private final FileTypeManager myFileTypeManager; + + private final ProjectRootManager myProjectRootManager; + private ProjectFileIndex myProjectFileIndex = null; + + private final ConcurrentMap myVFileToPsiDirMap = new ConcurrentHashMap(); + private final ConcurrentWeakValueHashMap myVFileToViewProviderMap = new ConcurrentWeakValueHashMap(); + + private final Map myDefaultUseScopesCache = new ConcurrentFactoryMap() { + @Override + protected GlobalSearchScope create(VirtualFile key) { + GlobalSearchScope scope = getInherentResolveScope(key); + for (ResolveScopeEnlarger enlarger : ResolveScopeEnlarger.EP_NAME.getExtensions()) { + final SearchScope extra = enlarger.getAdditionalResolveScope(key, myManager.getProject()); + if (extra != null) { + scope = scope.union(extra); + } + } + + return scope; + } + }; + + private boolean myInitialized = false; + private boolean myDisposed = false; + + private final FileDocumentManager myFileDocumentManager; + private final MessageBusConnection myConnection; + + public FileManagerImpl(PsiManagerImpl manager, + FileTypeManager fileTypeManager, + FileDocumentManager fileDocumentManager, + ProjectRootManager projectRootManager) { + myFileTypeManager = fileTypeManager; + myManager = manager; + myConnection = manager.getProject().getMessageBus().connect(); + + myFileDocumentManager = fileDocumentManager; + myProjectRootManager = projectRootManager; + + myConnection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() { + + public void enteredDumbMode() { + recalcAllViewProviders(); + } + + public void exitDumbMode() { + recalcAllViewProviders(); + } + }); + + manager.registerRunnableToRunOnChange(new Runnable() { + @Override + public void run() { + myDefaultUseScopesCache.clear(); + } + }); + } + + public void processQueue() { + myVFileToViewProviderMap.processQueue(); + } + + @TestOnly + public ConcurrentWeakValueHashMap getVFileToViewProviderMap() { + return myVFileToViewProviderMap; + } + + private void recalcAllViewProviders() { + handleFileTypesChange(new FileTypesChanged() { + protected void updateMaps() { + for (final FileViewProvider provider : myVFileToViewProviderMap.values()) { + if (!provider.getVirtualFile().isValid()) { + continue; + } + + for (Language language : provider.getLanguages()) { + final PsiFile psi = provider.getPsi(language); + if (psi instanceof PsiFileImpl) { + ((PsiFileImpl)psi).clearCaches(); + } + } + } + removeInvalidFilesAndDirs(false); + } + }); + } + + public void dispose() { + if (myInitialized) { + myConnection.disconnect(); + } + myDisposed = true; + } + + public void cleanupForNextTest() { + myVFileToViewProviderMap.clear(); + myVFileToPsiDirMap.clear(); + processQueue(); + } + + @NotNull + public FileViewProvider findViewProvider(@NotNull final VirtualFile file) { + FileViewProvider viewProvider = getFromInjected(file); + if (viewProvider != null) return viewProvider; + viewProvider = myVFileToViewProviderMap.get(file); + if(viewProvider == null) { + viewProvider = ConcurrencyUtil.cacheOrGet(myVFileToViewProviderMap, file, createFileViewProvider(file, true)); + } + return viewProvider; + } + + public FileViewProvider findCachedViewProvider(@NotNull final VirtualFile file) { + FileViewProvider viewProvider = getFromInjected(file); + if (viewProvider != null) return viewProvider; + return myVFileToViewProviderMap.get(file); + } + + private FileViewProvider getFromInjected(VirtualFile file) { + if (file instanceof VirtualFileWindow) { + DocumentWindow document = ((VirtualFileWindow)file).getDocumentWindow(); + PsiFile psiFile = PsiDocumentManager.getInstance(myManager.getProject()).getCachedPsiFile(document); + if (psiFile == null) return null; + return psiFile.getViewProvider(); + } + return null; + } + + public void setViewProvider(@NotNull final VirtualFile virtualFile, final FileViewProvider fileViewProvider) { + if (!(virtualFile instanceof VirtualFileWindow)) { + if (fileViewProvider == null) { + myVFileToViewProviderMap.remove(virtualFile); + } + else { + myVFileToViewProviderMap.put(virtualFile, fileViewProvider); + } + } + } + + @NotNull + public FileViewProvider createFileViewProvider(@NotNull final VirtualFile file, boolean physical) { + Language language = getLanguage(file); + final FileViewProviderFactory factory = language == null + ? FileTypeFileViewProviders.INSTANCE.forFileType(file.getFileType()) + : LanguageFileViewProviders.INSTANCE.forLanguage(language); + FileViewProvider viewProvider = factory == null ? null : factory.createFileViewProvider(file, language, myManager, physical); + + return viewProvider == null ? new SingleRootFileViewProvider(myManager, file, physical) : viewProvider; + } + + @Nullable + private Language getLanguage(final VirtualFile file) { + final FileType fileType = file.getFileType(); + Project project = myManager.getProject(); + if (fileType instanceof LanguageFileType) { + return LanguageSubstitutors.INSTANCE.substituteLanguage(((LanguageFileType)fileType).getLanguage(), file, project); + } + // Define language for binary file + final ContentBasedClassFileProcessor[] processors = Extensions.getExtensions(ContentBasedClassFileProcessor.EP_NAME); + for (ContentBasedClassFileProcessor processor : processors) { + Language language = processor.obtainLanguageForFile(file); + if (language != null) { + return language; + } + } + + return null; + } + + public void runStartupActivity() { + LOG.assertTrue(!myInitialized); + myDisposed = false; + myInitialized = true; + + myProjectFileIndex = myProjectRootManager.getFileIndex(); + + myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new MyVirtualFileListener())); + + myConnection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() { + public void beforeFileTypesChanged(FileTypeEvent event) {} + + public void fileTypesChanged(FileTypeEvent e) { + handleFileTypesChange(new FileTypesChanged() { + protected void updateMaps() { + removeInvalidFilesAndDirs(true); + } + }); + } + }); + + myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener()); + myConnection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new MyFileDocumentManagerAdapter()); + } + + private abstract class FileTypesChanged implements Runnable { + protected abstract void updateMaps(); + + public void run() { + PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager); + event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES); + myManager.beforePropertyChange(event); + + updateMaps(); + + event = new PsiTreeChangeEventImpl(myManager); + event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES); + myManager.propertyChanged(event); + } + } + + private boolean myProcessingFileTypesChange = false; + private void handleFileTypesChange(final FileTypesChanged runnable) { + if (myProcessingFileTypesChange) return; + myProcessingFileTypesChange = true; + try { + ApplicationManager.getApplication().runWriteAction(runnable); + } + finally { + myProcessingFileTypesChange = false; + } + } + + private void dispatchPendingEvents() { + if (!myInitialized) { + LOG.error("Project is not yet initialized"); + } + if (myDisposed) { + LOG.error("Project is already disposed"); + } + + myConnection.deliverImmediately(); + } + + @TestOnly + public void checkConsistency() { + HashMap fileToViewProvider = new HashMap(myVFileToViewProviderMap); + myVFileToViewProviderMap.clear(); + for (VirtualFile vFile : fileToViewProvider.keySet()) { + final FileViewProvider fileViewProvider = fileToViewProvider.get(vFile); + + LOG.assertTrue(vFile.isValid()); + PsiFile psiFile1 = findFile(vFile); + if (psiFile1 != null && fileViewProvider != null && fileViewProvider.isPhysical()) { // might get collected + PsiFile psi = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage()); + assert psi != null : fileViewProvider +"; "+fileViewProvider.getBaseLanguage()+"; "+psiFile1; + assert psiFile1.getClass().equals(psi.getClass()) : psiFile1 +"; "+psi + "; "+psiFile1.getClass() +"; "+psi.getClass(); + } + } + + HashMap fileToPsiDirMap = new HashMap(myVFileToPsiDirMap); + myVFileToPsiDirMap.clear(); + + for (VirtualFile vFile : fileToPsiDirMap.keySet()) { + LOG.assertTrue(vFile.isValid()); + PsiDirectory psiDir1 = findDirectory(vFile); + LOG.assertTrue(psiDir1 != null); + + VirtualFile parent = vFile.getParent(); + if (parent != null) { + LOG.assertTrue(myVFileToPsiDirMap.containsKey(parent)); + } + } + } + + @Nullable + public PsiFile findFile(@NotNull VirtualFile vFile) { + if (vFile.isDirectory()) return null; + final ProjectEx project = (ProjectEx)myManager.getProject(); + if (project.isDefault()) return null; + + ApplicationManager.getApplication().assertReadAccessAllowed(); + if (!vFile.isValid()) { + LOG.error("Invalid file: " + vFile); + return null; + } + + dispatchPendingEvents(); + final FileViewProvider viewProvider = findViewProvider(vFile); + return viewProvider.getPsi(viewProvider.getBaseLanguage()); + } + + @Nullable + public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) { + ApplicationManager.getApplication().assertReadAccessAllowed(); + LOG.assertTrue(vFile.isValid()); + LOG.assertTrue(!myDisposed); + if (!myInitialized) return null; + + dispatchPendingEvents(); + + return getCachedPsiFileInner(vFile); + } + + @NotNull + public GlobalSearchScope getResolveScope(@NotNull PsiElement element) { + ProgressManager.checkCanceled(); + + VirtualFile vFile; + final Project project = myManager.getProject(); + final PsiFile contextFile; + if (element instanceof PsiDirectory) { + vFile = ((PsiDirectory)element).getVirtualFile(); + contextFile = null; + } + else { + final PsiFile containingFile = element.getContainingFile(); + if (containingFile instanceof PsiCodeFragment) { + final GlobalSearchScope forcedScope = ((PsiCodeFragment)containingFile).getForcedResolveScope(); + if (forcedScope != null) { + return forcedScope; + } + final PsiElement context = containingFile.getContext(); + if (context == null) { + return GlobalSearchScope.allScope(project); + } + return getResolveScope(context); + } + + contextFile = containingFile != null ? FileContextUtil.getContextFile(containingFile) : null; + if (contextFile == null) { + return GlobalSearchScope.allScope(project); + } + else if (contextFile instanceof FileResolveScopeProvider) { + return ((FileResolveScopeProvider) contextFile).getFileResolveScope(); + } + vFile = contextFile.getOriginalFile().getVirtualFile(); + } + if (vFile == null || contextFile == null) { + return GlobalSearchScope.allScope(project); + } + + return getDefaultResolveScope(project, contextFile, vFile); + } + + + public GlobalSearchScope getDefaultResolveScope(final VirtualFile vFile) { + final Project project = myManager.getProject(); + final PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile); + assert psiFile != null; + return getDefaultResolveScope(project, psiFile, vFile); + } + + private GlobalSearchScope getDefaultResolveScope(@NotNull final Project project, @NotNull PsiFile psiFile, @NotNull final VirtualFile vFile) { + return myDefaultUseScopesCache.get(vFile); + } + + private GlobalSearchScope getInherentResolveScope(VirtualFile vFile) { + ProjectFileIndex projectFileIndex = myProjectRootManager.getFileIndex(); + Module module = projectFileIndex.getModuleForFile(vFile); + if (module != null) { + boolean includeTests = projectFileIndex.isInTestSourceContent(vFile) || + !projectFileIndex.isContentJavaSourceFile(vFile); + return GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, includeTests); + } + else { + // resolve references in libraries in context of all modules which contain it + List modulesLibraryUsedIn = new ArrayList(); + List orderEntries = projectFileIndex.getOrderEntriesForFile(vFile); + + for (OrderEntry entry : orderEntries) { + ProgressManager.checkCanceled(); + + if (entry instanceof JdkOrderEntry) { + return ((ProjectRootManagerEx)myProjectRootManager).getScopeForJdk((JdkOrderEntry)entry); + } + + if (entry instanceof LibraryOrderEntry || entry instanceof ModuleOrderEntry) { + modulesLibraryUsedIn.add(entry.getOwnerModule()); + } + } + + return ((ProjectRootManagerEx)myProjectRootManager).getScopeForLibraryUsedIn(modulesLibraryUsedIn); + } + } + + @NotNull + public GlobalSearchScope getUseScope(@NotNull PsiElement element) { + VirtualFile vFile; + final GlobalSearchScope allScope = GlobalSearchScope.allScope(myManager.getProject()); + if (element instanceof PsiDirectory) { + vFile = ((PsiDirectory)element).getVirtualFile(); + } + else { + final PsiFile containingFile = element.getContainingFile(); + if (containingFile == null) return allScope; + final VirtualFile virtualFile = containingFile.getVirtualFile(); + if (virtualFile == null) return allScope; + vFile = virtualFile.getParent(); + } + + if (vFile == null) return allScope; + ProjectFileIndex projectFileIndex = myProjectRootManager.getFileIndex(); + Module module = projectFileIndex.getModuleForFile(vFile); + if (module != null) { + boolean isTest = projectFileIndex.isInTestSourceContent(vFile); + return isTest + ? GlobalSearchScope.moduleTestsWithDependentsScope(module) + : GlobalSearchScope.moduleWithDependentsScope(module); + } + else { + final PsiFile f = element.getContainingFile(); + final VirtualFile vf = f == null ? null : f.getVirtualFile(); + + return f == null || vf == null || vf.isDirectory() || allScope.contains(vf) + ? allScope : GlobalSearchScope.fileScope(f).uniteWith(allScope); + } + } + + + @Nullable + private PsiFile createFileCopyWithNewName(VirtualFile vFile, String name) { + // TODO[ik] remove this. Event handling and generation must be in view providers mechanism since we + // need to track changes in _all_ psi views (e.g. namespace changes in XML) + final FileTypeManager instance = FileTypeManager.getInstance(); + if(instance.isFileIgnored(name)) return null; + final FileType fileTypeByFileName = instance.getFileTypeByFileName(name); + final Document document = FileDocumentManager.getInstance().getDocument(vFile); + return PsiFileFactory.getInstance(myManager.getProject()).createFileFromText(name, fileTypeByFileName, + document != null ? document.getCharsSequence() : "", vFile.getModificationStamp(), + true, false); + } + + @Nullable + public PsiDirectory findDirectory(@NotNull VirtualFile vFile) { + LOG.assertTrue(myInitialized, "Access to psi files should be performed only after startup activity"); + LOG.assertTrue(!myDisposed, "Access to psi files should not be performed after disposal"); + + ApplicationManager.getApplication().assertReadAccessAllowed(); + if (!vFile.isValid()) { + LOG.error("File is not valid:" + vFile.getName()); + } + + if (!vFile.isDirectory()) return null; + dispatchPendingEvents(); + + return findDirectoryImpl(vFile); + } + + private PsiDirectory findDirectoryImpl(final VirtualFile vFile) { + PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); + if (psiDir != null) return psiDir; + + if (myProjectRootManager.getFileIndex().isIgnored(vFile)) return null; + + VirtualFile parent = vFile.getParent(); + if (parent != null) { //? + findDirectoryImpl(parent);// need to cache parent directory - used for firing events + } + + psiDir = PsiDirectoryFactory.getInstance(myManager.getProject()).createDirectory(vFile); + return ConcurrencyUtil.cacheOrGet(myVFileToPsiDirMap, vFile, psiDir); + } + + + @Nullable + private PsiFile getCachedPsiFileInner(VirtualFile file) { + final FileViewProvider fileViewProvider = myVFileToViewProviderMap.get(file); + return fileViewProvider instanceof SingleRootFileViewProvider + ? ((SingleRootFileViewProvider)fileViewProvider).getCachedPsi(fileViewProvider.getBaseLanguage()) : null; + } + + public List getAllCachedFiles() { + List files = new ArrayList(); + for (FileViewProvider provider : myVFileToViewProviderMap.values()) { + if (provider instanceof SingleRootFileViewProvider) { + files.add(((SingleRootFileViewProvider)provider).getCachedPsi(provider.getBaseLanguage())); + } + } + return files; + } + + private void removeInvalidFilesAndDirs(boolean useFind) { + Map fileToPsiDirMap = new THashMap(myVFileToPsiDirMap); + if (useFind) { + myVFileToPsiDirMap.clear(); + } + for (Iterator iterator = fileToPsiDirMap.keySet().iterator(); iterator.hasNext();) { + VirtualFile vFile = iterator.next(); + if (!vFile.isValid()) { + iterator.remove(); + } + else { + PsiDirectory psiDir = findDirectory(vFile); + if (psiDir == null) { + iterator.remove(); + } + } + } + myVFileToPsiDirMap.clear(); + myVFileToPsiDirMap.putAll(fileToPsiDirMap); + + // note: important to update directories map first - findFile uses findDirectory! + Map fileToPsiFileMap = new THashMap(myVFileToViewProviderMap); + if (useFind) { + myVFileToViewProviderMap.clear(); + } + for (Iterator iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) { + VirtualFile vFile = iterator.next(); + + if (!vFile.isValid()) { + iterator.remove(); + continue; + } + + if (useFind) { + FileViewProvider view = fileToPsiFileMap.get(vFile); + if (view == null) { // soft ref. collected + iterator.remove(); + continue; + } + PsiFile psiFile1 = findFile(vFile); + if (psiFile1 == null) { + iterator.remove(); + continue; + } + + PsiFile psi = view.getPsi(view.getBaseLanguage()); + if (psi == null || !psiFile1.getClass().equals(psi.getClass()) || + psiFile1.getViewProvider().getBaseLanguage() != view.getBaseLanguage() // e.g. JSP <-> JSPX + ) { + iterator.remove(); + } + else if (psi instanceof PsiFileImpl) { + ((PsiFileImpl)psi).clearCaches(); + } + } + } + myVFileToViewProviderMap.clear(); + myVFileToViewProviderMap.putAll(fileToPsiFileMap); + } + + public void reloadFromDisk(@NotNull PsiFile file) { + reloadFromDisk(file, false); + } + + private void reloadFromDisk(PsiFile file, boolean ignoreDocument) { + VirtualFile vFile = file.getVirtualFile(); + assert vFile != null; + + if (!(file instanceof PsiBinaryFile)) { + FileDocumentManager fileDocumentManager = myFileDocumentManager; + Document document = fileDocumentManager.getCachedDocument(vFile); + if (document != null && !ignoreDocument){ + fileDocumentManager.reloadFromDisk(document); + } + else{ + PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager); + event.setParent(file); + event.setFile(file); + if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) { + event.setOffset(0); + event.setOldLength(file.getTextLength()); + } + myManager.beforeChildrenChange(event); + + if (file instanceof PsiFileEx) { + ((PsiFileEx)file).onContentReload(); + } + + myManager.childrenChanged(event); + } + } + } + + private class MyVirtualFileListener extends VirtualFileAdapter { + public void contentsChanged(final VirtualFileEvent event) { + // handled by FileDocumentManagerListener + } + + public void fileCreated(VirtualFileEvent event) { + final VirtualFile vFile = event.getFile(); + + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + VirtualFile parent = vFile.getParent(); + PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); + if (parentDir == null) return; // do not notifyListeners event if parent directory was never accessed via PSI + + if (!vFile.isDirectory()) { + PsiFile psiFile = findFile(vFile); + if (psiFile != null) { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + myManager.beforeChildAddition(treeEvent); + treeEvent.setChild(psiFile); + myManager.childAdded(treeEvent); + } + } + else { + PsiDirectory psiDir = findDirectory(vFile); + if (psiDir != null) { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + myManager.beforeChildAddition(treeEvent); + treeEvent.setChild(psiDir); + myManager.childAdded(treeEvent); + } + } + } + } + ); + } + + public void beforeFileDeletion(VirtualFileEvent event) { + final VirtualFile vFile = event.getFile(); + + VirtualFile parent = vFile.getParent(); + final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); + if (parentDir == null) return; // do not notify listeners if parent directory was never accessed via PSI + + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + if (!vFile.isDirectory()) { + PsiFile psiFile = getCachedPsiFile(vFile); + if (psiFile != null) { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + treeEvent.setChild(psiFile); + myManager.beforeChildRemoval(treeEvent); + } + } + else { + PsiDirectory psiDir = findDirectory(vFile); + if (psiDir != null) { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + treeEvent.setChild(psiDir); + myManager.beforeChildRemoval(treeEvent); + } + } + } + } + ); + } + + public void fileDeleted(final VirtualFileEvent event) { + final VirtualFile vFile = event.getFile(); + + VirtualFile parent = event.getParent(); + final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); + + final PsiFile psiFile = getCachedPsiFileInner(vFile); + if (psiFile != null) { + myVFileToViewProviderMap.remove(vFile); + + if (parentDir != null) { + ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() { + public void run() { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + treeEvent.setChild(psiFile); + myManager.childRemoved(treeEvent); + } + }); + } + } + else { + final PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); + if (psiDir != null) { + removeInvalidFilesAndDirs(false); + + if (parentDir != null) { + ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() { + public void run() { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + treeEvent.setChild(psiDir); + myManager.childRemoved(treeEvent); + } + }); + } + } + } + } + + public void beforePropertyChange(final VirtualFilePropertyEvent event) { + final VirtualFile vFile = event.getFile(); + final String propertyName = event.getPropertyName(); + + VirtualFile parent = vFile.getParent(); + final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); + if (parentDir == null) return; // do not notifyListeners event if parent directory was never accessed via PSI + + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + + if (VirtualFile.PROP_NAME.equals(propertyName)) { + final String newName = (String)event.getNewValue(); + + if (vFile.isDirectory()) { + PsiDirectory psiDir = findDirectory(vFile); + if (psiDir != null) { + if (!myFileTypeManager.isFileIgnored(newName)) { + treeEvent.setChild(psiDir); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_DIRECTORY_NAME); + treeEvent.setOldValue(vFile.getName()); + treeEvent.setNewValue(newName); + myManager.beforePropertyChange(treeEvent); + } + else { + treeEvent.setChild(psiDir); + myManager.beforeChildRemoval(treeEvent); + } + } + else { + if (!isExcludeRoot(vFile) && !myFileTypeManager.isFileIgnored(newName)) { + myManager.beforeChildAddition(treeEvent); + } + } + } + else { + final FileViewProvider viewProvider = findViewProvider(vFile); + PsiFile psiFile = viewProvider.getPsi(viewProvider.getBaseLanguage()); + PsiFile psiFile1 = createFileCopyWithNewName(vFile, newName); + + if (psiFile != null) { + if (psiFile1 == null) { + treeEvent.setChild(psiFile); + myManager.beforeChildRemoval(treeEvent); + } + else if (!psiFile1.getClass().equals(psiFile.getClass())) { + treeEvent.setOldChild(psiFile); + myManager.beforeChildReplacement(treeEvent); + } + else { + treeEvent.setChild(psiFile); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_FILE_NAME); + treeEvent.setOldValue(vFile.getName()); + treeEvent.setNewValue(newName); + myManager.beforePropertyChange(treeEvent); + } + } + else { + if (psiFile1 != null) { + myManager.beforeChildAddition(treeEvent); + } + } + } + } + else if (VirtualFile.PROP_WRITABLE.equals(propertyName)) { + PsiFile psiFile = getCachedPsiFileInner(vFile); + if (psiFile == null) return; + + treeEvent.setElement(psiFile); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_WRITABLE); + treeEvent.setOldValue(event.getOldValue()); + treeEvent.setNewValue(event.getNewValue()); + myManager.beforePropertyChange(treeEvent); + } + } + } + ); + } + + private boolean isExcludeRoot(VirtualFile file) { + VirtualFile parent = file.getParent(); + if (parent == null) return false; + + Module module = myProjectRootManager.getFileIndex().getModuleForFile(parent); + if (module == null) return false; + VirtualFile[] excludeRoots = ModuleRootManager.getInstance(module).getExcludeRoots(); + for (VirtualFile root : excludeRoots) { + if (root.equals(file)) return true; + } + return false; + } + + public void propertyChanged(final VirtualFilePropertyEvent event) { + final String propertyName = event.getPropertyName(); + final VirtualFile vFile = event.getFile(); + + final FileViewProvider oldFileViewProvider = findViewProvider(vFile); + final PsiFile oldPsiFile; + if (oldFileViewProvider instanceof SingleRootFileViewProvider) { + oldPsiFile = ((SingleRootFileViewProvider)oldFileViewProvider).getCachedPsi(oldFileViewProvider.getBaseLanguage()); + } + else { + oldPsiFile = null; + } + + VirtualFile parent = vFile.getParent(); + final PsiDirectory parentDir = parent == null ? null : myVFileToPsiDirMap.get(parent); + if (parentDir == null) { + boolean fire = VirtualFile.PROP_NAME.equals(propertyName) && vFile.isDirectory(); + if (fire) { + PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); + fire = psiDir != null; + } + if (!fire) return; // do not fire event if parent directory was never accessed via PSI + } + + if (oldPsiFile != null && oldPsiFile.isPhysical()) { + SmartPointerManagerImpl.fastenBelts(oldPsiFile); + } + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setParent(parentDir); + + if (VirtualFile.PROP_NAME.equals(propertyName)) { + if (vFile.isDirectory()) { + PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile); + if (psiDir != null) { + if (myFileTypeManager.isFileIgnored(vFile.getName())) { + removeFilesAndDirsRecursively(vFile); + + treeEvent.setChild(psiDir); + myManager.childRemoved(treeEvent); + } + else { + treeEvent.setElement(psiDir); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_DIRECTORY_NAME); + treeEvent.setOldValue(event.getOldValue()); + treeEvent.setNewValue(event.getNewValue()); + myManager.propertyChanged(treeEvent); + } + } + else { + PsiDirectory psiDir1 = findDirectory(vFile); + if (psiDir1 != null) { + treeEvent.setChild(psiDir1); + myManager.childAdded(treeEvent); + } + } + } + else { + final FileViewProvider fileViewProvider = createFileViewProvider(vFile, true); + final PsiFile newPsiFile = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage()); + if(oldPsiFile != null) { + if (newPsiFile == null) { + myVFileToViewProviderMap.remove(vFile); + + treeEvent.setChild(oldPsiFile); + myManager.childRemoved(treeEvent); + } + else if (!newPsiFile.getClass().equals(oldPsiFile.getClass()) || + newPsiFile.getFileType() != myFileTypeManager.getFileTypeByFileName((String)event.getOldValue()) || + languageDialectChanged(newPsiFile, (String)event.getOldValue()) || + !oldFileViewProvider.getLanguages().equals(fileViewProvider.getLanguages()) || + FileContentUtil.FORCE_RELOAD_REQUESTOR.equals(event.getRequestor()) + ) { + myVFileToViewProviderMap.put(vFile, fileViewProvider); + + treeEvent.setOldChild(oldPsiFile); + treeEvent.setNewChild(newPsiFile); + myManager.childReplaced(treeEvent); + } + else { + treeEvent.setElement(oldPsiFile); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_FILE_NAME); + treeEvent.setOldValue(event.getOldValue()); + treeEvent.setNewValue(event.getNewValue()); + myManager.propertyChanged(treeEvent); + } + } + else if (newPsiFile != null) { + myVFileToViewProviderMap.put(vFile, fileViewProvider); + treeEvent.setChild(newPsiFile); + myManager.childAdded(treeEvent); + } + } + } + else if (VirtualFile.PROP_WRITABLE.equals(propertyName)) { + if (oldPsiFile == null) return; + + treeEvent.setElement(oldPsiFile); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_WRITABLE); + treeEvent.setOldValue(event.getOldValue()); + treeEvent.setNewValue(event.getNewValue()); + myManager.propertyChanged(treeEvent); + } + } + } + ); + } + + public void beforeFileMovement(VirtualFileMoveEvent event) { + final VirtualFile vFile = event.getFile(); + + final PsiDirectory oldParentDir = findDirectory(event.getOldParent()); + final PsiDirectory newParentDir = findDirectory(event.getNewParent()); + if (oldParentDir == null && newParentDir == null) return; + if (myFileTypeManager.isFileIgnored(vFile.getName())) return; + + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + + boolean isExcluded = vFile.isDirectory() && myProjectFileIndex.isIgnored(vFile); + if (oldParentDir != null && !isExcluded) { + if (newParentDir != null) { + treeEvent.setOldParent(oldParentDir); + treeEvent.setNewParent(newParentDir); + if (vFile.isDirectory()) { + PsiDirectory psiDir = findDirectory(vFile); + treeEvent.setChild(psiDir); + } + else { + PsiFile psiFile = findFile(vFile); + treeEvent.setChild(psiFile); + } + myManager.beforeChildMovement(treeEvent); + } + else { + treeEvent.setParent(oldParentDir); + if (vFile.isDirectory()) { + PsiDirectory psiDir = findDirectory(vFile); + treeEvent.setChild(psiDir); + } + else { + PsiFile psiFile = findFile(vFile); + treeEvent.setChild(psiFile); + } + myManager.beforeChildRemoval(treeEvent); + } + } + else { + LOG.assertTrue(newParentDir != null); // checked above + treeEvent.setParent(newParentDir); + myManager.beforeChildAddition(treeEvent); + } + } + } + ); + } + + public void fileMoved(VirtualFileMoveEvent event) { + final VirtualFile vFile = event.getFile(); + + final PsiDirectory oldParentDir = findDirectory(event.getOldParent()); + final PsiDirectory newParentDir = findDirectory(event.getNewParent()); + if (oldParentDir == null && newParentDir == null) return; + + final PsiElement oldElement = vFile.isDirectory() ? myVFileToPsiDirMap.get(vFile) : getCachedPsiFileInner(vFile); + removeInvalidFilesAndDirs(true); + final FileViewProvider viewProvider = findViewProvider(vFile); + final PsiElement newElement; + final FileViewProvider newViewProvider; + if (!vFile.isDirectory()){ + newViewProvider = createFileViewProvider(vFile, true); + newElement = newViewProvider.getPsi(viewProvider.getBaseLanguage()); + } + else { + newElement = findDirectory(vFile); + newViewProvider = null; + } + + if (oldElement == null && newElement == null) return; + + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + if (oldElement == null) { + myVFileToViewProviderMap.put(vFile, newViewProvider); + treeEvent.setParent(newParentDir); + treeEvent.setChild(newElement); + myManager.childAdded(treeEvent); + } + else { + if (newElement == null) { + myVFileToViewProviderMap.remove(vFile); + treeEvent.setParent(oldParentDir); + treeEvent.setChild(oldElement); + myManager.childRemoved(treeEvent); + } + else { + if (oldElement.getClass().equals(newElement.getClass())) { + treeEvent.setOldParent(oldParentDir); + treeEvent.setNewParent(newParentDir); + treeEvent.setChild(newElement); + myManager.childMoved(treeEvent); + } + else { + myVFileToViewProviderMap.put(vFile, newViewProvider); + PsiTreeChangeEventImpl treeRemoveEvent = new PsiTreeChangeEventImpl(myManager); + treeRemoveEvent.setParent(oldParentDir); + treeRemoveEvent.setChild(oldElement); + myManager.childRemoved(treeRemoveEvent); + PsiTreeChangeEventImpl treeAddEvent = new PsiTreeChangeEventImpl(myManager); + treeAddEvent.setParent(newParentDir); + treeAddEvent.setChild(newElement); + myManager.childAdded(treeAddEvent); + } + } + } + } + } + ); + } + + private void removeFilesAndDirsRecursively(VirtualFile vFile) { + if (vFile.isDirectory()) { + myVFileToPsiDirMap.remove(vFile); + + VirtualFile[] children = vFile.getChildren(); + for (VirtualFile child : children) { + removeFilesAndDirsRecursively(child); + } + } + else myVFileToViewProviderMap.remove(vFile); + } + } + + // When file is renamed so that extension changes then language dialect might change and thus psiFile should be invalidated + // We could detect it right now with checks of parser definition equivalence + // The file name under passed psi file is "new" but parser def is from old name + private static boolean languageDialectChanged(final PsiFile newPsiFile, String oldFileName) { + return newPsiFile instanceof PsiFileBase + && LanguageParserDefinitions.INSTANCE.forLanguage(newPsiFile.getLanguage()).getClass() == ((PsiFileBase)newPsiFile).getParserDefinition().getClass() + && !FileUtil.getExtension(newPsiFile.getName()).equals(FileUtil.getExtension(oldFileName)); + } + + private class MyModuleRootListener implements ModuleRootListener { + private VirtualFile[] myOldContentRoots = null; + private volatile int depthCounter = 0; + public void beforeRootsChange(final ModuleRootEvent event) { + if (!myInitialized) return; + if (event.isCausedByFileTypesChange()) return; + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + depthCounter++; + if (depthCounter > 1) return; + + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_ROOTS); + final VirtualFile[] contentRoots = myProjectRootManager.getContentRoots(); + LOG.assertTrue(myOldContentRoots == null); + myOldContentRoots = contentRoots; + treeEvent.setOldValue(contentRoots); + myManager.beforePropertyChange(treeEvent); + } + } + ); + } + + public void rootsChanged(final ModuleRootEvent event) { + dispatchPendingEvents(); + + if (!myInitialized) return; + if (event.isCausedByFileTypesChange()) return; + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + depthCounter--; + assert depthCounter >= 0 : depthCounter; + if (depthCounter > 0) return; + + removeInvalidFilesAndDirs(true); + + PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); + treeEvent.setPropertyName(PsiTreeChangeEvent.PROP_ROOTS); + final VirtualFile[] contentRoots = myProjectRootManager.getContentRoots(); + treeEvent.setNewValue(contentRoots); + LOG.assertTrue(myOldContentRoots != null); + treeEvent.setOldValue(myOldContentRoots); + myOldContentRoots = null; + myManager.propertyChanged(treeEvent); + } + } + ); + } + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + public void dumpFilesWithContentLoaded(Writer out) throws IOException { + out.write("Files with content loaded cached in FileManagerImpl:\n"); + Set vFiles = myVFileToViewProviderMap.keySet(); + for (VirtualFile fileCacheEntry : vFiles) { + final FileViewProvider view = myVFileToViewProviderMap.get(fileCacheEntry); + PsiFile psiFile = view.getPsi(view.getBaseLanguage()); + if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) { + out.write(fileCacheEntry.getPresentableUrl()); + out.write("\n"); + } + } + } + + private class MyFileDocumentManagerAdapter extends FileDocumentManagerAdapter { + public void fileWithNoDocumentChanged(VirtualFile file) { + final PsiFile psiFile = getCachedPsiFileInner(file); + if (psiFile != null) { + ApplicationManager.getApplication().runWriteAction( + new ExternalChangeAction() { + public void run() { + reloadFromDisk(psiFile, true); // important to ignore document which might appear already! + } + } + ); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index a48b1ee0469a..064518fe73f7 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -1,955 +1,955 @@ -/* - * 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.impl.source; - -import com.intellij.extapi.psi.StubBasedPsiElementBase; -import com.intellij.ide.caches.FileContent; -import com.intellij.ide.util.EditSourceUtil; -import com.intellij.lang.ASTFactory; -import com.intellij.lang.ASTNode; -import com.intellij.lang.FileASTNode; -import com.intellij.lang.Language; -import com.intellij.navigation.ItemPresentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.colors.TextAttributesKey; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Queryable; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileWithId; -import com.intellij.psi.*; -import com.intellij.psi.impl.*; -import com.intellij.psi.impl.cache.impl.CacheUtil; -import com.intellij.psi.impl.file.PsiFileImplUtil; -import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; -import com.intellij.psi.impl.source.resolve.FileContextUtil; -import com.intellij.psi.impl.source.tree.*; -import com.intellij.psi.scope.PsiScopeProcessor; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.PsiElementProcessor; -import com.intellij.psi.search.SearchScope; -import com.intellij.psi.stubs.*; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.ILazyParseableElementType; -import com.intellij.psi.tree.IStubFileElementType; -import com.intellij.reference.SoftReference; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.PatchedSoftReference; -import com.intellij.util.PatchedWeakReference; -import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.lang.ref.Reference; -import java.lang.reflect.Array; -import java.util.*; - -public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiFileWithStubSupport, Queryable { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiFileImpl"); - - private IElementType myElementType; - protected IElementType myContentElementType; - - protected PsiFile myOriginalFile = null; - private final FileViewProvider myViewProvider; - private static final Key HARD_REFERENCE_TO_DOCUMENT = new Key("HARD_REFERENCE_TO_DOCUMENT"); - private final Object myStubLock = new Object(); - private SoftReference myStub; - protected final PsiManagerEx myManager; - private volatile Object myTreeElementPointer; // SoftReference/WeakReference to RepositoryTreeElement when has repository id, RepositoryTreeElement otherwise - public static final Key BUILDING_STUB = new Key("Don't use stubs mark!"); - - protected PsiFileImpl(@NotNull IElementType elementType, IElementType contentElementType, @NotNull FileViewProvider provider) { - this(provider); - init(elementType, contentElementType); - } - - protected PsiFileImpl(@NotNull FileViewProvider provider ) { - myManager = (PsiManagerEx)provider.getManager(); - myViewProvider = provider; - } - - public void setContentElementType(final IElementType contentElementType) { - myContentElementType = contentElementType; - } - - public IElementType getContentElementType() { - return myContentElementType; - } - - protected void init(@NotNull final IElementType elementType, final IElementType contentElementType) { - myElementType = elementType; - myContentElementType = contentElementType; - } - - public TreeElement createContentLeafElement(CharSequence leafText) { - if (myContentElementType instanceof ILazyParseableElementType) { - return ASTFactory.lazy((ILazyParseableElementType)myContentElementType, leafText); - } - return ASTFactory.leaf(myContentElementType, leafText); - } - - public boolean isDirectory() { - return false; - } - - public FileElement getTreeElement() { - final FileElement noLockAttempt = (FileElement)_getTreeElement(); - if (noLockAttempt != null) return noLockAttempt; - - synchronized (myStubLock) { - return getTreeElementNoLock(); - } - } - - public FileElement getTreeElementNoLock() { - if (!getViewProvider().isPhysical() && _getTreeElement() == null) { - setTreeElement(loadTreeElement()); - } - return (FileElement)_getTreeElement(); - } - - protected boolean isKeepTreeElementByHardReference() { - return !getViewProvider().isEventSystemEnabled(); - } - - private ASTNode _getTreeElement() { - final Object pointer = myTreeElementPointer; - if (pointer instanceof FileElement) { - return (FileElement)pointer; - } - else if (pointer instanceof Reference) { - FileElement treeElement = (FileElement)((Reference)pointer).get(); - if (treeElement != null) return treeElement; - - synchronized (myStubLock) { - if (myTreeElementPointer == pointer) { - myTreeElementPointer = null; - } - } - } - return null; - } - - - - public VirtualFile getVirtualFile() { - return getViewProvider().isEventSystemEnabled() ? getViewProvider().getVirtualFile() : null; - } - - public boolean processChildren(final PsiElementProcessor processor) { - return true; - } - - public boolean isValid() { - final VirtualFile vFile = getViewProvider().getVirtualFile(); - if (!vFile.isValid()) return false; - if (!getViewProvider().isPhysical()) return true; // "dummy" file - return isPsiUpToDate(vFile); - - //FileViewProvider viewProvider = getViewProvider(); - //if (!viewProvider.isPhysical()) return true; // "dummy" file - //final VirtualFile vFile = viewProvider.getVirtualFile(); - //if (!vFile.isValid() || !isPsiUpToDate(vFile)) return false; - //PsiManager manager = getManager(); - //boolean valid = manager != null && !manager.getProject().isDisposed(); - //return valid; - } - - protected boolean isPsiUpToDate(VirtualFile vFile) { - final FileViewProvider provider = myManager.findViewProvider(vFile); - return provider.getPsi(getLanguage()) == this || provider.getPsi(provider.getBaseLanguage()) == this; - } - - public boolean isContentsLoaded() { - return _getTreeElement() != null; - } - - public FileElement loadTreeElement() { - FileElement treeElement; - - synchronized (myStubLock) { - treeElement = (FileElement)_getTreeElement(); - if (treeElement != null) { - return treeElement; - } - - final FileViewProvider viewProvider = getViewProvider(); - if (viewProvider.isPhysical() && myManager.isAssertOnFileLoading(viewProvider.getVirtualFile())) { - LOG.error("Access to tree elements not allowed in tests. path='" + viewProvider.getVirtualFile().getPresentableUrl()+"'"); - } - - final Document document = viewProvider.isEventSystemEnabled() ? viewProvider.getDocument() : null; - treeElement = createFileElement(viewProvider.getContents()); - if (document != null) { - treeElement.putUserData(HARD_REFERENCE_TO_DOCUMENT, document); - } - treeElement.setPsi(this); - - StubTree stub = derefStub(); - - if (stub != null) { - final Iterator> stubs = stub.getPlainList().iterator(); - stubs.next(); // Skip file stub; - switchFromStubToAST(treeElement, stubs); - - myStub = null; - } - - setTreeElement(treeElement); - - if (LOG.isDebugEnabled() && getViewProvider().isPhysical()) { - LOG.debug("Loaded text for file " + getViewProvider().getVirtualFile().getPresentableUrl()); - } - } - - if (getViewProvider().isEventSystemEnabled()) { - ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myManager.getProject())).contentsLoaded(this); - } - - return treeElement; - } - - public ASTNode findTreeForStub(StubTree tree, StubElement stub) { - final Iterator> stubs = tree.getPlainList().iterator(); - final StubElement root = stubs.next(); - final CompositeElement ast = calcTreeElement(); - if (root == stub) return ast; - - return findTreeForStub(ast, stubs, stub); - } - - private static ASTNode findTreeForStub(ASTNode tree, final Iterator> stubs, final StubElement stub) { - final IElementType type = tree.getElementType(); - - if (type instanceof IStubElementType && ((IStubElementType) type).shouldCreateStub(tree)) { - final StubElement curStub = stubs.next(); - if (curStub == stub) return tree; - } - - for (ASTNode node : tree.getChildren(null)) { - final ASTNode treeForStub = findTreeForStub(node, stubs, stub); - if (treeForStub != null) return treeForStub; - } - - return null; - } - - - private void switchFromStubToAST(ASTNode root, final Iterator> stubs) { - final IElementType contentElementType = getContentElementType(); - if (!(contentElementType instanceof IStubFileElementType)) { - throw new AssertionError("Invalid content element type: " + contentElementType + "; elementType=" + myElementType + "; file=" + getClass() + "; viewProvider=" + getViewProvider()); - } - ((TreeElement)root).acceptTree(new RecursiveTreeElementWalkingVisitor() { - final StubBuilder builder = ((IStubFileElementType)contentElementType).getBuilder(); - - @Override - protected void visitNode(TreeElement tree) { - final IElementType type = tree.getElementType(); - CompositeElement treeParent = tree.getTreeParent(); - if(treeParent != null && - builder.skipChildProcessingWhenBuildingStubs(treeParent.getElementType(), type)) { - return; - } - if (type instanceof IStubElementType && ((IStubElementType)type).shouldCreateStub(tree)) { - if (!stubs.hasNext()) { - LOG.error("Stub list in" + - this + - " " + - getName() + - " has fewer elements than PSI. Last AST element: " + - tree.getElementType() + - " " + - tree); - } - final StubElement stub = stubs.next(); - if (stub.getStubType() != tree.getElementType()) { - rebuildStub(); - LOG.error("Stub and PSI element type mismatch in " + - getName() + - ": stub " + - stub + - ", AST " + - tree.getElementType() + - "; " + - tree); - } - final PsiElement psi = stub.getPsi(); - ((CompositeElement)tree).setPsi(psi); - final StubBasedPsiElementBase base = (StubBasedPsiElementBase)psi; - base.setNode(tree); - base.setStub(null); - } - super.visitNode(tree); - } - }); - } - - protected FileElement createFileElement(final CharSequence docText) { - final FileElement treeElement; - final TreeElement contentLeaf = createContentLeafElement(docText); - - if (contentLeaf instanceof FileElement) { - treeElement = (FileElement)contentLeaf; - } - else { - final CompositeElement xxx = ASTFactory.composite(myElementType); - assert xxx instanceof FileElement : "BUMM"; - treeElement = (FileElement)xxx; - treeElement.rawAddChildren(contentLeaf); - } - - if (CacheUtil.isCopy(this)) { - treeElement.setCharTable(IdentityCharTable.INSTANCE); - } - - return treeElement; - } - - public void unloadContent() { - LOG.assertTrue(getTreeElement() != null); - clearCaches(); - myViewProvider.beforeContentsSynchronized(); - setTreeElement(null); - synchronized (myStubLock) { - myStub = null; - } - } - - public void clearCaches() {} - - public String getText() { - return getViewProvider().getContents().toString(); - } - - public int getTextLength() { - final ASTNode tree = _getTreeElement(); - if (tree != null) return tree.getTextLength(); - - return getViewProvider().getContents().length(); - } - - public TextRange getTextRange() { - return new TextRange(0, getTextLength()); - } - - public PsiElement getNextSibling() { - return SharedPsiElementImplUtil.getNextSibling(this); - } - - public PsiElement getPrevSibling() { - return SharedPsiElementImplUtil.getPrevSibling(this); - } - - public long getModificationStamp() { - return getViewProvider().getModificationStamp(); - } - - public void subtreeChanged() { - doClearCaches(); - getViewProvider().rootChanged(this); - } - - private void doClearCaches() { - final FileElement tree = getTreeElement(); - if (tree != null) { - myTreeElementPointer = tree; - tree.clearCaches(); - } - - synchronized (myStubLock) { - myStub = null; - if (tree != null) { - tree.putUserData(STUB_TREE_IN_PARSED_TREE, null); - } - } - - clearCaches(); - } - - @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "CloneDoesntCallSuperClone"}) - protected PsiFileImpl clone() { - FileViewProvider viewProvider = getViewProvider(); - FileViewProvider providerCopy = viewProvider.clone(); - final Language language = getLanguage(); - if (providerCopy == null) { - throw new AssertionError("Unable to clone the view provider: " + viewProvider + "; " + language); - } - PsiFileImpl clone = (PsiFileImpl)providerCopy.getPsi(language); - assert clone != null : "Cannot find psi file with language:" + language + " from viewprovider:" + - providerCopy + " virtual file:" + getVirtualFile(); - - copyCopyableDataTo(clone); - - if (getTreeElement() != null) { - // not set by provider in clone - final FileElement treeClone = (FileElement)calcTreeElement().clone(); - clone.myTreeElementPointer = treeClone; // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963) - treeClone.setPsi(clone); - } - - if (viewProvider.isEventSystemEnabled()) { - clone.myOriginalFile = this; - } - else if (myOriginalFile != null) { - clone.myOriginalFile = myOriginalFile; - } - - return clone; - } - - @NotNull public String getName() { - return getViewProvider().getVirtualFile().getName(); - } - - public PsiElement setName(@NotNull String name) throws IncorrectOperationException { - checkSetName(name); - doClearCaches(); - return PsiFileImplUtil.setName(this, name); - } - - public void checkSetName(String name) throws IncorrectOperationException { - if (!getViewProvider().isEventSystemEnabled()) return; - PsiFileImplUtil.checkSetName(this, name); - } - - public boolean isWritable() { - return getViewProvider().getVirtualFile().isWritable() && !CacheUtil.isCopy(this); - } - - public PsiDirectory getParent() { - return getContainingDirectory(); - } - - public PsiDirectory getContainingDirectory() { - final VirtualFile parentFile = getViewProvider().getVirtualFile().getParent(); - if (parentFile == null) return null; - return getManager().findDirectory(parentFile); - } - - public PsiFile getContainingFile() { - return this; - } - - public void delete() throws IncorrectOperationException { - checkDelete(); - PsiFileImplUtil.doDelete(this); - } - - public void checkDelete() throws IncorrectOperationException { - if (!getViewProvider().isEventSystemEnabled()) { - throw new IncorrectOperationException(); - } - CheckUtil.checkWritable(this); - } - - @NotNull - public PsiFile getOriginalFile() { - return myOriginalFile == null ? this : myOriginalFile; - } - - public void setOriginalFile(@NotNull final PsiFile originalFile) { - myOriginalFile = originalFile.getOriginalFile(); - } - - @NotNull - public PsiFile[] getPsiRoots() { - final FileViewProvider viewProvider = getViewProvider(); - final Set languages = viewProvider.getLanguages(); - final PsiFile[] roots = new PsiFile[languages.size()]; - int i = 0; - for (Language language : languages) { - PsiFile psi = viewProvider.getPsi(language); - if (psi == null) { - LOG.error("PSI is null for "+language+"; in file: "+this); - } - roots[i++] = psi; - } - return roots; - } - - public boolean isPhysical() { - // TODO[ik] remove this shit with dummy file system - return getViewProvider().isEventSystemEnabled(); - } - - @NotNull - public Language getLanguage() { - return myElementType.getLanguage(); - } - - @NotNull - public FileViewProvider getViewProvider() { - return myViewProvider; - } - - public void setTreeElementPointer(FileElement element) { - myTreeElementPointer = element; - } - - public PsiElement findElementAt(int offset) { - return getViewProvider().findElementAt(offset); - } - - public PsiReference findReferenceAt(int offset) { - return getViewProvider().findReferenceAt(offset); - } - - @NotNull - public char[] textToCharArray() { - return CharArrayUtil.fromSequenceStrict(getViewProvider().getContents()); - } - - @NotNull - protected T[] findChildrenByClass(Class aClass) { - List result = new ArrayList(); - for (PsiElement child : getChildren()) { - if (aClass.isInstance(child)) result.add((T)child); - } - return result.toArray((T[]) Array.newInstance(aClass, result.size())); - } - - @Nullable - protected T findChildByClass(Class aClass) { - for (PsiElement child : getChildren()) { - if (aClass.isInstance(child)) return (T)child; - } - return null; - } - - public boolean isTemplateDataFile() { - return false; - } - - public PsiElement getContext() { - return FileContextUtil.getFileContext(this); - } - - public void onContentReload() { - subtreeChanged(); // important! otherwise cached information is not released - if (isContentsLoaded()) { - unloadContent(); - } - } - - public PsiFile cacheCopy(final FileContent content) { - if (isContentsLoaded()) { - return this; - } - else { - CharSequence text; - if (content == null) { - Document document = FileDocumentManager.getInstance().getDocument(getVirtualFile()); - text = document.getCharsSequence(); - } - else { - text = CacheUtil.getContentText(content); - } - - FileType fileType = getFileType(); - final String name = getName(); - PsiFile fileCopy = - PsiFileFactory.getInstance(getProject()).createFileFromText(name, fileType, text, getModificationStamp(), false, false); - fileCopy.putUserData(CacheUtil.CACHE_COPY_KEY, Boolean.TRUE); - - ((PsiFileImpl)fileCopy).setOriginalFile(this); - return fileCopy; - } - } - - @Nullable - public StubElement getStub() { - StubTree stubHolder = getStubTree(); - return stubHolder != null ? stubHolder.getRoot() : null; - } - - @Nullable - public StubTree getStubTree() { - ApplicationManager.getApplication().assertReadAccessAllowed(); - - if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null; - - final StubTree derefd = derefStub(); - if (derefd != null) return derefd; - if (getTreeElementNoLock() != null) return null; - - final VirtualFile vFile = getVirtualFile(); - if (!(vFile instanceof VirtualFileWithId)) return null; - - StubTree stubHolder = StubTree.readOrBuild(getProject(), vFile); - if (stubHolder == null) return null; - - synchronized (myStubLock) { - if (getTreeElementNoLock() != null) return null; - - final StubTree derefdOnLock = derefStub(); - if (derefdOnLock != null) return derefdOnLock; - - setStubTree(stubHolder); - - return derefStub(); - } - } - - public void setStubTree(StubTree stubHolder) { - synchronized (myStubLock) { - assert getTreeElementNoLock() == null; - myStub = new SoftReference(stubHolder); - StubBase base = (StubBase)stubHolder.getRoot(); - base.setPsi(this); - - base.putUserData(STUB_TREE_IN_PARSED_TREE, stubHolder); // This will prevent soft reference myStub to be collected before all of the stubs are collected. - } - } - - @Nullable - private StubTree derefStub() { - if (myStub == null) return null; - - synchronized (myStubLock) { - return myStub != null ? myStub.get() : null; - } - } - - protected PsiFileImpl cloneImpl(FileElement treeElementClone) { - PsiFileImpl clone = (PsiFileImpl)super.clone(); - clone.myTreeElementPointer = treeElementClone; // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963) - treeElementClone.setPsi(clone); - return clone; - } - - private void setTreeElement(ASTNode treeElement){ - Object newPointer; - if (treeElement == null) { - newPointer = null; - } - else if (isKeepTreeElementByHardReference()) { - newPointer = treeElement; - } - else { - newPointer = myManager.isBatchFilesProcessingMode() - ? new PatchedWeakReference(treeElement) - : new PatchedSoftReference(treeElement); - } - - synchronized (myStubLock) { - myTreeElementPointer = newPointer; - } - } - - public Object getStubLock() { - return myStubLock; - } - - public PsiManager getManager() { - return myManager; - } - - public PsiElement getNavigationElement() { - return this; - } - - public PsiElement getOriginalElement() { - return this; - } - - public final FileElement calcTreeElement() { - // Attempt to find (loaded) tree element without taking lock first. - FileElement treeElement = getTreeElement(); - if (treeElement != null) return treeElement; - - synchronized (myStubLock) { - treeElement = getTreeElement(); - if (treeElement != null) return treeElement; - - return loadTreeElement(); - } - } - - @NotNull - public PsiElement[] getChildren() { - return calcTreeElement().getChildrenAsPsiElements(null, PsiElementArrayConstructor.PSI_ELEMENT_ARRAY_CONSTRUCTOR); - } - - public PsiElement getFirstChild() { - return SharedImplUtil.getFirstChild(calcTreeElement()); - } - - public PsiElement getLastChild() { - return SharedImplUtil.getLastChild(calcTreeElement()); - } - - public void acceptChildren(@NotNull PsiElementVisitor visitor) { - SharedImplUtil.acceptChildren(visitor, calcTreeElement()); - } - - public int getStartOffsetInParent() { - return calcTreeElement().getStartOffsetInParent(); - } - public int getTextOffset() { - return calcTreeElement().getTextOffset(); - } - - public boolean textMatches(@NotNull CharSequence text) { - return calcTreeElement().textMatches(text); - } - - public boolean textMatches(@NotNull PsiElement element) { - return calcTreeElement().textMatches(element); - } - - public boolean textContains(char c) { - return calcTreeElement().textContains(c); - } - - public final PsiElement copy() { - return clone(); - } - - public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException { - CheckUtil.checkWritable(this); - TreeElement elementCopy = ChangeUtil.copyToElement(element); - calcTreeElement().addInternal(elementCopy, elementCopy, null, null); - elementCopy = ChangeUtil.decodeInformation(elementCopy); - return SourceTreeToPsiMap.treeElementToPsi(elementCopy); - } - - public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { - CheckUtil.checkWritable(this); - TreeElement elementCopy = ChangeUtil.copyToElement(element); - calcTreeElement().addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE); - elementCopy = ChangeUtil.decodeInformation(elementCopy); - return SourceTreeToPsiMap.treeElementToPsi(elementCopy); - } - - public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { - CheckUtil.checkWritable(this); - TreeElement elementCopy = ChangeUtil.copyToElement(element); - calcTreeElement().addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE); - elementCopy = ChangeUtil.decodeInformation(elementCopy); - return SourceTreeToPsiMap.treeElementToPsi(elementCopy); - } - - public final void checkAdd(@NotNull PsiElement element) throws IncorrectOperationException { - CheckUtil.checkWritable(this); - } - - public PsiElement addRange(PsiElement first, PsiElement last) throws IncorrectOperationException { - return SharedImplUtil.addRange(this, first, last, null, null); - } - - public PsiElement addRangeBefore(@NotNull PsiElement first, @NotNull PsiElement last, PsiElement anchor) - throws IncorrectOperationException { - return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE); - } - - public PsiElement addRangeAfter(PsiElement first, PsiElement last, PsiElement anchor) - throws IncorrectOperationException { - return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE); - } - - public void deleteChildRange(PsiElement first, PsiElement last) throws IncorrectOperationException { - CheckUtil.checkWritable(this); - if (first == null) { - LOG.assertTrue(last == null); - return; - } - ASTNode firstElement = SourceTreeToPsiMap.psiElementToTree(first); - ASTNode lastElement = SourceTreeToPsiMap.psiElementToTree(last); - CompositeElement treeElement = calcTreeElement(); - LOG.assertTrue(firstElement.getTreeParent() == treeElement); - LOG.assertTrue(lastElement.getTreeParent() == treeElement); - CodeEditUtil.removeChildren(treeElement, firstElement, lastElement); - } - - public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException { - CompositeElement treeElement = calcTreeElement(); - return SharedImplUtil.doReplace(this, treeElement, newElement); - } - - public PsiReference getReference() { - return null; - } - - @NotNull - public PsiReference[] getReferences() { - return SharedPsiElementImplUtil.getReferences(this); - } - - public boolean processDeclarations(@NotNull PsiScopeProcessor processor, - @NotNull ResolveState state, - PsiElement lastParent, - @NotNull PsiElement place) { - return true; - } - - @NotNull - public GlobalSearchScope getResolveScope() { - return ((PsiManagerEx)getManager()).getFileManager().getResolveScope(this); - } - - @NotNull - public SearchScope getUseScope() { - return ((PsiManagerEx) getManager()).getFileManager().getUseScope(this); - } - - public ItemPresentation getPresentation() { - return new ItemPresentation() { - public String getPresentableText() { - return getName(); - } - - public String getLocationString() { - final PsiDirectory psiDirectory = getParent(); - if (psiDirectory != null) { - return psiDirectory.getVirtualFile().getPresentableUrl(); - } - return null; - } - - public Icon getIcon(final boolean open) { - return PsiFileImpl.this.getIcon(open ? ICON_FLAG_OPEN : ICON_FLAG_CLOSED); - } - - public TextAttributesKey getTextAttributesKey() { - return null; - } - }; - } - - public void navigate(boolean requestFocus) { - EditSourceUtil.getDescriptor(this).navigate(requestFocus); - } - - public boolean canNavigate() { - return EditSourceUtil.canNavigate(this); - } - - public boolean canNavigateToSource() { - return canNavigate(); - } - - public FileStatus getFileStatus() { - return SharedImplUtil.getFileStatus(this); - } - - @NotNull - public Project getProject() { - final PsiManager manager = getManager(); - if (manager == null) throw new PsiInvalidElementAccessException(this); - - return manager.getProject(); - } - - public FileASTNode getNode() { - return calcTreeElement(); - } - - public boolean isEquivalentTo(final PsiElement another) { - return this == another; - } - - private static final Key STUB_TREE_IN_PARSED_TREE = new Key("STUB_TREE_IN_PARSED_TREE"); - - public StubTree calcStubTree() { - synchronized (myStubLock) { - final FileElement fileElement = (FileElement)calcTreeElement(); - StubTree tree = fileElement.getUserData(STUB_TREE_IN_PARSED_TREE); - if (tree == null) { - final StubElement currentStubTree = ((IStubFileElementType)getContentElementType()).getBuilder().buildStubTree(this); - tree = new StubTree((PsiFileStub)currentStubTree); - bindFakeStubsToTree(tree); - fileElement.putUserData(STUB_TREE_IN_PARSED_TREE, tree); - } - return tree; - } - } - - private void bindFakeStubsToTree(StubTree stubTree) { - final PsiFileImpl file = this; - - final Iterator> stubs = stubTree.getPlainList().iterator(); - stubs.next();// skip file root stub - final FileElement fileRoot = file.getTreeElement(); - assert fileRoot != null; - - bindStubs(fileRoot, stubs, ((IStubFileElementType)getContentElementType()).getBuilder()); - } - - @Nullable - private StubElement bindStubs(ASTNode tree, Iterator> stubs, StubBuilder builder) { - final IElementType type = tree.getElementType(); - - if (type instanceof IStubElementType && ((IStubElementType) type).shouldCreateStub(tree)) { - final StubElement stub = stubs.next(); - if (stub.getStubType() != tree.getElementType()) { - rebuildStub(); - - assert false: "Stub and PSI element type mismatch: stub " + stub + ", AST " + tree.getElementType(); - } - - ((StubBase)stub).setPsi(tree.getPsi()); - } - - for (ASTNode node : tree.getChildren(null)) { - if (!builder.skipChildProcessingWhenBuildingStubs(type, node.getElementType())) { - final StubElement res = bindStubs(node, stubs, builder); - if (res != null) { - return res; - } - } - } - - return null; - } - - private void rebuildStub() { - final VirtualFile vFile = getVirtualFile(); - - if (vFile != null && vFile.isValid()) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - final Document doc = FileDocumentManager.getInstance().getCachedDocument(vFile); - if (doc != null) { - FileDocumentManager.getInstance().saveDocument(doc); - } - } - }, ModalityState.NON_MODAL); - - FileBasedIndex.getInstance().requestReindex(vFile); - } - } - - public void putInfo(Map info) { - putInfo(this, info); - } - - public static void putInfo(PsiFile psiFile, Map info) { - info.put("fileName", psiFile.getName()); - info.put("fileType", psiFile.getFileType().toString()); - } -} +/* + * 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.impl.source; + +import com.intellij.extapi.psi.StubBasedPsiElementBase; +import com.intellij.ide.caches.FileContent; +import com.intellij.ide.util.EditSourceUtil; +import com.intellij.lang.ASTFactory; +import com.intellij.lang.ASTNode; +import com.intellij.lang.FileASTNode; +import com.intellij.lang.Language; +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Queryable; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileWithId; +import com.intellij.psi.*; +import com.intellij.psi.impl.*; +import com.intellij.psi.impl.cache.impl.CacheUtil; +import com.intellij.psi.impl.file.PsiFileImplUtil; +import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; +import com.intellij.psi.impl.source.resolve.FileContextUtil; +import com.intellij.psi.impl.source.tree.*; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiElementProcessor; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.stubs.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.ILazyParseableElementType; +import com.intellij.psi.tree.IStubFileElementType; +import com.intellij.reference.SoftReference; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PatchedSoftReference; +import com.intellij.util.PatchedWeakReference; +import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.lang.ref.Reference; +import java.lang.reflect.Array; +import java.util.*; + +public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiFileWithStubSupport, Queryable { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiFileImpl"); + + private IElementType myElementType; + protected IElementType myContentElementType; + + protected PsiFile myOriginalFile = null; + private final FileViewProvider myViewProvider; + private static final Key HARD_REFERENCE_TO_DOCUMENT = new Key("HARD_REFERENCE_TO_DOCUMENT"); + private final Object myStubLock = new Object(); + private SoftReference myStub; + protected final PsiManagerEx myManager; + private volatile Object myTreeElementPointer; // SoftReference/WeakReference to RepositoryTreeElement when has repository id, RepositoryTreeElement otherwise + public static final Key BUILDING_STUB = new Key("Don't use stubs mark!"); + + protected PsiFileImpl(@NotNull IElementType elementType, IElementType contentElementType, @NotNull FileViewProvider provider) { + this(provider); + init(elementType, contentElementType); + } + + protected PsiFileImpl(@NotNull FileViewProvider provider ) { + myManager = (PsiManagerEx)provider.getManager(); + myViewProvider = provider; + } + + public void setContentElementType(final IElementType contentElementType) { + myContentElementType = contentElementType; + } + + public IElementType getContentElementType() { + return myContentElementType; + } + + protected void init(@NotNull final IElementType elementType, final IElementType contentElementType) { + myElementType = elementType; + myContentElementType = contentElementType; + } + + public TreeElement createContentLeafElement(CharSequence leafText) { + if (myContentElementType instanceof ILazyParseableElementType) { + return ASTFactory.lazy((ILazyParseableElementType)myContentElementType, leafText); + } + return ASTFactory.leaf(myContentElementType, leafText); + } + + public boolean isDirectory() { + return false; + } + + public FileElement getTreeElement() { + final FileElement noLockAttempt = (FileElement)_getTreeElement(); + if (noLockAttempt != null) return noLockAttempt; + + synchronized (myStubLock) { + return getTreeElementNoLock(); + } + } + + public FileElement getTreeElementNoLock() { + if (!getViewProvider().isPhysical() && _getTreeElement() == null) { + setTreeElement(loadTreeElement()); + } + return (FileElement)_getTreeElement(); + } + + protected boolean isKeepTreeElementByHardReference() { + return !getViewProvider().isEventSystemEnabled(); + } + + private ASTNode _getTreeElement() { + final Object pointer = myTreeElementPointer; + if (pointer instanceof FileElement) { + return (FileElement)pointer; + } + else if (pointer instanceof Reference) { + FileElement treeElement = (FileElement)((Reference)pointer).get(); + if (treeElement != null) return treeElement; + + synchronized (myStubLock) { + if (myTreeElementPointer == pointer) { + myTreeElementPointer = null; + } + } + } + return null; + } + + + + public VirtualFile getVirtualFile() { + return getViewProvider().isEventSystemEnabled() ? getViewProvider().getVirtualFile() : null; + } + + public boolean processChildren(final PsiElementProcessor processor) { + return true; + } + + public boolean isValid() { + final VirtualFile vFile = getViewProvider().getVirtualFile(); + if (!vFile.isValid()) return false; + if (!getViewProvider().isPhysical()) return true; // "dummy" file + return isPsiUpToDate(vFile); + + //FileViewProvider viewProvider = getViewProvider(); + //if (!viewProvider.isPhysical()) return true; // "dummy" file + //final VirtualFile vFile = viewProvider.getVirtualFile(); + //if (!vFile.isValid() || !isPsiUpToDate(vFile)) return false; + //PsiManager manager = getManager(); + //boolean valid = manager != null && !manager.getProject().isDisposed(); + //return valid; + } + + protected boolean isPsiUpToDate(VirtualFile vFile) { + final FileViewProvider provider = myManager.findViewProvider(vFile); + return provider.getPsi(getLanguage()) == this || provider.getPsi(provider.getBaseLanguage()) == this; + } + + public boolean isContentsLoaded() { + return _getTreeElement() != null; + } + + public FileElement loadTreeElement() { + FileElement treeElement; + + synchronized (myStubLock) { + treeElement = (FileElement)_getTreeElement(); + if (treeElement != null) { + return treeElement; + } + + final FileViewProvider viewProvider = getViewProvider(); + if (viewProvider.isPhysical() && myManager.isAssertOnFileLoading(viewProvider.getVirtualFile())) { + LOG.error("Access to tree elements not allowed in tests. path='" + viewProvider.getVirtualFile().getPresentableUrl()+"'"); + } + + final Document document = viewProvider.isEventSystemEnabled() ? viewProvider.getDocument() : null; + treeElement = createFileElement(viewProvider.getContents()); + if (document != null) { + treeElement.putUserData(HARD_REFERENCE_TO_DOCUMENT, document); + } + treeElement.setPsi(this); + + StubTree stub = derefStub(); + + if (stub != null) { + final Iterator> stubs = stub.getPlainList().iterator(); + stubs.next(); // Skip file stub; + switchFromStubToAST(treeElement, stubs); + + myStub = null; + } + + setTreeElement(treeElement); + + if (LOG.isDebugEnabled() && getViewProvider().isPhysical()) { + LOG.debug("Loaded text for file " + getViewProvider().getVirtualFile().getPresentableUrl()); + } + } + + if (getViewProvider().isEventSystemEnabled()) { + ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myManager.getProject())).contentsLoaded(this); + } + + return treeElement; + } + + public ASTNode findTreeForStub(StubTree tree, StubElement stub) { + final Iterator> stubs = tree.getPlainList().iterator(); + final StubElement root = stubs.next(); + final CompositeElement ast = calcTreeElement(); + if (root == stub) return ast; + + return findTreeForStub(ast, stubs, stub); + } + + private static ASTNode findTreeForStub(ASTNode tree, final Iterator> stubs, final StubElement stub) { + final IElementType type = tree.getElementType(); + + if (type instanceof IStubElementType && ((IStubElementType) type).shouldCreateStub(tree)) { + final StubElement curStub = stubs.next(); + if (curStub == stub) return tree; + } + + for (ASTNode node : tree.getChildren(null)) { + final ASTNode treeForStub = findTreeForStub(node, stubs, stub); + if (treeForStub != null) return treeForStub; + } + + return null; + } + + + private void switchFromStubToAST(ASTNode root, final Iterator> stubs) { + final IElementType contentElementType = getContentElementType(); + if (!(contentElementType instanceof IStubFileElementType)) { + throw new AssertionError("Invalid content element type: " + contentElementType + "; elementType=" + myElementType + "; file=" + getClass() + "; viewProvider=" + getViewProvider()); + } + ((TreeElement)root).acceptTree(new RecursiveTreeElementWalkingVisitor() { + final StubBuilder builder = ((IStubFileElementType)contentElementType).getBuilder(); + + @Override + protected void visitNode(TreeElement tree) { + final IElementType type = tree.getElementType(); + CompositeElement treeParent = tree.getTreeParent(); + if(treeParent != null && + builder.skipChildProcessingWhenBuildingStubs(treeParent.getElementType(), type)) { + return; + } + if (type instanceof IStubElementType && ((IStubElementType)type).shouldCreateStub(tree)) { + if (!stubs.hasNext()) { + LOG.error("Stub list in" + + this + + " " + + getName() + + " has fewer elements than PSI. Last AST element: " + + tree.getElementType() + + " " + + tree); + } + final StubElement stub = stubs.next(); + if (stub.getStubType() != tree.getElementType()) { + rebuildStub(); + LOG.error("Stub and PSI element type mismatch in " + + getName() + + ": stub " + + stub + + ", AST " + + tree.getElementType() + + "; " + + tree); + } + final PsiElement psi = stub.getPsi(); + ((CompositeElement)tree).setPsi(psi); + final StubBasedPsiElementBase base = (StubBasedPsiElementBase)psi; + base.setNode(tree); + base.setStub(null); + } + super.visitNode(tree); + } + }); + } + + protected FileElement createFileElement(final CharSequence docText) { + final FileElement treeElement; + final TreeElement contentLeaf = createContentLeafElement(docText); + + if (contentLeaf instanceof FileElement) { + treeElement = (FileElement)contentLeaf; + } + else { + final CompositeElement xxx = ASTFactory.composite(myElementType); + assert xxx instanceof FileElement : "BUMM"; + treeElement = (FileElement)xxx; + treeElement.rawAddChildren(contentLeaf); + } + + if (CacheUtil.isCopy(this)) { + treeElement.setCharTable(IdentityCharTable.INSTANCE); + } + + return treeElement; + } + + public void unloadContent() { + LOG.assertTrue(getTreeElement() != null); + clearCaches(); + myViewProvider.beforeContentsSynchronized(); + setTreeElement(null); + synchronized (myStubLock) { + myStub = null; + } + } + + public void clearCaches() {} + + public String getText() { + return getViewProvider().getContents().toString(); + } + + public int getTextLength() { + final ASTNode tree = _getTreeElement(); + if (tree != null) return tree.getTextLength(); + + return getViewProvider().getContents().length(); + } + + public TextRange getTextRange() { + return new TextRange(0, getTextLength()); + } + + public PsiElement getNextSibling() { + return SharedPsiElementImplUtil.getNextSibling(this); + } + + public PsiElement getPrevSibling() { + return SharedPsiElementImplUtil.getPrevSibling(this); + } + + public long getModificationStamp() { + return getViewProvider().getModificationStamp(); + } + + public void subtreeChanged() { + doClearCaches(); + getViewProvider().rootChanged(this); + } + + private void doClearCaches() { + final FileElement tree = getTreeElement(); + if (tree != null) { + myTreeElementPointer = tree; + tree.clearCaches(); + } + + synchronized (myStubLock) { + myStub = null; + if (tree != null) { + tree.putUserData(STUB_TREE_IN_PARSED_TREE, null); + } + } + + clearCaches(); + } + + @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "CloneDoesntCallSuperClone"}) + protected PsiFileImpl clone() { + FileViewProvider viewProvider = getViewProvider(); + FileViewProvider providerCopy = viewProvider.clone(); + final Language language = getLanguage(); + if (providerCopy == null) { + throw new AssertionError("Unable to clone the view provider: " + viewProvider + "; " + language); + } + PsiFileImpl clone = (PsiFileImpl)providerCopy.getPsi(language); + assert clone != null : "Cannot find psi file with language:" + language + " from viewprovider:" + + providerCopy + " virtual file:" + getVirtualFile(); + + copyCopyableDataTo(clone); + + if (getTreeElement() != null) { + // not set by provider in clone + final FileElement treeClone = (FileElement)calcTreeElement().clone(); + clone.myTreeElementPointer = treeClone; // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963) + treeClone.setPsi(clone); + } + + if (viewProvider.isEventSystemEnabled()) { + clone.myOriginalFile = this; + } + else if (myOriginalFile != null) { + clone.myOriginalFile = myOriginalFile; + } + + return clone; + } + + @NotNull public String getName() { + return getViewProvider().getVirtualFile().getName(); + } + + public PsiElement setName(@NotNull String name) throws IncorrectOperationException { + checkSetName(name); + doClearCaches(); + return PsiFileImplUtil.setName(this, name); + } + + public void checkSetName(String name) throws IncorrectOperationException { + if (!getViewProvider().isEventSystemEnabled()) return; + PsiFileImplUtil.checkSetName(this, name); + } + + public boolean isWritable() { + return getViewProvider().getVirtualFile().isWritable() && !CacheUtil.isCopy(this); + } + + public PsiDirectory getParent() { + return getContainingDirectory(); + } + + public PsiDirectory getContainingDirectory() { + final VirtualFile parentFile = getViewProvider().getVirtualFile().getParent(); + if (parentFile == null) return null; + return getManager().findDirectory(parentFile); + } + + public PsiFile getContainingFile() { + return this; + } + + public void delete() throws IncorrectOperationException { + checkDelete(); + PsiFileImplUtil.doDelete(this); + } + + public void checkDelete() throws IncorrectOperationException { + if (!getViewProvider().isEventSystemEnabled()) { + throw new IncorrectOperationException(); + } + CheckUtil.checkWritable(this); + } + + @NotNull + public PsiFile getOriginalFile() { + return myOriginalFile == null ? this : myOriginalFile; + } + + public void setOriginalFile(@NotNull final PsiFile originalFile) { + myOriginalFile = originalFile.getOriginalFile(); + } + + @NotNull + public PsiFile[] getPsiRoots() { + final FileViewProvider viewProvider = getViewProvider(); + final Set languages = viewProvider.getLanguages(); + final PsiFile[] roots = new PsiFile[languages.size()]; + int i = 0; + for (Language language : languages) { + PsiFile psi = viewProvider.getPsi(language); + if (psi == null) { + LOG.error("PSI is null for "+language+"; in file: "+this); + } + roots[i++] = psi; + } + return roots; + } + + public boolean isPhysical() { + // TODO[ik] remove this shit with dummy file system + return getViewProvider().isEventSystemEnabled(); + } + + @NotNull + public Language getLanguage() { + return myElementType.getLanguage(); + } + + @NotNull + public FileViewProvider getViewProvider() { + return myViewProvider; + } + + public void setTreeElementPointer(FileElement element) { + myTreeElementPointer = element; + } + + public PsiElement findElementAt(int offset) { + return getViewProvider().findElementAt(offset); + } + + public PsiReference findReferenceAt(int offset) { + return getViewProvider().findReferenceAt(offset); + } + + @NotNull + public char[] textToCharArray() { + return CharArrayUtil.fromSequenceStrict(getViewProvider().getContents()); + } + + @NotNull + protected T[] findChildrenByClass(Class aClass) { + List result = new ArrayList(); + for (PsiElement child : getChildren()) { + if (aClass.isInstance(child)) result.add((T)child); + } + return result.toArray((T[]) Array.newInstance(aClass, result.size())); + } + + @Nullable + protected T findChildByClass(Class aClass) { + for (PsiElement child : getChildren()) { + if (aClass.isInstance(child)) return (T)child; + } + return null; + } + + public boolean isTemplateDataFile() { + return false; + } + + public PsiElement getContext() { + return FileContextUtil.getFileContext(this); + } + + public void onContentReload() { + subtreeChanged(); // important! otherwise cached information is not released + if (isContentsLoaded()) { + unloadContent(); + } + } + + public PsiFile cacheCopy(final FileContent content) { + if (isContentsLoaded()) { + return this; + } + else { + CharSequence text; + if (content == null) { + Document document = FileDocumentManager.getInstance().getDocument(getVirtualFile()); + text = document.getCharsSequence(); + } + else { + text = CacheUtil.getContentText(content); + } + + FileType fileType = getFileType(); + final String name = getName(); + PsiFile fileCopy = + PsiFileFactory.getInstance(getProject()).createFileFromText(name, fileType, text, getModificationStamp(), false, false); + fileCopy.putUserData(CacheUtil.CACHE_COPY_KEY, Boolean.TRUE); + + ((PsiFileImpl)fileCopy).setOriginalFile(this); + return fileCopy; + } + } + + @Nullable + public StubElement getStub() { + StubTree stubHolder = getStubTree(); + return stubHolder != null ? stubHolder.getRoot() : null; + } + + @Nullable + public StubTree getStubTree() { + ApplicationManager.getApplication().assertReadAccessAllowed(); + + if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null; + + final StubTree derefd = derefStub(); + if (derefd != null) return derefd; + if (getTreeElementNoLock() != null) return null; + + final VirtualFile vFile = getVirtualFile(); + if (!(vFile instanceof VirtualFileWithId)) return null; + + StubTree stubHolder = StubTree.readOrBuild(getProject(), vFile); + if (stubHolder == null) return null; + + synchronized (myStubLock) { + if (getTreeElementNoLock() != null) return null; + + final StubTree derefdOnLock = derefStub(); + if (derefdOnLock != null) return derefdOnLock; + + setStubTree(stubHolder); + + return derefStub(); + } + } + + public void setStubTree(StubTree stubHolder) { + synchronized (myStubLock) { + assert getTreeElementNoLock() == null; + myStub = new SoftReference(stubHolder); + StubBase base = (StubBase)stubHolder.getRoot(); + base.setPsi(this); + + base.putUserData(STUB_TREE_IN_PARSED_TREE, stubHolder); // This will prevent soft reference myStub to be collected before all of the stubs are collected. + } + } + + @Nullable + private StubTree derefStub() { + if (myStub == null) return null; + + synchronized (myStubLock) { + return myStub != null ? myStub.get() : null; + } + } + + protected PsiFileImpl cloneImpl(FileElement treeElementClone) { + PsiFileImpl clone = (PsiFileImpl)super.clone(); + clone.myTreeElementPointer = treeElementClone; // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963) + treeElementClone.setPsi(clone); + return clone; + } + + private void setTreeElement(ASTNode treeElement){ + Object newPointer; + if (treeElement == null) { + newPointer = null; + } + else if (isKeepTreeElementByHardReference()) { + newPointer = treeElement; + } + else { + newPointer = myManager.isBatchFilesProcessingMode() + ? new PatchedWeakReference(treeElement) + : new PatchedSoftReference(treeElement); + } + + synchronized (myStubLock) { + myTreeElementPointer = newPointer; + } + } + + public Object getStubLock() { + return myStubLock; + } + + public PsiManager getManager() { + return myManager; + } + + public PsiElement getNavigationElement() { + return this; + } + + public PsiElement getOriginalElement() { + return this; + } + + public final FileElement calcTreeElement() { + // Attempt to find (loaded) tree element without taking lock first. + FileElement treeElement = getTreeElement(); + if (treeElement != null) return treeElement; + + synchronized (myStubLock) { + treeElement = getTreeElement(); + if (treeElement != null) return treeElement; + + return loadTreeElement(); + } + } + + @NotNull + public PsiElement[] getChildren() { + return calcTreeElement().getChildrenAsPsiElements(null, PsiElementArrayConstructor.PSI_ELEMENT_ARRAY_CONSTRUCTOR); + } + + public PsiElement getFirstChild() { + return SharedImplUtil.getFirstChild(calcTreeElement()); + } + + public PsiElement getLastChild() { + return SharedImplUtil.getLastChild(calcTreeElement()); + } + + public void acceptChildren(@NotNull PsiElementVisitor visitor) { + SharedImplUtil.acceptChildren(visitor, calcTreeElement()); + } + + public int getStartOffsetInParent() { + return calcTreeElement().getStartOffsetInParent(); + } + public int getTextOffset() { + return calcTreeElement().getTextOffset(); + } + + public boolean textMatches(@NotNull CharSequence text) { + return calcTreeElement().textMatches(text); + } + + public boolean textMatches(@NotNull PsiElement element) { + return calcTreeElement().textMatches(element); + } + + public boolean textContains(char c) { + return calcTreeElement().textContains(c); + } + + public final PsiElement copy() { + return clone(); + } + + public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException { + CheckUtil.checkWritable(this); + TreeElement elementCopy = ChangeUtil.copyToElement(element); + calcTreeElement().addInternal(elementCopy, elementCopy, null, null); + elementCopy = ChangeUtil.decodeInformation(elementCopy); + return SourceTreeToPsiMap.treeElementToPsi(elementCopy); + } + + public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { + CheckUtil.checkWritable(this); + TreeElement elementCopy = ChangeUtil.copyToElement(element); + calcTreeElement().addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE); + elementCopy = ChangeUtil.decodeInformation(elementCopy); + return SourceTreeToPsiMap.treeElementToPsi(elementCopy); + } + + public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { + CheckUtil.checkWritable(this); + TreeElement elementCopy = ChangeUtil.copyToElement(element); + calcTreeElement().addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE); + elementCopy = ChangeUtil.decodeInformation(elementCopy); + return SourceTreeToPsiMap.treeElementToPsi(elementCopy); + } + + public final void checkAdd(@NotNull PsiElement element) throws IncorrectOperationException { + CheckUtil.checkWritable(this); + } + + public PsiElement addRange(PsiElement first, PsiElement last) throws IncorrectOperationException { + return SharedImplUtil.addRange(this, first, last, null, null); + } + + public PsiElement addRangeBefore(@NotNull PsiElement first, @NotNull PsiElement last, PsiElement anchor) + throws IncorrectOperationException { + return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE); + } + + public PsiElement addRangeAfter(PsiElement first, PsiElement last, PsiElement anchor) + throws IncorrectOperationException { + return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE); + } + + public void deleteChildRange(PsiElement first, PsiElement last) throws IncorrectOperationException { + CheckUtil.checkWritable(this); + if (first == null) { + LOG.assertTrue(last == null); + return; + } + ASTNode firstElement = SourceTreeToPsiMap.psiElementToTree(first); + ASTNode lastElement = SourceTreeToPsiMap.psiElementToTree(last); + CompositeElement treeElement = calcTreeElement(); + LOG.assertTrue(firstElement.getTreeParent() == treeElement); + LOG.assertTrue(lastElement.getTreeParent() == treeElement); + CodeEditUtil.removeChildren(treeElement, firstElement, lastElement); + } + + public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException { + CompositeElement treeElement = calcTreeElement(); + return SharedImplUtil.doReplace(this, treeElement, newElement); + } + + public PsiReference getReference() { + return null; + } + + @NotNull + public PsiReference[] getReferences() { + return SharedPsiElementImplUtil.getReferences(this); + } + + public boolean processDeclarations(@NotNull PsiScopeProcessor processor, + @NotNull ResolveState state, + PsiElement lastParent, + @NotNull PsiElement place) { + return true; + } + + @NotNull + public GlobalSearchScope getResolveScope() { + return ((PsiManagerEx)getManager()).getFileManager().getResolveScope(this); + } + + @NotNull + public SearchScope getUseScope() { + return ((PsiManagerEx) getManager()).getFileManager().getUseScope(this); + } + + public ItemPresentation getPresentation() { + return new ItemPresentation() { + public String getPresentableText() { + return getName(); + } + + public String getLocationString() { + final PsiDirectory psiDirectory = getParent(); + if (psiDirectory != null) { + return psiDirectory.getVirtualFile().getPresentableUrl(); + } + return null; + } + + public Icon getIcon(final boolean open) { + return PsiFileImpl.this.getIcon(open ? ICON_FLAG_OPEN : ICON_FLAG_CLOSED); + } + + public TextAttributesKey getTextAttributesKey() { + return null; + } + }; + } + + public void navigate(boolean requestFocus) { + EditSourceUtil.getDescriptor(this).navigate(requestFocus); + } + + public boolean canNavigate() { + return EditSourceUtil.canNavigate(this); + } + + public boolean canNavigateToSource() { + return canNavigate(); + } + + public FileStatus getFileStatus() { + return SharedImplUtil.getFileStatus(this); + } + + @NotNull + public Project getProject() { + final PsiManager manager = getManager(); + if (manager == null) throw new PsiInvalidElementAccessException(this); + + return manager.getProject(); + } + + public FileASTNode getNode() { + return calcTreeElement(); + } + + public boolean isEquivalentTo(final PsiElement another) { + return this == another; + } + + private static final Key STUB_TREE_IN_PARSED_TREE = new Key("STUB_TREE_IN_PARSED_TREE"); + + public StubTree calcStubTree() { + synchronized (myStubLock) { + final FileElement fileElement = (FileElement)calcTreeElement(); + StubTree tree = fileElement.getUserData(STUB_TREE_IN_PARSED_TREE); + if (tree == null) { + final StubElement currentStubTree = ((IStubFileElementType)getContentElementType()).getBuilder().buildStubTree(this); + tree = new StubTree((PsiFileStub)currentStubTree); + bindFakeStubsToTree(tree); + fileElement.putUserData(STUB_TREE_IN_PARSED_TREE, tree); + } + return tree; + } + } + + private void bindFakeStubsToTree(StubTree stubTree) { + final PsiFileImpl file = this; + + final Iterator> stubs = stubTree.getPlainList().iterator(); + stubs.next();// skip file root stub + final FileElement fileRoot = file.getTreeElement(); + assert fileRoot != null; + + bindStubs(fileRoot, stubs, ((IStubFileElementType)getContentElementType()).getBuilder()); + } + + @Nullable + private StubElement bindStubs(ASTNode tree, Iterator> stubs, StubBuilder builder) { + final IElementType type = tree.getElementType(); + + if (type instanceof IStubElementType && ((IStubElementType) type).shouldCreateStub(tree)) { + final StubElement stub = stubs.next(); + if (stub.getStubType() != tree.getElementType()) { + rebuildStub(); + + assert false: "Stub and PSI element type mismatch: stub " + stub + ", AST " + tree.getElementType(); + } + + ((StubBase)stub).setPsi(tree.getPsi()); + } + + for (ASTNode node : tree.getChildren(null)) { + if (!builder.skipChildProcessingWhenBuildingStubs(type, node.getElementType())) { + final StubElement res = bindStubs(node, stubs, builder); + if (res != null) { + return res; + } + } + } + + return null; + } + + private void rebuildStub() { + final VirtualFile vFile = getVirtualFile(); + + if (vFile != null && vFile.isValid()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final Document doc = FileDocumentManager.getInstance().getCachedDocument(vFile); + if (doc != null) { + FileDocumentManager.getInstance().saveDocument(doc); + } + } + }, ModalityState.NON_MODAL); + + FileBasedIndex.getInstance().requestReindex(vFile); + } + } + + public void putInfo(Map info) { + putInfo(this, info); + } + + public static void putInfo(PsiFile psiFile, Map info) { + info.put("fileName", psiFile.getName()); + info.put("fileType", psiFile.getFileType().toString()); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java index 2dd876e3a8f6..dcdc78135e83 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java @@ -1,476 +1,476 @@ -/* - * 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.impl.source.codeStyle; - -import com.intellij.formatting.FormatTextRanges; -import com.intellij.formatting.FormatterEx; -import com.intellij.formatting.FormattingModel; -import com.intellij.formatting.FormattingModelBuilder; -import com.intellij.injected.editor.DocumentWindow; -import com.intellij.lang.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.CaretModel; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.VisualPosition; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.codeStyle.Indent; -import com.intellij.psi.impl.CheckUtil; -import com.intellij.psi.impl.source.PostprocessReformattingAspect; -import com.intellij.psi.impl.source.SourceTreeToPsiMap; -import com.intellij.psi.impl.source.tree.*; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.CharTable; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CodeStyleManagerImpl extends CodeStyleManager { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl"); - private final Project myProject; - @NonNls private static final String DUMMY_IDENTIFIER = "xxx"; - - public CodeStyleManagerImpl(Project project) { - myProject = project; - } - - @NotNull - public Project getProject() { - return myProject; - } - - @NotNull - public PsiElement reformat(@NotNull PsiElement element) throws IncorrectOperationException { - return reformat(element, false); - } - - @NotNull - public PsiElement reformat(@NotNull PsiElement element, boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException { - CheckUtil.checkWritable(element); - if( !SourceTreeToPsiMap.hasTreeElement( element ) ) - { - return element; - } - - ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(element); - final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(new CodeFormatterFacade(getSettings()).processElement(treeElement)); - if (!canChangeWhiteSpacesOnly) { - return postProcessElement(formatted); - } else { - return formatted; - } - - } - - private PsiElement postProcessElement(final PsiElement formatted) { - PsiElement result = formatted; - for (PostFormatProcessor postFormatProcessor : Extensions.getExtensions(PostFormatProcessor.EP_NAME)) { - result = postFormatProcessor.processElement(result, getSettings()); - } - return result; - } - - private void postProcessText(final PsiFile file, final TextRange textRange) { - TextRange currentRange = textRange; - for (final PostFormatProcessor myPostFormatProcessor : Extensions.getExtensions(PostFormatProcessor.EP_NAME)) { - currentRange = myPostFormatProcessor.processText(file, currentRange, getSettings()); - } - } - - public PsiElement reformatRange(@NotNull PsiElement element, - int startOffset, - int endOffset, - boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException { - return reformatRangeImpl(element, startOffset, endOffset, canChangeWhiteSpacesOnly); - } - - public PsiElement reformatRange(@NotNull PsiElement element, int startOffset, int endOffset) - throws IncorrectOperationException { - return reformatRangeImpl(element, startOffset, endOffset, false); - - } - - private static void transformAllChildren(final ASTNode file) { - for (ASTNode child = file.getFirstChildNode(); child != null; child = child.getTreeNext()) { - transformAllChildren(child); - } - } - - - public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset) throws IncorrectOperationException { - ApplicationManager.getApplication().assertWriteAccessAllowed(); - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - - CheckUtil.checkWritable(file); - if (!SourceTreeToPsiMap.hasTreeElement(file)) { - return; - } - - ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(file); - transformAllChildren(treeElement); - - final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); - LOG.assertTrue(file.isValid()); - final PsiElement start = findElementInTreeWithFormatterEnabled(file, startOffset); - final PsiElement end = findElementInTreeWithFormatterEnabled(file, endOffset); - if (start != null && !start.isValid()) { - LOG.error("start=" + start + "; file=" + file); - } - if (end != null && !end.isValid()) { - LOG.error("end=" + start + "; end=" + file); - } - - Editor editor = PsiUtilBase.findEditor(file); - - // There is a possible case that cursor is located at the end of the line that contains only white spaces. For example: - // public void foo() { - // - // } - // Formatter removes such white spaces, i.e. keeps only line feed symbol. But we want to preserve caret position then. - // So, we check if it should be preserved and restore it after formatting if necessary - int visualColumnToRestore = -1; - - if (editor != null) { - Document document = editor.getDocument(); - int caretOffset = editor.getCaretModel().getOffset(); - caretOffset = Math.max(Math.min(caretOffset, document.getTextLength() - 1), 0); - CharSequence text = document.getCharsSequence(); - int caretLine = document.getLineNumber(caretOffset); - int lineStartOffset = document.getLineStartOffset(caretLine); - boolean fixCaretPosition = true; - for (int i = caretOffset; i>= lineStartOffset; i--) { - char c = text.charAt(i); - if (c != ' ' && c != '\t' && c != '\n') { - fixCaretPosition = false; - break; - } - } - if (fixCaretPosition) { - visualColumnToRestore = editor.getCaretModel().getVisualPosition().column; - } - } - - - boolean formatFromStart = startOffset == 0; - boolean formatToEnd = endOffset == file.getTextLength(); - - final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(getProject()); - final SmartPsiElementPointer startPointer = start == null ? null : smartPointerManager.createSmartPsiElementPointer(start); - - final SmartPsiElementPointer endPointer = end == null ? null : smartPointerManager.createSmartPsiElementPointer(end); - - codeFormatter.processText(file, new FormatTextRanges(new TextRange(startOffset, endOffset), true), true); - final PsiElement startElement = startPointer == null ? null : startPointer.getElement(); - final PsiElement endElement = endPointer == null ? null : endPointer.getElement(); - - if ((startElement != null || formatFromStart) && (endElement != null || formatToEnd)) { - postProcessText(file, new TextRange(formatFromStart ? 0 : startElement.getTextRange().getStartOffset(), - formatToEnd ? file.getTextLength() : endElement.getTextRange().getEndOffset())); - } - - if (visualColumnToRestore < 0) { - return; - } - CaretModel caretModel = editor.getCaretModel(); - VisualPosition position = caretModel.getVisualPosition(); - if (visualColumnToRestore != position.column) { - caretModel.moveToVisualPosition(new VisualPosition(position.line, visualColumnToRestore)); - } - } - - private PsiElement reformatRangeImpl(final PsiElement element, - final int startOffset, - final int endOffset, - boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException { - LOG.assertTrue(element.isValid()); - CheckUtil.checkWritable(element); - if( !SourceTreeToPsiMap.hasTreeElement( element ) ) - { - return element; - } - - ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(element); - final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); - final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(codeFormatter.processRange(treeElement, startOffset, endOffset)); - - return canChangeWhiteSpacesOnly ? formatted : postProcessElement(formatted); - } - - - public void reformatNewlyAddedElement(@NotNull final ASTNode parent, @NotNull final ASTNode addedElement) throws IncorrectOperationException { - - LOG.assertTrue(addedElement.getTreeParent() == parent, "addedElement must be added to parent"); - - final PsiElement psiElement = parent.getPsi(); - - PsiFile containingFile = psiElement.getContainingFile(); - final FileViewProvider fileViewProvider = containingFile.getViewProvider(); - if (fileViewProvider instanceof MultiplePsiFilesPerDocumentFileViewProvider) { - containingFile = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage()); - } - - TextRange textRange = addedElement.getTextRange(); - final Document document = fileViewProvider.getDocument(); - if (document instanceof DocumentWindow) { - containingFile = InjectedLanguageUtil.getTopLevelFile(containingFile); - textRange = ((DocumentWindow)document).injectedToHost(textRange); - } - - final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(containingFile); - if (builder != null) { - final FormattingModel model = builder.createModel(containingFile, getSettings()); - FormatterEx.getInstanceEx().formatAroundRange(model, getSettings(), textRange, containingFile.getFileType()); - } - - adjustLineIndent(containingFile, textRange); - } - - public int adjustLineIndent(@NotNull final PsiFile file, final int offset) throws IncorrectOperationException { - return PostprocessReformattingAspect.getInstance(file.getProject()).disablePostprocessFormattingInside(new Computable() { - public Integer compute() { - return doAdjustLineIndentByOffset(file, offset); - } - }); - } - - @Nullable - static PsiElement findElementInTreeWithFormatterEnabled(final PsiFile file, final int offset) { - final PsiElement bottomost = file.findElementAt(offset); - if (bottomost != null && LanguageFormatting.INSTANCE.forContext(bottomost) != null){ - return bottomost; - } - - final Language fileLang = file.getLanguage(); - if (fileLang instanceof CompositeLanguage) { - return file.getViewProvider().findElementAt(offset, fileLang); - } - - return bottomost; - } - - public int adjustLineIndent(@NotNull final Document document, final int offset) { - return PostprocessReformattingAspect.getInstance(getProject()).disablePostprocessFormattingInside(new Computable() { - public Integer compute() { - final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); - documentManager.commitDocument(document); - - PsiFile file = documentManager.getPsiFile(document); - if (file == null) return offset; - - return doAdjustLineIndentByOffset(file, offset); - } - }); - } - - private int doAdjustLineIndentByOffset(@NotNull PsiFile file, int offset) { - return new CodeStyleManagerRunnable(this) { - @Override - protected Integer doPerform(int offset, TextRange range) { - return FormatterEx.getInstanceEx().adjustLineIndent(myModel, mySettings, myIndentOptions, offset, mySignificantRange); - } - - @Override - protected Integer computeValueInsidePlainComment(PsiFile file, int offset, Integer defaultValue) { - return CharArrayUtil.shiftForward(file.getViewProvider().getContents(), offset, " \t"); - } - - @Override - protected Integer adjustResultForInjected(Integer result, DocumentWindow documentWindow) { - return documentWindow.hostToInjected(result); - } - }.perform(file, offset, null, offset); - } - - public void adjustLineIndent(@NotNull PsiFile file, TextRange rangeToAdjust) throws IncorrectOperationException { - new CodeStyleManagerRunnable(this) { - @Override - protected Object doPerform(int offset, TextRange range) { - FormatterEx.getInstanceEx().adjustLineIndentsForRange(myModel, mySettings, myIndentOptions, range); - return null; - } - }.perform(file, -1, rangeToAdjust, null); - } - - @Nullable - public String getLineIndent(@NotNull PsiFile file, int offset) { - return new CodeStyleManagerRunnable(this) { - @Override - protected boolean useDocumentBaseFormattingModel() { - return false; - } - - @Override - protected String doPerform(int offset, TextRange range) { - return FormatterEx.getInstanceEx().getLineIndent(myModel, mySettings, myIndentOptions, offset, mySignificantRange); - } - }.perform(file, offset, null, null); - } - - @Nullable - public String getLineIndent(@NotNull Editor editor) { - Document doc = editor.getDocument(); - int offset = editor.getCaretModel().getOffset(); - if (offset >= doc.getTextLength()) { - return ""; - } - - PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(doc); - if (file == null) return ""; - - return getLineIndent(file, offset); - } - - public boolean isLineToBeIndented(@NotNull PsiFile file, int offset) { - if (!SourceTreeToPsiMap.hasTreeElement(file)) { - return false; - } - IndentHelper indentHelper = HelperFactory.createHelper(file.getFileType(), myProject); - CharSequence chars = file.getViewProvider().getContents(); - int start = CharArrayUtil.shiftBackward(chars, offset - 1, " \t"); - if (start > 0 && chars.charAt(start) != '\n' && chars.charAt(start) != '\r') { - return false; - } - int end = CharArrayUtil.shiftForward(chars, offset, " \t"); - if (end >= chars.length()) { - return false; - } - ASTNode element = SourceTreeToPsiMap.psiElementToTree(findElementInTreeWithFormatterEnabled(file, end)); - if (element == null) { - return false; - } - if (element.getElementType() == TokenType.WHITE_SPACE) { - return false; - } - if (element.getElementType() == PlainTextTokenTypes.PLAIN_TEXT) { - return false; - } - /* - if( element.getElementType() instanceof IJspElementType ) - { - return false; - } - */ - if (getSettings().KEEP_FIRST_COLUMN_COMMENT && isCommentToken(element)) { - if (indentHelper.getIndent(element, true) == 0) { - return false; - } - } - return true; - } - - private static boolean isCommentToken(final ASTNode element) { - final Language language = element.getElementType().getLanguage(); - final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(language); - if (commenter instanceof CodeDocumentationAwareCommenter) { - final CodeDocumentationAwareCommenter documentationAwareCommenter = (CodeDocumentationAwareCommenter)commenter; - return element.getElementType() == documentationAwareCommenter.getBlockCommentTokenType() || - element.getElementType() == documentationAwareCommenter.getLineCommentTokenType(); - } - return false; - } - - @Nullable - public static PsiElement insertNewLineIndentMarker(@NotNull PsiFile file, int offset) throws IncorrectOperationException { - CheckUtil.checkWritable(file); - final CharTable charTable = ((FileElement)SourceTreeToPsiMap.psiElementToTree(file)).getCharTable(); - PsiElement elementAt = findElementInTreeWithFormatterEnabled(file, offset); - if( elementAt == null ) - { - return null; - } - ASTNode element = SourceTreeToPsiMap.psiElementToTree(elementAt); - ASTNode parent = element.getTreeParent(); - int elementStart = element.getTextRange().getStartOffset(); - if (element.getElementType() != TokenType.WHITE_SPACE) { - /* - if (elementStart < offset) return null; - Element marker = Factory.createLeafElement(ElementType.NEW_LINE_INDENT, "###".toCharArray(), 0, "###".length()); - ChangeUtil.addChild(parent, marker, element); - return marker; - */ - return null; - } - - ASTNode space1 = splitSpaceElement((TreeElement)element, offset - elementStart, charTable); - ASTNode marker = Factory.createSingleLeafElement(TokenType.NEW_LINE_INDENT, DUMMY_IDENTIFIER, charTable, file.getManager()); - parent.addChild(marker, space1.getTreeNext()); - return SourceTreeToPsiMap.treeElementToPsi(marker); - } - - public Indent getIndent(String text, FileType fileType) { - int indent = HelperFactory.createHelper(fileType, myProject).getIndent(text, true); - int indenLevel = indent / IndentHelper.INDENT_FACTOR; - int spaceCount = indent - indenLevel * IndentHelper.INDENT_FACTOR; - return new IndentImpl(getSettings(), indenLevel, spaceCount, fileType); - } - - public String fillIndent(Indent indent, FileType fileType) { - IndentImpl indent1 = (IndentImpl)indent; - int indentLevel = indent1.getIndentLevel(); - int spaceCount = indent1.getSpaceCount(); - if (indentLevel < 0) { - spaceCount += indentLevel * getSettings().getIndentSize(fileType); - indentLevel = 0; - if (spaceCount < 0) { - spaceCount = 0; - } - } - else { - if (spaceCount < 0) { - int v = (-spaceCount + getSettings().getIndentSize(fileType) - 1) / getSettings().getIndentSize(fileType); - indentLevel -= v; - spaceCount += v * getSettings().getIndentSize(fileType); - if (indentLevel < 0) { - indentLevel = 0; - } - } - } - return HelperFactory.createHelper(fileType, myProject).fillIndent(indentLevel * IndentHelper.INDENT_FACTOR + spaceCount); - } - - public Indent zeroIndent() { - return new IndentImpl(getSettings(), 0, 0, null); - } - - - private static ASTNode splitSpaceElement(TreeElement space, int offset, CharTable charTable) { - LOG.assertTrue(space.getElementType() == TokenType.WHITE_SPACE); - CharSequence chars = space.getChars(); - LeafElement space1 = Factory.createSingleLeafElement(TokenType.WHITE_SPACE, chars, 0, offset, charTable, SharedImplUtil.getManagerByTree(space)); - LeafElement space2 = Factory.createSingleLeafElement(TokenType.WHITE_SPACE, chars, offset, chars.length(), charTable, SharedImplUtil.getManagerByTree(space)); - ASTNode parent = space.getTreeParent(); - parent.replaceChild(space, space1); - parent.addChild(space2, space1.getTreeNext()); - return space1; - } - - private CodeStyleSettings getSettings() { - return CodeStyleSettingsManager.getSettings(myProject); - } -} +/* + * 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.impl.source.codeStyle; + +import com.intellij.formatting.FormatTextRanges; +import com.intellij.formatting.FormatterEx; +import com.intellij.formatting.FormattingModel; +import com.intellij.formatting.FormattingModelBuilder; +import com.intellij.injected.editor.DocumentWindow; +import com.intellij.lang.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.CaretModel; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.Indent; +import com.intellij.psi.impl.CheckUtil; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.psi.impl.source.SourceTreeToPsiMap; +import com.intellij.psi.impl.source.tree.*; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.CharTable; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CodeStyleManagerImpl extends CodeStyleManager { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl"); + private final Project myProject; + @NonNls private static final String DUMMY_IDENTIFIER = "xxx"; + + public CodeStyleManagerImpl(Project project) { + myProject = project; + } + + @NotNull + public Project getProject() { + return myProject; + } + + @NotNull + public PsiElement reformat(@NotNull PsiElement element) throws IncorrectOperationException { + return reformat(element, false); + } + + @NotNull + public PsiElement reformat(@NotNull PsiElement element, boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException { + CheckUtil.checkWritable(element); + if( !SourceTreeToPsiMap.hasTreeElement( element ) ) + { + return element; + } + + ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(element); + final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(new CodeFormatterFacade(getSettings()).processElement(treeElement)); + if (!canChangeWhiteSpacesOnly) { + return postProcessElement(formatted); + } else { + return formatted; + } + + } + + private PsiElement postProcessElement(final PsiElement formatted) { + PsiElement result = formatted; + for (PostFormatProcessor postFormatProcessor : Extensions.getExtensions(PostFormatProcessor.EP_NAME)) { + result = postFormatProcessor.processElement(result, getSettings()); + } + return result; + } + + private void postProcessText(final PsiFile file, final TextRange textRange) { + TextRange currentRange = textRange; + for (final PostFormatProcessor myPostFormatProcessor : Extensions.getExtensions(PostFormatProcessor.EP_NAME)) { + currentRange = myPostFormatProcessor.processText(file, currentRange, getSettings()); + } + } + + public PsiElement reformatRange(@NotNull PsiElement element, + int startOffset, + int endOffset, + boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException { + return reformatRangeImpl(element, startOffset, endOffset, canChangeWhiteSpacesOnly); + } + + public PsiElement reformatRange(@NotNull PsiElement element, int startOffset, int endOffset) + throws IncorrectOperationException { + return reformatRangeImpl(element, startOffset, endOffset, false); + + } + + private static void transformAllChildren(final ASTNode file) { + for (ASTNode child = file.getFirstChildNode(); child != null; child = child.getTreeNext()) { + transformAllChildren(child); + } + } + + + public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset) throws IncorrectOperationException { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + + CheckUtil.checkWritable(file); + if (!SourceTreeToPsiMap.hasTreeElement(file)) { + return; + } + + ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(file); + transformAllChildren(treeElement); + + final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); + LOG.assertTrue(file.isValid()); + final PsiElement start = findElementInTreeWithFormatterEnabled(file, startOffset); + final PsiElement end = findElementInTreeWithFormatterEnabled(file, endOffset); + if (start != null && !start.isValid()) { + LOG.error("start=" + start + "; file=" + file); + } + if (end != null && !end.isValid()) { + LOG.error("end=" + start + "; end=" + file); + } + + Editor editor = PsiUtilBase.findEditor(file); + + // There is a possible case that cursor is located at the end of the line that contains only white spaces. For example: + // public void foo() { + // + // } + // Formatter removes such white spaces, i.e. keeps only line feed symbol. But we want to preserve caret position then. + // So, we check if it should be preserved and restore it after formatting if necessary + int visualColumnToRestore = -1; + + if (editor != null) { + Document document = editor.getDocument(); + int caretOffset = editor.getCaretModel().getOffset(); + caretOffset = Math.max(Math.min(caretOffset, document.getTextLength() - 1), 0); + CharSequence text = document.getCharsSequence(); + int caretLine = document.getLineNumber(caretOffset); + int lineStartOffset = document.getLineStartOffset(caretLine); + boolean fixCaretPosition = true; + for (int i = caretOffset; i>= lineStartOffset; i--) { + char c = text.charAt(i); + if (c != ' ' && c != '\t' && c != '\n') { + fixCaretPosition = false; + break; + } + } + if (fixCaretPosition) { + visualColumnToRestore = editor.getCaretModel().getVisualPosition().column; + } + } + + + boolean formatFromStart = startOffset == 0; + boolean formatToEnd = endOffset == file.getTextLength(); + + final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(getProject()); + final SmartPsiElementPointer startPointer = start == null ? null : smartPointerManager.createSmartPsiElementPointer(start); + + final SmartPsiElementPointer endPointer = end == null ? null : smartPointerManager.createSmartPsiElementPointer(end); + + codeFormatter.processText(file, new FormatTextRanges(new TextRange(startOffset, endOffset), true), true); + final PsiElement startElement = startPointer == null ? null : startPointer.getElement(); + final PsiElement endElement = endPointer == null ? null : endPointer.getElement(); + + if ((startElement != null || formatFromStart) && (endElement != null || formatToEnd)) { + postProcessText(file, new TextRange(formatFromStart ? 0 : startElement.getTextRange().getStartOffset(), + formatToEnd ? file.getTextLength() : endElement.getTextRange().getEndOffset())); + } + + if (visualColumnToRestore < 0) { + return; + } + CaretModel caretModel = editor.getCaretModel(); + VisualPosition position = caretModel.getVisualPosition(); + if (visualColumnToRestore != position.column) { + caretModel.moveToVisualPosition(new VisualPosition(position.line, visualColumnToRestore)); + } + } + + private PsiElement reformatRangeImpl(final PsiElement element, + final int startOffset, + final int endOffset, + boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException { + LOG.assertTrue(element.isValid()); + CheckUtil.checkWritable(element); + if( !SourceTreeToPsiMap.hasTreeElement( element ) ) + { + return element; + } + + ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(element); + final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); + final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(codeFormatter.processRange(treeElement, startOffset, endOffset)); + + return canChangeWhiteSpacesOnly ? formatted : postProcessElement(formatted); + } + + + public void reformatNewlyAddedElement(@NotNull final ASTNode parent, @NotNull final ASTNode addedElement) throws IncorrectOperationException { + + LOG.assertTrue(addedElement.getTreeParent() == parent, "addedElement must be added to parent"); + + final PsiElement psiElement = parent.getPsi(); + + PsiFile containingFile = psiElement.getContainingFile(); + final FileViewProvider fileViewProvider = containingFile.getViewProvider(); + if (fileViewProvider instanceof MultiplePsiFilesPerDocumentFileViewProvider) { + containingFile = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage()); + } + + TextRange textRange = addedElement.getTextRange(); + final Document document = fileViewProvider.getDocument(); + if (document instanceof DocumentWindow) { + containingFile = InjectedLanguageUtil.getTopLevelFile(containingFile); + textRange = ((DocumentWindow)document).injectedToHost(textRange); + } + + final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(containingFile); + if (builder != null) { + final FormattingModel model = builder.createModel(containingFile, getSettings()); + FormatterEx.getInstanceEx().formatAroundRange(model, getSettings(), textRange, containingFile.getFileType()); + } + + adjustLineIndent(containingFile, textRange); + } + + public int adjustLineIndent(@NotNull final PsiFile file, final int offset) throws IncorrectOperationException { + return PostprocessReformattingAspect.getInstance(file.getProject()).disablePostprocessFormattingInside(new Computable() { + public Integer compute() { + return doAdjustLineIndentByOffset(file, offset); + } + }); + } + + @Nullable + static PsiElement findElementInTreeWithFormatterEnabled(final PsiFile file, final int offset) { + final PsiElement bottomost = file.findElementAt(offset); + if (bottomost != null && LanguageFormatting.INSTANCE.forContext(bottomost) != null){ + return bottomost; + } + + final Language fileLang = file.getLanguage(); + if (fileLang instanceof CompositeLanguage) { + return file.getViewProvider().findElementAt(offset, fileLang); + } + + return bottomost; + } + + public int adjustLineIndent(@NotNull final Document document, final int offset) { + return PostprocessReformattingAspect.getInstance(getProject()).disablePostprocessFormattingInside(new Computable() { + public Integer compute() { + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); + documentManager.commitDocument(document); + + PsiFile file = documentManager.getPsiFile(document); + if (file == null) return offset; + + return doAdjustLineIndentByOffset(file, offset); + } + }); + } + + private int doAdjustLineIndentByOffset(@NotNull PsiFile file, int offset) { + return new CodeStyleManagerRunnable(this) { + @Override + protected Integer doPerform(int offset, TextRange range) { + return FormatterEx.getInstanceEx().adjustLineIndent(myModel, mySettings, myIndentOptions, offset, mySignificantRange); + } + + @Override + protected Integer computeValueInsidePlainComment(PsiFile file, int offset, Integer defaultValue) { + return CharArrayUtil.shiftForward(file.getViewProvider().getContents(), offset, " \t"); + } + + @Override + protected Integer adjustResultForInjected(Integer result, DocumentWindow documentWindow) { + return documentWindow.hostToInjected(result); + } + }.perform(file, offset, null, offset); + } + + public void adjustLineIndent(@NotNull PsiFile file, TextRange rangeToAdjust) throws IncorrectOperationException { + new CodeStyleManagerRunnable(this) { + @Override + protected Object doPerform(int offset, TextRange range) { + FormatterEx.getInstanceEx().adjustLineIndentsForRange(myModel, mySettings, myIndentOptions, range); + return null; + } + }.perform(file, -1, rangeToAdjust, null); + } + + @Nullable + public String getLineIndent(@NotNull PsiFile file, int offset) { + return new CodeStyleManagerRunnable(this) { + @Override + protected boolean useDocumentBaseFormattingModel() { + return false; + } + + @Override + protected String doPerform(int offset, TextRange range) { + return FormatterEx.getInstanceEx().getLineIndent(myModel, mySettings, myIndentOptions, offset, mySignificantRange); + } + }.perform(file, offset, null, null); + } + + @Nullable + public String getLineIndent(@NotNull Editor editor) { + Document doc = editor.getDocument(); + int offset = editor.getCaretModel().getOffset(); + if (offset >= doc.getTextLength()) { + return ""; + } + + PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(doc); + if (file == null) return ""; + + return getLineIndent(file, offset); + } + + public boolean isLineToBeIndented(@NotNull PsiFile file, int offset) { + if (!SourceTreeToPsiMap.hasTreeElement(file)) { + return false; + } + IndentHelper indentHelper = HelperFactory.createHelper(file.getFileType(), myProject); + CharSequence chars = file.getViewProvider().getContents(); + int start = CharArrayUtil.shiftBackward(chars, offset - 1, " \t"); + if (start > 0 && chars.charAt(start) != '\n' && chars.charAt(start) != '\r') { + return false; + } + int end = CharArrayUtil.shiftForward(chars, offset, " \t"); + if (end >= chars.length()) { + return false; + } + ASTNode element = SourceTreeToPsiMap.psiElementToTree(findElementInTreeWithFormatterEnabled(file, end)); + if (element == null) { + return false; + } + if (element.getElementType() == TokenType.WHITE_SPACE) { + return false; + } + if (element.getElementType() == PlainTextTokenTypes.PLAIN_TEXT) { + return false; + } + /* + if( element.getElementType() instanceof IJspElementType ) + { + return false; + } + */ + if (getSettings().KEEP_FIRST_COLUMN_COMMENT && isCommentToken(element)) { + if (indentHelper.getIndent(element, true) == 0) { + return false; + } + } + return true; + } + + private static boolean isCommentToken(final ASTNode element) { + final Language language = element.getElementType().getLanguage(); + final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(language); + if (commenter instanceof CodeDocumentationAwareCommenter) { + final CodeDocumentationAwareCommenter documentationAwareCommenter = (CodeDocumentationAwareCommenter)commenter; + return element.getElementType() == documentationAwareCommenter.getBlockCommentTokenType() || + element.getElementType() == documentationAwareCommenter.getLineCommentTokenType(); + } + return false; + } + + @Nullable + public static PsiElement insertNewLineIndentMarker(@NotNull PsiFile file, int offset) throws IncorrectOperationException { + CheckUtil.checkWritable(file); + final CharTable charTable = ((FileElement)SourceTreeToPsiMap.psiElementToTree(file)).getCharTable(); + PsiElement elementAt = findElementInTreeWithFormatterEnabled(file, offset); + if( elementAt == null ) + { + return null; + } + ASTNode element = SourceTreeToPsiMap.psiElementToTree(elementAt); + ASTNode parent = element.getTreeParent(); + int elementStart = element.getTextRange().getStartOffset(); + if (element.getElementType() != TokenType.WHITE_SPACE) { + /* + if (elementStart < offset) return null; + Element marker = Factory.createLeafElement(ElementType.NEW_LINE_INDENT, "###".toCharArray(), 0, "###".length()); + ChangeUtil.addChild(parent, marker, element); + return marker; + */ + return null; + } + + ASTNode space1 = splitSpaceElement((TreeElement)element, offset - elementStart, charTable); + ASTNode marker = Factory.createSingleLeafElement(TokenType.NEW_LINE_INDENT, DUMMY_IDENTIFIER, charTable, file.getManager()); + parent.addChild(marker, space1.getTreeNext()); + return SourceTreeToPsiMap.treeElementToPsi(marker); + } + + public Indent getIndent(String text, FileType fileType) { + int indent = HelperFactory.createHelper(fileType, myProject).getIndent(text, true); + int indenLevel = indent / IndentHelper.INDENT_FACTOR; + int spaceCount = indent - indenLevel * IndentHelper.INDENT_FACTOR; + return new IndentImpl(getSettings(), indenLevel, spaceCount, fileType); + } + + public String fillIndent(Indent indent, FileType fileType) { + IndentImpl indent1 = (IndentImpl)indent; + int indentLevel = indent1.getIndentLevel(); + int spaceCount = indent1.getSpaceCount(); + if (indentLevel < 0) { + spaceCount += indentLevel * getSettings().getIndentSize(fileType); + indentLevel = 0; + if (spaceCount < 0) { + spaceCount = 0; + } + } + else { + if (spaceCount < 0) { + int v = (-spaceCount + getSettings().getIndentSize(fileType) - 1) / getSettings().getIndentSize(fileType); + indentLevel -= v; + spaceCount += v * getSettings().getIndentSize(fileType); + if (indentLevel < 0) { + indentLevel = 0; + } + } + } + return HelperFactory.createHelper(fileType, myProject).fillIndent(indentLevel * IndentHelper.INDENT_FACTOR + spaceCount); + } + + public Indent zeroIndent() { + return new IndentImpl(getSettings(), 0, 0, null); + } + + + private static ASTNode splitSpaceElement(TreeElement space, int offset, CharTable charTable) { + LOG.assertTrue(space.getElementType() == TokenType.WHITE_SPACE); + CharSequence chars = space.getChars(); + LeafElement space1 = Factory.createSingleLeafElement(TokenType.WHITE_SPACE, chars, 0, offset, charTable, SharedImplUtil.getManagerByTree(space)); + LeafElement space2 = Factory.createSingleLeafElement(TokenType.WHITE_SPACE, chars, offset, chars.length(), charTable, SharedImplUtil.getManagerByTree(space)); + ASTNode parent = space.getTreeParent(); + parent.replaceChild(space, space1); + parent.addChild(space2, space1.getTreeNext()); + return space1; + } + + private CodeStyleSettings getSettings() { + return CodeStyleSettingsManager.getSettings(myProject); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java b/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java index 38c1aefe79fe..24c63ca26335 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java @@ -1,144 +1,144 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.psi.impl.source.text; - -import com.intellij.lang.ASTNode; -import com.intellij.pom.PomManager; -import com.intellij.pom.tree.TreeAspect; -import com.intellij.pom.tree.events.ChangeInfo; -import com.intellij.pom.tree.events.impl.ChangeInfoImpl; -import com.intellij.pom.tree.events.impl.ReplaceChangeInfoImpl; -import com.intellij.pom.tree.events.impl.TreeChangeEventImpl; -import com.intellij.psi.PsiElement; -import com.intellij.psi.impl.DebugUtil; -import com.intellij.psi.impl.PsiManagerEx; -import com.intellij.psi.impl.PsiTreeChangeEventImpl; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.tree.CompositeElement; -import com.intellij.psi.impl.source.tree.FileElement; -import com.intellij.psi.impl.source.tree.TreeElement; -import com.intellij.psi.impl.source.tree.TreeUtil; -import com.intellij.util.diff.DiffTreeChangeBuilder; -import org.jetbrains.annotations.NotNull; - -public class ASTDiffBuilder implements DiffTreeChangeBuilder { - public static boolean DEBUG = false; - private final TreeChangeEventImpl myEvent; - private final PsiFileImpl myFile; - private final PsiManagerEx myPsiManager; - private final boolean myIsPhysicalScope; - - - public ASTDiffBuilder(final PsiFileImpl fileImpl) { - myFile = fileImpl; - myIsPhysicalScope = fileImpl.isPhysical(); - myPsiManager = (PsiManagerEx)fileImpl.getManager(); - myEvent = new TreeChangeEventImpl(PomManager.getModel(fileImpl.getProject()).getModelAspect(TreeAspect.class), fileImpl.getTreeElement()); - } - - public void nodeReplaced(@NotNull ASTNode oldNode, @NotNull ASTNode newNode) { - if (oldNode instanceof FileElement && newNode instanceof FileElement) { - BlockSupportImpl.replaceFileElement(myFile, (FileElement)oldNode, (FileElement)newNode, myPsiManager); - } - else { - final ASTNode parent = oldNode.getTreeParent(); - assert parent != null : "old:" + oldNode + " new:" + newNode; - - TreeUtil.ensureParsed(oldNode); - - final PsiElement psiParent = parent.getPsi(); - final PsiElement psiChild = myIsPhysicalScope ? oldNode.getPsi() : null; - if (psiParent != null && psiChild != null) { - final PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); - event.setParent(psiParent); - event.setChild(psiChild); - myPsiManager.beforeChildReplacement(event); - } - - ((TreeElement)newNode).rawRemove(); - ((TreeElement)oldNode).rawReplaceWithList((TreeElement)newNode); - - final ReplaceChangeInfoImpl change = (ReplaceChangeInfoImpl)ChangeInfoImpl.create(ChangeInfo.REPLACE, newNode); - - change.setReplaced(oldNode); - myEvent.addElementaryChange(newNode, change); - ((TreeElement)newNode).clearCaches(); - if (!(newNode instanceof FileElement)) { - ((CompositeElement)newNode.getTreeParent()).subtreeChanged(); - } - - if (DEBUG) { - DebugUtil.checkTreeStructure(parent); - } - } - } - - public void nodeDeleted(@NotNull ASTNode parent, @NotNull final ASTNode child) { - PsiElement psiParent = parent.getPsi(); - PsiElement psiChild = myIsPhysicalScope ? child.getPsi() : null; - - if (psiParent != null && psiChild != null) { - PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); - event.setParent(psiParent); - event.setChild(psiChild); - myPsiManager.beforeChildRemoval(event); - } - - myEvent.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.REMOVED, child)); - ((TreeElement)child).rawRemove(); - ((CompositeElement)parent).subtreeChanged(); - - if (DEBUG) { - DebugUtil.checkTreeStructure(parent); - } - } - - public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull ASTNode node, final int pos) { - ASTNode anchor = null; - for (int i = 0; i < pos; i++) { - anchor = anchor == null ? oldParent.getFirstChildNode() : anchor.getTreeNext(); - } - - ((TreeElement)node).rawRemove(); - if (anchor != null) { - ((TreeElement)anchor).rawInsertAfterMe((TreeElement)node); - } - else { - if (oldParent.getFirstChildNode() != null) { - ((TreeElement)oldParent.getFirstChildNode()).rawInsertBeforeMe((TreeElement)node); - } - else { - ((CompositeElement)oldParent).rawAddChildren((TreeElement)node); - } - } - - myEvent.addElementaryChange(node, ChangeInfoImpl.create(ChangeInfo.ADD, node)); - ((TreeElement)node).clearCaches(); - ((CompositeElement)oldParent).subtreeChanged(); - - if (DEBUG) { - DebugUtil.checkTreeStructure(oldParent); - } - } - - public TreeChangeEventImpl getEvent() { - return myEvent; - } -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.psi.impl.source.text; + +import com.intellij.lang.ASTNode; +import com.intellij.pom.PomManager; +import com.intellij.pom.tree.TreeAspect; +import com.intellij.pom.tree.events.ChangeInfo; +import com.intellij.pom.tree.events.impl.ChangeInfoImpl; +import com.intellij.pom.tree.events.impl.ReplaceChangeInfoImpl; +import com.intellij.pom.tree.events.impl.TreeChangeEventImpl; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.impl.PsiTreeChangeEventImpl; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.tree.CompositeElement; +import com.intellij.psi.impl.source.tree.FileElement; +import com.intellij.psi.impl.source.tree.TreeElement; +import com.intellij.psi.impl.source.tree.TreeUtil; +import com.intellij.util.diff.DiffTreeChangeBuilder; +import org.jetbrains.annotations.NotNull; + +public class ASTDiffBuilder implements DiffTreeChangeBuilder { + public static boolean DEBUG = false; + private final TreeChangeEventImpl myEvent; + private final PsiFileImpl myFile; + private final PsiManagerEx myPsiManager; + private final boolean myIsPhysicalScope; + + + public ASTDiffBuilder(final PsiFileImpl fileImpl) { + myFile = fileImpl; + myIsPhysicalScope = fileImpl.isPhysical(); + myPsiManager = (PsiManagerEx)fileImpl.getManager(); + myEvent = new TreeChangeEventImpl(PomManager.getModel(fileImpl.getProject()).getModelAspect(TreeAspect.class), fileImpl.getTreeElement()); + } + + public void nodeReplaced(@NotNull ASTNode oldNode, @NotNull ASTNode newNode) { + if (oldNode instanceof FileElement && newNode instanceof FileElement) { + BlockSupportImpl.replaceFileElement(myFile, (FileElement)oldNode, (FileElement)newNode, myPsiManager); + } + else { + final ASTNode parent = oldNode.getTreeParent(); + assert parent != null : "old:" + oldNode + " new:" + newNode; + + TreeUtil.ensureParsed(oldNode); + + final PsiElement psiParent = parent.getPsi(); + final PsiElement psiChild = myIsPhysicalScope ? oldNode.getPsi() : null; + if (psiParent != null && psiChild != null) { + final PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); + event.setParent(psiParent); + event.setChild(psiChild); + myPsiManager.beforeChildReplacement(event); + } + + ((TreeElement)newNode).rawRemove(); + ((TreeElement)oldNode).rawReplaceWithList((TreeElement)newNode); + + final ReplaceChangeInfoImpl change = (ReplaceChangeInfoImpl)ChangeInfoImpl.create(ChangeInfo.REPLACE, newNode); + + change.setReplaced(oldNode); + myEvent.addElementaryChange(newNode, change); + ((TreeElement)newNode).clearCaches(); + if (!(newNode instanceof FileElement)) { + ((CompositeElement)newNode.getTreeParent()).subtreeChanged(); + } + + if (DEBUG) { + DebugUtil.checkTreeStructure(parent); + } + } + } + + public void nodeDeleted(@NotNull ASTNode parent, @NotNull final ASTNode child) { + PsiElement psiParent = parent.getPsi(); + PsiElement psiChild = myIsPhysicalScope ? child.getPsi() : null; + + if (psiParent != null && psiChild != null) { + PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); + event.setParent(psiParent); + event.setChild(psiChild); + myPsiManager.beforeChildRemoval(event); + } + + myEvent.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.REMOVED, child)); + ((TreeElement)child).rawRemove(); + ((CompositeElement)parent).subtreeChanged(); + + if (DEBUG) { + DebugUtil.checkTreeStructure(parent); + } + } + + public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull ASTNode node, final int pos) { + ASTNode anchor = null; + for (int i = 0; i < pos; i++) { + anchor = anchor == null ? oldParent.getFirstChildNode() : anchor.getTreeNext(); + } + + ((TreeElement)node).rawRemove(); + if (anchor != null) { + ((TreeElement)anchor).rawInsertAfterMe((TreeElement)node); + } + else { + if (oldParent.getFirstChildNode() != null) { + ((TreeElement)oldParent.getFirstChildNode()).rawInsertBeforeMe((TreeElement)node); + } + else { + ((CompositeElement)oldParent).rawAddChildren((TreeElement)node); + } + } + + myEvent.addElementaryChange(node, ChangeInfoImpl.create(ChangeInfo.ADD, node)); + ((TreeElement)node).clearCaches(); + ((CompositeElement)oldParent).subtreeChanged(); + + if (DEBUG) { + DebugUtil.checkTreeStructure(oldParent); + } + } + + public TreeChangeEventImpl getEvent() { + return myEvent; + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java index fb3c4ac1a805..8f1afc94defb 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/MultiHostRegistrarImpl.java @@ -1,496 +1,496 @@ -/* - * 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.impl.source.tree.injected; - -import com.intellij.injected.editor.DocumentWindow; -import com.intellij.injected.editor.DocumentWindowImpl; -import com.intellij.injected.editor.VirtualFileWindow; -import com.intellij.injected.editor.VirtualFileWindowImpl; -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageParserDefinitions; -import com.intellij.lang.ParserDefinition; -import com.intellij.lang.injection.MultiHostRegistrar; -import com.intellij.lexer.Lexer; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; -import com.intellij.openapi.fileTypes.SyntaxHighlighter; -import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.impl.PsiDocumentManagerImpl; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.resolve.FileContextUtil; -import com.intellij.psi.impl.source.text.BlockSupportImpl; -import com.intellij.psi.impl.source.tree.*; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; -import com.intellij.util.ArrayUtil; -import com.intellij.util.SmartList; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * @author cdr -*/ -public class MultiHostRegistrarImpl implements MultiHostRegistrar { - List> result; - private Language myLanguage; - private List> escapers; - private List shreds; - private StringBuilder outChars; - private boolean isOneLineEditor; - private boolean cleared; - private final Project myProject; - private final PsiManager myPsiManager; - private DocumentEx myHostDocument; - private VirtualFile myHostVirtualFile; - private final PsiElement myContextElement; - private final PsiFile myHostPsiFile; - - MultiHostRegistrarImpl(@NotNull Project project, @NotNull PsiFile hostPsiFile, @NotNull PsiElement contextElement) { - myProject = project; - myContextElement = contextElement; - myHostPsiFile = PsiUtilBase.getTemplateLanguageFile(hostPsiFile); - myPsiManager = myHostPsiFile.getManager(); - cleared = true; - } - - public List> getResult() { - return result; - } - - @NotNull - public MultiHostRegistrar startInjecting(@NotNull Language language) { - escapers = new SmartList>(); - shreds = new SmartList(); - outChars = new StringBuilder(); - - if (!cleared) { - clear(); - throw new IllegalStateException("Seems you haven't called doneInjecting()"); - } - - if (LanguageParserDefinitions.INSTANCE.forLanguage(language) == null) { - throw new UnsupportedOperationException("Cannot inject language '" + language + "' since its getParserDefinition() returns null"); - } - myLanguage = language; - - FileViewProvider viewProvider = myHostPsiFile.getViewProvider(); - myHostVirtualFile = viewProvider.getVirtualFile(); - myHostDocument = (DocumentEx)viewProvider.getDocument(); - assert myHostDocument != null : myHostPsiFile + "; " + viewProvider; - return this; - } - - private void clear() { - escapers.clear(); - shreds.clear(); - outChars.setLength(0); - isOneLineEditor = false; - myLanguage = null; - - cleared = true; - } - - @NotNull - public MultiHostRegistrar addPlace(@NonNls @Nullable String prefix, - @NonNls @Nullable String suffix, - @NotNull PsiLanguageInjectionHost host, - @NotNull TextRange rangeInsideHost) { - ProperTextRange.assertProperRange(rangeInsideHost); - - PsiFile containingFile = PsiUtilBase.getTemplateLanguageFile(host); - assert containingFile == myHostPsiFile : exceptionContext("Trying to inject into foreign file: "+containingFile); - TextRange hostTextRange = host.getTextRange(); - if (!hostTextRange.contains(rangeInsideHost.shiftRight(hostTextRange.getStartOffset()))) { - clear(); - throw new IllegalArgumentException("rangeInsideHost must lie within host text range. rangeInsideHost:"+rangeInsideHost+"; host textRange:"+ - hostTextRange); - } - if (myLanguage == null) { - clear(); - throw new IllegalStateException("Seems you haven't called startInjecting()"); - } - - if (prefix == null) prefix = ""; - if (suffix == null) suffix = ""; - cleared = false; - int startOffset = outChars.length(); - outChars.append(prefix); - LiteralTextEscaper textEscaper = host.createLiteralTextEscaper(); - escapers.add(textEscaper); - isOneLineEditor |= textEscaper.isOneLine(); - TextRange relevantRange = textEscaper.getRelevantTextRange().intersection(rangeInsideHost); - if (relevantRange == null) { - relevantRange = TextRange.from(textEscaper.getRelevantTextRange().getStartOffset(), 0); - } - else { - int before = outChars.length(); - boolean result = textEscaper.decode(relevantRange, outChars); - int after = outChars.length(); - assert after >= before : "Escaper " + textEscaper + "("+textEscaper.getClass()+") must not mangle char buffer"; - if (!result) { - // if there are invalid chars, adjust the range - int offsetInHost = textEscaper.getOffsetInHost(outChars.length() - startOffset, rangeInsideHost); - relevantRange = relevantRange.intersection(new ProperTextRange(0, offsetInHost)); - } - } - outChars.append(suffix); - int endOffset = outChars.length(); - TextRange relevantRangeInHost = relevantRange.shiftRight(hostTextRange.getStartOffset()); - RangeMarker relevantMarker = myHostDocument.createRangeMarker(relevantRangeInHost); - relevantMarker.setGreedyToLeft(true); - relevantMarker.setGreedyToRight(true); - shreds.add(new PsiLanguageInjectionHost.Shred(host, relevantMarker, prefix, suffix, new ProperTextRange(startOffset, endOffset))); - return this; - } - - public void doneInjecting() { - try { - if (shreds.isEmpty()) { - throw new IllegalStateException("Seems you haven't called addPlace()"); - } - PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); - assert ArrayUtil.indexOf(documentManager.getUncommittedDocuments(), myHostDocument) == -1 : "document is uncommitted: "+myHostDocument; - assert myHostPsiFile.getText().equals(myHostDocument.getText()) : "host text mismatch"; - - Place place = new Place(shreds); - DocumentWindowImpl documentWindow = new DocumentWindowImpl(myHostDocument, isOneLineEditor, place); - VirtualFileWindowImpl virtualFile = new VirtualFileWindowImpl(myHostVirtualFile, documentWindow, myLanguage, outChars); - myLanguage = LanguageSubstitutors.INSTANCE.substituteLanguage(myLanguage, virtualFile, myProject); - virtualFile.setLanguage(myLanguage); - - DocumentImpl decodedDocument; - if (StringUtil.indexOf(outChars, '\r') == -1) { - decodedDocument = new DocumentImpl(outChars); - } - else { - decodedDocument = new DocumentImpl(true); - decodedDocument.setAcceptSlashR(true); - decodedDocument.replaceString(0,0,outChars); - } - FileDocumentManagerImpl.registerDocument(decodedDocument, virtualFile); - - InjectedFileViewProvider viewProvider = new InjectedFileViewProvider(myPsiManager, virtualFile, place, documentWindow, myLanguage); - ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(myLanguage); - assert parserDefinition != null : "Parser definition for language "+myLanguage+" is null"; - PsiFile psiFile = parserDefinition.createFile(viewProvider); - - SmartPsiElementPointer pointer = createHostSmartPointer(shreds.get(0).host); - - synchronized (PsiLock.LOCK) { - final ASTNode parsedNode = keepTreeFromChameleoningBack(psiFile); - - assert parsedNode instanceof FileElement : "Parsed to "+parsedNode+" instead of FileElement"; - - String documentText = documentWindow.getText(); - assert outChars.toString().equals(parsedNode.getText()) : exceptionContext("Before patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'"+outChars+"'"); - try { - patchLeafs(parsedNode, escapers, place); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (RuntimeException e) { - throw new RuntimeException(exceptionContext("Patch error"), e); - } - assert parsedNode.getText().equals(documentText) : exceptionContext("After patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'"+outChars+"'"); - - virtualFile.setContent(null, documentWindow.getText(), false); - - cacheEverything(place, documentWindow, viewProvider, psiFile, pointer); - - PsiFile cachedPsiFile = documentManager.getCachedPsiFile(documentWindow); - assert cachedPsiFile == psiFile : "Cached psi :"+ cachedPsiFile +" instead of "+psiFile; - - assert place.isValid(); - assert viewProvider.isValid(); - PsiFile newFile = registerDocument(documentWindow, psiFile, place, myHostPsiFile, documentManager); - boolean mergeHappened = newFile != psiFile; - if (mergeHappened) { - InjectedLanguageUtil.clearCaches(psiFile); - psiFile = newFile; - viewProvider = (InjectedFileViewProvider)psiFile.getViewProvider(); - documentWindow = (DocumentWindowImpl)viewProvider.getDocument(); - virtualFile = (VirtualFileWindowImpl)viewProvider.getVirtualFile(); - cacheEverything(place, documentWindow, viewProvider, psiFile, pointer); - } - - assert psiFile.isValid(); - assert place.isValid(); - assert viewProvider.isValid(); - - try { - List> tokens = obtainHighlightTokensFromLexer(myLanguage, outChars, escapers, place, virtualFile, myProject); - psiFile.putUserData(InjectedLanguageUtil.HIGHLIGHT_TOKENS, tokens); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (RuntimeException e) { - throw new RuntimeException(exceptionContext("Obtaining tokens error"), e); - } - - addToResults(place, psiFile); - - assertEverythingIsAllright(documentManager, documentWindow, psiFile); - } - } - finally { - clear(); - } - } - - private static void cacheEverything(Place place, - DocumentWindowImpl documentWindow, - InjectedFileViewProvider viewProvider, - PsiFile psiFile, - SmartPsiElementPointer pointer) { - FileDocumentManagerImpl.registerDocument(documentWindow, viewProvider.getVirtualFile()); - - viewProvider.forceCachedPsi(psiFile); - - psiFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, pointer); - PsiDocumentManagerImpl.cachePsi(documentWindow, psiFile); - - keepTreeFromChameleoningBack(psiFile); - - viewProvider.setShreds(place); - } - - - @NonNls - private String exceptionContext(@NonNls String msg) { - return msg + ".\n" + - myLanguage+";\n "+ - "Host file: "+myHostPsiFile+" in '" + myHostVirtualFile.getPresentableUrl() + "'\n" + - "Context element "+myContextElement.getTextRange() + ": '" + myContextElement +"'; "+ - "Ranges: "+shreds; - } - - private static final Key TREE_HARD_REF = Key.create("TREE_HARD_REF"); - private static ASTNode keepTreeFromChameleoningBack(PsiFile psiFile) { - psiFile.getFirstChild(); - // need to keep tree reacheable to avoid being garbage-collected (via WeakReference in PsiFileImpl) - // and then being reparsed from wrong (escaped) document content - ASTNode node = psiFile.getNode(); - assert !TreeUtil.isCollapsedChameleon(node) : "Chameleon "+node+" is collapsed"; - psiFile.putUserData(TREE_HARD_REF, node); - return node; - } - - private void assertEverythingIsAllright(PsiDocumentManager documentManager, DocumentWindowImpl documentWindow, PsiFile psiFile) { - boolean isAncestor = false; - for (PsiLanguageInjectionHost.Shred shred : shreds) { - PsiLanguageInjectionHost host = shred.host; - isAncestor |= PsiTreeUtil.isAncestor(myContextElement, host, false); - } - assert isAncestor : exceptionContext(myContextElement + " must be the parent of at least one of injection hosts"); - - InjectedFileViewProvider injectedFileViewProvider = (InjectedFileViewProvider)psiFile.getViewProvider(); - assert injectedFileViewProvider.isValid() : "Invalid view provider: "+injectedFileViewProvider; - assert documentWindow.getText().equals(psiFile.getText()) : "Document window text mismatch"; - assert injectedFileViewProvider.getDocument() == documentWindow : "Provider document mismatch"; - assert documentManager.getCachedDocument(psiFile) == documentWindow : "Cached document mismatch"; - assert psiFile.getVirtualFile() == injectedFileViewProvider.getVirtualFile() : "Virtual file mismatch: "+psiFile.getVirtualFile()+"; "+injectedFileViewProvider.getVirtualFile(); - PsiDocumentManagerImpl.checkConsistency(psiFile, documentWindow); - } - - void addToResults(Place place, PsiFile psiFile) { - if (result == null) { - result = new SmartList>(); - } - result.add(Pair.create(place, psiFile)); - } - - private static SmartPsiElementPointer createHostSmartPointer(final T host) { - return host.isPhysical() - ? SmartPointerManager.getInstance(host.getProject()).createSmartPsiElementPointer(host) - : new IdentitySmartPointer(host); - } - - private static void patchLeafs(ASTNode parsedNode, List> escapers, Place shreds) { - LeafPatcher patcher = new LeafPatcher(shreds, escapers); - ((TreeElement)parsedNode).acceptTree(patcher); - - String nodeText = parsedNode.getText(); - assert nodeText.equals(patcher.catLeafs.toString()) : "Malformed PSI structure: leaf texts do not add up to the whole file text." + - "\nFile text (from tree) :'"+nodeText+"'" + - "\nFile text (from PSI) :'"+parsedNode.getPsi().getText()+"'" + - "\nLeaf texts concatenated:'"+ patcher.catLeafs +"';" + - "\nFile root: "+parsedNode+ - "\nLanguage: "+parsedNode.getPsi().getLanguage()+ - "\nHost file: "+shreds.get(0).host.getContainingFile().getVirtualFile() - ; - for (Map.Entry entry : patcher.newTexts.entrySet()) { - LeafElement leaf = entry.getKey(); - String newText = entry.getValue(); - leaf.rawReplaceWithText(newText); - } - ((TreeElement)parsedNode).acceptTree(new RecursiveTreeElementWalkingVisitor(){ - protected void visitNode(TreeElement element) { - element.clearCaches(); - super.visitNode(element); - } - }); - } - - private static PsiFile registerDocument(final DocumentWindowImpl documentWindow, - final PsiFile injectedPsi, - final Place shreds, - final PsiFile hostPsiFile, - final PsiDocumentManager documentManager) { - DocumentEx hostDocument = documentWindow.getDelegate(); - List injected = InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); - - for (int i = injected.size()-1; i>=0; i--) { - DocumentWindowImpl oldDocument = (DocumentWindowImpl)injected.get(i); - final PsiFileImpl oldFile = (PsiFileImpl)documentManager.getCachedPsiFile(oldDocument); - FileViewProvider viewProvider; - - if (oldFile == null || - !oldFile.isValid() || - !((viewProvider = oldFile.getViewProvider()) instanceof InjectedFileViewProvider) || - ((InjectedFileViewProvider)viewProvider).isDisposed() - ) { - injected.remove(i); - Disposer.dispose(oldDocument); - continue; - } - InjectedFileViewProvider oldViewProvider = (InjectedFileViewProvider)viewProvider; - - final ASTNode injectedNode = injectedPsi.getNode(); - final ASTNode oldFileNode = oldFile.getNode(); - assert injectedNode != null : "New node is null"; - assert oldFileNode != null : "Old node is null"; - if (oldDocument.areRangesEqual(documentWindow)) { - if (oldFile.getFileType() != injectedPsi.getFileType() || oldFile.getLanguage() != injectedPsi.getLanguage()) { - injected.remove(i); - Disposer.dispose(oldDocument); - continue; - } - oldFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, injectedPsi.getUserData(FileContextUtil.INJECTED_IN_ELEMENT)); - - assert shreds.isValid(); - oldViewProvider.performNonPhysically(new Runnable() { - public void run() { - BlockSupportImpl.mergeTrees(oldFile, oldFileNode, injectedNode); - } - }); - assert shreds.isValid(); - - return oldFile; - } - } - injected.add(documentWindow); - - cacheInjectedRegion(documentWindow, hostDocument); - return injectedPsi; - } - - private static void cacheInjectedRegion(DocumentWindowImpl documentWindow, DocumentEx hostDocument) { - List injectedRegions = InjectedLanguageUtil.getCachedInjectedRegions(hostDocument); - RangeMarker newMarker = documentWindow.getHostRanges()[0]; - TextRange newRange = InjectedLanguageUtil.toTextRange(newMarker); - for (int i = 0; i < injectedRegions.size(); i++) { - RangeMarker stored = injectedRegions.get(i); - TextRange storedRange = InjectedLanguageUtil.toTextRange(stored); - if (storedRange.intersects(newRange)) { - injectedRegions.set(i, newMarker); - break; - } - if (storedRange.getStartOffset() > newRange.getEndOffset()) { - injectedRegions.add(i, newMarker); - break; - } - } - if (injectedRegions.isEmpty() || newRange.getStartOffset() > injectedRegions.get(injectedRegions.size()-1).getEndOffset()) { - injectedRegions.add(newMarker); - } - } - - // returns lexer elemet types with corresponsing ranges in encoded (injection host based) PSI - private static List> obtainHighlightTokensFromLexer(Language language, - StringBuilder outChars, - List> escapers, - Place shreds, - VirtualFileWindow virtualFile, - Project project) { - List> tokens = new ArrayList>(10); - SyntaxHighlighter syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language, project, (VirtualFile)virtualFile); - Lexer lexer = syntaxHighlighter.getHighlightingLexer(); - lexer.start(outChars); - int hostNum = -1; - int prevHostEndOffset = 0; - PsiLanguageInjectionHost host = null; - LiteralTextEscaper escaper = null; - int prefixLength = 0; - int suffixLength = 0; - TextRange rangeInsideHost = null; - int shredEndOffset = -1; - for (IElementType tokenType = lexer.getTokenType(); tokenType != null; lexer.advance(), tokenType = lexer.getTokenType()) { - TextRange range = new ProperTextRange(lexer.getTokenStart(), lexer.getTokenEnd()); - while (range != null && !range.isEmpty()) { - if (range.getStartOffset() >= shredEndOffset) { - hostNum++; - shredEndOffset = shreds.get(hostNum).range.getEndOffset(); - prevHostEndOffset = range.getStartOffset(); - host = shreds.get(hostNum).host; - escaper = escapers.get(hostNum); - rangeInsideHost = shreds.get(hostNum).getRangeInsideHost(); - prefixLength = shreds.get(hostNum).prefix.length(); - suffixLength = shreds.get(hostNum).suffix.length(); - } - //in prefix/suffix or spills over to next fragment - if (range.getStartOffset() < prevHostEndOffset + prefixLength) { - range = new TextRange(prevHostEndOffset + prefixLength, range.getEndOffset()); - } - TextRange spilled = null; - if (range.getEndOffset() >= shredEndOffset - suffixLength) { - spilled = new TextRange(shredEndOffset, range.getEndOffset()); - range = new TextRange(range.getStartOffset(), shredEndOffset); - } - if (!range.isEmpty()) { - int start = escaper.getOffsetInHost(range.getStartOffset() - prevHostEndOffset - prefixLength, rangeInsideHost); - if (start == -1) start = rangeInsideHost.getStartOffset(); - int end = escaper.getOffsetInHost(range.getEndOffset() - prevHostEndOffset - prefixLength, rangeInsideHost); - if (end == -1) { - end = rangeInsideHost.getEndOffset(); - prevHostEndOffset = shredEndOffset; - } - TextRange rangeInHost = new ProperTextRange(start, end); - tokens.add(Trinity.create(tokenType, host, rangeInHost)); - } - range = spilled; - } - } - return tokens; - } -} +/* + * 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.impl.source.tree.injected; + +import com.intellij.injected.editor.DocumentWindow; +import com.intellij.injected.editor.DocumentWindowImpl; +import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.injected.editor.VirtualFileWindowImpl; +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageParserDefinitions; +import com.intellij.lang.ParserDefinition; +import com.intellij.lang.injection.MultiHostRegistrar; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.PsiDocumentManagerImpl; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.resolve.FileContextUtil; +import com.intellij.psi.impl.source.text.BlockSupportImpl; +import com.intellij.psi.impl.source.tree.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author cdr +*/ +public class MultiHostRegistrarImpl implements MultiHostRegistrar { + List> result; + private Language myLanguage; + private List> escapers; + private List shreds; + private StringBuilder outChars; + private boolean isOneLineEditor; + private boolean cleared; + private final Project myProject; + private final PsiManager myPsiManager; + private DocumentEx myHostDocument; + private VirtualFile myHostVirtualFile; + private final PsiElement myContextElement; + private final PsiFile myHostPsiFile; + + MultiHostRegistrarImpl(@NotNull Project project, @NotNull PsiFile hostPsiFile, @NotNull PsiElement contextElement) { + myProject = project; + myContextElement = contextElement; + myHostPsiFile = PsiUtilBase.getTemplateLanguageFile(hostPsiFile); + myPsiManager = myHostPsiFile.getManager(); + cleared = true; + } + + public List> getResult() { + return result; + } + + @NotNull + public MultiHostRegistrar startInjecting(@NotNull Language language) { + escapers = new SmartList>(); + shreds = new SmartList(); + outChars = new StringBuilder(); + + if (!cleared) { + clear(); + throw new IllegalStateException("Seems you haven't called doneInjecting()"); + } + + if (LanguageParserDefinitions.INSTANCE.forLanguage(language) == null) { + throw new UnsupportedOperationException("Cannot inject language '" + language + "' since its getParserDefinition() returns null"); + } + myLanguage = language; + + FileViewProvider viewProvider = myHostPsiFile.getViewProvider(); + myHostVirtualFile = viewProvider.getVirtualFile(); + myHostDocument = (DocumentEx)viewProvider.getDocument(); + assert myHostDocument != null : myHostPsiFile + "; " + viewProvider; + return this; + } + + private void clear() { + escapers.clear(); + shreds.clear(); + outChars.setLength(0); + isOneLineEditor = false; + myLanguage = null; + + cleared = true; + } + + @NotNull + public MultiHostRegistrar addPlace(@NonNls @Nullable String prefix, + @NonNls @Nullable String suffix, + @NotNull PsiLanguageInjectionHost host, + @NotNull TextRange rangeInsideHost) { + ProperTextRange.assertProperRange(rangeInsideHost); + + PsiFile containingFile = PsiUtilBase.getTemplateLanguageFile(host); + assert containingFile == myHostPsiFile : exceptionContext("Trying to inject into foreign file: "+containingFile); + TextRange hostTextRange = host.getTextRange(); + if (!hostTextRange.contains(rangeInsideHost.shiftRight(hostTextRange.getStartOffset()))) { + clear(); + throw new IllegalArgumentException("rangeInsideHost must lie within host text range. rangeInsideHost:"+rangeInsideHost+"; host textRange:"+ + hostTextRange); + } + if (myLanguage == null) { + clear(); + throw new IllegalStateException("Seems you haven't called startInjecting()"); + } + + if (prefix == null) prefix = ""; + if (suffix == null) suffix = ""; + cleared = false; + int startOffset = outChars.length(); + outChars.append(prefix); + LiteralTextEscaper textEscaper = host.createLiteralTextEscaper(); + escapers.add(textEscaper); + isOneLineEditor |= textEscaper.isOneLine(); + TextRange relevantRange = textEscaper.getRelevantTextRange().intersection(rangeInsideHost); + if (relevantRange == null) { + relevantRange = TextRange.from(textEscaper.getRelevantTextRange().getStartOffset(), 0); + } + else { + int before = outChars.length(); + boolean result = textEscaper.decode(relevantRange, outChars); + int after = outChars.length(); + assert after >= before : "Escaper " + textEscaper + "("+textEscaper.getClass()+") must not mangle char buffer"; + if (!result) { + // if there are invalid chars, adjust the range + int offsetInHost = textEscaper.getOffsetInHost(outChars.length() - startOffset, rangeInsideHost); + relevantRange = relevantRange.intersection(new ProperTextRange(0, offsetInHost)); + } + } + outChars.append(suffix); + int endOffset = outChars.length(); + TextRange relevantRangeInHost = relevantRange.shiftRight(hostTextRange.getStartOffset()); + RangeMarker relevantMarker = myHostDocument.createRangeMarker(relevantRangeInHost); + relevantMarker.setGreedyToLeft(true); + relevantMarker.setGreedyToRight(true); + shreds.add(new PsiLanguageInjectionHost.Shred(host, relevantMarker, prefix, suffix, new ProperTextRange(startOffset, endOffset))); + return this; + } + + public void doneInjecting() { + try { + if (shreds.isEmpty()) { + throw new IllegalStateException("Seems you haven't called addPlace()"); + } + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); + assert ArrayUtil.indexOf(documentManager.getUncommittedDocuments(), myHostDocument) == -1 : "document is uncommitted: "+myHostDocument; + assert myHostPsiFile.getText().equals(myHostDocument.getText()) : "host text mismatch"; + + Place place = new Place(shreds); + DocumentWindowImpl documentWindow = new DocumentWindowImpl(myHostDocument, isOneLineEditor, place); + VirtualFileWindowImpl virtualFile = new VirtualFileWindowImpl(myHostVirtualFile, documentWindow, myLanguage, outChars); + myLanguage = LanguageSubstitutors.INSTANCE.substituteLanguage(myLanguage, virtualFile, myProject); + virtualFile.setLanguage(myLanguage); + + DocumentImpl decodedDocument; + if (StringUtil.indexOf(outChars, '\r') == -1) { + decodedDocument = new DocumentImpl(outChars); + } + else { + decodedDocument = new DocumentImpl(true); + decodedDocument.setAcceptSlashR(true); + decodedDocument.replaceString(0,0,outChars); + } + FileDocumentManagerImpl.registerDocument(decodedDocument, virtualFile); + + InjectedFileViewProvider viewProvider = new InjectedFileViewProvider(myPsiManager, virtualFile, place, documentWindow, myLanguage); + ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(myLanguage); + assert parserDefinition != null : "Parser definition for language "+myLanguage+" is null"; + PsiFile psiFile = parserDefinition.createFile(viewProvider); + + SmartPsiElementPointer pointer = createHostSmartPointer(shreds.get(0).host); + + synchronized (PsiLock.LOCK) { + final ASTNode parsedNode = keepTreeFromChameleoningBack(psiFile); + + assert parsedNode instanceof FileElement : "Parsed to "+parsedNode+" instead of FileElement"; + + String documentText = documentWindow.getText(); + assert outChars.toString().equals(parsedNode.getText()) : exceptionContext("Before patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'"+outChars+"'"); + try { + patchLeafs(parsedNode, escapers, place); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (RuntimeException e) { + throw new RuntimeException(exceptionContext("Patch error"), e); + } + assert parsedNode.getText().equals(documentText) : exceptionContext("After patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'"+outChars+"'"); + + virtualFile.setContent(null, documentWindow.getText(), false); + + cacheEverything(place, documentWindow, viewProvider, psiFile, pointer); + + PsiFile cachedPsiFile = documentManager.getCachedPsiFile(documentWindow); + assert cachedPsiFile == psiFile : "Cached psi :"+ cachedPsiFile +" instead of "+psiFile; + + assert place.isValid(); + assert viewProvider.isValid(); + PsiFile newFile = registerDocument(documentWindow, psiFile, place, myHostPsiFile, documentManager); + boolean mergeHappened = newFile != psiFile; + if (mergeHappened) { + InjectedLanguageUtil.clearCaches(psiFile); + psiFile = newFile; + viewProvider = (InjectedFileViewProvider)psiFile.getViewProvider(); + documentWindow = (DocumentWindowImpl)viewProvider.getDocument(); + virtualFile = (VirtualFileWindowImpl)viewProvider.getVirtualFile(); + cacheEverything(place, documentWindow, viewProvider, psiFile, pointer); + } + + assert psiFile.isValid(); + assert place.isValid(); + assert viewProvider.isValid(); + + try { + List> tokens = obtainHighlightTokensFromLexer(myLanguage, outChars, escapers, place, virtualFile, myProject); + psiFile.putUserData(InjectedLanguageUtil.HIGHLIGHT_TOKENS, tokens); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (RuntimeException e) { + throw new RuntimeException(exceptionContext("Obtaining tokens error"), e); + } + + addToResults(place, psiFile); + + assertEverythingIsAllright(documentManager, documentWindow, psiFile); + } + } + finally { + clear(); + } + } + + private static void cacheEverything(Place place, + DocumentWindowImpl documentWindow, + InjectedFileViewProvider viewProvider, + PsiFile psiFile, + SmartPsiElementPointer pointer) { + FileDocumentManagerImpl.registerDocument(documentWindow, viewProvider.getVirtualFile()); + + viewProvider.forceCachedPsi(psiFile); + + psiFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, pointer); + PsiDocumentManagerImpl.cachePsi(documentWindow, psiFile); + + keepTreeFromChameleoningBack(psiFile); + + viewProvider.setShreds(place); + } + + + @NonNls + private String exceptionContext(@NonNls String msg) { + return msg + ".\n" + + myLanguage+";\n "+ + "Host file: "+myHostPsiFile+" in '" + myHostVirtualFile.getPresentableUrl() + "'\n" + + "Context element "+myContextElement.getTextRange() + ": '" + myContextElement +"'; "+ + "Ranges: "+shreds; + } + + private static final Key TREE_HARD_REF = Key.create("TREE_HARD_REF"); + private static ASTNode keepTreeFromChameleoningBack(PsiFile psiFile) { + psiFile.getFirstChild(); + // need to keep tree reacheable to avoid being garbage-collected (via WeakReference in PsiFileImpl) + // and then being reparsed from wrong (escaped) document content + ASTNode node = psiFile.getNode(); + assert !TreeUtil.isCollapsedChameleon(node) : "Chameleon "+node+" is collapsed"; + psiFile.putUserData(TREE_HARD_REF, node); + return node; + } + + private void assertEverythingIsAllright(PsiDocumentManager documentManager, DocumentWindowImpl documentWindow, PsiFile psiFile) { + boolean isAncestor = false; + for (PsiLanguageInjectionHost.Shred shred : shreds) { + PsiLanguageInjectionHost host = shred.host; + isAncestor |= PsiTreeUtil.isAncestor(myContextElement, host, false); + } + assert isAncestor : exceptionContext(myContextElement + " must be the parent of at least one of injection hosts"); + + InjectedFileViewProvider injectedFileViewProvider = (InjectedFileViewProvider)psiFile.getViewProvider(); + assert injectedFileViewProvider.isValid() : "Invalid view provider: "+injectedFileViewProvider; + assert documentWindow.getText().equals(psiFile.getText()) : "Document window text mismatch"; + assert injectedFileViewProvider.getDocument() == documentWindow : "Provider document mismatch"; + assert documentManager.getCachedDocument(psiFile) == documentWindow : "Cached document mismatch"; + assert psiFile.getVirtualFile() == injectedFileViewProvider.getVirtualFile() : "Virtual file mismatch: "+psiFile.getVirtualFile()+"; "+injectedFileViewProvider.getVirtualFile(); + PsiDocumentManagerImpl.checkConsistency(psiFile, documentWindow); + } + + void addToResults(Place place, PsiFile psiFile) { + if (result == null) { + result = new SmartList>(); + } + result.add(Pair.create(place, psiFile)); + } + + private static SmartPsiElementPointer createHostSmartPointer(final T host) { + return host.isPhysical() + ? SmartPointerManager.getInstance(host.getProject()).createSmartPsiElementPointer(host) + : new IdentitySmartPointer(host); + } + + private static void patchLeafs(ASTNode parsedNode, List> escapers, Place shreds) { + LeafPatcher patcher = new LeafPatcher(shreds, escapers); + ((TreeElement)parsedNode).acceptTree(patcher); + + String nodeText = parsedNode.getText(); + assert nodeText.equals(patcher.catLeafs.toString()) : "Malformed PSI structure: leaf texts do not add up to the whole file text." + + "\nFile text (from tree) :'"+nodeText+"'" + + "\nFile text (from PSI) :'"+parsedNode.getPsi().getText()+"'" + + "\nLeaf texts concatenated:'"+ patcher.catLeafs +"';" + + "\nFile root: "+parsedNode+ + "\nLanguage: "+parsedNode.getPsi().getLanguage()+ + "\nHost file: "+shreds.get(0).host.getContainingFile().getVirtualFile() + ; + for (Map.Entry entry : patcher.newTexts.entrySet()) { + LeafElement leaf = entry.getKey(); + String newText = entry.getValue(); + leaf.rawReplaceWithText(newText); + } + ((TreeElement)parsedNode).acceptTree(new RecursiveTreeElementWalkingVisitor(){ + protected void visitNode(TreeElement element) { + element.clearCaches(); + super.visitNode(element); + } + }); + } + + private static PsiFile registerDocument(final DocumentWindowImpl documentWindow, + final PsiFile injectedPsi, + final Place shreds, + final PsiFile hostPsiFile, + final PsiDocumentManager documentManager) { + DocumentEx hostDocument = documentWindow.getDelegate(); + List injected = InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); + + for (int i = injected.size()-1; i>=0; i--) { + DocumentWindowImpl oldDocument = (DocumentWindowImpl)injected.get(i); + final PsiFileImpl oldFile = (PsiFileImpl)documentManager.getCachedPsiFile(oldDocument); + FileViewProvider viewProvider; + + if (oldFile == null || + !oldFile.isValid() || + !((viewProvider = oldFile.getViewProvider()) instanceof InjectedFileViewProvider) || + ((InjectedFileViewProvider)viewProvider).isDisposed() + ) { + injected.remove(i); + Disposer.dispose(oldDocument); + continue; + } + InjectedFileViewProvider oldViewProvider = (InjectedFileViewProvider)viewProvider; + + final ASTNode injectedNode = injectedPsi.getNode(); + final ASTNode oldFileNode = oldFile.getNode(); + assert injectedNode != null : "New node is null"; + assert oldFileNode != null : "Old node is null"; + if (oldDocument.areRangesEqual(documentWindow)) { + if (oldFile.getFileType() != injectedPsi.getFileType() || oldFile.getLanguage() != injectedPsi.getLanguage()) { + injected.remove(i); + Disposer.dispose(oldDocument); + continue; + } + oldFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, injectedPsi.getUserData(FileContextUtil.INJECTED_IN_ELEMENT)); + + assert shreds.isValid(); + oldViewProvider.performNonPhysically(new Runnable() { + public void run() { + BlockSupportImpl.mergeTrees(oldFile, oldFileNode, injectedNode); + } + }); + assert shreds.isValid(); + + return oldFile; + } + } + injected.add(documentWindow); + + cacheInjectedRegion(documentWindow, hostDocument); + return injectedPsi; + } + + private static void cacheInjectedRegion(DocumentWindowImpl documentWindow, DocumentEx hostDocument) { + List injectedRegions = InjectedLanguageUtil.getCachedInjectedRegions(hostDocument); + RangeMarker newMarker = documentWindow.getHostRanges()[0]; + TextRange newRange = InjectedLanguageUtil.toTextRange(newMarker); + for (int i = 0; i < injectedRegions.size(); i++) { + RangeMarker stored = injectedRegions.get(i); + TextRange storedRange = InjectedLanguageUtil.toTextRange(stored); + if (storedRange.intersects(newRange)) { + injectedRegions.set(i, newMarker); + break; + } + if (storedRange.getStartOffset() > newRange.getEndOffset()) { + injectedRegions.add(i, newMarker); + break; + } + } + if (injectedRegions.isEmpty() || newRange.getStartOffset() > injectedRegions.get(injectedRegions.size()-1).getEndOffset()) { + injectedRegions.add(newMarker); + } + } + + // returns lexer elemet types with corresponsing ranges in encoded (injection host based) PSI + private static List> obtainHighlightTokensFromLexer(Language language, + StringBuilder outChars, + List> escapers, + Place shreds, + VirtualFileWindow virtualFile, + Project project) { + List> tokens = new ArrayList>(10); + SyntaxHighlighter syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language, project, (VirtualFile)virtualFile); + Lexer lexer = syntaxHighlighter.getHighlightingLexer(); + lexer.start(outChars); + int hostNum = -1; + int prevHostEndOffset = 0; + PsiLanguageInjectionHost host = null; + LiteralTextEscaper escaper = null; + int prefixLength = 0; + int suffixLength = 0; + TextRange rangeInsideHost = null; + int shredEndOffset = -1; + for (IElementType tokenType = lexer.getTokenType(); tokenType != null; lexer.advance(), tokenType = lexer.getTokenType()) { + TextRange range = new ProperTextRange(lexer.getTokenStart(), lexer.getTokenEnd()); + while (range != null && !range.isEmpty()) { + if (range.getStartOffset() >= shredEndOffset) { + hostNum++; + shredEndOffset = shreds.get(hostNum).range.getEndOffset(); + prevHostEndOffset = range.getStartOffset(); + host = shreds.get(hostNum).host; + escaper = escapers.get(hostNum); + rangeInsideHost = shreds.get(hostNum).getRangeInsideHost(); + prefixLength = shreds.get(hostNum).prefix.length(); + suffixLength = shreds.get(hostNum).suffix.length(); + } + //in prefix/suffix or spills over to next fragment + if (range.getStartOffset() < prevHostEndOffset + prefixLength) { + range = new TextRange(prevHostEndOffset + prefixLength, range.getEndOffset()); + } + TextRange spilled = null; + if (range.getEndOffset() >= shredEndOffset - suffixLength) { + spilled = new TextRange(shredEndOffset, range.getEndOffset()); + range = new TextRange(range.getStartOffset(), shredEndOffset); + } + if (!range.isEmpty()) { + int start = escaper.getOffsetInHost(range.getStartOffset() - prevHostEndOffset - prefixLength, rangeInsideHost); + if (start == -1) start = rangeInsideHost.getStartOffset(); + int end = escaper.getOffsetInHost(range.getEndOffset() - prevHostEndOffset - prefixLength, rangeInsideHost); + if (end == -1) { + end = rangeInsideHost.getEndOffset(); + prevHostEndOffset = shredEndOffset; + } + TextRange rangeInHost = new ProperTextRange(start, end); + tokens.add(Trinity.create(tokenType, host, rangeInHost)); + } + range = spilled; + } + } + return tokens; + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java index eb40cef0805b..5b103ac8fd88 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java @@ -1,418 +1,418 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.psi.stubs; - -import com.intellij.lang.ASTNode; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.components.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.newvfs.ManagingFS; -import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.PsiPlainTextFile; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.PsiFileWithStubSupport; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.IStubFileElementType; -import com.intellij.util.indexing.*; -import com.intellij.util.io.DataExternalizer; -import com.intellij.util.io.DataInputOutputUtil; -import gnu.trove.TIntArrayList; -import gnu.trove.TObjectIntHashMap; -import org.jetbrains.annotations.NotNull; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.locks.Lock; - -@State( - name = "FileBasedIndex", - roamingType = RoamingType.DISABLED, - storages = { - @Storage( - id = "stubIndex", - file = "$APP_CONFIG$/stubIndex.xml") - } -) -public class StubIndexImpl extends StubIndex implements ApplicationComponent, PersistentStateComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.StubIndexImpl"); - private final Map, MyIndex> myIndices = new HashMap, MyIndex>(); - private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); - - private StubIndexState myPreviouslyRegistered; - - public StubIndexImpl() throws IOException { - final StubIndexExtension[] extensions = Extensions.getExtensions(StubIndexExtension.EP_NAME); - boolean needRebuild = false; - for (StubIndexExtension extension : extensions) { - //noinspection unchecked - needRebuild |= registerIndexer(extension); - } - if (needRebuild) { - requestRebuild(); - } - dropUnregisteredIndices(); - } - - private boolean registerIndexer(final StubIndexExtension extension) throws IOException { - final StubIndexKey indexKey = extension.getKey(); - final int version = extension.getVersion(); - myIndexIdToVersionMap.put(indexKey, version); - final File versionFile = IndexInfrastructure.getVersionFile(indexKey); - final boolean versionFileExisted = versionFile.exists(); - final File indexRootDir = IndexInfrastructure.getIndexRootDir(indexKey); - boolean needRebuild = false; - if (IndexInfrastructure.versionDiffers(versionFile, version)) { - final String[] children = indexRootDir.list(); - // rebuild only if there exists what to rebuild - needRebuild = versionFileExisted || children != null && children.length > 0; - if (needRebuild) { - LOG.info("Version has changed for stub index " + extension.getKey() + ". The index will be rebuilt."); - } - FileUtil.delete(indexRootDir); - IndexInfrastructure.rewriteVersion(versionFile, version); - } - - for (int attempt = 0; attempt < 2; attempt++) { - try { - final MapIndexStorage storage = new MapIndexStorage(IndexInfrastructure.getStorageFile(indexKey), extension.getKeyDescriptor(), new StubIdExternalizer(), 2 * 1024); - final MemoryIndexStorage memStorage = new MemoryIndexStorage(storage); - myIndices.put(indexKey, new MyIndex(memStorage)); - break; - } - catch (IOException e) { - LOG.info(e); - needRebuild = true; - FileUtil.delete(indexRootDir); - IndexInfrastructure.rewriteVersion(versionFile, version); - } - } - return needRebuild; - } - - private static class StubIdExternalizer implements DataExternalizer { - public void save(final DataOutput out, final TIntArrayList value) throws IOException { - int size = value.size(); - if (size == 0) { - DataInputOutputUtil.writeSINT(out, Integer.MAX_VALUE); - } - else if (size == 1) { - DataInputOutputUtil.writeSINT(out, -value.get(0)); - } - else { - DataInputOutputUtil.writeSINT(out, size); - for (int i = 0; i < size; i++) { - DataInputOutputUtil.writeINT(out, value.get(i)); - } - } - } - - public TIntArrayList read(final DataInput in) throws IOException { - int size = DataInputOutputUtil.readSINT(in); - if (size == Integer.MAX_VALUE) { - return new TIntArrayList(); - } - else if (size <= 0) { - TIntArrayList result = new TIntArrayList(1); - result.add(-size); - return result; - } - else { - TIntArrayList result = new TIntArrayList(size); - for (int i = 0; i < size; i++) { - result.add(DataInputOutputUtil.readINT(in)); - } - return result; - } - } - } - - public Collection get(@NotNull final StubIndexKey indexKey, @NotNull final Key key, final Project project, - final GlobalSearchScope scope) { - FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, scope); - - final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); - final PsiManager psiManager = PsiManager.getInstance(project); - - final List result = new ArrayList(); - final MyIndex index = (MyIndex)myIndices.get(indexKey); - - try { - try { - // disable up-to-date check to avoid locks on attempt to acquire index write lock while holding at the same time the readLock for this index - FileBasedIndex.disableUpToDateCheckForCurrentThread(); - index.getReadLock().lock(); - final ValueContainer container = index.getData(key); - - container.forEach(new ValueContainer.ContainerAction() { - public void perform(final int id, final TIntArrayList value) { - final VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); - if (file != null && (scope == null || scope.contains(file))) { - StubTree stubTree = null; - - final PsiFile _psifile = psiManager.findFile(file); - PsiFileWithStubSupport psiFile = null; - - if (_psifile != null && !(_psifile instanceof PsiPlainTextFile)) { - if (_psifile instanceof PsiFileWithStubSupport) { - psiFile = (PsiFileWithStubSupport)_psifile; - stubTree = psiFile.getStubTree(); - if (stubTree == null && psiFile instanceof PsiFileImpl) { - stubTree = ((PsiFileImpl)psiFile).calcStubTree(); - } - } - } - - if (stubTree != null || psiFile != null) { - if (stubTree == null) { - stubTree = StubTree.readFromVFile(project, file); - if (stubTree != null) { - final List> plained = stubTree.getPlainList(); - for (int i = 0; i < value.size(); i++) { - final StubElement stub = plained.get(value.get(i)); - final ASTNode tree = psiFile.findTreeForStub(stubTree, stub); - - if (tree != null) { - if (tree.getElementType() == stubType(stub)) { - result.add((Psi)tree.getPsi()); - } - else { - String persistedStubTree = ((PsiFileStubImpl)stubTree.getRoot()).printTree(); - - String stubTreeJustBuilt = - ((PsiFileStubImpl)((IStubFileElementType)((PsiFileImpl)psiFile).getContentElementType()).getBuilder() - .buildStubTree(psiFile)).printTree(); - - StringBuilder builder = new StringBuilder(); - builder.append("Oops\n"); - - - builder.append("Recorded stub:-----------------------------------\n"); - builder.append(persistedStubTree); - builder.append("\nAST built stub: ------------------------------------\n"); - builder.append(stubTreeJustBuilt); - builder.append("\n"); - LOG.info(builder.toString()); - - // requestReindex() may want to acquire write lock (for indices not requiring content loading) - // thus, because here we are under read lock, need to use invoke later - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - FileBasedIndex.getInstance().requestReindex(file); - } - }, ModalityState.NON_MODAL); - } - } - } - } - } - else { - final List> plained = stubTree.getPlainList(); - for (int i = 0; i < value.size(); i++) { - result.add((Psi)plained.get(value.get(i)).getPsi()); - } - } - } - } - } - }); - } - finally { - index.getReadLock().unlock(); - FileBasedIndex.enableUpToDateCheckForCurrentThread(); - } - } - catch (StorageException e) { - forceRebuild(e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException || cause instanceof StorageException) { - forceRebuild(e); - } - else { - throw e; - } - } - - return result; - } - - private static IElementType stubType(final StubElement stub) { - if (stub instanceof PsiFileStub) { - return ((PsiFileStub)stub).getType(); - } - - return stub.getStubType(); - } - - private static void forceRebuild(Throwable e) { - LOG.info(e); - requestRebuild(); - FileBasedIndex.getInstance().scheduleRebuild(StubUpdatingIndex.INDEX_ID, e); - } - - private static void requestRebuild() { - FileBasedIndex.requestRebuild(StubUpdatingIndex.INDEX_ID); - } - - public Collection getAllKeys(final StubIndexKey indexKey, @NotNull Project project) { - FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, GlobalSearchScope.allScope(project)); - - final MyIndex index = (MyIndex)myIndices.get(indexKey); - try { - return index.getAllKeys(); - } - catch (StorageException e) { - forceRebuild(e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException || cause instanceof StorageException) { - forceRebuild(e); - } - throw e; - } - return Collections.emptyList(); - } - - @NotNull - public String getComponentName() { - return "Stub.IndexManager"; - } - - public void initComponent() { - } - - public void disposeComponent() { - // This index must be disposed only after StubUpdatingIndex is disposed - // To ensure this, disposing is done explicitly from StubUpdatingIndex by calling dispose() method - // do not call this method here to avoid double-disposal - } - - public void dispose() { - for (UpdatableIndex index : myIndices.values()) { - index.dispose(); - } - } - - public void setDataBufferingEnabled(final boolean enabled) { - for (UpdatableIndex index : myIndices.values()) { - final IndexStorage indexStorage = ((MapReduceIndex)index).getStorage(); - ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled); - } - } - - public void cleanupMemoryStorage() { - for (UpdatableIndex index : myIndices.values()) { - final IndexStorage indexStorage = ((MapReduceIndex)index).getStorage(); - index.getWriteLock().lock(); - try { - ((MemoryIndexStorage)indexStorage).clearMemoryMap(); - } - finally { - index.getWriteLock().unlock(); - } - } - } - - - public void clearAllIndices() { - for (UpdatableIndex index : myIndices.values()) { - try { - index.clear(); - } - catch (StorageException e) { - LOG.error(e); - throw new RuntimeException(e); - } - } - } - - private void dropUnregisteredIndices() { - final Set indicesToDrop = new HashSet(myPreviouslyRegistered != null? myPreviouslyRegistered.registeredIndices : Collections.emptyList()); - for (ID key : myIndices.keySet()) { - indicesToDrop.remove(key.toString()); - } - - for (String s : indicesToDrop) { - FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); - } - } - - public StubIndexState getState() { - return new StubIndexState(myIndices.keySet()); - } - - public void loadState(final StubIndexState state) { - myPreviouslyRegistered = state; - } - - public Lock getWriteLock(StubIndexKey indexKey) { - return myIndices.get(indexKey).getWriteLock(); - } - - public Collection getAllStubIndexKeys() { - return Collections.unmodifiableCollection(myIndices.keySet()); - } - - public void flush(StubIndexKey key) throws StorageException { - final MyIndex index = myIndices.get(key); - index.flush(); - } - - public void updateIndex(StubIndexKey key, int fileId, final Map oldValues, Map newValues) { - try { - final MyIndex index = (MyIndex)myIndices.get(key); - index.updateWithMap(fileId, newValues, new Callable>() { - public Collection call() throws Exception { - return oldValues.keySet(); - } - }); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(); - } - } - - private static class MyIndex extends MapReduceIndex { - public MyIndex(final IndexStorage storage) { - super(null, null, storage); - } - - public void updateWithMap(final int inputId, final Map newData, Callable> oldKeysGetter) throws StorageException { - super.updateWithMap(inputId, newData, oldKeysGetter); - } - } - -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.psi.stubs; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.components.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.PsiPlainTextFile; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.PsiFileWithStubSupport; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.IStubFileElementType; +import com.intellij.util.indexing.*; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.DataInputOutputUtil; +import gnu.trove.TIntArrayList; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.locks.Lock; + +@State( + name = "FileBasedIndex", + roamingType = RoamingType.DISABLED, + storages = { + @Storage( + id = "stubIndex", + file = "$APP_CONFIG$/stubIndex.xml") + } +) +public class StubIndexImpl extends StubIndex implements ApplicationComponent, PersistentStateComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.StubIndexImpl"); + private final Map, MyIndex> myIndices = new HashMap, MyIndex>(); + private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); + + private StubIndexState myPreviouslyRegistered; + + public StubIndexImpl() throws IOException { + final StubIndexExtension[] extensions = Extensions.getExtensions(StubIndexExtension.EP_NAME); + boolean needRebuild = false; + for (StubIndexExtension extension : extensions) { + //noinspection unchecked + needRebuild |= registerIndexer(extension); + } + if (needRebuild) { + requestRebuild(); + } + dropUnregisteredIndices(); + } + + private boolean registerIndexer(final StubIndexExtension extension) throws IOException { + final StubIndexKey indexKey = extension.getKey(); + final int version = extension.getVersion(); + myIndexIdToVersionMap.put(indexKey, version); + final File versionFile = IndexInfrastructure.getVersionFile(indexKey); + final boolean versionFileExisted = versionFile.exists(); + final File indexRootDir = IndexInfrastructure.getIndexRootDir(indexKey); + boolean needRebuild = false; + if (IndexInfrastructure.versionDiffers(versionFile, version)) { + final String[] children = indexRootDir.list(); + // rebuild only if there exists what to rebuild + needRebuild = versionFileExisted || children != null && children.length > 0; + if (needRebuild) { + LOG.info("Version has changed for stub index " + extension.getKey() + ". The index will be rebuilt."); + } + FileUtil.delete(indexRootDir); + IndexInfrastructure.rewriteVersion(versionFile, version); + } + + for (int attempt = 0; attempt < 2; attempt++) { + try { + final MapIndexStorage storage = new MapIndexStorage(IndexInfrastructure.getStorageFile(indexKey), extension.getKeyDescriptor(), new StubIdExternalizer(), 2 * 1024); + final MemoryIndexStorage memStorage = new MemoryIndexStorage(storage); + myIndices.put(indexKey, new MyIndex(memStorage)); + break; + } + catch (IOException e) { + LOG.info(e); + needRebuild = true; + FileUtil.delete(indexRootDir); + IndexInfrastructure.rewriteVersion(versionFile, version); + } + } + return needRebuild; + } + + private static class StubIdExternalizer implements DataExternalizer { + public void save(final DataOutput out, final TIntArrayList value) throws IOException { + int size = value.size(); + if (size == 0) { + DataInputOutputUtil.writeSINT(out, Integer.MAX_VALUE); + } + else if (size == 1) { + DataInputOutputUtil.writeSINT(out, -value.get(0)); + } + else { + DataInputOutputUtil.writeSINT(out, size); + for (int i = 0; i < size; i++) { + DataInputOutputUtil.writeINT(out, value.get(i)); + } + } + } + + public TIntArrayList read(final DataInput in) throws IOException { + int size = DataInputOutputUtil.readSINT(in); + if (size == Integer.MAX_VALUE) { + return new TIntArrayList(); + } + else if (size <= 0) { + TIntArrayList result = new TIntArrayList(1); + result.add(-size); + return result; + } + else { + TIntArrayList result = new TIntArrayList(size); + for (int i = 0; i < size; i++) { + result.add(DataInputOutputUtil.readINT(in)); + } + return result; + } + } + } + + public Collection get(@NotNull final StubIndexKey indexKey, @NotNull final Key key, final Project project, + final GlobalSearchScope scope) { + FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, scope); + + final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); + final PsiManager psiManager = PsiManager.getInstance(project); + + final List result = new ArrayList(); + final MyIndex index = (MyIndex)myIndices.get(indexKey); + + try { + try { + // disable up-to-date check to avoid locks on attempt to acquire index write lock while holding at the same time the readLock for this index + FileBasedIndex.disableUpToDateCheckForCurrentThread(); + index.getReadLock().lock(); + final ValueContainer container = index.getData(key); + + container.forEach(new ValueContainer.ContainerAction() { + public void perform(final int id, final TIntArrayList value) { + final VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); + if (file != null && (scope == null || scope.contains(file))) { + StubTree stubTree = null; + + final PsiFile _psifile = psiManager.findFile(file); + PsiFileWithStubSupport psiFile = null; + + if (_psifile != null && !(_psifile instanceof PsiPlainTextFile)) { + if (_psifile instanceof PsiFileWithStubSupport) { + psiFile = (PsiFileWithStubSupport)_psifile; + stubTree = psiFile.getStubTree(); + if (stubTree == null && psiFile instanceof PsiFileImpl) { + stubTree = ((PsiFileImpl)psiFile).calcStubTree(); + } + } + } + + if (stubTree != null || psiFile != null) { + if (stubTree == null) { + stubTree = StubTree.readFromVFile(project, file); + if (stubTree != null) { + final List> plained = stubTree.getPlainList(); + for (int i = 0; i < value.size(); i++) { + final StubElement stub = plained.get(value.get(i)); + final ASTNode tree = psiFile.findTreeForStub(stubTree, stub); + + if (tree != null) { + if (tree.getElementType() == stubType(stub)) { + result.add((Psi)tree.getPsi()); + } + else { + String persistedStubTree = ((PsiFileStubImpl)stubTree.getRoot()).printTree(); + + String stubTreeJustBuilt = + ((PsiFileStubImpl)((IStubFileElementType)((PsiFileImpl)psiFile).getContentElementType()).getBuilder() + .buildStubTree(psiFile)).printTree(); + + StringBuilder builder = new StringBuilder(); + builder.append("Oops\n"); + + + builder.append("Recorded stub:-----------------------------------\n"); + builder.append(persistedStubTree); + builder.append("\nAST built stub: ------------------------------------\n"); + builder.append(stubTreeJustBuilt); + builder.append("\n"); + LOG.info(builder.toString()); + + // requestReindex() may want to acquire write lock (for indices not requiring content loading) + // thus, because here we are under read lock, need to use invoke later + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + FileBasedIndex.getInstance().requestReindex(file); + } + }, ModalityState.NON_MODAL); + } + } + } + } + } + else { + final List> plained = stubTree.getPlainList(); + for (int i = 0; i < value.size(); i++) { + result.add((Psi)plained.get(value.get(i)).getPsi()); + } + } + } + } + } + }); + } + finally { + index.getReadLock().unlock(); + FileBasedIndex.enableUpToDateCheckForCurrentThread(); + } + } + catch (StorageException e) { + forceRebuild(e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException || cause instanceof StorageException) { + forceRebuild(e); + } + else { + throw e; + } + } + + return result; + } + + private static IElementType stubType(final StubElement stub) { + if (stub instanceof PsiFileStub) { + return ((PsiFileStub)stub).getType(); + } + + return stub.getStubType(); + } + + private static void forceRebuild(Throwable e) { + LOG.info(e); + requestRebuild(); + FileBasedIndex.getInstance().scheduleRebuild(StubUpdatingIndex.INDEX_ID, e); + } + + private static void requestRebuild() { + FileBasedIndex.requestRebuild(StubUpdatingIndex.INDEX_ID); + } + + public Collection getAllKeys(final StubIndexKey indexKey, @NotNull Project project) { + FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, GlobalSearchScope.allScope(project)); + + final MyIndex index = (MyIndex)myIndices.get(indexKey); + try { + return index.getAllKeys(); + } + catch (StorageException e) { + forceRebuild(e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException || cause instanceof StorageException) { + forceRebuild(e); + } + throw e; + } + return Collections.emptyList(); + } + + @NotNull + public String getComponentName() { + return "Stub.IndexManager"; + } + + public void initComponent() { + } + + public void disposeComponent() { + // This index must be disposed only after StubUpdatingIndex is disposed + // To ensure this, disposing is done explicitly from StubUpdatingIndex by calling dispose() method + // do not call this method here to avoid double-disposal + } + + public void dispose() { + for (UpdatableIndex index : myIndices.values()) { + index.dispose(); + } + } + + public void setDataBufferingEnabled(final boolean enabled) { + for (UpdatableIndex index : myIndices.values()) { + final IndexStorage indexStorage = ((MapReduceIndex)index).getStorage(); + ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled); + } + } + + public void cleanupMemoryStorage() { + for (UpdatableIndex index : myIndices.values()) { + final IndexStorage indexStorage = ((MapReduceIndex)index).getStorage(); + index.getWriteLock().lock(); + try { + ((MemoryIndexStorage)indexStorage).clearMemoryMap(); + } + finally { + index.getWriteLock().unlock(); + } + } + } + + + public void clearAllIndices() { + for (UpdatableIndex index : myIndices.values()) { + try { + index.clear(); + } + catch (StorageException e) { + LOG.error(e); + throw new RuntimeException(e); + } + } + } + + private void dropUnregisteredIndices() { + final Set indicesToDrop = new HashSet(myPreviouslyRegistered != null? myPreviouslyRegistered.registeredIndices : Collections.emptyList()); + for (ID key : myIndices.keySet()) { + indicesToDrop.remove(key.toString()); + } + + for (String s : indicesToDrop) { + FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); + } + } + + public StubIndexState getState() { + return new StubIndexState(myIndices.keySet()); + } + + public void loadState(final StubIndexState state) { + myPreviouslyRegistered = state; + } + + public Lock getWriteLock(StubIndexKey indexKey) { + return myIndices.get(indexKey).getWriteLock(); + } + + public Collection getAllStubIndexKeys() { + return Collections.unmodifiableCollection(myIndices.keySet()); + } + + public void flush(StubIndexKey key) throws StorageException { + final MyIndex index = myIndices.get(key); + index.flush(); + } + + public void updateIndex(StubIndexKey key, int fileId, final Map oldValues, Map newValues) { + try { + final MyIndex index = (MyIndex)myIndices.get(key); + index.updateWithMap(fileId, newValues, new Callable>() { + public Collection call() throws Exception { + return oldValues.keySet(); + } + }); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(); + } + } + + private static class MyIndex extends MapReduceIndex { + public MyIndex(final IndexStorage storage) { + super(null, null, storage); + } + + public void updateWithMap(final int inputId, final Map newData, Callable> oldKeysGetter) throws StorageException { + super.updateWithMap(inputId, newData, oldKeysGetter); + } + } + +} diff --git a/platform/lang-impl/src/com/intellij/psi/util/proximity/PsiProximityComparator.java b/platform/lang-impl/src/com/intellij/psi/util/proximity/PsiProximityComparator.java index a7598df72234..669d4cdabb86 100644 --- a/platform/lang-impl/src/com/intellij/psi/util/proximity/PsiProximityComparator.java +++ b/platform/lang-impl/src/com/intellij/psi/util/proximity/PsiProximityComparator.java @@ -1,107 +1,107 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: cdr - * Date: Jul 13, 2007 - * Time: 2:09:28 PM - */ -package com.intellij.psi.util.proximity; - -import com.intellij.extapi.psi.MetadataPsiElementBase; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.util.Key; -import com.intellij.psi.PsiElement; -import com.intellij.psi.WeighingComparable; -import com.intellij.psi.WeighingService; -import com.intellij.psi.statistics.StatisticsManager; -import com.intellij.psi.util.ProximityLocation; -import com.intellij.util.ProcessingContext; -import com.intellij.util.containers.FactoryMap; -import org.jetbrains.annotations.Nullable; - -import java.util.Comparator; - -public class PsiProximityComparator implements Comparator { - public static final Key STATISTICS_KEY = Key.create("proximity"); - public static final Key WEIGHER_KEY = Key.create("proximity"); - private final PsiElement myContext; - private final FactoryMap> myProximities = new FactoryMap>() { - @Override - protected WeighingComparable create(final PsiElement key) { - return getProximity(key, myContext); - } - }; - private static final Key MODULE_BY_LOCATION = Key.create("ModuleByLocation"); - - public PsiProximityComparator(@Nullable PsiElement context) { - myContext = context; - } - - public int compare(final Object o1, final Object o2) { - PsiElement element1 = o1 instanceof PsiElement ? (PsiElement)o1 : null; - PsiElement element2 = o2 instanceof PsiElement ? (PsiElement)o2 : null; - if (element1 == null) return element2 == null ? 0 : 1; - if (element2 == null) return -1; - - final WeighingComparable proximity1 = myProximities.get(element1); - final WeighingComparable proximity2 = myProximities.get(element2); - if (proximity1 == null || proximity2 == null) { - return 0; - } - if (!proximity1.equals(proximity2)) { - return - proximity1.compareTo(proximity2); - } - - if (myContext == null) return 0; - Module contextModule = ModuleUtil.findModuleForPsiElement(myContext); - if (contextModule == null) return 0; - - StatisticsManager statisticsManager = StatisticsManager.getInstance(); - final ProximityLocation location = new ProximityLocation(myContext, contextModule); - int count1 = statisticsManager.getUseCount(STATISTICS_KEY, element1, location); - int count2 = statisticsManager.getUseCount(STATISTICS_KEY, element1, location); - return count2 - count1; - } - - - @Nullable - public static WeighingComparable getProximity(final PsiElement element, final PsiElement context) { - if (element == null) return null; - if (element instanceof MetadataPsiElementBase) return null; - final Module contextModule = context != null ? ModuleUtil.findModuleForPsiElement(context) : null; - return WeighingService.weigh(WEIGHER_KEY, element, new ProximityLocation(context, contextModule)); - } - - @Nullable - public static WeighingComparable getProximity(final PsiElement element, final PsiElement context, ProcessingContext processingContext) { - if (element == null) return null; - if (element instanceof MetadataPsiElementBase) return null; - if (context == null) return null; - Module contextModule = processingContext.get(MODULE_BY_LOCATION); - if (contextModule == null) { - contextModule = ModuleUtil.findModuleForPsiElement(context); - processingContext.put(MODULE_BY_LOCATION, contextModule); - } - - if (contextModule == null) return null; - - return WeighingService.weigh(WEIGHER_KEY, element, new ProximityLocation(context, contextModule, processingContext)); - } - +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: cdr + * Date: Jul 13, 2007 + * Time: 2:09:28 PM + */ +package com.intellij.psi.util.proximity; + +import com.intellij.extapi.psi.MetadataPsiElementBase; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiElement; +import com.intellij.psi.WeighingComparable; +import com.intellij.psi.WeighingService; +import com.intellij.psi.statistics.StatisticsManager; +import com.intellij.psi.util.ProximityLocation; +import com.intellij.util.ProcessingContext; +import com.intellij.util.containers.FactoryMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Comparator; + +public class PsiProximityComparator implements Comparator { + public static final Key STATISTICS_KEY = Key.create("proximity"); + public static final Key WEIGHER_KEY = Key.create("proximity"); + private final PsiElement myContext; + private final FactoryMap> myProximities = new FactoryMap>() { + @Override + protected WeighingComparable create(final PsiElement key) { + return getProximity(key, myContext); + } + }; + private static final Key MODULE_BY_LOCATION = Key.create("ModuleByLocation"); + + public PsiProximityComparator(@Nullable PsiElement context) { + myContext = context; + } + + public int compare(final Object o1, final Object o2) { + PsiElement element1 = o1 instanceof PsiElement ? (PsiElement)o1 : null; + PsiElement element2 = o2 instanceof PsiElement ? (PsiElement)o2 : null; + if (element1 == null) return element2 == null ? 0 : 1; + if (element2 == null) return -1; + + final WeighingComparable proximity1 = myProximities.get(element1); + final WeighingComparable proximity2 = myProximities.get(element2); + if (proximity1 == null || proximity2 == null) { + return 0; + } + if (!proximity1.equals(proximity2)) { + return - proximity1.compareTo(proximity2); + } + + if (myContext == null) return 0; + Module contextModule = ModuleUtil.findModuleForPsiElement(myContext); + if (contextModule == null) return 0; + + StatisticsManager statisticsManager = StatisticsManager.getInstance(); + final ProximityLocation location = new ProximityLocation(myContext, contextModule); + int count1 = statisticsManager.getUseCount(STATISTICS_KEY, element1, location); + int count2 = statisticsManager.getUseCount(STATISTICS_KEY, element1, location); + return count2 - count1; + } + + + @Nullable + public static WeighingComparable getProximity(final PsiElement element, final PsiElement context) { + if (element == null) return null; + if (element instanceof MetadataPsiElementBase) return null; + final Module contextModule = context != null ? ModuleUtil.findModuleForPsiElement(context) : null; + return WeighingService.weigh(WEIGHER_KEY, element, new ProximityLocation(context, contextModule)); + } + + @Nullable + public static WeighingComparable getProximity(final PsiElement element, final PsiElement context, ProcessingContext processingContext) { + if (element == null) return null; + if (element instanceof MetadataPsiElementBase) return null; + if (context == null) return null; + Module contextModule = processingContext.get(MODULE_BY_LOCATION); + if (contextModule == null) { + contextModule = ModuleUtil.findModuleForPsiElement(context); + processingContext.put(MODULE_BY_LOCATION, contextModule); + } + + if (contextModule == null) return null; + + return WeighingService.weigh(WEIGHER_KEY, element, new ProximityLocation(context, contextModule, processingContext)); + } + } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesDialog.java b/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesDialog.java index 308ecf22ceaa..56cf4eb7db70 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesDialog.java +++ b/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesDialog.java @@ -1,258 +1,258 @@ -/* - * 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.refactoring.copy; - -import com.intellij.ide.util.DirectoryUtil; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.help.HelpManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.impl.DialogWrapperPeerImpl; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.refactoring.RefactoringBundle; -import com.intellij.ui.*; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import java.awt.*; -import java.io.File; - -class CopyFilesOrDirectoriesDialog extends DialogWrapper{ - private JLabel myInformationLabel; - private EditorComboWithBrowseButton myTargetDirectoryField; - private JTextField myNewNameField; - private final Project myProject; - private final boolean myShowDirectoryField; - private final boolean myShowNewNameField; - - private PsiDirectory myTargetDirectory; - @NonNls private static final String RECENT_KEYS = "CopyFile.RECENT_KEYS"; - - public CopyFilesOrDirectoriesDialog(PsiElement[] elements, PsiDirectory defaultTargetDirectory, Project project, boolean doClone) { - super(project, true); - myProject = project; - myShowDirectoryField = !doClone; - myShowNewNameField = elements.length == 1; - - if (doClone && elements.length != 1) { - throw new IllegalArgumentException("wrong number of elements to clone: " + elements.length); - } - - setTitle(doClone ? - RefactoringBundle.message("copy.files.clone.title") : - RefactoringBundle.message("copy.files.copy.title")); - init(); - - if (elements.length == 1) { - String text; - if (elements[0] instanceof PsiFile) { - PsiFile file = (PsiFile)elements[0]; - text = doClone ? - RefactoringBundle.message("copy.files.clone.file.0", file.getVirtualFile().getPresentableUrl()) : - RefactoringBundle.message("copy.files.copy.file.0", file.getVirtualFile().getPresentableUrl()); - final String fileName = file.getName(); - myNewNameField.setText(fileName); - final int dotIdx = fileName.lastIndexOf("."); - if (dotIdx > -1) { - myNewNameField.select(0, dotIdx); - myNewNameField.putClientProperty(DialogWrapperPeerImpl.HAVE_INITIAL_SELECTION, true); - } - } - else { - PsiDirectory directory = (PsiDirectory)elements[0]; - text = doClone ? - RefactoringBundle.message("copy.files.clone.directory.0", directory.getVirtualFile().getPresentableUrl()) : - RefactoringBundle.message("copy.files.copy.directory.0", directory.getVirtualFile().getPresentableUrl()); - myNewNameField.setText(directory.getName()); - } - myInformationLabel.setText(text); - } - else { - setMultipleElementCopyLabel(elements); - } - - if (myShowDirectoryField) { - myTargetDirectoryField.prependItem(defaultTargetDirectory == null ? "" : defaultTargetDirectory.getVirtualFile().getPresentableUrl()); - } - validateOKButton(); - } - - private void setMultipleElementCopyLabel(PsiElement[] elements) { - boolean allFiles = true; - boolean allDirectories = true; - for (PsiElement element : elements) { - if (element instanceof PsiDirectory) { - allFiles = false; - } - else { - allDirectories = false; - } - } - if (allFiles) { - myInformationLabel.setText(RefactoringBundle.message("copy.files.copy.specified.files.label")); - } - else if (allDirectories) { - myInformationLabel.setText(RefactoringBundle.message("copy.files.copy.specified.directories.label")); - } - else { - myInformationLabel.setText(RefactoringBundle.message("copy.files.copy.specified.mixed.label")); - } - } - - protected Action[] createActions(){ - return new Action[]{getOKAction(),getCancelAction(),getHelpAction()}; - } - - public JComponent getPreferredFocusedComponent() { - return myNewNameField; - } - - protected JComponent createCenterPanel() { - return new JPanel(new BorderLayout()); - } - - protected JComponent createNorthPanel() { - JPanel panel = new JPanel(new GridBagLayout()); - - panel.setBorder(IdeBorderFactory.createRoundedBorder()); - - myInformationLabel = new JLabel(); - - panel.add(myInformationLabel, new GridBagConstraints(0,0,2,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,8,4,8),0,0)); - - DocumentListener documentListener = new DocumentAdapter() { - public void textChanged(DocumentEvent event) { - validateOKButton(); - } - }; - if (myShowNewNameField) { - myNewNameField = new JTextField(); - Dimension size = myNewNameField.getPreferredSize(); - FontMetrics fontMetrics = myNewNameField.getFontMetrics(myNewNameField.getFont()); - size.width = fontMetrics.charWidth('a') * 60; - myNewNameField.setPreferredSize(size); - - panel.add(new JLabel(RefactoringBundle.message("copy.files.new.name.label")), new GridBagConstraints(0,1,1,1,0,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,8,4,8),0,0)); - - panel.add(myNewNameField, new GridBagConstraints(1,1,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,0,4,8),0,0)); - - myNewNameField.getDocument().addDocumentListener(documentListener); - } - - if (myShowDirectoryField) { - panel.add(new JLabel(RefactoringBundle.message("copy.files.to.directory.label")), new GridBagConstraints(0,2,1,1,0,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,8,4,8),0,0)); - - myTargetDirectoryField = new EditorComboWithBrowseButton(null, "", myProject, - RECENT_KEYS); - myTargetDirectoryField.addBrowseFolderListener(RefactoringBundle.message("select.target.directory"), - RefactoringBundle.message("the.file.will.be.copied.to.this.directory"), - myProject, FileChooserDescriptorFactory.createSingleFolderDescriptor(), - EditorComboBox.COMPONENT_ACCESSOR); - myTargetDirectoryField.setTextFieldPreferredWidth(60); - panel.add(myTargetDirectoryField, new GridBagConstraints(1,2,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,0,4,8),0,0)); - - myTargetDirectoryField.getChildComponent().getDocument().addDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() { - @Override - public void documentChanged(com.intellij.openapi.editor.event.DocumentEvent e) { - validateOKButton(); - } - }); - } - - return panel; - } - - public PsiDirectory getTargetDirectory() { - return myTargetDirectory; - } - - public String getNewName() { - return myNewNameField != null ? myNewNameField.getText().trim() : null; - } - - protected void doOKAction(){ - if (myShowNewNameField) { - String newName = getNewName(); - - if (newName.length() == 0) { - Messages.showMessageDialog(myProject, RefactoringBundle.message("no.new.name.specified"), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); - return; - } - } - - if (myShowDirectoryField) { - final String targetDirectoryName = myTargetDirectoryField.getText(); - - if (targetDirectoryName.length() == 0) { - Messages.showMessageDialog(myProject, RefactoringBundle.message("no.target.directory.specified"), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); - return; - } - - RecentsManager.getInstance(myProject).registerRecentEntry(RECENT_KEYS, targetDirectoryName); - - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - myTargetDirectory = DirectoryUtil.mkdirs(PsiManager.getInstance(myProject), targetDirectoryName.replace(File.separatorChar, '/')); - } - catch (IncorrectOperationException e) { - } - } - }); - } - }, RefactoringBundle.message("create.directory"), null); - - if (myTargetDirectory == null) { - Messages.showMessageDialog(myProject, RefactoringBundle.message("cannot.create.directory"), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); - return; - } - } - - super.doOKAction(); - } - - private void validateOKButton() { - if (myShowDirectoryField) { - if (myTargetDirectoryField.getText().length() == 0) { - setOKActionEnabled(false); - return; - } - } - if (myShowNewNameField) { - if (getNewName().length() == 0) { - setOKActionEnabled(false); - return; - } - } - setOKActionEnabled(true); - } - - protected void doHelpAction() { - HelpManager.getInstance().invokeHelp("refactoring.copyClass"); - } -} +/* + * 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.refactoring.copy; + +import com.intellij.ide.util.DirectoryUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.impl.DialogWrapperPeerImpl; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.ui.*; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.io.File; + +class CopyFilesOrDirectoriesDialog extends DialogWrapper{ + private JLabel myInformationLabel; + private EditorComboWithBrowseButton myTargetDirectoryField; + private JTextField myNewNameField; + private final Project myProject; + private final boolean myShowDirectoryField; + private final boolean myShowNewNameField; + + private PsiDirectory myTargetDirectory; + @NonNls private static final String RECENT_KEYS = "CopyFile.RECENT_KEYS"; + + public CopyFilesOrDirectoriesDialog(PsiElement[] elements, PsiDirectory defaultTargetDirectory, Project project, boolean doClone) { + super(project, true); + myProject = project; + myShowDirectoryField = !doClone; + myShowNewNameField = elements.length == 1; + + if (doClone && elements.length != 1) { + throw new IllegalArgumentException("wrong number of elements to clone: " + elements.length); + } + + setTitle(doClone ? + RefactoringBundle.message("copy.files.clone.title") : + RefactoringBundle.message("copy.files.copy.title")); + init(); + + if (elements.length == 1) { + String text; + if (elements[0] instanceof PsiFile) { + PsiFile file = (PsiFile)elements[0]; + text = doClone ? + RefactoringBundle.message("copy.files.clone.file.0", file.getVirtualFile().getPresentableUrl()) : + RefactoringBundle.message("copy.files.copy.file.0", file.getVirtualFile().getPresentableUrl()); + final String fileName = file.getName(); + myNewNameField.setText(fileName); + final int dotIdx = fileName.lastIndexOf("."); + if (dotIdx > -1) { + myNewNameField.select(0, dotIdx); + myNewNameField.putClientProperty(DialogWrapperPeerImpl.HAVE_INITIAL_SELECTION, true); + } + } + else { + PsiDirectory directory = (PsiDirectory)elements[0]; + text = doClone ? + RefactoringBundle.message("copy.files.clone.directory.0", directory.getVirtualFile().getPresentableUrl()) : + RefactoringBundle.message("copy.files.copy.directory.0", directory.getVirtualFile().getPresentableUrl()); + myNewNameField.setText(directory.getName()); + } + myInformationLabel.setText(text); + } + else { + setMultipleElementCopyLabel(elements); + } + + if (myShowDirectoryField) { + myTargetDirectoryField.prependItem(defaultTargetDirectory == null ? "" : defaultTargetDirectory.getVirtualFile().getPresentableUrl()); + } + validateOKButton(); + } + + private void setMultipleElementCopyLabel(PsiElement[] elements) { + boolean allFiles = true; + boolean allDirectories = true; + for (PsiElement element : elements) { + if (element instanceof PsiDirectory) { + allFiles = false; + } + else { + allDirectories = false; + } + } + if (allFiles) { + myInformationLabel.setText(RefactoringBundle.message("copy.files.copy.specified.files.label")); + } + else if (allDirectories) { + myInformationLabel.setText(RefactoringBundle.message("copy.files.copy.specified.directories.label")); + } + else { + myInformationLabel.setText(RefactoringBundle.message("copy.files.copy.specified.mixed.label")); + } + } + + protected Action[] createActions(){ + return new Action[]{getOKAction(),getCancelAction(),getHelpAction()}; + } + + public JComponent getPreferredFocusedComponent() { + return myNewNameField; + } + + protected JComponent createCenterPanel() { + return new JPanel(new BorderLayout()); + } + + protected JComponent createNorthPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + + panel.setBorder(IdeBorderFactory.createRoundedBorder()); + + myInformationLabel = new JLabel(); + + panel.add(myInformationLabel, new GridBagConstraints(0,0,2,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,8,4,8),0,0)); + + DocumentListener documentListener = new DocumentAdapter() { + public void textChanged(DocumentEvent event) { + validateOKButton(); + } + }; + if (myShowNewNameField) { + myNewNameField = new JTextField(); + Dimension size = myNewNameField.getPreferredSize(); + FontMetrics fontMetrics = myNewNameField.getFontMetrics(myNewNameField.getFont()); + size.width = fontMetrics.charWidth('a') * 60; + myNewNameField.setPreferredSize(size); + + panel.add(new JLabel(RefactoringBundle.message("copy.files.new.name.label")), new GridBagConstraints(0,1,1,1,0,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,8,4,8),0,0)); + + panel.add(myNewNameField, new GridBagConstraints(1,1,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,0,4,8),0,0)); + + myNewNameField.getDocument().addDocumentListener(documentListener); + } + + if (myShowDirectoryField) { + panel.add(new JLabel(RefactoringBundle.message("copy.files.to.directory.label")), new GridBagConstraints(0,2,1,1,0,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,8,4,8),0,0)); + + myTargetDirectoryField = new EditorComboWithBrowseButton(null, "", myProject, + RECENT_KEYS); + myTargetDirectoryField.addBrowseFolderListener(RefactoringBundle.message("select.target.directory"), + RefactoringBundle.message("the.file.will.be.copied.to.this.directory"), + myProject, FileChooserDescriptorFactory.createSingleFolderDescriptor(), + EditorComboBox.COMPONENT_ACCESSOR); + myTargetDirectoryField.setTextFieldPreferredWidth(60); + panel.add(myTargetDirectoryField, new GridBagConstraints(1,2,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(4,0,4,8),0,0)); + + myTargetDirectoryField.getChildComponent().getDocument().addDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() { + @Override + public void documentChanged(com.intellij.openapi.editor.event.DocumentEvent e) { + validateOKButton(); + } + }); + } + + return panel; + } + + public PsiDirectory getTargetDirectory() { + return myTargetDirectory; + } + + public String getNewName() { + return myNewNameField != null ? myNewNameField.getText().trim() : null; + } + + protected void doOKAction(){ + if (myShowNewNameField) { + String newName = getNewName(); + + if (newName.length() == 0) { + Messages.showMessageDialog(myProject, RefactoringBundle.message("no.new.name.specified"), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); + return; + } + } + + if (myShowDirectoryField) { + final String targetDirectoryName = myTargetDirectoryField.getText(); + + if (targetDirectoryName.length() == 0) { + Messages.showMessageDialog(myProject, RefactoringBundle.message("no.target.directory.specified"), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); + return; + } + + RecentsManager.getInstance(myProject).registerRecentEntry(RECENT_KEYS, targetDirectoryName); + + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + myTargetDirectory = DirectoryUtil.mkdirs(PsiManager.getInstance(myProject), targetDirectoryName.replace(File.separatorChar, '/')); + } + catch (IncorrectOperationException e) { + } + } + }); + } + }, RefactoringBundle.message("create.directory"), null); + + if (myTargetDirectory == null) { + Messages.showMessageDialog(myProject, RefactoringBundle.message("cannot.create.directory"), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); + return; + } + } + + super.doOKAction(); + } + + private void validateOKButton() { + if (myShowDirectoryField) { + if (myTargetDirectoryField.getText().length() == 0) { + setOKActionEnabled(false); + return; + } + } + if (myShowNewNameField) { + if (getNewName().length() == 0) { + setOKActionEnabled(false); + return; + } + } + setOKActionEnabled(true); + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp("refactoring.copyClass"); + } +} diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/RenameUtil.java b/platform/lang-impl/src/com/intellij/refactoring/rename/RenameUtil.java index 4d344629ae29..9d18e8486329 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/RenameUtil.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/RenameUtil.java @@ -1,340 +1,340 @@ -/* - * 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.refactoring.rename; - -import com.intellij.codeInsight.CodeInsightUtilBase; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageNamesValidation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.pom.PomTargetPsiElement; -import com.intellij.psi.*; -import com.intellij.psi.meta.PsiMetaData; -import com.intellij.psi.meta.PsiMetaOwner; -import com.intellij.psi.meta.PsiWritableMetaData; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.refactoring.RefactoringBundle; -import com.intellij.refactoring.listeners.RefactoringElementListener; -import com.intellij.refactoring.util.*; -import com.intellij.usageView.UsageInfo; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.containers.HashMap; -import com.intellij.util.containers.MultiMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class RenameUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameUtil"); - - private RenameUtil() { - } - - @NotNull - public static UsageInfo[] findUsages(final PsiElement element, - String newName, - boolean searchInStringsAndComments, - boolean searchForTextOccurrences, - Map allRenames) { - final List result = Collections.synchronizedList(new ArrayList()); - - PsiManager manager = element.getManager(); - GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject()); - RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element); - - Collection refs = processor.findReferences(element); - for (PsiReference ref : refs) { - if (ref == null) { - LOG.error("null reference from processor " + processor); - continue; - } - PsiElement referenceElement = ref.getElement(); - result.add(new MoveRenameUsageInfo(referenceElement, ref, ref.getRangeInElement().getStartOffset(), - ref.getRangeInElement().getEndOffset(), element, - ref.resolve() == null)); - } - - processor.findCollisions(element, newName, allRenames, result); - - final PsiElement searchForInComments = processor.getElementToSearchInStringsAndComments(element); - - if (searchInStringsAndComments && searchForInComments != null) { - String stringToSearch = ElementDescriptionUtil.getElementDescription(searchForInComments, NonCodeSearchDescriptionLocation.STRINGS_AND_COMMENTS); - if (stringToSearch.length() > 0) { - final String stringToReplace = getStringToReplace(element, newName, false, processor); - TextOccurrencesUtil.UsageInfoFactory factory = new NonCodeUsageInfoFactory(searchForInComments, stringToReplace); - TextOccurrencesUtil.addUsagesInStringsAndComments(searchForInComments, stringToSearch, result, factory); - } - } - - if (searchForTextOccurrences && searchForInComments != null) { - String stringToSearch = ElementDescriptionUtil.getElementDescription(searchForInComments, NonCodeSearchDescriptionLocation.NON_JAVA); - if (stringToSearch.length() > 0) { - final String stringToReplace = getStringToReplace(element, newName, true, processor); - addTextOccurrence(searchForInComments, result, projectScope, stringToSearch, stringToReplace); - } - - final Pair additionalStringToSearch = processor.getTextOccurrenceSearchStrings(searchForInComments, newName); - if (additionalStringToSearch != null && additionalStringToSearch.first.length() > 0) { - addTextOccurrence(searchForInComments, result, projectScope, additionalStringToSearch.first, additionalStringToSearch.second); - } - } - - return result.toArray(new UsageInfo[result.size()]); - } - - private static void addTextOccurrence(final PsiElement element, final List result, final GlobalSearchScope projectScope, - final String stringToSearch, final String stringToReplace) { - TextOccurrencesUtil.UsageInfoFactory factory = new TextOccurrencesUtil.UsageInfoFactory() { - public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) { - TextRange textRange = usage.getTextRange(); - int start = textRange == null ? 0 : textRange.getStartOffset(); - return NonCodeUsageInfo.create(usage.getContainingFile(), start + startOffset, start + endOffset, element, stringToReplace); - } - }; - TextOccurrencesUtil.addTextOccurences(element, stringToSearch, projectScope, result, factory); - } - - - public static void buildPackagePrefixChangedMessage(final VirtualFile[] virtualFiles, StringBuffer message, final String qualifiedName) { - if (virtualFiles.length > 0) { - message.append(RefactoringBundle.message("package.occurs.in.package.prefixes.of.the.following.source.folders.n", qualifiedName)); - for (final VirtualFile virtualFile : virtualFiles) { - message.append(virtualFile.getPresentableUrl()).append("\n"); - } - message.append(RefactoringBundle.message("these.package.prefixes.will.be.changed")); - } - } - - private static String getStringToReplace(PsiElement element, String newName, boolean nonJava, final RenamePsiElementProcessor theProcessor) { - if (element instanceof PsiMetaOwner) { - final PsiMetaOwner psiMetaOwner = (PsiMetaOwner)element; - final PsiMetaData metaData = psiMetaOwner.getMetaData(); - if (metaData != null) { - return metaData.getName(); - } - } - - if (theProcessor != null) { - String result = theProcessor.getQualifiedNameAfterRename(element, newName, nonJava); - if (result != null) { - return result; - } - } - - if (element instanceof PsiNamedElement) { - return newName; - } - else { - LOG.error("Unknown element type"); - return null; - } - } - - public static String getQualifiedNameAfterRename(String qName, String newName) { - if (qName == null) return newName; - int index = qName.lastIndexOf('.'); - return index < 0 ? newName : qName.substring(0, index + 1) + newName; - } - - public static void checkRename(PsiElement element, String newName) throws IncorrectOperationException { - if (element instanceof PsiCheckedRenameElement) { - ((PsiCheckedRenameElement)element).checkSetName(newName); - } - } - - public static void doRename(final PsiElement element, String newName, UsageInfo[] usages, final Project project, - RefactoringElementListener listener) { - final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element); - try { - processor.renameElement(element, newName, usages, listener); - } - catch (final IncorrectOperationException e) { - // may happen if the file or package cannot be renamed. e.g. locked by another application - if (ApplicationManager.getApplication().isUnitTestMode()) { - throw new RuntimeException(e); - //LOG.error(e); - //return; - } - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), e.getMessage(), processor.getHelpID(element), project); - } - }); - } - } - - public static void doRenameGenericNamedElement(PsiElement namedElement, String newName, UsageInfo[] usages, - @Nullable RefactoringElementListener listener) throws IncorrectOperationException { - PsiWritableMetaData writableMetaData = null; - if (namedElement instanceof PsiMetaOwner) { - final PsiMetaData metaData = ((PsiMetaOwner)namedElement).getMetaData(); - if (metaData instanceof PsiWritableMetaData) { - writableMetaData = (PsiWritableMetaData)metaData; - } - } - if (writableMetaData == null && !(namedElement instanceof PsiNamedElement)) { - LOG.error("Unknown element type"); - } - - boolean hasBindables = false; - for (UsageInfo usage : usages) { - if (!(usage.getReference() instanceof BindablePsiReference)) { - rename(usage, newName); - } else { - hasBindables = true; - } - } - - if (writableMetaData != null) { - writableMetaData.setName(newName); - } - else { - PsiElement namedElementAfterRename = ((PsiNamedElement)namedElement).setName(newName); - if (namedElementAfterRename != null) namedElement = namedElementAfterRename; - } - - if (hasBindables) { - for (UsageInfo usage : usages) { - final PsiReference ref = usage.getReference(); - if (ref instanceof BindablePsiReference) { - try { - ref.bindToElement(namedElement); - } - catch (IncorrectOperationException e) {//fall back to old scheme - ref.handleElementRename(newName); - } - } - } - } - if (listener != null) { - listener.elementRenamed(namedElement); - } - } - - public static void rename(UsageInfo info, String newName) throws IncorrectOperationException { - if (info.getElement() == null) return; - PsiReference ref = info.getReference(); - if (ref == null) return; - ref.handleElementRename(newName); - } - - @Nullable - public static List removeConflictUsages(Set usages) { - final List result = new ArrayList(); - for (Iterator iterator = usages.iterator(); iterator.hasNext();) { - UsageInfo usageInfo = iterator.next(); - if (usageInfo instanceof UnresolvableCollisionUsageInfo) { - result.add((UnresolvableCollisionUsageInfo)usageInfo); - iterator.remove(); - } - } - return result.isEmpty() ? null : result; - } - - public static void addConflictDescriptions(UsageInfo[] usages, MultiMap conflicts) { - for (UsageInfo usage : usages) { - if (usage instanceof UnresolvableCollisionUsageInfo) { - conflicts.putValue(usage.getElement(), ((UnresolvableCollisionUsageInfo)usage).getDescription()); - } - } - } - - public static void renameNonCodeUsages(@NotNull Project project, @NotNull NonCodeUsageInfo[] usages) { - PsiDocumentManager.getInstance(project).commitAllDocuments(); - Map> docsToOffsetsMap = new HashMap>(); - final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); - for (NonCodeUsageInfo usage : usages) { - PsiElement element = usage.getElement(); - - if (element == null) continue; - element = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(element); - if (element == null) continue; - final PsiFile containingFile = element.getContainingFile(); - final Document document = psiDocumentManager.getDocument(containingFile); - int fileOffset = element.getTextRange().getStartOffset() + usage.startOffset; - - ArrayList list = docsToOffsetsMap.get(document); - if (list == null) { - list = new ArrayList(); - docsToOffsetsMap.put(document, list); - } - list.add(new UsageOffset(fileOffset, fileOffset + usage.endOffset - usage.startOffset, usage.newText)); - } - - for (Document document : docsToOffsetsMap.keySet()) { - ArrayList list = docsToOffsetsMap.get(document); - UsageOffset[] offsets = list.toArray(new UsageOffset[list.size()]); - Arrays.sort(offsets); - - for (int i = offsets.length - 1; i >= 0; i--) { - UsageOffset usageOffset = offsets[i]; - document.replaceString(usageOffset.startOffset, usageOffset.endOffset, usageOffset.newText); - } - PsiDocumentManager.getInstance(project).commitDocument(document); - } - PsiDocumentManager.getInstance(project).commitAllDocuments(); - } - - public static boolean isValidName(final Project project, final PsiElement psiElement, final String newName) { - if (newName == null || newName.length() == 0) { - return false; - } - final Condition inputValidator = RenameInputValidatorRegistry.getInputValidator(psiElement); - if (inputValidator != null) { - return inputValidator.value(newName); - } - if (psiElement instanceof PsiFile || psiElement instanceof PsiDirectory) { - return newName.indexOf('\\') < 0 && newName.indexOf('/') < 0; - } - if (psiElement instanceof PomTargetPsiElement) { - return !StringUtil.isEmptyOrSpaces(newName); - } - - final PsiFile file = psiElement.getContainingFile(); - final Language elementLanguage = psiElement.getLanguage(); - - final Language fileLanguage = file == null ? null : file.getLanguage(); - Language language = fileLanguage == null ? elementLanguage : fileLanguage.isKindOf(elementLanguage) ? fileLanguage : elementLanguage; - - return LanguageNamesValidation.INSTANCE.forLanguage(language).isIdentifier(newName.trim(), project); - } - - private static class UsageOffset implements Comparable { - final int startOffset; - final int endOffset; - final String newText; - - public UsageOffset(int startOffset, int endOffset, String newText) { - this.startOffset = startOffset; - this.endOffset = endOffset; - this.newText = newText; - } - - public int compareTo(final UsageOffset o) { - return startOffset - o.startOffset; - } - } -} +/* + * 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.refactoring.rename; + +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageNamesValidation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.PomTargetPsiElement; +import com.intellij.psi.*; +import com.intellij.psi.meta.PsiMetaData; +import com.intellij.psi.meta.PsiMetaOwner; +import com.intellij.psi.meta.PsiWritableMetaData; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.listeners.RefactoringElementListener; +import com.intellij.refactoring.util.*; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class RenameUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameUtil"); + + private RenameUtil() { + } + + @NotNull + public static UsageInfo[] findUsages(final PsiElement element, + String newName, + boolean searchInStringsAndComments, + boolean searchForTextOccurrences, + Map allRenames) { + final List result = Collections.synchronizedList(new ArrayList()); + + PsiManager manager = element.getManager(); + GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject()); + RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element); + + Collection refs = processor.findReferences(element); + for (PsiReference ref : refs) { + if (ref == null) { + LOG.error("null reference from processor " + processor); + continue; + } + PsiElement referenceElement = ref.getElement(); + result.add(new MoveRenameUsageInfo(referenceElement, ref, ref.getRangeInElement().getStartOffset(), + ref.getRangeInElement().getEndOffset(), element, + ref.resolve() == null)); + } + + processor.findCollisions(element, newName, allRenames, result); + + final PsiElement searchForInComments = processor.getElementToSearchInStringsAndComments(element); + + if (searchInStringsAndComments && searchForInComments != null) { + String stringToSearch = ElementDescriptionUtil.getElementDescription(searchForInComments, NonCodeSearchDescriptionLocation.STRINGS_AND_COMMENTS); + if (stringToSearch.length() > 0) { + final String stringToReplace = getStringToReplace(element, newName, false, processor); + TextOccurrencesUtil.UsageInfoFactory factory = new NonCodeUsageInfoFactory(searchForInComments, stringToReplace); + TextOccurrencesUtil.addUsagesInStringsAndComments(searchForInComments, stringToSearch, result, factory); + } + } + + if (searchForTextOccurrences && searchForInComments != null) { + String stringToSearch = ElementDescriptionUtil.getElementDescription(searchForInComments, NonCodeSearchDescriptionLocation.NON_JAVA); + if (stringToSearch.length() > 0) { + final String stringToReplace = getStringToReplace(element, newName, true, processor); + addTextOccurrence(searchForInComments, result, projectScope, stringToSearch, stringToReplace); + } + + final Pair additionalStringToSearch = processor.getTextOccurrenceSearchStrings(searchForInComments, newName); + if (additionalStringToSearch != null && additionalStringToSearch.first.length() > 0) { + addTextOccurrence(searchForInComments, result, projectScope, additionalStringToSearch.first, additionalStringToSearch.second); + } + } + + return result.toArray(new UsageInfo[result.size()]); + } + + private static void addTextOccurrence(final PsiElement element, final List result, final GlobalSearchScope projectScope, + final String stringToSearch, final String stringToReplace) { + TextOccurrencesUtil.UsageInfoFactory factory = new TextOccurrencesUtil.UsageInfoFactory() { + public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) { + TextRange textRange = usage.getTextRange(); + int start = textRange == null ? 0 : textRange.getStartOffset(); + return NonCodeUsageInfo.create(usage.getContainingFile(), start + startOffset, start + endOffset, element, stringToReplace); + } + }; + TextOccurrencesUtil.addTextOccurences(element, stringToSearch, projectScope, result, factory); + } + + + public static void buildPackagePrefixChangedMessage(final VirtualFile[] virtualFiles, StringBuffer message, final String qualifiedName) { + if (virtualFiles.length > 0) { + message.append(RefactoringBundle.message("package.occurs.in.package.prefixes.of.the.following.source.folders.n", qualifiedName)); + for (final VirtualFile virtualFile : virtualFiles) { + message.append(virtualFile.getPresentableUrl()).append("\n"); + } + message.append(RefactoringBundle.message("these.package.prefixes.will.be.changed")); + } + } + + private static String getStringToReplace(PsiElement element, String newName, boolean nonJava, final RenamePsiElementProcessor theProcessor) { + if (element instanceof PsiMetaOwner) { + final PsiMetaOwner psiMetaOwner = (PsiMetaOwner)element; + final PsiMetaData metaData = psiMetaOwner.getMetaData(); + if (metaData != null) { + return metaData.getName(); + } + } + + if (theProcessor != null) { + String result = theProcessor.getQualifiedNameAfterRename(element, newName, nonJava); + if (result != null) { + return result; + } + } + + if (element instanceof PsiNamedElement) { + return newName; + } + else { + LOG.error("Unknown element type"); + return null; + } + } + + public static String getQualifiedNameAfterRename(String qName, String newName) { + if (qName == null) return newName; + int index = qName.lastIndexOf('.'); + return index < 0 ? newName : qName.substring(0, index + 1) + newName; + } + + public static void checkRename(PsiElement element, String newName) throws IncorrectOperationException { + if (element instanceof PsiCheckedRenameElement) { + ((PsiCheckedRenameElement)element).checkSetName(newName); + } + } + + public static void doRename(final PsiElement element, String newName, UsageInfo[] usages, final Project project, + RefactoringElementListener listener) { + final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element); + try { + processor.renameElement(element, newName, usages, listener); + } + catch (final IncorrectOperationException e) { + // may happen if the file or package cannot be renamed. e.g. locked by another application + if (ApplicationManager.getApplication().isUnitTestMode()) { + throw new RuntimeException(e); + //LOG.error(e); + //return; + } + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), e.getMessage(), processor.getHelpID(element), project); + } + }); + } + } + + public static void doRenameGenericNamedElement(PsiElement namedElement, String newName, UsageInfo[] usages, + @Nullable RefactoringElementListener listener) throws IncorrectOperationException { + PsiWritableMetaData writableMetaData = null; + if (namedElement instanceof PsiMetaOwner) { + final PsiMetaData metaData = ((PsiMetaOwner)namedElement).getMetaData(); + if (metaData instanceof PsiWritableMetaData) { + writableMetaData = (PsiWritableMetaData)metaData; + } + } + if (writableMetaData == null && !(namedElement instanceof PsiNamedElement)) { + LOG.error("Unknown element type"); + } + + boolean hasBindables = false; + for (UsageInfo usage : usages) { + if (!(usage.getReference() instanceof BindablePsiReference)) { + rename(usage, newName); + } else { + hasBindables = true; + } + } + + if (writableMetaData != null) { + writableMetaData.setName(newName); + } + else { + PsiElement namedElementAfterRename = ((PsiNamedElement)namedElement).setName(newName); + if (namedElementAfterRename != null) namedElement = namedElementAfterRename; + } + + if (hasBindables) { + for (UsageInfo usage : usages) { + final PsiReference ref = usage.getReference(); + if (ref instanceof BindablePsiReference) { + try { + ref.bindToElement(namedElement); + } + catch (IncorrectOperationException e) {//fall back to old scheme + ref.handleElementRename(newName); + } + } + } + } + if (listener != null) { + listener.elementRenamed(namedElement); + } + } + + public static void rename(UsageInfo info, String newName) throws IncorrectOperationException { + if (info.getElement() == null) return; + PsiReference ref = info.getReference(); + if (ref == null) return; + ref.handleElementRename(newName); + } + + @Nullable + public static List removeConflictUsages(Set usages) { + final List result = new ArrayList(); + for (Iterator iterator = usages.iterator(); iterator.hasNext();) { + UsageInfo usageInfo = iterator.next(); + if (usageInfo instanceof UnresolvableCollisionUsageInfo) { + result.add((UnresolvableCollisionUsageInfo)usageInfo); + iterator.remove(); + } + } + return result.isEmpty() ? null : result; + } + + public static void addConflictDescriptions(UsageInfo[] usages, MultiMap conflicts) { + for (UsageInfo usage : usages) { + if (usage instanceof UnresolvableCollisionUsageInfo) { + conflicts.putValue(usage.getElement(), ((UnresolvableCollisionUsageInfo)usage).getDescription()); + } + } + } + + public static void renameNonCodeUsages(@NotNull Project project, @NotNull NonCodeUsageInfo[] usages) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + Map> docsToOffsetsMap = new HashMap>(); + final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); + for (NonCodeUsageInfo usage : usages) { + PsiElement element = usage.getElement(); + + if (element == null) continue; + element = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(element); + if (element == null) continue; + final PsiFile containingFile = element.getContainingFile(); + final Document document = psiDocumentManager.getDocument(containingFile); + int fileOffset = element.getTextRange().getStartOffset() + usage.startOffset; + + ArrayList list = docsToOffsetsMap.get(document); + if (list == null) { + list = new ArrayList(); + docsToOffsetsMap.put(document, list); + } + list.add(new UsageOffset(fileOffset, fileOffset + usage.endOffset - usage.startOffset, usage.newText)); + } + + for (Document document : docsToOffsetsMap.keySet()) { + ArrayList list = docsToOffsetsMap.get(document); + UsageOffset[] offsets = list.toArray(new UsageOffset[list.size()]); + Arrays.sort(offsets); + + for (int i = offsets.length - 1; i >= 0; i--) { + UsageOffset usageOffset = offsets[i]; + document.replaceString(usageOffset.startOffset, usageOffset.endOffset, usageOffset.newText); + } + PsiDocumentManager.getInstance(project).commitDocument(document); + } + PsiDocumentManager.getInstance(project).commitAllDocuments(); + } + + public static boolean isValidName(final Project project, final PsiElement psiElement, final String newName) { + if (newName == null || newName.length() == 0) { + return false; + } + final Condition inputValidator = RenameInputValidatorRegistry.getInputValidator(psiElement); + if (inputValidator != null) { + return inputValidator.value(newName); + } + if (psiElement instanceof PsiFile || psiElement instanceof PsiDirectory) { + return newName.indexOf('\\') < 0 && newName.indexOf('/') < 0; + } + if (psiElement instanceof PomTargetPsiElement) { + return !StringUtil.isEmptyOrSpaces(newName); + } + + final PsiFile file = psiElement.getContainingFile(); + final Language elementLanguage = psiElement.getLanguage(); + + final Language fileLanguage = file == null ? null : file.getLanguage(); + Language language = fileLanguage == null ? elementLanguage : fileLanguage.isKindOf(elementLanguage) ? fileLanguage : elementLanguage; + + return LanguageNamesValidation.INSTANCE.forLanguage(language).isIdentifier(newName.trim(), project); + } + + private static class UsageOffset implements Comparable { + final int startOffset; + final int endOffset; + final String newText; + + public UsageOffset(int startOffset, int endOffset, String newText) { + this.startOffset = startOffset; + this.endOffset = endOffset; + this.newText = newText; + } + + public int compareTo(final UsageOffset o) { + return startOffset - o.startOffset; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java index fdfe03cb65bd..7b34748412d9 100644 --- a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java @@ -1,291 +1,291 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.ui; - -import com.intellij.concurrency.Job; -import com.intellij.concurrency.JobUtil; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.util.Ref; -import com.intellij.util.Alarm; -import com.intellij.util.Function; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import javax.swing.plaf.TreeUI; -import javax.swing.plaf.basic.BasicTreeUI; -import java.awt.*; -import java.lang.ref.WeakReference; -import java.util.LinkedHashSet; -import java.util.Set; - -public class DeferredIconImpl implements DeferredIcon { - private static final RepaintScheduler ourRepaintScheduler = new RepaintScheduler(); - private volatile Icon myDelegateIcon; - private Function myEvaluator; - private volatile boolean myIsScheduled = false; - private T myParam; - private WeakReference myLastTarget = null; - private static final EmptyIcon EMPTY_ICON = new EmptyIcon(16, 16); - private boolean myNeedReadAction; - private boolean myDone; - - private IconDisposer myDisposer; - - public DeferredIconImpl(Icon baseIcon, T param, Function evaluator) { - this(baseIcon, param, true, evaluator); - } - - public DeferredIconImpl(Icon baseIcon, T param, final boolean needReadAction, Function evaluator) { - myParam = param; - myDelegateIcon = nonNull(baseIcon); - myEvaluator = evaluator; - myNeedReadAction = needReadAction; - } - - private static Icon nonNull(final Icon icon) { - return icon != null ? icon : EMPTY_ICON; - } - - public void paintIcon(final Component c, final Graphics g, final int x, final int y) { - myDelegateIcon.paintIcon(c, g, x, y); - - if (!myIsScheduled && !isDone()) { - myIsScheduled = true; - - final Ref target = new Ref(null); - final Ref paintingParent = new Ref(null); - final Ref paintingParentRec = new Ref(null); - - final Container list = SwingUtilities.getAncestorOfClass(JList.class, c); - if (list != null) { - target.set(list); - } - else { - final Container tree = SwingUtilities.getAncestorOfClass(JTree.class, c); - if (tree != null) { - target.set(tree); - } - else { - final Container table = SwingUtilities.getAncestorOfClass(JTable.class, c); - if (table != null) { - target.set(table); - } - else { - final Container box = SwingUtilities.getAncestorOfClass(JComboBox.class, c); - if (box != null) { - target.set(box); - } - else { - target.set(c); - } - } - } - } - - Container pp = SwingUtilities.getAncestorOfClass(PaintingParent.class, c); - paintingParent.set(pp); - if (paintingParent.get() != null) { - paintingParentRec.set(((PaintingParent)pp).getChildRec(c)); - } - - myLastTarget = new WeakReference(target.get()); - - JobUtil.submitToJobThread(new Runnable() { - public void run() { - int oldWidth = myDelegateIcon.getIconWidth(); - myDelegateIcon = evaluate(); - - final boolean shouldRevalidate = myDelegateIcon.getIconWidth() != oldWidth; - - //noinspection SSBasedInspection - SwingUtilities.invokeLater(new Runnable() { - public void run() { - setDone(); - - Component actualTarget = target.get(); - if (SwingUtilities.getWindowAncestor(actualTarget) == null) { - actualTarget = paintingParent.get(); - if (actualTarget == null || SwingUtilities.getWindowAncestor(actualTarget) == null) { - actualTarget = null; - myLastTarget = null; - } - } - - if (actualTarget == null) return; - - myLastTarget = new WeakReference(actualTarget); - - if (shouldRevalidate) { - // revalidate will not work: jtree caches size of nodes - if (actualTarget instanceof JTree) { - final TreeUI ui = ((JTree)actualTarget).getUI(); - if (ui instanceof BasicTreeUI) { - // this call is "fake" and only need to reset tree layout cache - ((BasicTreeUI)ui).setLeftChildIndent(UIUtil.getTreeLeftChildIndent()); - } - } - } - - if (c == actualTarget) { - c.repaint(x, y, getIconWidth(), getIconHeight()); - } - else { - Rectangle rec = null; - if (paintingParentRec.get() != null) { - rec = paintingParentRec.get(); - } - - ourRepaintScheduler.pushDirtyComponent(actualTarget, rec); - } - } - }); - } - }, Job.DEFAULT_PRIORITY); - } - } - - private void setDone() { - if (myDisposer != null) { - myDisposer.dispose(myParam); - } - - myDone = true; - myEvaluator = null; - myParam = null; - } - - public Icon evaluate() { - final Icon[] evaluated = new Icon[1]; - final Runnable runnable = new Runnable() { - public void run() { - try { - evaluated[0] = nonNull(myEvaluator.fun(myParam)); - } - catch (ProcessCanceledException e) { - evaluated[0] = EMPTY_ICON; - } - catch (IndexNotReadyException e) { - evaluated[0] = EMPTY_ICON; - } - } - }; - if (myNeedReadAction) { - IconDeferrerImpl.evaluateDeferredInReadAction(runnable); - } - else { - IconDeferrerImpl.evaluateDeferred(runnable); - } - - checkDoesntReferenceThis(evaluated[0]); - - return evaluated[0]; - } - - private void checkDoesntReferenceThis(final Icon icon) { - if (icon == this) { - throw new IllegalStateException("Loop in icons delegation"); - } - - if (icon instanceof DeferredIconImpl) { - checkDoesntReferenceThis(((DeferredIconImpl)icon).myDelegateIcon); - } - else if (icon instanceof LayeredIcon) { - for (Icon layer : ((LayeredIcon)icon).getAllLayers()) { - checkDoesntReferenceThis(layer); - } - } - else if (icon instanceof RowIcon) { - final RowIcon rowIcon = (RowIcon)icon; - final int count = rowIcon.getIconCount(); - for (int i = 0; i < count; i++) { - checkDoesntReferenceThis(rowIcon.getIcon(i)); - } - } - } - - public int getIconWidth() { - return myDelegateIcon.getIconWidth(); - } - - public int getIconHeight() { - return myDelegateIcon.getIconHeight(); - } - - public boolean isDone() { - return myDone; - } - - private static class RepaintScheduler { - private final Alarm myAlarm = new Alarm(); - private final Set myQueue = new LinkedHashSet(); - - public void pushDirtyComponent(final Component c, final Rectangle rec) { - myAlarm.cancelAllRequests(); - myAlarm.addRequest(new Runnable() { - public void run() { - for (RepaintRequest each : myQueue) { - Rectangle r = each.getRectangle(); - if (r != null) { - each.getComponent().repaint(r.x, r.y, r.width, r.height); - } else { - each.getComponent().repaint(); - } - } - myQueue.clear(); - } - }, 50); - - myQueue.add(new RepaintRequest(c, rec)); - } - } - - private static class RepaintRequest { - private Component myComponent; - private Rectangle myRectangle; - - private RepaintRequest(Component component, Rectangle rectangle) { - myComponent = component; - myRectangle = rectangle; - } - - public Component getComponent() { - return myComponent; - } - - public Rectangle getRectangle() { - return myRectangle; - } - } - - - public DeferredIconImpl setDisposer(IconDisposer disposer) { - myDisposer = disposer; - return this; - } - - public interface IconDisposer { - - void dispose(T key); - - } - -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.ui; + +import com.intellij.concurrency.Job; +import com.intellij.concurrency.JobUtil; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.util.Ref; +import com.intellij.util.Alarm; +import com.intellij.util.Function; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import javax.swing.plaf.TreeUI; +import javax.swing.plaf.basic.BasicTreeUI; +import java.awt.*; +import java.lang.ref.WeakReference; +import java.util.LinkedHashSet; +import java.util.Set; + +public class DeferredIconImpl implements DeferredIcon { + private static final RepaintScheduler ourRepaintScheduler = new RepaintScheduler(); + private volatile Icon myDelegateIcon; + private Function myEvaluator; + private volatile boolean myIsScheduled = false; + private T myParam; + private WeakReference myLastTarget = null; + private static final EmptyIcon EMPTY_ICON = new EmptyIcon(16, 16); + private boolean myNeedReadAction; + private boolean myDone; + + private IconDisposer myDisposer; + + public DeferredIconImpl(Icon baseIcon, T param, Function evaluator) { + this(baseIcon, param, true, evaluator); + } + + public DeferredIconImpl(Icon baseIcon, T param, final boolean needReadAction, Function evaluator) { + myParam = param; + myDelegateIcon = nonNull(baseIcon); + myEvaluator = evaluator; + myNeedReadAction = needReadAction; + } + + private static Icon nonNull(final Icon icon) { + return icon != null ? icon : EMPTY_ICON; + } + + public void paintIcon(final Component c, final Graphics g, final int x, final int y) { + myDelegateIcon.paintIcon(c, g, x, y); + + if (!myIsScheduled && !isDone()) { + myIsScheduled = true; + + final Ref target = new Ref(null); + final Ref paintingParent = new Ref(null); + final Ref paintingParentRec = new Ref(null); + + final Container list = SwingUtilities.getAncestorOfClass(JList.class, c); + if (list != null) { + target.set(list); + } + else { + final Container tree = SwingUtilities.getAncestorOfClass(JTree.class, c); + if (tree != null) { + target.set(tree); + } + else { + final Container table = SwingUtilities.getAncestorOfClass(JTable.class, c); + if (table != null) { + target.set(table); + } + else { + final Container box = SwingUtilities.getAncestorOfClass(JComboBox.class, c); + if (box != null) { + target.set(box); + } + else { + target.set(c); + } + } + } + } + + Container pp = SwingUtilities.getAncestorOfClass(PaintingParent.class, c); + paintingParent.set(pp); + if (paintingParent.get() != null) { + paintingParentRec.set(((PaintingParent)pp).getChildRec(c)); + } + + myLastTarget = new WeakReference(target.get()); + + JobUtil.submitToJobThread(new Runnable() { + public void run() { + int oldWidth = myDelegateIcon.getIconWidth(); + myDelegateIcon = evaluate(); + + final boolean shouldRevalidate = myDelegateIcon.getIconWidth() != oldWidth; + + //noinspection SSBasedInspection + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setDone(); + + Component actualTarget = target.get(); + if (SwingUtilities.getWindowAncestor(actualTarget) == null) { + actualTarget = paintingParent.get(); + if (actualTarget == null || SwingUtilities.getWindowAncestor(actualTarget) == null) { + actualTarget = null; + myLastTarget = null; + } + } + + if (actualTarget == null) return; + + myLastTarget = new WeakReference(actualTarget); + + if (shouldRevalidate) { + // revalidate will not work: jtree caches size of nodes + if (actualTarget instanceof JTree) { + final TreeUI ui = ((JTree)actualTarget).getUI(); + if (ui instanceof BasicTreeUI) { + // this call is "fake" and only need to reset tree layout cache + ((BasicTreeUI)ui).setLeftChildIndent(UIUtil.getTreeLeftChildIndent()); + } + } + } + + if (c == actualTarget) { + c.repaint(x, y, getIconWidth(), getIconHeight()); + } + else { + Rectangle rec = null; + if (paintingParentRec.get() != null) { + rec = paintingParentRec.get(); + } + + ourRepaintScheduler.pushDirtyComponent(actualTarget, rec); + } + } + }); + } + }, Job.DEFAULT_PRIORITY); + } + } + + private void setDone() { + if (myDisposer != null) { + myDisposer.dispose(myParam); + } + + myDone = true; + myEvaluator = null; + myParam = null; + } + + public Icon evaluate() { + final Icon[] evaluated = new Icon[1]; + final Runnable runnable = new Runnable() { + public void run() { + try { + evaluated[0] = nonNull(myEvaluator.fun(myParam)); + } + catch (ProcessCanceledException e) { + evaluated[0] = EMPTY_ICON; + } + catch (IndexNotReadyException e) { + evaluated[0] = EMPTY_ICON; + } + } + }; + if (myNeedReadAction) { + IconDeferrerImpl.evaluateDeferredInReadAction(runnable); + } + else { + IconDeferrerImpl.evaluateDeferred(runnable); + } + + checkDoesntReferenceThis(evaluated[0]); + + return evaluated[0]; + } + + private void checkDoesntReferenceThis(final Icon icon) { + if (icon == this) { + throw new IllegalStateException("Loop in icons delegation"); + } + + if (icon instanceof DeferredIconImpl) { + checkDoesntReferenceThis(((DeferredIconImpl)icon).myDelegateIcon); + } + else if (icon instanceof LayeredIcon) { + for (Icon layer : ((LayeredIcon)icon).getAllLayers()) { + checkDoesntReferenceThis(layer); + } + } + else if (icon instanceof RowIcon) { + final RowIcon rowIcon = (RowIcon)icon; + final int count = rowIcon.getIconCount(); + for (int i = 0; i < count; i++) { + checkDoesntReferenceThis(rowIcon.getIcon(i)); + } + } + } + + public int getIconWidth() { + return myDelegateIcon.getIconWidth(); + } + + public int getIconHeight() { + return myDelegateIcon.getIconHeight(); + } + + public boolean isDone() { + return myDone; + } + + private static class RepaintScheduler { + private final Alarm myAlarm = new Alarm(); + private final Set myQueue = new LinkedHashSet(); + + public void pushDirtyComponent(final Component c, final Rectangle rec) { + myAlarm.cancelAllRequests(); + myAlarm.addRequest(new Runnable() { + public void run() { + for (RepaintRequest each : myQueue) { + Rectangle r = each.getRectangle(); + if (r != null) { + each.getComponent().repaint(r.x, r.y, r.width, r.height); + } else { + each.getComponent().repaint(); + } + } + myQueue.clear(); + } + }, 50); + + myQueue.add(new RepaintRequest(c, rec)); + } + } + + private static class RepaintRequest { + private Component myComponent; + private Rectangle myRectangle; + + private RepaintRequest(Component component, Rectangle rectangle) { + myComponent = component; + myRectangle = rectangle; + } + + public Component getComponent() { + return myComponent; + } + + public Rectangle getRectangle() { + return myRectangle; + } + } + + + public DeferredIconImpl setDisposer(IconDisposer disposer) { + myDisposer = disposer; + return this; + } + + public interface IconDisposer { + + void dispose(T key); + + } + +} diff --git a/platform/lang-impl/src/com/intellij/ui/IconDeferrerImpl.java b/platform/lang-impl/src/com/intellij/ui/IconDeferrerImpl.java index b90500d4f5ed..f157d521aa9c 100644 --- a/platform/lang-impl/src/com/intellij/ui/IconDeferrerImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/IconDeferrerImpl.java @@ -1,115 +1,115 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.ui; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.impl.ProjectLifecycleListener; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.events.VFileEvent; -import com.intellij.util.Function; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class IconDeferrerImpl extends IconDeferrer { - private final Object LOCK = new Object(); - private final Map myIconsCache = new HashMap(); - - public IconDeferrerImpl(MessageBus bus) { - final MessageBusConnection connection = bus.connect(); - connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { - public void before(final List events) { - } - - public void after(final List events) { - clear(); - } - }); - connection.subscribe(ProjectLifecycleListener.TOPIC, new ProjectLifecycleListener.Adapter() { - @Override - public void afterProjectClosed(@NotNull Project project) { - clear(); - } - }); - } - - private void clear() { - synchronized (LOCK) { - myIconsCache.clear(); - } - } - - public Icon defer(final Icon base, final T param, final Function f) { - if (myEvaluationIsInProgress.get().booleanValue()) { - return f.fun(param); - } - - synchronized (LOCK) { - Icon result = myIconsCache.get(param); - if (result == null) { - result = new DeferredIconImpl(base, param, f).setDisposer(new DeferredIconImpl.IconDisposer() { - @Override - public void dispose(T key) { - synchronized (LOCK) { - myIconsCache.remove(key); - } - } - }); - myIconsCache.put(param, result); - } - - return result; - } - } - - private static final ThreadLocal myEvaluationIsInProgress = new ThreadLocal() { - @Override - protected Boolean initialValue() { - return Boolean.FALSE; - } - }; - - public static void evaluateDeferredInReadAction(final Runnable runnable) { - try { - myEvaluationIsInProgress.set(Boolean.TRUE); - ApplicationManager.getApplication().runReadAction(runnable); - } - finally { - myEvaluationIsInProgress.set(Boolean.FALSE); - } - } - - public static void evaluateDeferred(final Runnable runnable) { - try { - myEvaluationIsInProgress.set(Boolean.TRUE); - runnable.run(); - } - finally { - myEvaluationIsInProgress.set(Boolean.FALSE); - } - } -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.ui; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.impl.ProjectLifecycleListener; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.util.Function; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class IconDeferrerImpl extends IconDeferrer { + private final Object LOCK = new Object(); + private final Map myIconsCache = new HashMap(); + + public IconDeferrerImpl(MessageBus bus) { + final MessageBusConnection connection = bus.connect(); + connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + public void before(final List events) { + } + + public void after(final List events) { + clear(); + } + }); + connection.subscribe(ProjectLifecycleListener.TOPIC, new ProjectLifecycleListener.Adapter() { + @Override + public void afterProjectClosed(@NotNull Project project) { + clear(); + } + }); + } + + private void clear() { + synchronized (LOCK) { + myIconsCache.clear(); + } + } + + public Icon defer(final Icon base, final T param, final Function f) { + if (myEvaluationIsInProgress.get().booleanValue()) { + return f.fun(param); + } + + synchronized (LOCK) { + Icon result = myIconsCache.get(param); + if (result == null) { + result = new DeferredIconImpl(base, param, f).setDisposer(new DeferredIconImpl.IconDisposer() { + @Override + public void dispose(T key) { + synchronized (LOCK) { + myIconsCache.remove(key); + } + } + }); + myIconsCache.put(param, result); + } + + return result; + } + } + + private static final ThreadLocal myEvaluationIsInProgress = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + + public static void evaluateDeferredInReadAction(final Runnable runnable) { + try { + myEvaluationIsInProgress.set(Boolean.TRUE); + ApplicationManager.getApplication().runReadAction(runnable); + } + finally { + myEvaluationIsInProgress.set(Boolean.FALSE); + } + } + + public static void evaluateDeferred(final Runnable runnable) { + try { + myEvaluationIsInProgress.set(Boolean.TRUE); + runnable.run(); + } + finally { + myEvaluationIsInProgress.set(Boolean.FALSE); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorManagerImpl.java b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorManagerImpl.java index 75bc50ec7b58..2f6866f58737 100644 --- a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorManagerImpl.java @@ -1,230 +1,230 @@ -/* - * 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.ui.tabs; - -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.ui.ColorUtil; -import com.intellij.ui.FileColorManager; -import com.intellij.ui.LightColors; -import com.intellij.util.containers.hash.LinkedHashMap; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; -import java.util.*; -import java.util.List; - -/** - * @author spleaner - * @author Konstantin Bulenkov - */ -@State( - name = "FileColors", - storages = {@Storage(id = "other", file = "$WORKSPACE_FILE$")}) -public class FileColorManagerImpl extends FileColorManager implements PersistentStateComponent { - public static final String FC_ENABLED = "FileColorsEnabled"; - public static final String FC_TABS_ENABLED = "FileColorsForTabsEnabled"; - private final Project myProject; - private final FileColorsModel myModel; - private FileColorSharedConfigurationManager mySharedConfigurationManager; - - private static final Map ourDefaultColors; - - static { - ourDefaultColors = new LinkedHashMap(); - ourDefaultColors.put("Blue", new Color(215, 237, 243)); - ourDefaultColors.put("Green", new Color(228, 241, 209)); - ourDefaultColors.put("Orange", new Color(246, 224, 202)); - ourDefaultColors.put("Rose", new Color(242, 206, 202)); - ourDefaultColors.put("Violet", new Color(222, 213, 241)); - ourDefaultColors.put("Yellow", new Color(247, 241, 203)); - } - - public FileColorManagerImpl(@NotNull final Project project) { - myProject = project; - myModel = new FileColorsModel(project); - } - - private void initSharedConfigurations() { - if (mySharedConfigurationManager == null) { - mySharedConfigurationManager = ServiceManager.getService(myProject, FileColorSharedConfigurationManager.class); - } - } - - public boolean isEnabled() { - return PropertiesComponent.getInstance().getBoolean(FC_ENABLED, true); - } - - public void setEnabled(boolean enabled) { - PropertiesComponent.getInstance().setValue(FC_ENABLED, Boolean.toString(enabled)); - } - - public void setEnabledForTabs(boolean enabled) { - PropertiesComponent.getInstance().setValue(FC_TABS_ENABLED, Boolean.toString(enabled)); - } - - public boolean isEnabledForTabs() { - return PropertiesComponent.getInstance().getBoolean(FC_TABS_ENABLED, true); - } - - public Element getState(final boolean shared) { - Element element = new Element("state"); - //if (!shared) { - // element.setAttribute("enabled", Boolean.toString(myEnabled)); - // element.setAttribute("enabledForTabs", Boolean.toString(myEnabledForTabs)); - //} - - myModel.save(element, shared); - //if (!shared) { - // final boolean exists = findConfigurationByName(NonProjectFilesScope.NAME, myModel.getLocalConfigurations()) != null; - // if (myEnabledForNonProject && !exists) { - // myEnabledForNonProject = false; - // } else if (!myEnabledForNonProject && exists) { - // myEnabledForNonProject = true; - // } - // - // element.setAttribute("showNonProject", Boolean.toString(myEnabledForNonProject)); - //} - - return element; - } - - @SuppressWarnings({"MethodMayBeStatic"}) - @Nullable - public Color getColor(@NotNull final String name) { - final Color color = ourDefaultColors.get(name); - return color == null ? ColorUtil.fromHex(name, null) : color; - } - - public Element getState() { - initSharedConfigurations(); - return getState(false); - } - - @SuppressWarnings({"AutoUnboxing"}) - void loadState(Element state, final boolean shared) { - if (!shared) { - //final String enabled = state.getAttributeValue("enabled"); - //myEnabled = enabled == null ? true : Boolean.valueOf(enabled); - // - //final String enabledForTabs = state.getAttributeValue("enabledForTabs"); - //myEnabledForTabs = enabledForTabs == null ? true : Boolean.valueOf(enabledForTabs); - - //final String showNonProject = state.getAttributeValue("showNonProject"); - //myEnabledForNonProject = showNonProject == null ? true : Boolean.valueOf(showNonProject); - } - - myModel.load(state, shared); - //final List local = myModel.getLocalConfigurations(); - //if (!shared && myEnabledForNonProject && findConfigurationByName(NonProjectFilesScope.NAME, local) == null) { - // local.add(new FileColorConfiguration(NonProjectFilesScope.NAME, NonProjectFilesScope.DEFAULT_COLOR)); - //} - } - - @SuppressWarnings({"MethodMayBeStatic"}) - public Collection getColorNames() { - final Set names = ourDefaultColors.keySet(); - final List sorted = new ArrayList(names); - Collections.sort(sorted); - return sorted; - } - - @SuppressWarnings({"AutoUnboxing"}) - public void loadState(Element state) { - initSharedConfigurations(); - loadState(state, false); - } - - @Override - public boolean isColored(@NotNull final String scopeName, final boolean shared) { - return myModel.isColored(scopeName, shared); - } - - @Nullable - @Override - public Color getRendererBackground(VirtualFile file) { - return getRendererBackground(PsiManager.getInstance(myProject).findFile(file)); - } - - @Nullable - @Override - public Color getRendererBackground(PsiFile file) { - if (file == null) return null; - - if (isEnabled()) { - final Color fileColor = getFileColor(file); - if (fileColor != null) return fileColor; - } - - final VirtualFile vFile = file.getVirtualFile(); - if (vFile == null) return null; - - return FileEditorManager.getInstance(myProject).isFileOpen(vFile) ? LightColors.SLIGHTLY_GREEN : null; - } - - @Nullable - public Color getFileColor(@NotNull final PsiFile file) { - initSharedConfigurations(); - - final String colorName = myModel.getColor(file); - return colorName == null ? null : getColor(colorName); - } - - public boolean isShared(@NotNull final String scopeName) { - return myModel.isShared(scopeName); - } - - FileColorsModel getModel() { - return myModel; - } - - boolean isShared(FileColorConfiguration configuration) { - return myModel.isShared(configuration); - } - - public Project getProject() { - return myProject; - } - - public List getLocalConfigurations() { - return myModel.getLocalConfigurations(); - } - - public List getSharedConfigurations() { - return myModel.getSharedConfigurations(); - } - - @Nullable - private static FileColorConfiguration findConfigurationByName(String name, List configurations) { - for (FileColorConfiguration configuration : configurations) { - if (name.equals(configuration.getScopeName())) { - return configuration; - } - } - return null; - } -} +/* + * 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.ui.tabs; + +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.ui.ColorUtil; +import com.intellij.ui.FileColorManager; +import com.intellij.ui.LightColors; +import com.intellij.util.containers.hash.LinkedHashMap; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * @author spleaner + * @author Konstantin Bulenkov + */ +@State( + name = "FileColors", + storages = {@Storage(id = "other", file = "$WORKSPACE_FILE$")}) +public class FileColorManagerImpl extends FileColorManager implements PersistentStateComponent { + public static final String FC_ENABLED = "FileColorsEnabled"; + public static final String FC_TABS_ENABLED = "FileColorsForTabsEnabled"; + private final Project myProject; + private final FileColorsModel myModel; + private FileColorSharedConfigurationManager mySharedConfigurationManager; + + private static final Map ourDefaultColors; + + static { + ourDefaultColors = new LinkedHashMap(); + ourDefaultColors.put("Blue", new Color(215, 237, 243)); + ourDefaultColors.put("Green", new Color(228, 241, 209)); + ourDefaultColors.put("Orange", new Color(246, 224, 202)); + ourDefaultColors.put("Rose", new Color(242, 206, 202)); + ourDefaultColors.put("Violet", new Color(222, 213, 241)); + ourDefaultColors.put("Yellow", new Color(247, 241, 203)); + } + + public FileColorManagerImpl(@NotNull final Project project) { + myProject = project; + myModel = new FileColorsModel(project); + } + + private void initSharedConfigurations() { + if (mySharedConfigurationManager == null) { + mySharedConfigurationManager = ServiceManager.getService(myProject, FileColorSharedConfigurationManager.class); + } + } + + public boolean isEnabled() { + return PropertiesComponent.getInstance().getBoolean(FC_ENABLED, true); + } + + public void setEnabled(boolean enabled) { + PropertiesComponent.getInstance().setValue(FC_ENABLED, Boolean.toString(enabled)); + } + + public void setEnabledForTabs(boolean enabled) { + PropertiesComponent.getInstance().setValue(FC_TABS_ENABLED, Boolean.toString(enabled)); + } + + public boolean isEnabledForTabs() { + return PropertiesComponent.getInstance().getBoolean(FC_TABS_ENABLED, true); + } + + public Element getState(final boolean shared) { + Element element = new Element("state"); + //if (!shared) { + // element.setAttribute("enabled", Boolean.toString(myEnabled)); + // element.setAttribute("enabledForTabs", Boolean.toString(myEnabledForTabs)); + //} + + myModel.save(element, shared); + //if (!shared) { + // final boolean exists = findConfigurationByName(NonProjectFilesScope.NAME, myModel.getLocalConfigurations()) != null; + // if (myEnabledForNonProject && !exists) { + // myEnabledForNonProject = false; + // } else if (!myEnabledForNonProject && exists) { + // myEnabledForNonProject = true; + // } + // + // element.setAttribute("showNonProject", Boolean.toString(myEnabledForNonProject)); + //} + + return element; + } + + @SuppressWarnings({"MethodMayBeStatic"}) + @Nullable + public Color getColor(@NotNull final String name) { + final Color color = ourDefaultColors.get(name); + return color == null ? ColorUtil.fromHex(name, null) : color; + } + + public Element getState() { + initSharedConfigurations(); + return getState(false); + } + + @SuppressWarnings({"AutoUnboxing"}) + void loadState(Element state, final boolean shared) { + if (!shared) { + //final String enabled = state.getAttributeValue("enabled"); + //myEnabled = enabled == null ? true : Boolean.valueOf(enabled); + // + //final String enabledForTabs = state.getAttributeValue("enabledForTabs"); + //myEnabledForTabs = enabledForTabs == null ? true : Boolean.valueOf(enabledForTabs); + + //final String showNonProject = state.getAttributeValue("showNonProject"); + //myEnabledForNonProject = showNonProject == null ? true : Boolean.valueOf(showNonProject); + } + + myModel.load(state, shared); + //final List local = myModel.getLocalConfigurations(); + //if (!shared && myEnabledForNonProject && findConfigurationByName(NonProjectFilesScope.NAME, local) == null) { + // local.add(new FileColorConfiguration(NonProjectFilesScope.NAME, NonProjectFilesScope.DEFAULT_COLOR)); + //} + } + + @SuppressWarnings({"MethodMayBeStatic"}) + public Collection getColorNames() { + final Set names = ourDefaultColors.keySet(); + final List sorted = new ArrayList(names); + Collections.sort(sorted); + return sorted; + } + + @SuppressWarnings({"AutoUnboxing"}) + public void loadState(Element state) { + initSharedConfigurations(); + loadState(state, false); + } + + @Override + public boolean isColored(@NotNull final String scopeName, final boolean shared) { + return myModel.isColored(scopeName, shared); + } + + @Nullable + @Override + public Color getRendererBackground(VirtualFile file) { + return getRendererBackground(PsiManager.getInstance(myProject).findFile(file)); + } + + @Nullable + @Override + public Color getRendererBackground(PsiFile file) { + if (file == null) return null; + + if (isEnabled()) { + final Color fileColor = getFileColor(file); + if (fileColor != null) return fileColor; + } + + final VirtualFile vFile = file.getVirtualFile(); + if (vFile == null) return null; + + return FileEditorManager.getInstance(myProject).isFileOpen(vFile) ? LightColors.SLIGHTLY_GREEN : null; + } + + @Nullable + public Color getFileColor(@NotNull final PsiFile file) { + initSharedConfigurations(); + + final String colorName = myModel.getColor(file); + return colorName == null ? null : getColor(colorName); + } + + public boolean isShared(@NotNull final String scopeName) { + return myModel.isShared(scopeName); + } + + FileColorsModel getModel() { + return myModel; + } + + boolean isShared(FileColorConfiguration configuration) { + return myModel.isShared(configuration); + } + + public Project getProject() { + return myProject; + } + + public List getLocalConfigurations() { + return myModel.getLocalConfigurations(); + } + + public List getSharedConfigurations() { + return myModel.getSharedConfigurations(); + } + + @Nullable + private static FileColorConfiguration findConfigurationByName(String name, List configurations) { + for (FileColorConfiguration configuration : configurations) { + if (name.equals(configuration.getScopeName())) { + return configuration; + } + } + return null; + } +} diff --git a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java index ce92cc45b048..66ae80271e0a 100644 --- a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java +++ b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java @@ -1,300 +1,300 @@ -/* - * 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.ui.tabs; - -import com.intellij.ui.FileColorManager; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.openapi.ui.StripeTable; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; - -/** - * @author spleaner - */ -public abstract class FileColorSettingsTable extends StripeTable { - private static final int NAME_COLUMN = 0; - private static final int COLOR_COLUMN = 1; - - private final List myOriginal; - - public FileColorSettingsTable(@NotNull final FileColorManager manager, @NotNull final List configurations) { - super(new ModelAdapter(copy(configurations))); - - myOriginal = configurations; - - setAutoResizeMode(AUTO_RESIZE_LAST_COLUMN); - - final TableColumnModel columnModel = getColumnModel(); - final TableColumn nameColumn = columnModel.getColumn(NAME_COLUMN); - nameColumn.setCellRenderer(new ScopeNameRenderer()); - - final TableColumn colorColumn = columnModel.getColumn(COLOR_COLUMN); - colorColumn.setCellRenderer(new ColorCellRenderer(manager)); - } - - private static List copy(@NotNull final List configurations) { - final List result = new ArrayList(); - for (FileColorConfiguration c : configurations) { - try { - result.add(c.clone()); - } - catch (CloneNotSupportedException e) { - assert false : "Should not happen!"; - } - } - - return result; - } - - protected abstract void apply(@NotNull final List configurations); - - public ModelAdapter getModel() { - return (ModelAdapter) super.getModel(); - } - - public boolean isModified() { - final List current = getModel().getConfigurations(); - - if (myOriginal.size() != current.size()) { - return true; - } - - for (int i = 0; i < current.size(); i++) { - if (!myOriginal.get(i).equals(current.get(i))) { - return true; - } - } - - return false; - } - - public void reset() { - getModel().setConfigurations(myOriginal); - } - - public void performRemove() { - final int rowCount = getSelectedRowCount(); - if (rowCount > 0) { - final int[] rows = getSelectedRows(); - for (int i = rows.length - 1; i >= 0; i--) { - removeConfiguration(rows[i]); - } - } - } - - public void moveUp() { - final int rowCount = getSelectedRowCount(); - if (rowCount == 1) { - final int index = getModel().moveUp(getSelectedRows()[0]); - if (index > -1) { - getSelectionModel().setSelectionInterval(index, index); - } - } - } - - public void moveDown() { - final int rowCount = getSelectedRowCount(); - if (rowCount == 1) { - final int index = getModel().moveDown(getSelectedRows()[0]); - if (index > -1) { - getSelectionModel().setSelectionInterval(index, index); - } - } - } - - public void apply() { - if (isModified()) { - apply(getModel().getConfigurations()); - } - } - - public FileColorConfiguration removeConfiguration(final int index) { - final FileColorConfiguration removed = getModel().remove(index); - - final int rowCount = getRowCount(); - if (rowCount > 0) { - if (index > rowCount - 1) { - getSelectionModel().setSelectionInterval(rowCount - 1, rowCount - 1); - } else { - getSelectionModel().setSelectionInterval(index, index); - } - } - - return removed; - } - - public void addConfiguration(@NotNull final FileColorConfiguration configuration) { - getModel().add(configuration); - } - - private static class ModelAdapter extends AbstractTableModel { - private List myConfigurations; - - private ModelAdapter(final List configurations) { - myConfigurations = configurations; - } - - @Override - public String getColumnName(int column) { - return column == NAME_COLUMN ? "Scope" : "Color"; - } - - public int getRowCount() { - return myConfigurations.size(); - } - - public int getColumnCount() { - return 2; - } - - public Object getValueAt(int rowIndex, int columnIndex) { - return myConfigurations.get(rowIndex); - } - - @NotNull - public List getConfigurations() { - return myConfigurations; - } - - public FileColorConfiguration remove(int index) { - final FileColorConfiguration removed = myConfigurations.remove(index); - fireTableRowsDeleted(index, index); - return removed; - } - - public void add(@NotNull final FileColorConfiguration configuration) { - myConfigurations.add(configuration); - fireTableRowsInserted(myConfigurations.size() - 1, myConfigurations.size() - 1); - } - - public void setConfigurations(List original) { - myConfigurations = copy(original); - fireTableDataChanged(); - } - - public int moveUp(int index) { - if (index > 0) { - final FileColorConfiguration configuration = myConfigurations.get(index); - myConfigurations.remove(index); - myConfigurations.add(index - 1, configuration); - fireTableRowsUpdated(index - 1, index); - return index - 1; - } - - return -1; - } - - public int moveDown(int index) { - if (index < getRowCount() - 1) { - final FileColorConfiguration configuration = myConfigurations.get(index); - myConfigurations.remove(index); - myConfigurations.add(index + 1, configuration); - fireTableRowsUpdated(index, index + 1); - return index + 1; - } - - return -1; - } - } - - private static class ScopeNameRenderer extends JLabel implements TableCellRenderer { - private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); - - private ScopeNameRenderer() { - setOpaque(true); - } - - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (!(value instanceof FileColorConfiguration)) { - return this; - } - - preinit(table, isSelected, hasFocus); - - final FileColorConfiguration configuration = (FileColorConfiguration)value; - setText(configuration.getScopeName()); - return this; - } - - protected void preinit(JTable table, boolean isSelected, boolean hasFocus) { - setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); - - setBorder(hasFocus ? UIUtil.getTableFocusCellHighlightBorder() - : NO_FOCUS_BORDER); - } - } - - private static class ColorCellRenderer extends ScopeNameRenderer { - private Color myColor; - private final FileColorManager myManager; - - private ColorCellRenderer(final FileColorManager manager) { - setOpaque(true); - - myManager = manager; - - setIcon(new EmptyIcon(16)); - } - - private void setIconColor(final Color color) { - myColor = color; - } - - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (!(value instanceof FileColorConfiguration)) { - return this; - } - - preinit(table, isSelected, hasFocus); - - final FileColorConfiguration configuration = (FileColorConfiguration)value; - setIconColor(myManager.getColor(configuration.getColorName())); - setText(configuration.getColorPresentableName()); - - return this; - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - if (myColor != null) { - final Icon icon = getIcon(); - final int width = icon.getIconWidth(); - final int height = icon.getIconHeight(); - - final Color old = g.getColor(); - - g.setColor(myColor); - g.fillRect(0, 0, width, height); - - g.setColor(old); - } - } - } -} +/* + * 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.ui.tabs; + +import com.intellij.ui.FileColorManager; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.openapi.ui.StripeTable; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author spleaner + */ +public abstract class FileColorSettingsTable extends StripeTable { + private static final int NAME_COLUMN = 0; + private static final int COLOR_COLUMN = 1; + + private final List myOriginal; + + public FileColorSettingsTable(@NotNull final FileColorManager manager, @NotNull final List configurations) { + super(new ModelAdapter(copy(configurations))); + + myOriginal = configurations; + + setAutoResizeMode(AUTO_RESIZE_LAST_COLUMN); + + final TableColumnModel columnModel = getColumnModel(); + final TableColumn nameColumn = columnModel.getColumn(NAME_COLUMN); + nameColumn.setCellRenderer(new ScopeNameRenderer()); + + final TableColumn colorColumn = columnModel.getColumn(COLOR_COLUMN); + colorColumn.setCellRenderer(new ColorCellRenderer(manager)); + } + + private static List copy(@NotNull final List configurations) { + final List result = new ArrayList(); + for (FileColorConfiguration c : configurations) { + try { + result.add(c.clone()); + } + catch (CloneNotSupportedException e) { + assert false : "Should not happen!"; + } + } + + return result; + } + + protected abstract void apply(@NotNull final List configurations); + + public ModelAdapter getModel() { + return (ModelAdapter) super.getModel(); + } + + public boolean isModified() { + final List current = getModel().getConfigurations(); + + if (myOriginal.size() != current.size()) { + return true; + } + + for (int i = 0; i < current.size(); i++) { + if (!myOriginal.get(i).equals(current.get(i))) { + return true; + } + } + + return false; + } + + public void reset() { + getModel().setConfigurations(myOriginal); + } + + public void performRemove() { + final int rowCount = getSelectedRowCount(); + if (rowCount > 0) { + final int[] rows = getSelectedRows(); + for (int i = rows.length - 1; i >= 0; i--) { + removeConfiguration(rows[i]); + } + } + } + + public void moveUp() { + final int rowCount = getSelectedRowCount(); + if (rowCount == 1) { + final int index = getModel().moveUp(getSelectedRows()[0]); + if (index > -1) { + getSelectionModel().setSelectionInterval(index, index); + } + } + } + + public void moveDown() { + final int rowCount = getSelectedRowCount(); + if (rowCount == 1) { + final int index = getModel().moveDown(getSelectedRows()[0]); + if (index > -1) { + getSelectionModel().setSelectionInterval(index, index); + } + } + } + + public void apply() { + if (isModified()) { + apply(getModel().getConfigurations()); + } + } + + public FileColorConfiguration removeConfiguration(final int index) { + final FileColorConfiguration removed = getModel().remove(index); + + final int rowCount = getRowCount(); + if (rowCount > 0) { + if (index > rowCount - 1) { + getSelectionModel().setSelectionInterval(rowCount - 1, rowCount - 1); + } else { + getSelectionModel().setSelectionInterval(index, index); + } + } + + return removed; + } + + public void addConfiguration(@NotNull final FileColorConfiguration configuration) { + getModel().add(configuration); + } + + private static class ModelAdapter extends AbstractTableModel { + private List myConfigurations; + + private ModelAdapter(final List configurations) { + myConfigurations = configurations; + } + + @Override + public String getColumnName(int column) { + return column == NAME_COLUMN ? "Scope" : "Color"; + } + + public int getRowCount() { + return myConfigurations.size(); + } + + public int getColumnCount() { + return 2; + } + + public Object getValueAt(int rowIndex, int columnIndex) { + return myConfigurations.get(rowIndex); + } + + @NotNull + public List getConfigurations() { + return myConfigurations; + } + + public FileColorConfiguration remove(int index) { + final FileColorConfiguration removed = myConfigurations.remove(index); + fireTableRowsDeleted(index, index); + return removed; + } + + public void add(@NotNull final FileColorConfiguration configuration) { + myConfigurations.add(configuration); + fireTableRowsInserted(myConfigurations.size() - 1, myConfigurations.size() - 1); + } + + public void setConfigurations(List original) { + myConfigurations = copy(original); + fireTableDataChanged(); + } + + public int moveUp(int index) { + if (index > 0) { + final FileColorConfiguration configuration = myConfigurations.get(index); + myConfigurations.remove(index); + myConfigurations.add(index - 1, configuration); + fireTableRowsUpdated(index - 1, index); + return index - 1; + } + + return -1; + } + + public int moveDown(int index) { + if (index < getRowCount() - 1) { + final FileColorConfiguration configuration = myConfigurations.get(index); + myConfigurations.remove(index); + myConfigurations.add(index + 1, configuration); + fireTableRowsUpdated(index, index + 1); + return index + 1; + } + + return -1; + } + } + + private static class ScopeNameRenderer extends JLabel implements TableCellRenderer { + private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); + + private ScopeNameRenderer() { + setOpaque(true); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (!(value instanceof FileColorConfiguration)) { + return this; + } + + preinit(table, isSelected, hasFocus); + + final FileColorConfiguration configuration = (FileColorConfiguration)value; + setText(configuration.getScopeName()); + return this; + } + + protected void preinit(JTable table, boolean isSelected, boolean hasFocus) { + setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); + setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); + + setBorder(hasFocus ? UIUtil.getTableFocusCellHighlightBorder() + : NO_FOCUS_BORDER); + } + } + + private static class ColorCellRenderer extends ScopeNameRenderer { + private Color myColor; + private final FileColorManager myManager; + + private ColorCellRenderer(final FileColorManager manager) { + setOpaque(true); + + myManager = manager; + + setIcon(new EmptyIcon(16)); + } + + private void setIconColor(final Color color) { + myColor = color; + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (!(value instanceof FileColorConfiguration)) { + return this; + } + + preinit(table, isSelected, hasFocus); + + final FileColorConfiguration configuration = (FileColorConfiguration)value; + setIconColor(myManager.getColor(configuration.getColorName())); + setText(configuration.getColorPresentableName()); + + return this; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + if (myColor != null) { + final Icon icon = getIcon(); + final int width = icon.getIconWidth(); + final int height = icon.getIconHeight(); + + final Color old = g.getColor(); + + g.setColor(myColor); + g.fillRect(0, 0, width, height); + + g.setColor(old); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index ffe699d8456b..ee72101f67c8 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -1,1984 +1,1984 @@ -/* - * 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.util.indexing; - -import com.intellij.AppTopics; -import com.intellij.concurrency.JobScheduler; -import com.intellij.history.LocalHistory; -import com.intellij.ide.caches.CacheUpdater; -import com.intellij.lang.ASTNode; -import com.intellij.openapi.application.ApplicationAdapter; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; -import com.intellij.openapi.fileTypes.*; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.*; -import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; -import com.intellij.openapi.project.*; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; -import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.ManagingFS; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.events.VFileEvent; -import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; -import com.intellij.psi.*; -import com.intellij.psi.impl.PsiDocumentTransactionListener; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.search.EverythingGlobalScope; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.util.ArrayUtil; -import com.intellij.util.CommonProcessors; -import com.intellij.util.Processor; -import com.intellij.util.concurrency.Semaphore; -import com.intellij.util.containers.ConcurrentHashSet; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.containers.HashSet; -import com.intellij.util.io.*; -import com.intellij.util.io.storage.HeavyProcessLatch; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.TIntHashSet; -import gnu.trove.TIntIterator; -import gnu.trove.TObjectIntHashMap; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.*; -import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; - -/** - * @author Eugene Zhuravlev - * Date: Dec 20, 2007 - */ - -public class FileBasedIndex implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndex"); - @NonNls - private static final String CORRUPTION_MARKER_NAME = "corruption.marker"; - private final Map, Pair, InputFilter>> myIndices = new HashMap, Pair, InputFilter>>(); - private final Map, Semaphore> myUnsavedDataIndexingSemaphores = new HashMap, Semaphore>(); - private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); - private final Set> myNotRequiringContentIndices = new HashSet>(); - private final Set myNoLimitCheckTypes = new HashSet(); - - private final PerIndexDocumentMap myLastIndexedDocStamps = new PerIndexDocumentMap() { - @Override - protected Long createDefault(Document document) { - return 0L; - } - }; - - private final ChangedFilesCollector myChangedFilesCollector; - - private final List myIndexableSets = ContainerUtil.createEmptyCOWList(); - private final Map myIndexableSetToProjectMap = new HashMap(); - - private static final int OK = 1; - private static final int REQUIRES_REBUILD = 2; - private static final int REBUILD_IN_PROGRESS = 3; - private static final Map, AtomicInteger> ourRebuildStatus = new HashMap, AtomicInteger>(); - - private final VirtualFileManagerEx myVfManager; - private final FileDocumentManager myFileDocumentManager; - private final ConcurrentHashSet> myUpToDateIndices = new ConcurrentHashSet>(); - private final Map myTransactionMap = new HashMap(); - - private static final int ALREADY_PROCESSED = 0x02; - @Nullable private final String myConfigPath; - @Nullable private final String mySystemPath; - private final boolean myIsUnitTestMode; - private ScheduledFuture myFlushingFuture; - private volatile int myLocalModCount; - - public void requestReindex(final VirtualFile file) { - myChangedFilesCollector.invalidateIndices(file, true); - } - - public interface InputFilter { - boolean acceptInput(VirtualFile file); - } - - public FileBasedIndex(final VirtualFileManagerEx vfManager, FileDocumentManager fdm, MessageBus bus) throws IOException { - myVfManager = vfManager; - myFileDocumentManager = fdm; - myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); - myConfigPath = calcConfigPath(PathManager.getConfigPath()); - mySystemPath = calcConfigPath(PathManager.getSystemPath()); - - final MessageBusConnection connection = bus.connect(); - connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() { - public void transactionStarted(final Document doc, final PsiFile file) { - if (file != null) { - myTransactionMap.put(doc, file); - myUpToDateIndices.clear(); - } - } - - public void transactionCompleted(final Document doc, final PsiFile file) { - myTransactionMap.remove(doc); - } - }); - - connection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() { - private Map> myTypeToExtensionMap; - public void beforeFileTypesChanged(final FileTypeEvent event) { - cleanupProcessedFlag(); - myTypeToExtensionMap = new HashMap>(); - final FileTypeManager manager = event.getManager(); - for (FileType type : manager.getRegisteredFileTypes()) { - myTypeToExtensionMap.put(type, getExtensions(manager, type)); - } - } - - public void fileTypesChanged(final FileTypeEvent event) { - final Map> oldExtensions = myTypeToExtensionMap; - myTypeToExtensionMap = null; - if (oldExtensions != null) { - final FileTypeManager manager = event.getManager(); - final Map> newExtensions = new HashMap>(); - for (FileType type : manager.getRegisteredFileTypes()) { - newExtensions.put(type, getExtensions(manager, type)); - } - // we are interested only in extension changes or removals. - // addition of an extension is handled separately by RootsChanged event - if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) { - rebuildAllndices(); - return; - } - for (Map.Entry> entry : oldExtensions.entrySet()) { - FileType fileType = entry.getKey(); - Set strings = entry.getValue(); - if (!newExtensions.get(fileType).containsAll(strings)) { - rebuildAllndices(); - return; - } - } - } - } - - private Set getExtensions(FileTypeManager manager, FileType type) { - final Set set = new HashSet(); - for (FileNameMatcher matcher : manager.getAssociations(type)) { - set.add(matcher.getPresentableString()); - } - return set; - } - - private void rebuildAllndices() { - for (ID indexId : myIndices.keySet()) { - try { - clearIndex(indexId); - } - catch (StorageException e) { - LOG.info(e); - } - } - scheduleIndexRebuild(true); - } - }); - - connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { - public void before(List events) { - for (VFileEvent event : events) { - final Object requestor = event.getRequestor(); - if (requestor instanceof FileDocumentManager || requestor instanceof PsiManager || requestor == LocalHistory.VFS_EVENT_REQUESTOR) { - cleanupMemoryStorage(); - break; - } - } - } - - public void after(List events) { - } - }); - - connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { - public void fileContentReloaded(VirtualFile file, Document document) { - cleanupMemoryStorage(); - } - - public void unsavedDocumentsDropped() { - cleanupMemoryStorage(); - } - }); - - ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() { - public void writeActionStarted(Object action) { - myUpToDateIndices.clear(); - } - }); - - myChangedFilesCollector = new ChangedFilesCollector(); - - /* - final File workInProgressFile = getMarkerFile(); - if (workInProgressFile.exists()) { - // previous IDEA session was closed incorrectly, so drop all indices - FileUtil.delete(PathManager.getIndexRoot()); - } - */ - - try { - final FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME); - for (FileBasedIndexExtension extension : extensions) { - ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK)); - } - - final File corruptionMarker = new File(PathManager.getIndexRoot(), CORRUPTION_MARKER_NAME); - final boolean currentVersionCorrupted = corruptionMarker.exists(); - for (FileBasedIndexExtension extension : extensions) { - registerIndexer(extension, currentVersionCorrupted); - } - FileUtil.delete(corruptionMarker); - dropUnregisteredIndices(); - - // check if rebuild was requested for any index during registration - for (ID indexId : myIndices.keySet()) { - if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) { - try { - clearIndex(indexId); - } - catch (StorageException e) { - requestRebuild(indexId); - LOG.error(e); - } - } - } - - myVfManager.addVirtualFileListener(myChangedFilesCollector); - - registerIndexableSet(new AdditionalIndexableFileSet(), null); - } - finally { - ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { - public void run() { - performShutdown(); - } - }); - //FileUtil.createIfDoesntExist(workInProgressFile); - saveRegisteredIndices(myIndices.keySet()); - myFlushingFuture = JobScheduler.getScheduler().scheduleAtFixedRate(new Runnable() { - int lastModCount = 0; - public void run() { - if (lastModCount == myLocalModCount && !HeavyProcessLatch.INSTANCE.isRunning()) { - flushAllIndices(); - } - lastModCount = myLocalModCount; - } - }, 5000, 5000, TimeUnit.MILLISECONDS); - - } - } - - public void initComponent() { - } - - private static String calcConfigPath(final String path) { - try { - final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath()); - return _path.endsWith("/")? _path : _path + "/" ; - } - catch (IOException e) { - LOG.info(e); - return null; - } - } - - private static class FileBasedIndexHolder { - private static final FileBasedIndex ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class); - } - - public static FileBasedIndex getInstance() { - return FileBasedIndexHolder.ourInstance; - } - - /** - * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted @param extension - * @param isCurrentVersionCorrupted - */ - private void registerIndexer(final FileBasedIndexExtension extension, final boolean isCurrentVersionCorrupted) throws IOException { - final ID name = extension.getName(); - final int version = extension.getVersion(); - if (!extension.dependsOnFileContent()) { - myNotRequiringContentIndices.add(name); - } - myIndexIdToVersionMap.put(name, version); - final File versionFile = IndexInfrastructure.getVersionFile(name); - if (isCurrentVersionCorrupted || IndexInfrastructure.versionDiffers(versionFile, version)) { - if (!isCurrentVersionCorrupted) { - LOG.info("Version has changed for index " + extension.getName() + ". The index will be rebuilt."); - } - FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); - IndexInfrastructure.rewriteVersion(versionFile, version); - } - - for (int attempt = 0; attempt < 2; attempt++) { - try { - final MapIndexStorage storage = new MapIndexStorage(IndexInfrastructure.getStorageFile(name), extension.getKeyDescriptor(), extension.getValueExternalizer(), extension.getCacheSize()); - final MemoryIndexStorage memStorage = new MemoryIndexStorage(storage); - final UpdatableIndex index = createIndex(name, extension, memStorage); - myIndices.put(name, new Pair, InputFilter>(index, new IndexableFilesFilter(extension.getInputFilter()))); - myUnsavedDataIndexingSemaphores.put(name, new Semaphore()); - myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable()); - break; - } - catch (IOException e) { - LOG.info(e); - FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); - IndexInfrastructure.rewriteVersion(versionFile, version); - } - } - } - - private static void saveRegisteredIndices(Collection> ids) { - final File file = getRegisteredIndicesFile(); - try { - FileUtil.createIfDoesntExist(file); - final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); - try { - os.writeInt(ids.size()); - for (ID id : ids) { - IOUtil.writeString(id.toString(), os); - } - } - finally { - os.close(); - } - } - catch (IOException ignored) { - } - } - - private static Set readRegistsredIndexNames() { - final Set result = new HashSet(); - try { - final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile()))); - try { - final int size = in.readInt(); - for (int idx = 0; idx < size; idx++) { - result.add(IOUtil.readString(in)); - } - } - finally { - in.close(); - } - } - catch (IOException ignored) { - } - return result; - } - - private static File getRegisteredIndicesFile() { - return new File(PathManager.getIndexRoot(), "registered"); - } - - private UpdatableIndex createIndex(final ID indexId, final FileBasedIndexExtension extension, final MemoryIndexStorage storage) throws IOException { - final MapReduceIndex index; - if (extension instanceof CustomImplementationFileBasedIndexExtension) { - final UpdatableIndex custom = - ((CustomImplementationFileBasedIndexExtension)extension).createIndexImplementation(indexId, this, storage); - if (!(custom instanceof MapReduceIndex)) { - return custom; - } - index = (MapReduceIndex)custom; - } - else { - index = new MapReduceIndex(indexId, extension.getIndexer(), storage); - } - - final KeyDescriptor keyDescriptor = extension.getKeyDescriptor(); - index.setInputIdToDataKeysIndex(new Factory>>() { - public PersistentHashMap> create() { - try { - return createIdToDataKeysIndex(indexId, keyDescriptor, storage); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - - return index; - } - - private static PersistentHashMap> createIdToDataKeysIndex(final ID indexId, - final KeyDescriptor keyDescriptor, - MemoryIndexStorage storage) throws IOException { - final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId); - final Ref isBufferingMode = new Ref(false); - final Map> tempMap = new HashMap>(); - - final DataExternalizer> dataExternalizer = new DataExternalizer>() { - public void save(DataOutput out, Collection value) throws IOException { - try { - DataInputOutputUtil.writeINT(out, value.size()); - for (K key : value) { - keyDescriptor.save(out, key); - } - } - catch (IOException e) { - throw e; - } - catch (IllegalArgumentException e) { - throw new IOException("Error saving data for index " + indexId, e); - } - } - - public Collection read(DataInput in) throws IOException { - try { - final int size = DataInputOutputUtil.readINT(in); - final List list = new ArrayList(); - for (int idx = 0; idx < size; idx++) { - list.add(keyDescriptor.read(in)); - } - return list; - } - catch (IOException e) { - throw e; - } - catch (IllegalArgumentException e) { - throw new IOException("Error reading data for index " + indexId, e); - } - } - }; - - // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage. - // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage). - // Otherwise data in IdToDataKeysIndex will not be in sync with the 'main' data in the index on disk and index updates will be based on the - // wrong sets of keys for the given file. This will lead to unpretictable results in main index because it will not be - // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects. - - final PersistentHashMap> map = new PersistentHashMap>( - indexStorageFile, new EnumeratorIntegerDescriptor(), dataExternalizer - ) { - - @Override - public synchronized Collection get(Integer integer) throws IOException { - if (isBufferingMode.get()) { - final Collection collection = tempMap.get(integer); - if (collection != null) { - return collection; - } - } - return super.get(integer); - } - - @Override - public synchronized void put(Integer integer, Collection ks) throws IOException { - if (isBufferingMode.get()) { - tempMap.put(integer, ks == null? Collections.emptySet() : ks); - } - else { - super.put(integer, ks); - } - } - - @Override - public synchronized void remove(Integer integer) throws IOException { - if (isBufferingMode.get()) { - tempMap.put(integer, Collections.emptySet()); - } - else { - super.remove(integer); - } - } - }; - - storage.addBufferingStateListsner(new MemoryIndexStorage.BufferingStateListener() { - public void bufferingStateChanged(boolean newState) { - synchronized (map) { - isBufferingMode.set(newState); - } - } - public void memoryStorageCleared() { - synchronized (map) { - tempMap.clear(); - } - } - }); - return map; - } - - @NonNls - @NotNull - public String getComponentName() { - return "FileBasedIndex"; - } - - public void disposeComponent() { - performShutdown(); - } - - private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false); - - private void performShutdown() { - if (!myShutdownPerformed.compareAndSet(false, true)) { - return; // already shut down - } - try { - if (myFlushingFuture != null) { - myFlushingFuture.cancel(false); - myFlushingFuture = null; - } - - myFileDocumentManager.saveAllDocuments(); - } - finally { - LOG.info("START INDEX SHUTDOWN"); - try { - myChangedFilesCollector.forceUpdate(null, null, true); - - for (ID indexId : myIndices.keySet()) { - final UpdatableIndex index = getIndex(indexId); - assert index != null; - checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it - //LOG.info("DISPOSING " + indexId); - index.dispose(); - } - - myVfManager.removeVirtualFileListener(myChangedFilesCollector); - - //FileUtil.delete(getMarkerFile()); - } - catch (Throwable e) { - LOG.info("Problems during index shutdown", e); - throw new RuntimeException(e); - } - LOG.info("END INDEX SHUTDOWN"); - } - } - - private void flushAllIndices() { - IndexingStamp.flushCache(); - for (ID indexId : new ArrayList>(myIndices.keySet())) { - if (HeavyProcessLatch.INSTANCE.isRunning()) { - return; - } - try { - final UpdatableIndex index = getIndex(indexId); - if (index != null) { - index.flush(); - } - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - } - - /** - * @param project it is guaranteeed to return data which is up-to-date withing the project - * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist - */ - @NotNull - public Collection getAllKeys(final ID indexId, @NotNull Project project) { - Set allKeys = new HashSet(); - processAllKeys(indexId, new CommonProcessors.CollectProcessor(allKeys), project); - return allKeys; - } - - /** - * @param project it is guaranteeed to return data which is up-to-date withing the project - * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist - */ - public boolean processAllKeys(final ID indexId, Processor processor, @Nullable Project project) { - try { - final UpdatableIndex index = getIndex(indexId); - if (index == null) { - return true; - } - ensureUpToDate(indexId, project, project != null? GlobalSearchScope.allScope(project) : new EverythingGlobalScope()); - return index.processAllKeys(processor); - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, cause); - } - else { - throw e; - } - } - - return false; - } - - private static final ThreadLocal myUpToDateCheckState = new ThreadLocal(); - - public static void disableUpToDateCheckForCurrentThread() { - final Integer currentValue = myUpToDateCheckState.get(); - myUpToDateCheckState.set(currentValue == null? 1 : currentValue.intValue() + 1); - } - - public static void enableUpToDateCheckForCurrentThread() { - final Integer currentValue = myUpToDateCheckState.get(); - if (currentValue != null) { - final int newValue = currentValue.intValue() - 1; - if (newValue != 0) { - myUpToDateCheckState.set(newValue); - } - else { - myUpToDateCheckState.remove(); - } - } - } - - private static boolean isUpToDateCheckEnabled() { - final Integer value = myUpToDateCheckState.get(); - return value == null || value.intValue() == 0; - } - - - private final ThreadLocal myReentrancyGuard = new ThreadLocal() { - protected Boolean initialValue() { - return Boolean.FALSE; - } - }; - - /** - * DO NOT CALL DIRECTLY IN CLIENT CODE - * The method is internal to indexing engine end is called internally. The method is public due to implementation details - */ - public void ensureUpToDate(final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) { - if (!needsFileContentLoading(indexId)) { - return; //indexed eagerly in foreground while building unindexed file list - } - if (isDumb(project)) { - handleDumbMode(project); - } - - if (myReentrancyGuard.get().booleanValue()) { - //assert false : "ensureUpToDate() is not reentrant!"; - return; - } - myReentrancyGuard.set(Boolean.TRUE); - - try { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - if (isUpToDateCheckEnabled()) { - try { - checkRebuild(indexId, false); - myChangedFilesCollector.forceUpdate(project, filter, false); - indexUnsavedDocuments(indexId, project, filter); - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, e); - } - else { - throw e; - } - } - } - } - finally { - myReentrancyGuard.set(Boolean.FALSE); - } - } - - private static void handleDumbMode(@Nullable Project project) { - ProgressManager.checkCanceled(); // DumbModeAction.CANCEL - - if (project != null) { - final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - if (progressIndicator instanceof BackgroundableProcessIndicator) { - final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator; - if (indicator.getDumbModeAction() == DumbModeAction.WAIT) { - assert !ApplicationManager.getApplication().isDispatchThread(); - DumbService.getInstance(project).waitForSmartMode(); - return; - } - } - } - - throw new IndexNotReadyException(); - } - - private static boolean isDumb(@Nullable Project project) { - if (project != null) { - return DumbServiceImpl.getInstance(project).isDumb(); - } - for (Project proj : ProjectManager.getInstance().getOpenProjects()) { - if (DumbServiceImpl.getInstance(proj).isDumb()) { - return true; - } - } - return false; - } - - @NotNull - public List getValues(final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { - final List values = new ArrayList(); - processValuesImpl(indexId, dataKey, true, null, new ValueProcessor() { - public boolean process(final VirtualFile file, final V value) { - values.add(value); - return true; - } - }, filter); - return values; - } - - @NotNull - public Collection getContainingFiles(final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { - final Set files = new HashSet(); - processValuesImpl(indexId, dataKey, false, null, new ValueProcessor() { - public boolean process(final VirtualFile file, final V value) { - files.add(file); - return true; - } - }, filter); - return files; - } - - - public interface ValueProcessor { - /** - * @param value a value to process - * @param file the file the value came from - * @return false if no further processing is needed, true otherwise - */ - boolean process(VirtualFile file, V value); - } - - /** - * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all - */ - public boolean processValues(final ID indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile, - ValueProcessor processor, @NotNull final GlobalSearchScope filter) { - return processValuesImpl(indexId, dataKey, false, inFile, processor, filter); - } - - - - private boolean processValuesImpl(final ID indexId, final K dataKey, boolean ensureValueProcessedOnce, - @Nullable final VirtualFile restrictToFile, ValueProcessor processor, - final GlobalSearchScope filter) { - try { - final UpdatableIndex index = getIndex(indexId); - if (index == null) { - return true; - } - final Project project = filter.getProject(); - //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; - ensureUpToDate(indexId, project, filter); - - final Lock readLock = index.getReadLock(); - try { - readLock.lock(); - final ValueContainer container = index.getData(dataKey); - - boolean shouldContinue = true; - - if (restrictToFile != null) { - if (restrictToFile instanceof VirtualFileWithId) { - final int restrictedFileId = getFileId(restrictToFile); - for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { - final V value = valueIt.next(); - if (container.isAssociated(value, restrictedFileId)) { - shouldContinue = processor.process(restrictToFile, value); - if (!shouldContinue) { - break; - } - } - } - } - } - else { - final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); - VALUES_LOOP: for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { - final V value = valueIt.next(); - for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { - final int id = inputIdsIterator.next(); - VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); - if (file != null && filter.accept(file)) { - shouldContinue = processor.process(file, value); - if (!shouldContinue) { - break VALUES_LOOP; - } - if (ensureValueProcessedOnce) { - break; // continue with the next value - } - } - } - } - } - return shouldContinue; - } - finally { - index.getReadLock().unlock(); - } - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, cause); - } - else { - throw e; - } - } - return true; - } - - public boolean getFilesWithKey(final ID indexId, final Set dataKeys, Processor processor, GlobalSearchScope filter) { - try { - final UpdatableIndex index = getIndex(indexId); - if (index == null) { - return true; - } - final Project project = filter.getProject(); - //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; - ensureUpToDate(indexId, project, filter); - - final Lock readLock = index.getReadLock(); - try { - readLock.lock(); - List locals = new ArrayList(); - for (K dataKey : dataKeys) { - TIntHashSet local = new TIntHashSet(); - locals.add(local); - final ValueContainer container = index.getData(dataKey); - - for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { - final V value = valueIt.next(); - for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { - final int id = inputIdsIterator.next(); - local.add(id); - } - } - } - - if (locals.isEmpty()) return true; - - Collections.sort(locals, new Comparator() { - public int compare(TIntHashSet o1, TIntHashSet o2) { - return o1.size() - o2.size(); - } - }); - - final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); - TIntIterator ids = join(locals).iterator(); - while (ids.hasNext()) { - int id = ids.next(); - //VirtualFile file = IndexInfrastructure.findFileById(fs, id); - VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); - if (file != null && filter.accept(file)) { - if (!processor.process(file)) return false; - } - } - } - finally { - index.getReadLock().unlock(); - } - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, cause); - } - else { - throw e; - } - } - return true; - } - - private static TIntHashSet join(List locals) { - TIntHashSet result = locals.get(0); - if (locals.size() > 1) { - TIntIterator it = result.iterator(); - - while (it.hasNext()) { - int id = it.next(); - for (int i = 1; i < locals.size(); i++) { - if (!locals.get(i).contains(id)) { - it.remove(); - break; - } - } - } - } - return result; - } - - public void scheduleRebuild(final ID indexId, final Throwable e) { - LOG.info(e); - requestRebuild(indexId); - checkRebuild(indexId, false); - } - - private void checkRebuild(final ID indexId, final boolean cleanupOnly) { - final AtomicInteger status = ourRebuildStatus.get(indexId); - if (status.get() == OK) return; - if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) { - cleanupProcessedFlag(); - - final Runnable rebuildRunnable = new Runnable() { - public void run() { - try { - clearIndex(indexId); - if (!cleanupOnly) { - scheduleIndexRebuild(false); - } - } - catch (StorageException e) { - requestRebuild(indexId); - LOG.info(e); - } - finally { - status.compareAndSet(REBUILD_IN_PROGRESS, OK); - } - } - }; - - if (cleanupOnly || myIsUnitTestMode) { - rebuildRunnable.run(); - } - else { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - new Task.Modal(null, "Updating index", false) { - public void run(@NotNull final ProgressIndicator indicator) { - indicator.setIndeterminate(true); - rebuildRunnable.run(); - } - }.queue(); - } - }); - } - } - - if (status.get() == REBUILD_IN_PROGRESS) { - throw new ProcessCanceledException(); - } - } - - private void scheduleIndexRebuild(boolean forceDumbMode) { - for (Project project : ProjectManager.getInstance().getOpenProjects()) { - final Set updatersToRun = Collections.singleton(new UnindexedFilesUpdater(project, this)); - final DumbServiceImpl service = DumbServiceImpl.getInstance(project); - if (forceDumbMode) { - service.queueCacheUpdateInDumbMode(updatersToRun); - } - else { - service.queueCacheUpdate(updatersToRun); - } - } - } - - private void clearIndex(final ID indexId) throws StorageException { - final UpdatableIndex index = getIndex(indexId); - assert index != null; - index.clear(); - try { - IndexInfrastructure.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId)); - } - catch (IOException e) { - LOG.error(e); - } - } - - private Set getUnsavedOrTransactedDocuments() { - Set docs = new HashSet(Arrays.asList(myFileDocumentManager.getUnsavedDocuments())); - docs.addAll(myTransactionMap.keySet()); - return docs; - } - - private void indexUnsavedDocuments(ID indexId, @Nullable Project project, GlobalSearchScope filter) throws StorageException { - - if (myUpToDateIndices.contains(indexId)) { - return; // no need to index unsaved docs - } - - final Set documents = getUnsavedOrTransactedDocuments(); - if (!documents.isEmpty()) { - // now index unsaved data - final StorageGuard.Holder guard = setDataBufferingEnabled(true); - try { - final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId); - - assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId; - - boolean allDocsProcessed = true; - semaphore.down(); - try { - for (Document document : documents) { - allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter); - } - } - finally { - semaphore.up(); - - while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing - if (Thread.holdsLock(PsiLock.LOCK)) { - break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. - } - } - if (allDocsProcessed) { - myUpToDateIndices.add(indexId); // safe to set the flag here, becase it will be cleared under the WriteAction - } - } - } - finally { - guard.leave(); - } - } - } - - private interface DocumentContent { - String getText(); - long getModificationStamp(); - } - - private static class AuthenticContent implements DocumentContent { - private final Document myDocument; - - private AuthenticContent(final Document document) { - myDocument = document; - } - - public String getText() { - return myDocument.getText(); - } - - public long getModificationStamp() { - return myDocument.getModificationStamp(); - } - } - - private static class PsiContent implements DocumentContent { - private final Document myDocument; - private final PsiFile myFile; - - private PsiContent(final Document document, final PsiFile file) { - myDocument = document; - myFile = file; - } - - public String getText() { - if (myFile.getModificationStamp() != myDocument.getModificationStamp()) { - final ASTNode node = myFile.getNode(); - assert node != null; - return node.getText(); - } - return myDocument.getText(); - } - - public long getModificationStamp() { - return myFile.getModificationStamp(); - } - } - -// returns false if doc was not indexed because the file does not fit in scope - private boolean indexUnsavedDocument(final Document document, final ID requestedIndexId, final Project project, GlobalSearchScope filter) throws StorageException { - final VirtualFile vFile = myFileDocumentManager.getFile(document); - if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) { - return true; - } - if (filter != null && !filter.accept(vFile)) { - return false; - } - final PsiFile dominantContentFile = findDominantPsiForDocument(document, project); - - final DocumentContent content; - if (dominantContentFile != null && dominantContentFile.getModificationStamp() != document.getModificationStamp()) { - content = new PsiContent(document, dominantContentFile); - } - else { - content = new AuthenticContent(document); - } - - final long currentDocStamp = content.getModificationStamp(); - if (currentDocStamp != myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp).longValue()) { - final Ref exRef = new Ref(null); - ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { - public void run() { - try { - final FileContent newFc = new FileContent(vFile, content.getText(), vFile.getCharset()); - - if (dominantContentFile != null) { - dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, true); - newFc.putUserData(PSI_FILE, dominantContentFile); - } - - if (content instanceof AuthenticContent) { - newFc.putUserData(EDITOR_HIGHLIGHTER, document instanceof DocumentImpl ? ((DocumentImpl)document) .getEditorHighlighterForCachesBuilding() : null); - } - - if (getInputFilter(requestedIndexId).acceptInput(vFile)) { - newFc.putUserData(PROJECT, project); - final int inputId = Math.abs(getFileId(vFile)); - getIndex(requestedIndexId).update(inputId, newFc); - } - - if (dominantContentFile != null) { - dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, null); - } - } - catch (StorageException e) { - exRef.set(e); - } - } - }); - final StorageException storageException = exRef.get(); - if (storageException != null) { - throw storageException; - } - } - return true; - } - - public static final Key PSI_FILE = new Key("PSI for stubs"); - public static final Key EDITOR_HIGHLIGHTER = new Key("Editor"); - public static final Key PROJECT = new Key("Context project"); - public static final Key VIRTUAL_FILE = new Key("Context virtual file"); - - @Nullable - private PsiFile findDominantPsiForDocument(final Document document, @Nullable Project project) { - if (myTransactionMap.containsKey(document)) { - return myTransactionMap.get(document); - } - - return project == null? null : findLatestKnownPsiForUncomittedDocument(document, project); - } - - private final StorageGuard myStorageLock = new StorageGuard(); - - private StorageGuard.Holder setDataBufferingEnabled(final boolean enabled) { - final StorageGuard.Holder holder = myStorageLock.enter(enabled); - for (ID indexId : myIndices.keySet()) { - final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); - assert index != null; - final IndexStorage indexStorage = index.getStorage(); - ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled); - } - return holder; - } - - private void cleanupMemoryStorage() { - synchronized (myLastIndexedDocStamps) { - myLastIndexedDocStamps.clear(); - } - for (ID indexId : myIndices.keySet()) { - final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); - assert index != null; - final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage(); - index.getWriteLock().lock(); - try { - memStorage.clearMemoryMap(); - } - finally { - index.getWriteLock().unlock(); - } - memStorage.fireMemoryStorageCleared(); - } - } - - private void dropUnregisteredIndices() { - final Set indicesToDrop = readRegistsredIndexNames(); - for (ID key : myIndices.keySet()) { - indicesToDrop.remove(key.toString()); - } - for (String s : indicesToDrop) { - FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); - } - } - - public static void requestRebuild(ID indexId) { - requestRebuild(indexId, new Throwable()); - } - - public static void requestRebuild(ID indexId, Throwable throwable) { - cleanupProcessedFlag(); - LOG.info("Rebuild requested for index " + indexId, throwable); - ourRebuildStatus.get(indexId).set(REQUIRES_REBUILD); - } - - private UpdatableIndex getIndex(ID indexId) { - final Pair, InputFilter> pair = myIndices.get(indexId); - //noinspection unchecked - return pair != null? (UpdatableIndex)pair.getFirst() : null; - } - - private InputFilter getInputFilter(ID indexId) { - final Pair, InputFilter> pair = myIndices.get(indexId); - return pair != null? pair.getSecond() : null; - } - - public int getNumberOfPendingInvalidations() { - return myChangedFilesCollector.getNumberOfPendingInvalidations(); - } - - public Collection getFilesToUpdate(final Project project) { - return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition() { - public boolean value(VirtualFile virtualFile) { - for (IndexableFileSet set : myIndexableSets) { - final Project proj = myIndexableSetToProjectMap.get(set); - if (proj != null && !proj.equals(project)) { - continue; // skip this set as associated with a different project - } - if (set.isInSet(virtualFile)) { - return true; - } - } - return false; - } - }); - } - - public void processRefreshedFile(@NotNull Project project, final com.intellij.ide.caches.FileContent fileContent) { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - myChangedFilesCollector.processFileImpl(project, fileContent, false); - } - - public void indexFileContent(@Nullable Project project, com.intellij.ide.caches.FileContent content) { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - final VirtualFile file = content.getVirtualFile(); - FileContent fc = null; - - PsiFile psiFile = null; - - for (final ID indexId : myIndices.keySet()) { - if (shouldIndexFile(file, indexId)) { - if (fc == null) { - byte[] currentBytes; - try { - currentBytes = content.getBytes(); - } - catch (IOException e) { - currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY; - } - fc = new FileContent(file, currentBytes); - - psiFile = content.getUserData(PSI_FILE); - if (psiFile != null) { - psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true); - fc.putUserData(PSI_FILE, psiFile); - } - if (project == null) { - project = ProjectUtil.guessProjectForFile(file); - } - fc.putUserData(PROJECT, project); - } - - try { - ProgressManager.checkCanceled(); - updateSingleIndex(indexId, file, fc); - } - catch (ProcessCanceledException e) { - myChangedFilesCollector.scheduleForUpdate(file); - throw e; - } - catch (StorageException e) { - requestRebuild(indexId); - LOG.info(e); - } - } - } - - if (psiFile != null) { - psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null); - } - } - - private void updateSingleIndex(final ID indexId, final VirtualFile file, final FileContent currentFC) - throws StorageException { - if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) { - return; // the index is scheduled for rebuild, no need to update - } - myLocalModCount++; - - final StorageGuard.Holder lock = setDataBufferingEnabled(false); - - try { - final int inputId = Math.abs(getFileId(file)); - - final UpdatableIndex index = getIndex(indexId); - assert index != null; - - final Ref exRef = new Ref(null); - ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { - public void run() { - try { - index.update(inputId, currentFC); - } - catch (StorageException e) { - exRef.set(e); - } - } - }); - final StorageException storageException = exRef.get(); - if (storageException != null) { - throw storageException; - } - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - if (file.isValid()) { - if (currentFC != null) { - IndexingStamp.update(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)); - } - else { - // mark the file as unindexed - IndexingStamp.update(file, indexId, -1L); - } - } - } - }); - } - finally { - lock.leave(); - } - } - - public static int getFileId(final VirtualFile file) { - if (file instanceof VirtualFileWithId) { - return ((VirtualFileWithId)file).getId(); - } - - throw new IllegalArgumentException("Virtual file doesn't support id: " + file + ", implementation class: " + file.getClass().getName()); - } - - private boolean needsFileContentLoading(ID indexId) { - return !myNotRequiringContentIndices.contains(indexId); - } - - private abstract static class InvalidationTask implements Runnable { - private final VirtualFile mySubj; - - protected InvalidationTask(final VirtualFile subj) { - mySubj = subj; - } - - public VirtualFile getSubj() { - return mySubj; - } - } - - private final class ChangedFilesCollector extends VirtualFileAdapter { - private final Set myFilesToUpdate = new ConcurrentHashSet(); - private final Queue myFutureInvalidations = new ConcurrentLinkedQueue(); - - private final ManagingFS myManagingFS = ManagingFS.getInstance(); - // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact. - - public void fileCreated(final VirtualFileEvent event) { - markDirty(event); - } - - public void fileDeleted(final VirtualFileEvent event) { - myFilesToUpdate.remove(event.getFile()); // no need to update it anymore - } - - public void fileCopied(final VirtualFileCopyEvent event) { - markDirty(event); - } - - public void beforeFileDeletion(final VirtualFileEvent event) { - invalidateIndices(event.getFile(), false); - } - - public void beforeContentsChange(final VirtualFileEvent event) { - invalidateIndices(event.getFile(), true); - } - - public void contentsChanged(final VirtualFileEvent event) { - markDirty(event); - } - - public void beforePropertyChange(final VirtualFilePropertyEvent event) { - if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { - // indexes may depend on file name - final VirtualFile file = event.getFile(); - if (!file.isDirectory()) { - // name change may lead to filetype change so the file might become not indexable - // in general case have to 'unindex' the file and index it again if needed after the name has been changed - invalidateIndices(file, false); - } - } - } - - public void propertyChanged(final VirtualFilePropertyEvent event) { - if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { - // indexes may depend on file name - if (!event.getFile().isDirectory()) { - markDirty(event); - } - } - } - - private void markDirty(final VirtualFileEvent event) { - final VirtualFile eventFile = event.getFile(); - cleanProcessedFlag(eventFile); - iterateIndexableFiles(eventFile, new Processor() { - public boolean process(final VirtualFile file) { - FileContent fileContent = null; - // handle 'content-less' indices separately - for (ID indexId : myNotRequiringContentIndices) { - if (getInputFilter(indexId).acceptInput(file)) { - try { - if (fileContent == null) { - fileContent = new FileContent(file); - } - updateSingleIndex(indexId, file, fileContent); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - } - // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it - if (!isTooLarge(file)) { - for (ID indexId : myIndices.keySet()) { - if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) { - scheduleForUpdate(file); - break; // no need to iterate further, as the file is already marked - } - } - } - - return true; - } - }); - IndexingStamp.flushCache(); - } - - public void scheduleForUpdate(VirtualFile file) { - myFilesToUpdate.add(file); - } - - void invalidateIndices(final VirtualFile file, final boolean markForReindex) { - if (isUnderConfigOrSystem(file)) { - return; - } - if (file.isDirectory()) { - if (isMock(file) || myManagingFS.wereChildrenAccessed(file)) { - final Iterable children = file instanceof NewVirtualFile - ? ((NewVirtualFile)file).iterInDbChildren() : Arrays.asList(file.getChildren()); - for (VirtualFile child : children) { - invalidateIndices(child, markForReindex); - } - } - } - else { - cleanProcessedFlag(file); - IndexingStamp.flushCache(); - final List> affectedIndices = new ArrayList>(myIndices.size()); - - final boolean isTooLarge = isTooLarge(file); - for (final ID indexId : myIndices.keySet()) { - try { - if (!needsFileContentLoading(indexId)) { - if (shouldUpdateIndex(file, indexId)) { - updateSingleIndex(indexId, file, null); - } - } - else { // the index requires file content - if (!isTooLarge && shouldUpdateIndex(file, indexId)) { - affectedIndices.add(indexId); - } - } - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - - if (!affectedIndices.isEmpty()) { - if (markForReindex) { - // only mark the file as unindexed, reindex will be done lazily - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (ID indexId : affectedIndices) { - IndexingStamp.update(file, indexId, -1L); - } - } - }); - iterateIndexableFiles(file, new Processor() { - public boolean process(final VirtualFile file) { - scheduleForUpdate(file); - return true; - } - }); - } - else { - final InvalidationTask invalidator = new InvalidationTask(file) { - public void run() { - removeFileDataFromIndices(affectedIndices, file); - } - }; - myFutureInvalidations.offer(invalidator); - } - } - if (!markForReindex) { - myFilesToUpdate.remove(file); // no need to update it anymore - } - IndexingStamp.flushCache(); - } - } - - private void removeFileDataFromIndices(List> affectedIndices, VirtualFile file) { - Throwable unexpectedError = null; - for (ID indexId : affectedIndices) { - try { - updateSingleIndex(indexId, file, null); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - catch (ProcessCanceledException ignored) { - } - catch (Throwable e) { - LOG.info(e); - if (unexpectedError == null) { - unexpectedError = e; - } - } - } - IndexingStamp.flushCache(); - if (unexpectedError != null) { - LOG.error(unexpectedError); - } - } - - public int getNumberOfPendingInvalidations() { - return myFutureInvalidations.size(); - } - - public void ensureAllInvalidateTasksCompleted() { - final int size = getNumberOfPendingInvalidations(); - if (size == 0) return; - final ProgressIndicator current = ProgressManager.getInstance().getProgressIndicator(); - final ProgressIndicator indicator = current != null ? current : new EmptyProgressIndicator(); - indicator.setText(""); - int count = 0; - while (true) { - InvalidationTask task = myFutureInvalidations.poll(); - - if (task == null) { - break; - } - indicator.setFraction((double)count++ /size); - indicator.setText2(task.getSubj().getPresentableUrl()); - task.run(); - } - } - - private void iterateIndexableFiles(final VirtualFile file, final Processor processor) { - if (file.isDirectory()) { - final ContentIterator iterator = new ContentIterator() { - public boolean processFile(final VirtualFile fileOrDir) { - if (!fileOrDir.isDirectory()) { - processor.process(fileOrDir); - } - return true; - } - }; - - for (IndexableFileSet set : myIndexableSets) { - set.iterateIndexableFilesIn(file, iterator); - } - } - else { - for (IndexableFileSet set : myIndexableSets) { - if (set.isInSet(file)) { - processor.process(file); - break; - } - } - } - } - - public Collection getAllFilesToUpdate() { - if (myFilesToUpdate.isEmpty()) { - return Collections.emptyList(); - } - return new ArrayList(myFilesToUpdate); - } - - private final Semaphore myForceUpdateSemaphore = new Semaphore(); - - public void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, boolean onlyRemoveOutdatedData) { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - for (VirtualFile file: getAllFilesToUpdate()) { - if (filter == null || filter.accept(file)) { - try { - myForceUpdateSemaphore.down(); - // process only files that can affect result - processFileImpl(project, new com.intellij.ide.caches.FileContent(file), onlyRemoveOutdatedData); - } - finally { - myForceUpdateSemaphore.up(); - } - } - } - - // If several threads entered the method at the same time and there were files to update, - // all the threads should leave the method synchronously after all the files scheduled for update are reindexed, - // no matter which thread will do reindexing job. - // Thus we ensure that all the threads that entered the method will get the most recent data - - while (!myForceUpdateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing - if (Thread.holdsLock(PsiLock.LOCK)) { - break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. - } - } - } - - private void processFileImpl(Project project, final com.intellij.ide.caches.FileContent fileContent, boolean onlyRemoveOutdatedData) { - final VirtualFile file = fileContent.getVirtualFile(); - final boolean reallyRemoved = myFilesToUpdate.remove(file); - if (reallyRemoved && file.isValid()) { - if (onlyRemoveOutdatedData) { - // on shutdown there is no need to re-index the file, just remove outdated data from indices - final List> affected = new ArrayList>(); - for (final ID indexId : myIndices.keySet()) { - if (getInputFilter(indexId).acceptInput(file)) { - affected.add(indexId); - } - } - removeFileDataFromIndices(affected, file); - } - else { - indexFileContent(project, fileContent); - } - IndexingStamp.flushCache(); - } - } - } - - private class UnindexedFilesFinder implements CollectingContentIterator { - private final List myFiles = new ArrayList(); - private final ProgressIndicator myProgressIndicator; - - private UnindexedFilesFinder() { - myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); - } - - public List getFiles() { - return myFiles; - } - - public boolean processFile(final VirtualFile file) { - if (!file.isDirectory()) { - if (file instanceof NewVirtualFile && ((NewVirtualFile)file).getFlag(ALREADY_PROCESSED)) { - return true; - } - - if (file instanceof VirtualFileWithId) { - boolean oldStuff = true; - if (!isTooLarge(file)) { - for (ID indexId : myIndices.keySet()) { - try { - if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) { - myFiles.add(file); - oldStuff = false; - break; - } - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException || cause instanceof StorageException) { - LOG.info(e); - requestRebuild(indexId); - } - else { - throw e; - } - } - } - } - FileContent fileContent = null; - for (ID indexId : myNotRequiringContentIndices) { - if (shouldIndexFile(file, indexId)) { - oldStuff = false; - try { - if (fileContent == null) { - fileContent = new FileContent(file); - } - updateSingleIndex(indexId, file, fileContent); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - } - IndexingStamp.flushCache(); - - if (oldStuff && file instanceof NewVirtualFile) { - ((NewVirtualFile)file).setFlag(ALREADY_PROCESSED, true); - } - } - } - else { - if (myProgressIndicator != null) { - myProgressIndicator.setText("Scanning files to index"); - myProgressIndicator.setText2(file.getPresentableUrl()); - } - } - return true; - } - } - - private boolean shouldUpdateIndex(final VirtualFile file, final ID indexId) { - return getInputFilter(indexId).acceptInput(file) && - (isMock(file) || IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); - } - - private boolean shouldIndexFile(final VirtualFile file, final ID indexId) { - return getInputFilter(indexId).acceptInput(file) && - (isMock(file) || !IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); - } - - private boolean isUnderConfigOrSystem(VirtualFile file) { - final String filePath = file.getPath(); - return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) || - mySystemPath != null && FileUtil.startsWith(filePath, mySystemPath); - } - - private static boolean isMock(final VirtualFile file) { - return !(file instanceof NewVirtualFile); - } - - private boolean isTooLarge(VirtualFile file) { - if (SingleRootFileViewProvider.isTooLarge(file)) { - final FileType type = FileTypeManager.getInstance().getFileTypeByFile(file); - return !myNoLimitCheckTypes.contains(type); - } - return false; - } - - public CollectingContentIterator createContentIterator() { - return new UnindexedFilesFinder(); - } - - public void registerIndexableSet(IndexableFileSet set, @Nullable Project project) { - myIndexableSets.add(set); - myIndexableSetToProjectMap.put(set, project); - } - - public void removeIndexableSet(IndexableFileSet set) { - myChangedFilesCollector.forceUpdate(null, null, true); - myIndexableSets.remove(set); - myIndexableSetToProjectMap.remove(set); - } - - @Nullable - private static PsiFile findLatestKnownPsiForUncomittedDocument(Document doc, Project project) { - return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc); - } - - private static class IndexableFilesFilter implements InputFilter { - private final InputFilter myDelegate; - - private IndexableFilesFilter(InputFilter delegate) { - myDelegate = delegate; - } - - public boolean acceptInput(final VirtualFile file) { - return file instanceof VirtualFileWithId && myDelegate.acceptInput(file); - } - } - - private static void cleanupProcessedFlag() { - final VirtualFile[] roots = ManagingFS.getInstance().getRoots(); - for (VirtualFile root : roots) { - cleanProcessedFlag(root); - } - } - - private static void cleanProcessedFlag(final VirtualFile file) { - if (!(file instanceof NewVirtualFile)) return; - - final NewVirtualFile nvf = (NewVirtualFile)file; - if (file.isDirectory()) { - for (VirtualFile child : nvf.getCachedChildren()) { - cleanProcessedFlag(child); - } - } - else { -/* nvf.clearCachedFileType(); */ - nvf.setFlag(ALREADY_PROCESSED, false); - } - } - - public static void iterateIndexableFiles(final ContentIterator processor, Project project) { - if (project.isDisposed()) { - return; - } - final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - // iterate project content - projectFileIndex.iterateContent(processor); - - if (project.isDisposed()) { - return; - } - ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - - Set visitedRoots = new HashSet(); - for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) { - //important not to depend on project here, to support per-project background reindex - // each client gives a project to FileBasedIndex - if (project.isDisposed()) { - return; - } - for (VirtualFile root : IndexableSetContributor.getRootsToIndex(provider, project)) { - if (visitedRoots.add(root)) { - iterateRecursively(root, processor, indicator); - } - } - } - - if (project.isDisposed()) { - return; - } - // iterate associated libraries - for (Module module : ModuleManager.getInstance(project).getModules()) { - if (module.isDisposed()) { - return; - } - OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries(); - for (OrderEntry orderEntry : orderEntries) { - if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { - if (orderEntry.isValid()) { - final VirtualFile[] libSources = orderEntry.getFiles(OrderRootType.SOURCES); - final VirtualFile[] libClasses = orderEntry.getFiles(OrderRootType.CLASSES); - for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) { - for (VirtualFile root : roots) { - if (visitedRoots.add(root)) { - iterateRecursively(root, processor, indicator); - } - } - } - } - } - } - } - } - - private static void iterateRecursively(@Nullable final VirtualFile root, final ContentIterator processor, ProgressIndicator indicator) { - if (root != null) { - if (indicator != null) { - indicator.setText2(root.getPresentableUrl()); - } - - if (root.isDirectory()) { - for (VirtualFile file : root.getChildren()) { - if (file.isDirectory()) { - iterateRecursively(file, processor, indicator); - } - else { - processor.processFile(file); - } - } - } else { - processor.processFile(root); - } - } - } - - private static class StorageGuard { - private int myHolds = 0; - - public interface Holder { - void leave(); - } - - private final Holder myTrueHolder = new Holder() { - public void leave() { - StorageGuard.this.leave(true); - } - }; - private final Holder myFalseHolder = new Holder() { - public void leave() { - StorageGuard.this.leave(false); - } - }; - - public synchronized Holder enter(boolean mode) { - if (mode) { - while (myHolds < 0) { - try { - wait(); - } - catch (InterruptedException ignored) { - } - } - myHolds++; - return myTrueHolder; - } - else { - while (myHolds > 0) { - try { - wait(); - } - catch (InterruptedException ignored) { - } - } - myHolds--; - return myFalseHolder; - } - } - - private synchronized void leave(boolean mode) { - myHolds += mode? -1 : 1; - if (myHolds == 0) { - notifyAll(); - } - } - - } -} +/* + * 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.util.indexing; + +import com.intellij.AppTopics; +import com.intellij.concurrency.JobScheduler; +import com.intellij.history.LocalHistory; +import com.intellij.ide.caches.CacheUpdater; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.application.ApplicationAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; +import com.intellij.openapi.fileTypes.*; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.*; +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; +import com.intellij.openapi.project.*; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import com.intellij.psi.*; +import com.intellij.psi.impl.PsiDocumentTransactionListener; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.search.EverythingGlobalScope; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.ArrayUtil; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Processor; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.containers.ConcurrentHashSet; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.HashSet; +import com.intellij.util.io.*; +import com.intellij.util.io.storage.HeavyProcessLatch; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIterator; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.*; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; + +/** + * @author Eugene Zhuravlev + * Date: Dec 20, 2007 + */ + +public class FileBasedIndex implements ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndex"); + @NonNls + private static final String CORRUPTION_MARKER_NAME = "corruption.marker"; + private final Map, Pair, InputFilter>> myIndices = new HashMap, Pair, InputFilter>>(); + private final Map, Semaphore> myUnsavedDataIndexingSemaphores = new HashMap, Semaphore>(); + private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); + private final Set> myNotRequiringContentIndices = new HashSet>(); + private final Set myNoLimitCheckTypes = new HashSet(); + + private final PerIndexDocumentMap myLastIndexedDocStamps = new PerIndexDocumentMap() { + @Override + protected Long createDefault(Document document) { + return 0L; + } + }; + + private final ChangedFilesCollector myChangedFilesCollector; + + private final List myIndexableSets = ContainerUtil.createEmptyCOWList(); + private final Map myIndexableSetToProjectMap = new HashMap(); + + private static final int OK = 1; + private static final int REQUIRES_REBUILD = 2; + private static final int REBUILD_IN_PROGRESS = 3; + private static final Map, AtomicInteger> ourRebuildStatus = new HashMap, AtomicInteger>(); + + private final VirtualFileManagerEx myVfManager; + private final FileDocumentManager myFileDocumentManager; + private final ConcurrentHashSet> myUpToDateIndices = new ConcurrentHashSet>(); + private final Map myTransactionMap = new HashMap(); + + private static final int ALREADY_PROCESSED = 0x02; + @Nullable private final String myConfigPath; + @Nullable private final String mySystemPath; + private final boolean myIsUnitTestMode; + private ScheduledFuture myFlushingFuture; + private volatile int myLocalModCount; + + public void requestReindex(final VirtualFile file) { + myChangedFilesCollector.invalidateIndices(file, true); + } + + public interface InputFilter { + boolean acceptInput(VirtualFile file); + } + + public FileBasedIndex(final VirtualFileManagerEx vfManager, FileDocumentManager fdm, MessageBus bus) throws IOException { + myVfManager = vfManager; + myFileDocumentManager = fdm; + myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); + myConfigPath = calcConfigPath(PathManager.getConfigPath()); + mySystemPath = calcConfigPath(PathManager.getSystemPath()); + + final MessageBusConnection connection = bus.connect(); + connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() { + public void transactionStarted(final Document doc, final PsiFile file) { + if (file != null) { + myTransactionMap.put(doc, file); + myUpToDateIndices.clear(); + } + } + + public void transactionCompleted(final Document doc, final PsiFile file) { + myTransactionMap.remove(doc); + } + }); + + connection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() { + private Map> myTypeToExtensionMap; + public void beforeFileTypesChanged(final FileTypeEvent event) { + cleanupProcessedFlag(); + myTypeToExtensionMap = new HashMap>(); + final FileTypeManager manager = event.getManager(); + for (FileType type : manager.getRegisteredFileTypes()) { + myTypeToExtensionMap.put(type, getExtensions(manager, type)); + } + } + + public void fileTypesChanged(final FileTypeEvent event) { + final Map> oldExtensions = myTypeToExtensionMap; + myTypeToExtensionMap = null; + if (oldExtensions != null) { + final FileTypeManager manager = event.getManager(); + final Map> newExtensions = new HashMap>(); + for (FileType type : manager.getRegisteredFileTypes()) { + newExtensions.put(type, getExtensions(manager, type)); + } + // we are interested only in extension changes or removals. + // addition of an extension is handled separately by RootsChanged event + if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) { + rebuildAllndices(); + return; + } + for (Map.Entry> entry : oldExtensions.entrySet()) { + FileType fileType = entry.getKey(); + Set strings = entry.getValue(); + if (!newExtensions.get(fileType).containsAll(strings)) { + rebuildAllndices(); + return; + } + } + } + } + + private Set getExtensions(FileTypeManager manager, FileType type) { + final Set set = new HashSet(); + for (FileNameMatcher matcher : manager.getAssociations(type)) { + set.add(matcher.getPresentableString()); + } + return set; + } + + private void rebuildAllndices() { + for (ID indexId : myIndices.keySet()) { + try { + clearIndex(indexId); + } + catch (StorageException e) { + LOG.info(e); + } + } + scheduleIndexRebuild(true); + } + }); + + connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + public void before(List events) { + for (VFileEvent event : events) { + final Object requestor = event.getRequestor(); + if (requestor instanceof FileDocumentManager || requestor instanceof PsiManager || requestor == LocalHistory.VFS_EVENT_REQUESTOR) { + cleanupMemoryStorage(); + break; + } + } + } + + public void after(List events) { + } + }); + + connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { + public void fileContentReloaded(VirtualFile file, Document document) { + cleanupMemoryStorage(); + } + + public void unsavedDocumentsDropped() { + cleanupMemoryStorage(); + } + }); + + ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() { + public void writeActionStarted(Object action) { + myUpToDateIndices.clear(); + } + }); + + myChangedFilesCollector = new ChangedFilesCollector(); + + /* + final File workInProgressFile = getMarkerFile(); + if (workInProgressFile.exists()) { + // previous IDEA session was closed incorrectly, so drop all indices + FileUtil.delete(PathManager.getIndexRoot()); + } + */ + + try { + final FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME); + for (FileBasedIndexExtension extension : extensions) { + ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK)); + } + + final File corruptionMarker = new File(PathManager.getIndexRoot(), CORRUPTION_MARKER_NAME); + final boolean currentVersionCorrupted = corruptionMarker.exists(); + for (FileBasedIndexExtension extension : extensions) { + registerIndexer(extension, currentVersionCorrupted); + } + FileUtil.delete(corruptionMarker); + dropUnregisteredIndices(); + + // check if rebuild was requested for any index during registration + for (ID indexId : myIndices.keySet()) { + if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) { + try { + clearIndex(indexId); + } + catch (StorageException e) { + requestRebuild(indexId); + LOG.error(e); + } + } + } + + myVfManager.addVirtualFileListener(myChangedFilesCollector); + + registerIndexableSet(new AdditionalIndexableFileSet(), null); + } + finally { + ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { + public void run() { + performShutdown(); + } + }); + //FileUtil.createIfDoesntExist(workInProgressFile); + saveRegisteredIndices(myIndices.keySet()); + myFlushingFuture = JobScheduler.getScheduler().scheduleAtFixedRate(new Runnable() { + int lastModCount = 0; + public void run() { + if (lastModCount == myLocalModCount && !HeavyProcessLatch.INSTANCE.isRunning()) { + flushAllIndices(); + } + lastModCount = myLocalModCount; + } + }, 5000, 5000, TimeUnit.MILLISECONDS); + + } + } + + public void initComponent() { + } + + private static String calcConfigPath(final String path) { + try { + final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath()); + return _path.endsWith("/")? _path : _path + "/" ; + } + catch (IOException e) { + LOG.info(e); + return null; + } + } + + private static class FileBasedIndexHolder { + private static final FileBasedIndex ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class); + } + + public static FileBasedIndex getInstance() { + return FileBasedIndexHolder.ourInstance; + } + + /** + * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted @param extension + * @param isCurrentVersionCorrupted + */ + private void registerIndexer(final FileBasedIndexExtension extension, final boolean isCurrentVersionCorrupted) throws IOException { + final ID name = extension.getName(); + final int version = extension.getVersion(); + if (!extension.dependsOnFileContent()) { + myNotRequiringContentIndices.add(name); + } + myIndexIdToVersionMap.put(name, version); + final File versionFile = IndexInfrastructure.getVersionFile(name); + if (isCurrentVersionCorrupted || IndexInfrastructure.versionDiffers(versionFile, version)) { + if (!isCurrentVersionCorrupted) { + LOG.info("Version has changed for index " + extension.getName() + ". The index will be rebuilt."); + } + FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); + IndexInfrastructure.rewriteVersion(versionFile, version); + } + + for (int attempt = 0; attempt < 2; attempt++) { + try { + final MapIndexStorage storage = new MapIndexStorage(IndexInfrastructure.getStorageFile(name), extension.getKeyDescriptor(), extension.getValueExternalizer(), extension.getCacheSize()); + final MemoryIndexStorage memStorage = new MemoryIndexStorage(storage); + final UpdatableIndex index = createIndex(name, extension, memStorage); + myIndices.put(name, new Pair, InputFilter>(index, new IndexableFilesFilter(extension.getInputFilter()))); + myUnsavedDataIndexingSemaphores.put(name, new Semaphore()); + myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable()); + break; + } + catch (IOException e) { + LOG.info(e); + FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); + IndexInfrastructure.rewriteVersion(versionFile, version); + } + } + } + + private static void saveRegisteredIndices(Collection> ids) { + final File file = getRegisteredIndicesFile(); + try { + FileUtil.createIfDoesntExist(file); + final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + try { + os.writeInt(ids.size()); + for (ID id : ids) { + IOUtil.writeString(id.toString(), os); + } + } + finally { + os.close(); + } + } + catch (IOException ignored) { + } + } + + private static Set readRegistsredIndexNames() { + final Set result = new HashSet(); + try { + final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile()))); + try { + final int size = in.readInt(); + for (int idx = 0; idx < size; idx++) { + result.add(IOUtil.readString(in)); + } + } + finally { + in.close(); + } + } + catch (IOException ignored) { + } + return result; + } + + private static File getRegisteredIndicesFile() { + return new File(PathManager.getIndexRoot(), "registered"); + } + + private UpdatableIndex createIndex(final ID indexId, final FileBasedIndexExtension extension, final MemoryIndexStorage storage) throws IOException { + final MapReduceIndex index; + if (extension instanceof CustomImplementationFileBasedIndexExtension) { + final UpdatableIndex custom = + ((CustomImplementationFileBasedIndexExtension)extension).createIndexImplementation(indexId, this, storage); + if (!(custom instanceof MapReduceIndex)) { + return custom; + } + index = (MapReduceIndex)custom; + } + else { + index = new MapReduceIndex(indexId, extension.getIndexer(), storage); + } + + final KeyDescriptor keyDescriptor = extension.getKeyDescriptor(); + index.setInputIdToDataKeysIndex(new Factory>>() { + public PersistentHashMap> create() { + try { + return createIdToDataKeysIndex(indexId, keyDescriptor, storage); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + + return index; + } + + private static PersistentHashMap> createIdToDataKeysIndex(final ID indexId, + final KeyDescriptor keyDescriptor, + MemoryIndexStorage storage) throws IOException { + final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId); + final Ref isBufferingMode = new Ref(false); + final Map> tempMap = new HashMap>(); + + final DataExternalizer> dataExternalizer = new DataExternalizer>() { + public void save(DataOutput out, Collection value) throws IOException { + try { + DataInputOutputUtil.writeINT(out, value.size()); + for (K key : value) { + keyDescriptor.save(out, key); + } + } + catch (IOException e) { + throw e; + } + catch (IllegalArgumentException e) { + throw new IOException("Error saving data for index " + indexId, e); + } + } + + public Collection read(DataInput in) throws IOException { + try { + final int size = DataInputOutputUtil.readINT(in); + final List list = new ArrayList(); + for (int idx = 0; idx < size; idx++) { + list.add(keyDescriptor.read(in)); + } + return list; + } + catch (IOException e) { + throw e; + } + catch (IllegalArgumentException e) { + throw new IOException("Error reading data for index " + indexId, e); + } + } + }; + + // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage. + // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage). + // Otherwise data in IdToDataKeysIndex will not be in sync with the 'main' data in the index on disk and index updates will be based on the + // wrong sets of keys for the given file. This will lead to unpretictable results in main index because it will not be + // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects. + + final PersistentHashMap> map = new PersistentHashMap>( + indexStorageFile, new EnumeratorIntegerDescriptor(), dataExternalizer + ) { + + @Override + public synchronized Collection get(Integer integer) throws IOException { + if (isBufferingMode.get()) { + final Collection collection = tempMap.get(integer); + if (collection != null) { + return collection; + } + } + return super.get(integer); + } + + @Override + public synchronized void put(Integer integer, Collection ks) throws IOException { + if (isBufferingMode.get()) { + tempMap.put(integer, ks == null? Collections.emptySet() : ks); + } + else { + super.put(integer, ks); + } + } + + @Override + public synchronized void remove(Integer integer) throws IOException { + if (isBufferingMode.get()) { + tempMap.put(integer, Collections.emptySet()); + } + else { + super.remove(integer); + } + } + }; + + storage.addBufferingStateListsner(new MemoryIndexStorage.BufferingStateListener() { + public void bufferingStateChanged(boolean newState) { + synchronized (map) { + isBufferingMode.set(newState); + } + } + public void memoryStorageCleared() { + synchronized (map) { + tempMap.clear(); + } + } + }); + return map; + } + + @NonNls + @NotNull + public String getComponentName() { + return "FileBasedIndex"; + } + + public void disposeComponent() { + performShutdown(); + } + + private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false); + + private void performShutdown() { + if (!myShutdownPerformed.compareAndSet(false, true)) { + return; // already shut down + } + try { + if (myFlushingFuture != null) { + myFlushingFuture.cancel(false); + myFlushingFuture = null; + } + + myFileDocumentManager.saveAllDocuments(); + } + finally { + LOG.info("START INDEX SHUTDOWN"); + try { + myChangedFilesCollector.forceUpdate(null, null, true); + + for (ID indexId : myIndices.keySet()) { + final UpdatableIndex index = getIndex(indexId); + assert index != null; + checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it + //LOG.info("DISPOSING " + indexId); + index.dispose(); + } + + myVfManager.removeVirtualFileListener(myChangedFilesCollector); + + //FileUtil.delete(getMarkerFile()); + } + catch (Throwable e) { + LOG.info("Problems during index shutdown", e); + throw new RuntimeException(e); + } + LOG.info("END INDEX SHUTDOWN"); + } + } + + private void flushAllIndices() { + IndexingStamp.flushCache(); + for (ID indexId : new ArrayList>(myIndices.keySet())) { + if (HeavyProcessLatch.INSTANCE.isRunning()) { + return; + } + try { + final UpdatableIndex index = getIndex(indexId); + if (index != null) { + index.flush(); + } + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + } + + /** + * @param project it is guaranteeed to return data which is up-to-date withing the project + * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist + */ + @NotNull + public Collection getAllKeys(final ID indexId, @NotNull Project project) { + Set allKeys = new HashSet(); + processAllKeys(indexId, new CommonProcessors.CollectProcessor(allKeys), project); + return allKeys; + } + + /** + * @param project it is guaranteeed to return data which is up-to-date withing the project + * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist + */ + public boolean processAllKeys(final ID indexId, Processor processor, @Nullable Project project) { + try { + final UpdatableIndex index = getIndex(indexId); + if (index == null) { + return true; + } + ensureUpToDate(indexId, project, project != null? GlobalSearchScope.allScope(project) : new EverythingGlobalScope()); + return index.processAllKeys(processor); + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, cause); + } + else { + throw e; + } + } + + return false; + } + + private static final ThreadLocal myUpToDateCheckState = new ThreadLocal(); + + public static void disableUpToDateCheckForCurrentThread() { + final Integer currentValue = myUpToDateCheckState.get(); + myUpToDateCheckState.set(currentValue == null? 1 : currentValue.intValue() + 1); + } + + public static void enableUpToDateCheckForCurrentThread() { + final Integer currentValue = myUpToDateCheckState.get(); + if (currentValue != null) { + final int newValue = currentValue.intValue() - 1; + if (newValue != 0) { + myUpToDateCheckState.set(newValue); + } + else { + myUpToDateCheckState.remove(); + } + } + } + + private static boolean isUpToDateCheckEnabled() { + final Integer value = myUpToDateCheckState.get(); + return value == null || value.intValue() == 0; + } + + + private final ThreadLocal myReentrancyGuard = new ThreadLocal() { + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + + /** + * DO NOT CALL DIRECTLY IN CLIENT CODE + * The method is internal to indexing engine end is called internally. The method is public due to implementation details + */ + public void ensureUpToDate(final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) { + if (!needsFileContentLoading(indexId)) { + return; //indexed eagerly in foreground while building unindexed file list + } + if (isDumb(project)) { + handleDumbMode(project); + } + + if (myReentrancyGuard.get().booleanValue()) { + //assert false : "ensureUpToDate() is not reentrant!"; + return; + } + myReentrancyGuard.set(Boolean.TRUE); + + try { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + if (isUpToDateCheckEnabled()) { + try { + checkRebuild(indexId, false); + myChangedFilesCollector.forceUpdate(project, filter, false); + indexUnsavedDocuments(indexId, project, filter); + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, e); + } + else { + throw e; + } + } + } + } + finally { + myReentrancyGuard.set(Boolean.FALSE); + } + } + + private static void handleDumbMode(@Nullable Project project) { + ProgressManager.checkCanceled(); // DumbModeAction.CANCEL + + if (project != null) { + final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); + if (progressIndicator instanceof BackgroundableProcessIndicator) { + final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator; + if (indicator.getDumbModeAction() == DumbModeAction.WAIT) { + assert !ApplicationManager.getApplication().isDispatchThread(); + DumbService.getInstance(project).waitForSmartMode(); + return; + } + } + } + + throw new IndexNotReadyException(); + } + + private static boolean isDumb(@Nullable Project project) { + if (project != null) { + return DumbServiceImpl.getInstance(project).isDumb(); + } + for (Project proj : ProjectManager.getInstance().getOpenProjects()) { + if (DumbServiceImpl.getInstance(proj).isDumb()) { + return true; + } + } + return false; + } + + @NotNull + public List getValues(final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { + final List values = new ArrayList(); + processValuesImpl(indexId, dataKey, true, null, new ValueProcessor() { + public boolean process(final VirtualFile file, final V value) { + values.add(value); + return true; + } + }, filter); + return values; + } + + @NotNull + public Collection getContainingFiles(final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { + final Set files = new HashSet(); + processValuesImpl(indexId, dataKey, false, null, new ValueProcessor() { + public boolean process(final VirtualFile file, final V value) { + files.add(file); + return true; + } + }, filter); + return files; + } + + + public interface ValueProcessor { + /** + * @param value a value to process + * @param file the file the value came from + * @return false if no further processing is needed, true otherwise + */ + boolean process(VirtualFile file, V value); + } + + /** + * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all + */ + public boolean processValues(final ID indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile, + ValueProcessor processor, @NotNull final GlobalSearchScope filter) { + return processValuesImpl(indexId, dataKey, false, inFile, processor, filter); + } + + + + private boolean processValuesImpl(final ID indexId, final K dataKey, boolean ensureValueProcessedOnce, + @Nullable final VirtualFile restrictToFile, ValueProcessor processor, + final GlobalSearchScope filter) { + try { + final UpdatableIndex index = getIndex(indexId); + if (index == null) { + return true; + } + final Project project = filter.getProject(); + //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; + ensureUpToDate(indexId, project, filter); + + final Lock readLock = index.getReadLock(); + try { + readLock.lock(); + final ValueContainer container = index.getData(dataKey); + + boolean shouldContinue = true; + + if (restrictToFile != null) { + if (restrictToFile instanceof VirtualFileWithId) { + final int restrictedFileId = getFileId(restrictToFile); + for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { + final V value = valueIt.next(); + if (container.isAssociated(value, restrictedFileId)) { + shouldContinue = processor.process(restrictToFile, value); + if (!shouldContinue) { + break; + } + } + } + } + } + else { + final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); + VALUES_LOOP: for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { + final V value = valueIt.next(); + for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { + final int id = inputIdsIterator.next(); + VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); + if (file != null && filter.accept(file)) { + shouldContinue = processor.process(file, value); + if (!shouldContinue) { + break VALUES_LOOP; + } + if (ensureValueProcessedOnce) { + break; // continue with the next value + } + } + } + } + } + return shouldContinue; + } + finally { + index.getReadLock().unlock(); + } + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, cause); + } + else { + throw e; + } + } + return true; + } + + public boolean getFilesWithKey(final ID indexId, final Set dataKeys, Processor processor, GlobalSearchScope filter) { + try { + final UpdatableIndex index = getIndex(indexId); + if (index == null) { + return true; + } + final Project project = filter.getProject(); + //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; + ensureUpToDate(indexId, project, filter); + + final Lock readLock = index.getReadLock(); + try { + readLock.lock(); + List locals = new ArrayList(); + for (K dataKey : dataKeys) { + TIntHashSet local = new TIntHashSet(); + locals.add(local); + final ValueContainer container = index.getData(dataKey); + + for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { + final V value = valueIt.next(); + for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { + final int id = inputIdsIterator.next(); + local.add(id); + } + } + } + + if (locals.isEmpty()) return true; + + Collections.sort(locals, new Comparator() { + public int compare(TIntHashSet o1, TIntHashSet o2) { + return o1.size() - o2.size(); + } + }); + + final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); + TIntIterator ids = join(locals).iterator(); + while (ids.hasNext()) { + int id = ids.next(); + //VirtualFile file = IndexInfrastructure.findFileById(fs, id); + VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); + if (file != null && filter.accept(file)) { + if (!processor.process(file)) return false; + } + } + } + finally { + index.getReadLock().unlock(); + } + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, cause); + } + else { + throw e; + } + } + return true; + } + + private static TIntHashSet join(List locals) { + TIntHashSet result = locals.get(0); + if (locals.size() > 1) { + TIntIterator it = result.iterator(); + + while (it.hasNext()) { + int id = it.next(); + for (int i = 1; i < locals.size(); i++) { + if (!locals.get(i).contains(id)) { + it.remove(); + break; + } + } + } + } + return result; + } + + public void scheduleRebuild(final ID indexId, final Throwable e) { + LOG.info(e); + requestRebuild(indexId); + checkRebuild(indexId, false); + } + + private void checkRebuild(final ID indexId, final boolean cleanupOnly) { + final AtomicInteger status = ourRebuildStatus.get(indexId); + if (status.get() == OK) return; + if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) { + cleanupProcessedFlag(); + + final Runnable rebuildRunnable = new Runnable() { + public void run() { + try { + clearIndex(indexId); + if (!cleanupOnly) { + scheduleIndexRebuild(false); + } + } + catch (StorageException e) { + requestRebuild(indexId); + LOG.info(e); + } + finally { + status.compareAndSet(REBUILD_IN_PROGRESS, OK); + } + } + }; + + if (cleanupOnly || myIsUnitTestMode) { + rebuildRunnable.run(); + } + else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + new Task.Modal(null, "Updating index", false) { + public void run(@NotNull final ProgressIndicator indicator) { + indicator.setIndeterminate(true); + rebuildRunnable.run(); + } + }.queue(); + } + }); + } + } + + if (status.get() == REBUILD_IN_PROGRESS) { + throw new ProcessCanceledException(); + } + } + + private void scheduleIndexRebuild(boolean forceDumbMode) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + final Set updatersToRun = Collections.singleton(new UnindexedFilesUpdater(project, this)); + final DumbServiceImpl service = DumbServiceImpl.getInstance(project); + if (forceDumbMode) { + service.queueCacheUpdateInDumbMode(updatersToRun); + } + else { + service.queueCacheUpdate(updatersToRun); + } + } + } + + private void clearIndex(final ID indexId) throws StorageException { + final UpdatableIndex index = getIndex(indexId); + assert index != null; + index.clear(); + try { + IndexInfrastructure.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId)); + } + catch (IOException e) { + LOG.error(e); + } + } + + private Set getUnsavedOrTransactedDocuments() { + Set docs = new HashSet(Arrays.asList(myFileDocumentManager.getUnsavedDocuments())); + docs.addAll(myTransactionMap.keySet()); + return docs; + } + + private void indexUnsavedDocuments(ID indexId, @Nullable Project project, GlobalSearchScope filter) throws StorageException { + + if (myUpToDateIndices.contains(indexId)) { + return; // no need to index unsaved docs + } + + final Set documents = getUnsavedOrTransactedDocuments(); + if (!documents.isEmpty()) { + // now index unsaved data + final StorageGuard.Holder guard = setDataBufferingEnabled(true); + try { + final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId); + + assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId; + + boolean allDocsProcessed = true; + semaphore.down(); + try { + for (Document document : documents) { + allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter); + } + } + finally { + semaphore.up(); + + while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing + if (Thread.holdsLock(PsiLock.LOCK)) { + break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. + } + } + if (allDocsProcessed) { + myUpToDateIndices.add(indexId); // safe to set the flag here, becase it will be cleared under the WriteAction + } + } + } + finally { + guard.leave(); + } + } + } + + private interface DocumentContent { + String getText(); + long getModificationStamp(); + } + + private static class AuthenticContent implements DocumentContent { + private final Document myDocument; + + private AuthenticContent(final Document document) { + myDocument = document; + } + + public String getText() { + return myDocument.getText(); + } + + public long getModificationStamp() { + return myDocument.getModificationStamp(); + } + } + + private static class PsiContent implements DocumentContent { + private final Document myDocument; + private final PsiFile myFile; + + private PsiContent(final Document document, final PsiFile file) { + myDocument = document; + myFile = file; + } + + public String getText() { + if (myFile.getModificationStamp() != myDocument.getModificationStamp()) { + final ASTNode node = myFile.getNode(); + assert node != null; + return node.getText(); + } + return myDocument.getText(); + } + + public long getModificationStamp() { + return myFile.getModificationStamp(); + } + } + +// returns false if doc was not indexed because the file does not fit in scope + private boolean indexUnsavedDocument(final Document document, final ID requestedIndexId, final Project project, GlobalSearchScope filter) throws StorageException { + final VirtualFile vFile = myFileDocumentManager.getFile(document); + if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) { + return true; + } + if (filter != null && !filter.accept(vFile)) { + return false; + } + final PsiFile dominantContentFile = findDominantPsiForDocument(document, project); + + final DocumentContent content; + if (dominantContentFile != null && dominantContentFile.getModificationStamp() != document.getModificationStamp()) { + content = new PsiContent(document, dominantContentFile); + } + else { + content = new AuthenticContent(document); + } + + final long currentDocStamp = content.getModificationStamp(); + if (currentDocStamp != myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp).longValue()) { + final Ref exRef = new Ref(null); + ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { + public void run() { + try { + final FileContent newFc = new FileContent(vFile, content.getText(), vFile.getCharset()); + + if (dominantContentFile != null) { + dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, true); + newFc.putUserData(PSI_FILE, dominantContentFile); + } + + if (content instanceof AuthenticContent) { + newFc.putUserData(EDITOR_HIGHLIGHTER, document instanceof DocumentImpl ? ((DocumentImpl)document) .getEditorHighlighterForCachesBuilding() : null); + } + + if (getInputFilter(requestedIndexId).acceptInput(vFile)) { + newFc.putUserData(PROJECT, project); + final int inputId = Math.abs(getFileId(vFile)); + getIndex(requestedIndexId).update(inputId, newFc); + } + + if (dominantContentFile != null) { + dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, null); + } + } + catch (StorageException e) { + exRef.set(e); + } + } + }); + final StorageException storageException = exRef.get(); + if (storageException != null) { + throw storageException; + } + } + return true; + } + + public static final Key PSI_FILE = new Key("PSI for stubs"); + public static final Key EDITOR_HIGHLIGHTER = new Key("Editor"); + public static final Key PROJECT = new Key("Context project"); + public static final Key VIRTUAL_FILE = new Key("Context virtual file"); + + @Nullable + private PsiFile findDominantPsiForDocument(final Document document, @Nullable Project project) { + if (myTransactionMap.containsKey(document)) { + return myTransactionMap.get(document); + } + + return project == null? null : findLatestKnownPsiForUncomittedDocument(document, project); + } + + private final StorageGuard myStorageLock = new StorageGuard(); + + private StorageGuard.Holder setDataBufferingEnabled(final boolean enabled) { + final StorageGuard.Holder holder = myStorageLock.enter(enabled); + for (ID indexId : myIndices.keySet()) { + final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); + assert index != null; + final IndexStorage indexStorage = index.getStorage(); + ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled); + } + return holder; + } + + private void cleanupMemoryStorage() { + synchronized (myLastIndexedDocStamps) { + myLastIndexedDocStamps.clear(); + } + for (ID indexId : myIndices.keySet()) { + final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); + assert index != null; + final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage(); + index.getWriteLock().lock(); + try { + memStorage.clearMemoryMap(); + } + finally { + index.getWriteLock().unlock(); + } + memStorage.fireMemoryStorageCleared(); + } + } + + private void dropUnregisteredIndices() { + final Set indicesToDrop = readRegistsredIndexNames(); + for (ID key : myIndices.keySet()) { + indicesToDrop.remove(key.toString()); + } + for (String s : indicesToDrop) { + FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); + } + } + + public static void requestRebuild(ID indexId) { + requestRebuild(indexId, new Throwable()); + } + + public static void requestRebuild(ID indexId, Throwable throwable) { + cleanupProcessedFlag(); + LOG.info("Rebuild requested for index " + indexId, throwable); + ourRebuildStatus.get(indexId).set(REQUIRES_REBUILD); + } + + private UpdatableIndex getIndex(ID indexId) { + final Pair, InputFilter> pair = myIndices.get(indexId); + //noinspection unchecked + return pair != null? (UpdatableIndex)pair.getFirst() : null; + } + + private InputFilter getInputFilter(ID indexId) { + final Pair, InputFilter> pair = myIndices.get(indexId); + return pair != null? pair.getSecond() : null; + } + + public int getNumberOfPendingInvalidations() { + return myChangedFilesCollector.getNumberOfPendingInvalidations(); + } + + public Collection getFilesToUpdate(final Project project) { + return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition() { + public boolean value(VirtualFile virtualFile) { + for (IndexableFileSet set : myIndexableSets) { + final Project proj = myIndexableSetToProjectMap.get(set); + if (proj != null && !proj.equals(project)) { + continue; // skip this set as associated with a different project + } + if (set.isInSet(virtualFile)) { + return true; + } + } + return false; + } + }); + } + + public void processRefreshedFile(@NotNull Project project, final com.intellij.ide.caches.FileContent fileContent) { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + myChangedFilesCollector.processFileImpl(project, fileContent, false); + } + + public void indexFileContent(@Nullable Project project, com.intellij.ide.caches.FileContent content) { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + final VirtualFile file = content.getVirtualFile(); + FileContent fc = null; + + PsiFile psiFile = null; + + for (final ID indexId : myIndices.keySet()) { + if (shouldIndexFile(file, indexId)) { + if (fc == null) { + byte[] currentBytes; + try { + currentBytes = content.getBytes(); + } + catch (IOException e) { + currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY; + } + fc = new FileContent(file, currentBytes); + + psiFile = content.getUserData(PSI_FILE); + if (psiFile != null) { + psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true); + fc.putUserData(PSI_FILE, psiFile); + } + if (project == null) { + project = ProjectUtil.guessProjectForFile(file); + } + fc.putUserData(PROJECT, project); + } + + try { + ProgressManager.checkCanceled(); + updateSingleIndex(indexId, file, fc); + } + catch (ProcessCanceledException e) { + myChangedFilesCollector.scheduleForUpdate(file); + throw e; + } + catch (StorageException e) { + requestRebuild(indexId); + LOG.info(e); + } + } + } + + if (psiFile != null) { + psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null); + } + } + + private void updateSingleIndex(final ID indexId, final VirtualFile file, final FileContent currentFC) + throws StorageException { + if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) { + return; // the index is scheduled for rebuild, no need to update + } + myLocalModCount++; + + final StorageGuard.Holder lock = setDataBufferingEnabled(false); + + try { + final int inputId = Math.abs(getFileId(file)); + + final UpdatableIndex index = getIndex(indexId); + assert index != null; + + final Ref exRef = new Ref(null); + ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { + public void run() { + try { + index.update(inputId, currentFC); + } + catch (StorageException e) { + exRef.set(e); + } + } + }); + final StorageException storageException = exRef.get(); + if (storageException != null) { + throw storageException; + } + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + if (file.isValid()) { + if (currentFC != null) { + IndexingStamp.update(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)); + } + else { + // mark the file as unindexed + IndexingStamp.update(file, indexId, -1L); + } + } + } + }); + } + finally { + lock.leave(); + } + } + + public static int getFileId(final VirtualFile file) { + if (file instanceof VirtualFileWithId) { + return ((VirtualFileWithId)file).getId(); + } + + throw new IllegalArgumentException("Virtual file doesn't support id: " + file + ", implementation class: " + file.getClass().getName()); + } + + private boolean needsFileContentLoading(ID indexId) { + return !myNotRequiringContentIndices.contains(indexId); + } + + private abstract static class InvalidationTask implements Runnable { + private final VirtualFile mySubj; + + protected InvalidationTask(final VirtualFile subj) { + mySubj = subj; + } + + public VirtualFile getSubj() { + return mySubj; + } + } + + private final class ChangedFilesCollector extends VirtualFileAdapter { + private final Set myFilesToUpdate = new ConcurrentHashSet(); + private final Queue myFutureInvalidations = new ConcurrentLinkedQueue(); + + private final ManagingFS myManagingFS = ManagingFS.getInstance(); + // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact. + + public void fileCreated(final VirtualFileEvent event) { + markDirty(event); + } + + public void fileDeleted(final VirtualFileEvent event) { + myFilesToUpdate.remove(event.getFile()); // no need to update it anymore + } + + public void fileCopied(final VirtualFileCopyEvent event) { + markDirty(event); + } + + public void beforeFileDeletion(final VirtualFileEvent event) { + invalidateIndices(event.getFile(), false); + } + + public void beforeContentsChange(final VirtualFileEvent event) { + invalidateIndices(event.getFile(), true); + } + + public void contentsChanged(final VirtualFileEvent event) { + markDirty(event); + } + + public void beforePropertyChange(final VirtualFilePropertyEvent event) { + if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { + // indexes may depend on file name + final VirtualFile file = event.getFile(); + if (!file.isDirectory()) { + // name change may lead to filetype change so the file might become not indexable + // in general case have to 'unindex' the file and index it again if needed after the name has been changed + invalidateIndices(file, false); + } + } + } + + public void propertyChanged(final VirtualFilePropertyEvent event) { + if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { + // indexes may depend on file name + if (!event.getFile().isDirectory()) { + markDirty(event); + } + } + } + + private void markDirty(final VirtualFileEvent event) { + final VirtualFile eventFile = event.getFile(); + cleanProcessedFlag(eventFile); + iterateIndexableFiles(eventFile, new Processor() { + public boolean process(final VirtualFile file) { + FileContent fileContent = null; + // handle 'content-less' indices separately + for (ID indexId : myNotRequiringContentIndices) { + if (getInputFilter(indexId).acceptInput(file)) { + try { + if (fileContent == null) { + fileContent = new FileContent(file); + } + updateSingleIndex(indexId, file, fileContent); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + } + // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it + if (!isTooLarge(file)) { + for (ID indexId : myIndices.keySet()) { + if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) { + scheduleForUpdate(file); + break; // no need to iterate further, as the file is already marked + } + } + } + + return true; + } + }); + IndexingStamp.flushCache(); + } + + public void scheduleForUpdate(VirtualFile file) { + myFilesToUpdate.add(file); + } + + void invalidateIndices(final VirtualFile file, final boolean markForReindex) { + if (isUnderConfigOrSystem(file)) { + return; + } + if (file.isDirectory()) { + if (isMock(file) || myManagingFS.wereChildrenAccessed(file)) { + final Iterable children = file instanceof NewVirtualFile + ? ((NewVirtualFile)file).iterInDbChildren() : Arrays.asList(file.getChildren()); + for (VirtualFile child : children) { + invalidateIndices(child, markForReindex); + } + } + } + else { + cleanProcessedFlag(file); + IndexingStamp.flushCache(); + final List> affectedIndices = new ArrayList>(myIndices.size()); + + final boolean isTooLarge = isTooLarge(file); + for (final ID indexId : myIndices.keySet()) { + try { + if (!needsFileContentLoading(indexId)) { + if (shouldUpdateIndex(file, indexId)) { + updateSingleIndex(indexId, file, null); + } + } + else { // the index requires file content + if (!isTooLarge && shouldUpdateIndex(file, indexId)) { + affectedIndices.add(indexId); + } + } + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + + if (!affectedIndices.isEmpty()) { + if (markForReindex) { + // only mark the file as unindexed, reindex will be done lazily + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (ID indexId : affectedIndices) { + IndexingStamp.update(file, indexId, -1L); + } + } + }); + iterateIndexableFiles(file, new Processor() { + public boolean process(final VirtualFile file) { + scheduleForUpdate(file); + return true; + } + }); + } + else { + final InvalidationTask invalidator = new InvalidationTask(file) { + public void run() { + removeFileDataFromIndices(affectedIndices, file); + } + }; + myFutureInvalidations.offer(invalidator); + } + } + if (!markForReindex) { + myFilesToUpdate.remove(file); // no need to update it anymore + } + IndexingStamp.flushCache(); + } + } + + private void removeFileDataFromIndices(List> affectedIndices, VirtualFile file) { + Throwable unexpectedError = null; + for (ID indexId : affectedIndices) { + try { + updateSingleIndex(indexId, file, null); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + catch (ProcessCanceledException ignored) { + } + catch (Throwable e) { + LOG.info(e); + if (unexpectedError == null) { + unexpectedError = e; + } + } + } + IndexingStamp.flushCache(); + if (unexpectedError != null) { + LOG.error(unexpectedError); + } + } + + public int getNumberOfPendingInvalidations() { + return myFutureInvalidations.size(); + } + + public void ensureAllInvalidateTasksCompleted() { + final int size = getNumberOfPendingInvalidations(); + if (size == 0) return; + final ProgressIndicator current = ProgressManager.getInstance().getProgressIndicator(); + final ProgressIndicator indicator = current != null ? current : new EmptyProgressIndicator(); + indicator.setText(""); + int count = 0; + while (true) { + InvalidationTask task = myFutureInvalidations.poll(); + + if (task == null) { + break; + } + indicator.setFraction((double)count++ /size); + indicator.setText2(task.getSubj().getPresentableUrl()); + task.run(); + } + } + + private void iterateIndexableFiles(final VirtualFile file, final Processor processor) { + if (file.isDirectory()) { + final ContentIterator iterator = new ContentIterator() { + public boolean processFile(final VirtualFile fileOrDir) { + if (!fileOrDir.isDirectory()) { + processor.process(fileOrDir); + } + return true; + } + }; + + for (IndexableFileSet set : myIndexableSets) { + set.iterateIndexableFilesIn(file, iterator); + } + } + else { + for (IndexableFileSet set : myIndexableSets) { + if (set.isInSet(file)) { + processor.process(file); + break; + } + } + } + } + + public Collection getAllFilesToUpdate() { + if (myFilesToUpdate.isEmpty()) { + return Collections.emptyList(); + } + return new ArrayList(myFilesToUpdate); + } + + private final Semaphore myForceUpdateSemaphore = new Semaphore(); + + public void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, boolean onlyRemoveOutdatedData) { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + for (VirtualFile file: getAllFilesToUpdate()) { + if (filter == null || filter.accept(file)) { + try { + myForceUpdateSemaphore.down(); + // process only files that can affect result + processFileImpl(project, new com.intellij.ide.caches.FileContent(file), onlyRemoveOutdatedData); + } + finally { + myForceUpdateSemaphore.up(); + } + } + } + + // If several threads entered the method at the same time and there were files to update, + // all the threads should leave the method synchronously after all the files scheduled for update are reindexed, + // no matter which thread will do reindexing job. + // Thus we ensure that all the threads that entered the method will get the most recent data + + while (!myForceUpdateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing + if (Thread.holdsLock(PsiLock.LOCK)) { + break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. + } + } + } + + private void processFileImpl(Project project, final com.intellij.ide.caches.FileContent fileContent, boolean onlyRemoveOutdatedData) { + final VirtualFile file = fileContent.getVirtualFile(); + final boolean reallyRemoved = myFilesToUpdate.remove(file); + if (reallyRemoved && file.isValid()) { + if (onlyRemoveOutdatedData) { + // on shutdown there is no need to re-index the file, just remove outdated data from indices + final List> affected = new ArrayList>(); + for (final ID indexId : myIndices.keySet()) { + if (getInputFilter(indexId).acceptInput(file)) { + affected.add(indexId); + } + } + removeFileDataFromIndices(affected, file); + } + else { + indexFileContent(project, fileContent); + } + IndexingStamp.flushCache(); + } + } + } + + private class UnindexedFilesFinder implements CollectingContentIterator { + private final List myFiles = new ArrayList(); + private final ProgressIndicator myProgressIndicator; + + private UnindexedFilesFinder() { + myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); + } + + public List getFiles() { + return myFiles; + } + + public boolean processFile(final VirtualFile file) { + if (!file.isDirectory()) { + if (file instanceof NewVirtualFile && ((NewVirtualFile)file).getFlag(ALREADY_PROCESSED)) { + return true; + } + + if (file instanceof VirtualFileWithId) { + boolean oldStuff = true; + if (!isTooLarge(file)) { + for (ID indexId : myIndices.keySet()) { + try { + if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) { + myFiles.add(file); + oldStuff = false; + break; + } + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException || cause instanceof StorageException) { + LOG.info(e); + requestRebuild(indexId); + } + else { + throw e; + } + } + } + } + FileContent fileContent = null; + for (ID indexId : myNotRequiringContentIndices) { + if (shouldIndexFile(file, indexId)) { + oldStuff = false; + try { + if (fileContent == null) { + fileContent = new FileContent(file); + } + updateSingleIndex(indexId, file, fileContent); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + } + IndexingStamp.flushCache(); + + if (oldStuff && file instanceof NewVirtualFile) { + ((NewVirtualFile)file).setFlag(ALREADY_PROCESSED, true); + } + } + } + else { + if (myProgressIndicator != null) { + myProgressIndicator.setText("Scanning files to index"); + myProgressIndicator.setText2(file.getPresentableUrl()); + } + } + return true; + } + } + + private boolean shouldUpdateIndex(final VirtualFile file, final ID indexId) { + return getInputFilter(indexId).acceptInput(file) && + (isMock(file) || IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); + } + + private boolean shouldIndexFile(final VirtualFile file, final ID indexId) { + return getInputFilter(indexId).acceptInput(file) && + (isMock(file) || !IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); + } + + private boolean isUnderConfigOrSystem(VirtualFile file) { + final String filePath = file.getPath(); + return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) || + mySystemPath != null && FileUtil.startsWith(filePath, mySystemPath); + } + + private static boolean isMock(final VirtualFile file) { + return !(file instanceof NewVirtualFile); + } + + private boolean isTooLarge(VirtualFile file) { + if (SingleRootFileViewProvider.isTooLarge(file)) { + final FileType type = FileTypeManager.getInstance().getFileTypeByFile(file); + return !myNoLimitCheckTypes.contains(type); + } + return false; + } + + public CollectingContentIterator createContentIterator() { + return new UnindexedFilesFinder(); + } + + public void registerIndexableSet(IndexableFileSet set, @Nullable Project project) { + myIndexableSets.add(set); + myIndexableSetToProjectMap.put(set, project); + } + + public void removeIndexableSet(IndexableFileSet set) { + myChangedFilesCollector.forceUpdate(null, null, true); + myIndexableSets.remove(set); + myIndexableSetToProjectMap.remove(set); + } + + @Nullable + private static PsiFile findLatestKnownPsiForUncomittedDocument(Document doc, Project project) { + return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc); + } + + private static class IndexableFilesFilter implements InputFilter { + private final InputFilter myDelegate; + + private IndexableFilesFilter(InputFilter delegate) { + myDelegate = delegate; + } + + public boolean acceptInput(final VirtualFile file) { + return file instanceof VirtualFileWithId && myDelegate.acceptInput(file); + } + } + + private static void cleanupProcessedFlag() { + final VirtualFile[] roots = ManagingFS.getInstance().getRoots(); + for (VirtualFile root : roots) { + cleanProcessedFlag(root); + } + } + + private static void cleanProcessedFlag(final VirtualFile file) { + if (!(file instanceof NewVirtualFile)) return; + + final NewVirtualFile nvf = (NewVirtualFile)file; + if (file.isDirectory()) { + for (VirtualFile child : nvf.getCachedChildren()) { + cleanProcessedFlag(child); + } + } + else { +/* nvf.clearCachedFileType(); */ + nvf.setFlag(ALREADY_PROCESSED, false); + } + } + + public static void iterateIndexableFiles(final ContentIterator processor, Project project) { + if (project.isDisposed()) { + return; + } + final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + // iterate project content + projectFileIndex.iterateContent(processor); + + if (project.isDisposed()) { + return; + } + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + + Set visitedRoots = new HashSet(); + for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) { + //important not to depend on project here, to support per-project background reindex + // each client gives a project to FileBasedIndex + if (project.isDisposed()) { + return; + } + for (VirtualFile root : IndexableSetContributor.getRootsToIndex(provider, project)) { + if (visitedRoots.add(root)) { + iterateRecursively(root, processor, indicator); + } + } + } + + if (project.isDisposed()) { + return; + } + // iterate associated libraries + for (Module module : ModuleManager.getInstance(project).getModules()) { + if (module.isDisposed()) { + return; + } + OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries(); + for (OrderEntry orderEntry : orderEntries) { + if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { + if (orderEntry.isValid()) { + final VirtualFile[] libSources = orderEntry.getFiles(OrderRootType.SOURCES); + final VirtualFile[] libClasses = orderEntry.getFiles(OrderRootType.CLASSES); + for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) { + for (VirtualFile root : roots) { + if (visitedRoots.add(root)) { + iterateRecursively(root, processor, indicator); + } + } + } + } + } + } + } + } + + private static void iterateRecursively(@Nullable final VirtualFile root, final ContentIterator processor, ProgressIndicator indicator) { + if (root != null) { + if (indicator != null) { + indicator.setText2(root.getPresentableUrl()); + } + + if (root.isDirectory()) { + for (VirtualFile file : root.getChildren()) { + if (file.isDirectory()) { + iterateRecursively(file, processor, indicator); + } + else { + processor.processFile(file); + } + } + } else { + processor.processFile(root); + } + } + } + + private static class StorageGuard { + private int myHolds = 0; + + public interface Holder { + void leave(); + } + + private final Holder myTrueHolder = new Holder() { + public void leave() { + StorageGuard.this.leave(true); + } + }; + private final Holder myFalseHolder = new Holder() { + public void leave() { + StorageGuard.this.leave(false); + } + }; + + public synchronized Holder enter(boolean mode) { + if (mode) { + while (myHolds < 0) { + try { + wait(); + } + catch (InterruptedException ignored) { + } + } + myHolds++; + return myTrueHolder; + } + else { + while (myHolds > 0) { + try { + wait(); + } + catch (InterruptedException ignored) { + } + } + myHolds--; + return myFalseHolder; + } + } + + private synchronized void leave(boolean mode) { + myHolds += mode? -1 : 1; + if (myHolds == 0) { + notifyAll(); + } + } + + } +} diff --git a/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java b/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java index 34e29da2e676..72eec3aacfe7 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java @@ -1,179 +1,179 @@ -/* - * 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.util.indexing; - -import com.intellij.openapi.vfs.InvalidVirtualFileAccessException; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.newvfs.FileAttribute; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.util.containers.SLRUCache; -import com.intellij.util.io.DataInputOutputUtil; -import gnu.trove.TObjectLongHashMap; -import gnu.trove.TObjectLongProcedure; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -/** - * @author Eugene Zhuravlev - * Date: Dec 25, 2007 - */ -public class IndexingStamp { - private IndexingStamp() { - } - - /** - * The class is meant to be accessed from synchronized block only - */ - private static class Timestamps { - private static final FileAttribute PERSISTENCE = new FileAttribute("__index_stamps__", 1, false); - private TObjectLongHashMap> myIndexStamps; - private boolean myIsDirty = false; - - public Timestamps(@Nullable DataInputStream stream) throws IOException { - if (stream != null) { - try { - int count = DataInputOutputUtil.readINT(stream); - if (count > 0) { - myIndexStamps = new TObjectLongHashMap>(20); - for (int i = 0; i < count; i++) { - ID id = ID.findById(DataInputOutputUtil.readINT(stream)); - long timestamp = DataInputOutputUtil.readTIME(stream); - if (id != null) { - myIndexStamps.put(id, timestamp); - } - } - } - } - finally { - stream.close(); - } - } - } - - public void writeToStream(final DataOutputStream stream) throws IOException { - if (myIndexStamps != null) { - final int size = myIndexStamps.size(); - final int[] count = new int[]{0}; - DataInputOutputUtil.writeINT(stream, size); - myIndexStamps.forEachEntry(new TObjectLongProcedure>() { - public boolean execute(final ID id, final long timestamp) { - try { - DataInputOutputUtil.writeINT(stream, id.getUniqueId()); - DataInputOutputUtil.writeTIME(stream, timestamp); - count[0]++; - return true; - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - assert count[0] == size; - } - } - - public long get(ID id) { - return myIndexStamps != null? myIndexStamps.get(id) : 0L; - } - - public void set(ID id, long tmst) { - try { - if (myIndexStamps == null) { - myIndexStamps = new TObjectLongHashMap>(20); - } - myIndexStamps.put(id, tmst); - } - finally { - myIsDirty = true; - } - } - - public boolean isDirty() { - return myIsDirty; - } - } - - private static final SLRUCache myTimestampsCache = new SLRUCache(5, 5) { - @NotNull - public Timestamps createValue(final VirtualFile key) { - try { - final DataInputStream stream = Timestamps.PERSISTENCE.readAttribute(key); - return new Timestamps(stream); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - protected void onDropFromCache(final VirtualFile key, final Timestamps value) { - try { - if (value.isDirty()) { - final DataOutputStream sink = Timestamps.PERSISTENCE.writeAttribute(key); - value.writeToStream(sink); - sink.close(); - } - } - catch (InvalidVirtualFileAccessException ignored /*ok to ignore it here*/) { - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - }; - - public static boolean isFileIndexed(VirtualFile file, ID indexName, final long indexCreationStamp) { - try { - if (file instanceof NewVirtualFile && file.isValid()) { - synchronized (myTimestampsCache) { - return myTimestampsCache.get(file).get(indexName) == indexCreationStamp; - } - } - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (!(cause instanceof IOException)) { - throw e; // in case of IO exceptions consider file unindexed - } - } - - return false; - } - - public static void update(final VirtualFile file, final ID indexName, final long indexCreationStamp) { - try { - if (file instanceof NewVirtualFile && file.isValid()) { - synchronized (myTimestampsCache) { - myTimestampsCache.get(file).set(indexName, indexCreationStamp); - } - } - } - catch (InvalidVirtualFileAccessException ignored /*ok to ignore it here*/) { - } - } - - public static void flushCache() { - synchronized (myTimestampsCache) { - myTimestampsCache.clear(); - } - } - -} +/* + * 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.util.indexing; + +import com.intellij.openapi.vfs.InvalidVirtualFileAccessException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.FileAttribute; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.util.containers.SLRUCache; +import com.intellij.util.io.DataInputOutputUtil; +import gnu.trove.TObjectLongHashMap; +import gnu.trove.TObjectLongProcedure; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * @author Eugene Zhuravlev + * Date: Dec 25, 2007 + */ +public class IndexingStamp { + private IndexingStamp() { + } + + /** + * The class is meant to be accessed from synchronized block only + */ + private static class Timestamps { + private static final FileAttribute PERSISTENCE = new FileAttribute("__index_stamps__", 1, false); + private TObjectLongHashMap> myIndexStamps; + private boolean myIsDirty = false; + + public Timestamps(@Nullable DataInputStream stream) throws IOException { + if (stream != null) { + try { + int count = DataInputOutputUtil.readINT(stream); + if (count > 0) { + myIndexStamps = new TObjectLongHashMap>(20); + for (int i = 0; i < count; i++) { + ID id = ID.findById(DataInputOutputUtil.readINT(stream)); + long timestamp = DataInputOutputUtil.readTIME(stream); + if (id != null) { + myIndexStamps.put(id, timestamp); + } + } + } + } + finally { + stream.close(); + } + } + } + + public void writeToStream(final DataOutputStream stream) throws IOException { + if (myIndexStamps != null) { + final int size = myIndexStamps.size(); + final int[] count = new int[]{0}; + DataInputOutputUtil.writeINT(stream, size); + myIndexStamps.forEachEntry(new TObjectLongProcedure>() { + public boolean execute(final ID id, final long timestamp) { + try { + DataInputOutputUtil.writeINT(stream, id.getUniqueId()); + DataInputOutputUtil.writeTIME(stream, timestamp); + count[0]++; + return true; + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + assert count[0] == size; + } + } + + public long get(ID id) { + return myIndexStamps != null? myIndexStamps.get(id) : 0L; + } + + public void set(ID id, long tmst) { + try { + if (myIndexStamps == null) { + myIndexStamps = new TObjectLongHashMap>(20); + } + myIndexStamps.put(id, tmst); + } + finally { + myIsDirty = true; + } + } + + public boolean isDirty() { + return myIsDirty; + } + } + + private static final SLRUCache myTimestampsCache = new SLRUCache(5, 5) { + @NotNull + public Timestamps createValue(final VirtualFile key) { + try { + final DataInputStream stream = Timestamps.PERSISTENCE.readAttribute(key); + return new Timestamps(stream); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void onDropFromCache(final VirtualFile key, final Timestamps value) { + try { + if (value.isDirty()) { + final DataOutputStream sink = Timestamps.PERSISTENCE.writeAttribute(key); + value.writeToStream(sink); + sink.close(); + } + } + catch (InvalidVirtualFileAccessException ignored /*ok to ignore it here*/) { + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + + public static boolean isFileIndexed(VirtualFile file, ID indexName, final long indexCreationStamp) { + try { + if (file instanceof NewVirtualFile && file.isValid()) { + synchronized (myTimestampsCache) { + return myTimestampsCache.get(file).get(indexName) == indexCreationStamp; + } + } + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (!(cause instanceof IOException)) { + throw e; // in case of IO exceptions consider file unindexed + } + } + + return false; + } + + public static void update(final VirtualFile file, final ID indexName, final long indexCreationStamp) { + try { + if (file instanceof NewVirtualFile && file.isValid()) { + synchronized (myTimestampsCache) { + myTimestampsCache.get(file).set(indexName, indexCreationStamp); + } + } + } + catch (InvalidVirtualFileAccessException ignored /*ok to ignore it here*/) { + } + } + + public static void flushCache() { + synchronized (myTimestampsCache) { + myTimestampsCache.clear(); + } + } + +} diff --git a/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java b/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java index b1cb7791f0e7..e6bf49e10b4b 100644 --- a/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java +++ b/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java @@ -1,312 +1,312 @@ -/* - * 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.execution.process; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.vfs.encoding.EncodingManager; -import com.intellij.util.Consumer; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.nio.charset.Charset; -import java.util.concurrent.*; - -public class OSProcessHandler extends ProcessHandler { - private static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.OSProcessHandler"); - private final Process myProcess; - private final String myCommandLine; - - private final ProcessWaitFor myWaitFor; - - private static class ExecutorServiceHolder { - private static final ExecutorService ourThreadExecutorsService = createServiceImpl(); - - private static ThreadPoolExecutor createServiceImpl() { - return new ThreadPoolExecutor(10, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), new ThreadFactory() { - @SuppressWarnings({"HardCodedStringLiteral"}) - public Thread newThread(Runnable r) { - return new Thread(r, "OSProcessHandler pooled thread"); - } - }); - } - } - - /** - * Override this method in order to execute the task with a custom pool - * - * @param task a task to run - */ - protected Future executeOnPooledThread(Runnable task) { - final Application application = ApplicationManager.getApplication(); - - if (application != null) { - return application.executeOnPooledThread(task); - } - - return ExecutorServiceHolder.ourThreadExecutorsService.submit(task); - } - - public OSProcessHandler(final Process process, final String commandLine) { - myProcess = process; - myCommandLine = commandLine; - myWaitFor = new ProcessWaitFor(process); - } - - private class ProcessWaitFor { - private final Future myWaitForThreadFuture; - private final BlockingQueue> myTerminationCallback = new ArrayBlockingQueue>(1); - - public void detach() { - myWaitForThreadFuture.cancel(true); - } - - - public ProcessWaitFor(final Process process) { - myWaitForThreadFuture = executeOnPooledThread(new Runnable() { - public void run() { - int exitCode = 0; - try { - exitCode = process.waitFor(); - } - catch (InterruptedException ignored) { - } - finally { - try { - myTerminationCallback.take().consume(exitCode); - } - catch (InterruptedException e) { - // Ignore - } - } - } - }); - } - - public void setTerminationCallback(Consumer r) { - myTerminationCallback.offer(r); - } - } - - public Process getProcess() { - return myProcess; - } - - public void startNotify() { - final ReadProcessThread stdoutThread = new ReadProcessThread(createProcessOutReader()) { - protected void textAvailable(String s) { - notifyTextAvailable(s, ProcessOutputTypes.STDOUT); - } - }; - - final ReadProcessThread stderrThread = new ReadProcessThread(createProcessErrReader()) { - protected void textAvailable(String s) { - notifyTextAvailable(s, ProcessOutputTypes.STDERR); - } - }; - - notifyTextAvailable(myCommandLine + '\n', ProcessOutputTypes.SYSTEM); - - addProcessListener(new ProcessAdapter() { - public void startNotified(final ProcessEvent event) { - try { - final Future stdOutReadingFuture = executeOnPooledThread(stdoutThread); - final Future stdErrReadingFuture = executeOnPooledThread(stderrThread); - - myWaitFor.setTerminationCallback(new Consumer() { - @Override - public void consume(Integer exitCode) { - try { - // tell threads that no more attempts to read process' output should be made - stderrThread.setProcessTerminated(true); - stdoutThread.setProcessTerminated(true); - - stdErrReadingFuture.get(); - stdOutReadingFuture.get(); - } - catch (InterruptedException ignored) { - } - catch (ExecutionException e) { - LOG.error(e); - } - finally { - onOSProcessTerminated(exitCode); - } - } - }); - } - finally { - removeProcessListener(this); - } - } - }); - - super.startNotify(); - } - - protected void onOSProcessTerminated(final int exitCode) { - notifyProcessTerminated(exitCode); - } - - protected Reader createProcessOutReader() { - return new InputStreamReader(myProcess.getInputStream(), getCharset()); - } - - protected Reader createProcessErrReader() { - return new InputStreamReader(myProcess.getErrorStream(), getCharset()); - } - - protected void destroyProcessImpl() { - try { - closeStreams(); - } - finally { - myProcess.destroy(); - } - } - - protected void detachProcessImpl() { - final Runnable runnable = new Runnable() { - public void run() { - closeStreams(); - - myWaitFor.detach(); - notifyProcessDetached(); - } - }; - - executeOnPooledThread(runnable); - } - - private void closeStreams() { - try { - myProcess.getOutputStream().close(); - } - catch (IOException e) { - LOG.error(e); - } - } - - public boolean detachIsDefault() { - return false; - } - - public OutputStream getProcessInput() { - return myProcess.getOutputStream(); - } - - // todo: to remove - public String getCommandLine() { - return myCommandLine; - } - - - public Charset getCharset() { - return EncodingManager.getInstance().getDefaultCharset(); - } - - private abstract static class ReadProcessThread implements Runnable { - private final Reader myReader; - private boolean skipLF = false; - - private boolean myIsProcessTerminated = false; - private final char[] myBuffer = new char[8192]; - - public ReadProcessThread(final Reader reader) { - myReader = reader; - } - - public synchronized void setProcessTerminated(boolean isProcessTerminated) { - myIsProcessTerminated = isProcessTerminated; - } - - public void run() { - try { - while (true) { - final int rc = readAvailable(); - if (rc == DONE) break; - Thread.sleep(rc == READ_SOME ? 1L : 50L); - } - } - catch (InterruptedException ignore) { - } - catch (IOException e) { - LOG.info(e); - } - catch (Exception e) { - LOG.error(e); - } - } - - private static final int DONE = 0; - private static final int READ_SOME = 1; - private static final int READ_NONE = 2; - - private synchronized int readAvailable() throws IOException { - char[] buffer = myBuffer; - StringBuilder token = new StringBuilder(); - int rc = READ_NONE; - while (myReader.ready()) { - int n = myReader.read(buffer); - if (n <= 0) break; - rc = READ_SOME; - - for (int i = 0; i < n; i++) { - char c = buffer[i]; - if (skipLF && c != '\n') { - token.append('\r'); - } - - if (c == '\r') { - skipLF = true; - } - else { - skipLF = false; - token.append(c); - } - - if (c == '\n') { - textAvailable(token.toString()); - token.setLength(0); - } - } - } - - if (token.length() != 0) { - textAvailable(token.toString()); - token.setLength(0); - } - - if (myIsProcessTerminated) { - try { - myReader.close(); - } - catch (IOException e1) { - // supressed - } - - return DONE; - } - - return rc; - } - - protected abstract void textAvailable(final String s); - } -} +/* + * 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.execution.process; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.util.Consumer; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.concurrent.*; + +public class OSProcessHandler extends ProcessHandler { + private static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.OSProcessHandler"); + private final Process myProcess; + private final String myCommandLine; + + private final ProcessWaitFor myWaitFor; + + private static class ExecutorServiceHolder { + private static final ExecutorService ourThreadExecutorsService = createServiceImpl(); + + private static ThreadPoolExecutor createServiceImpl() { + return new ThreadPoolExecutor(10, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), new ThreadFactory() { + @SuppressWarnings({"HardCodedStringLiteral"}) + public Thread newThread(Runnable r) { + return new Thread(r, "OSProcessHandler pooled thread"); + } + }); + } + } + + /** + * Override this method in order to execute the task with a custom pool + * + * @param task a task to run + */ + protected Future executeOnPooledThread(Runnable task) { + final Application application = ApplicationManager.getApplication(); + + if (application != null) { + return application.executeOnPooledThread(task); + } + + return ExecutorServiceHolder.ourThreadExecutorsService.submit(task); + } + + public OSProcessHandler(final Process process, final String commandLine) { + myProcess = process; + myCommandLine = commandLine; + myWaitFor = new ProcessWaitFor(process); + } + + private class ProcessWaitFor { + private final Future myWaitForThreadFuture; + private final BlockingQueue> myTerminationCallback = new ArrayBlockingQueue>(1); + + public void detach() { + myWaitForThreadFuture.cancel(true); + } + + + public ProcessWaitFor(final Process process) { + myWaitForThreadFuture = executeOnPooledThread(new Runnable() { + public void run() { + int exitCode = 0; + try { + exitCode = process.waitFor(); + } + catch (InterruptedException ignored) { + } + finally { + try { + myTerminationCallback.take().consume(exitCode); + } + catch (InterruptedException e) { + // Ignore + } + } + } + }); + } + + public void setTerminationCallback(Consumer r) { + myTerminationCallback.offer(r); + } + } + + public Process getProcess() { + return myProcess; + } + + public void startNotify() { + final ReadProcessThread stdoutThread = new ReadProcessThread(createProcessOutReader()) { + protected void textAvailable(String s) { + notifyTextAvailable(s, ProcessOutputTypes.STDOUT); + } + }; + + final ReadProcessThread stderrThread = new ReadProcessThread(createProcessErrReader()) { + protected void textAvailable(String s) { + notifyTextAvailable(s, ProcessOutputTypes.STDERR); + } + }; + + notifyTextAvailable(myCommandLine + '\n', ProcessOutputTypes.SYSTEM); + + addProcessListener(new ProcessAdapter() { + public void startNotified(final ProcessEvent event) { + try { + final Future stdOutReadingFuture = executeOnPooledThread(stdoutThread); + final Future stdErrReadingFuture = executeOnPooledThread(stderrThread); + + myWaitFor.setTerminationCallback(new Consumer() { + @Override + public void consume(Integer exitCode) { + try { + // tell threads that no more attempts to read process' output should be made + stderrThread.setProcessTerminated(true); + stdoutThread.setProcessTerminated(true); + + stdErrReadingFuture.get(); + stdOutReadingFuture.get(); + } + catch (InterruptedException ignored) { + } + catch (ExecutionException e) { + LOG.error(e); + } + finally { + onOSProcessTerminated(exitCode); + } + } + }); + } + finally { + removeProcessListener(this); + } + } + }); + + super.startNotify(); + } + + protected void onOSProcessTerminated(final int exitCode) { + notifyProcessTerminated(exitCode); + } + + protected Reader createProcessOutReader() { + return new InputStreamReader(myProcess.getInputStream(), getCharset()); + } + + protected Reader createProcessErrReader() { + return new InputStreamReader(myProcess.getErrorStream(), getCharset()); + } + + protected void destroyProcessImpl() { + try { + closeStreams(); + } + finally { + myProcess.destroy(); + } + } + + protected void detachProcessImpl() { + final Runnable runnable = new Runnable() { + public void run() { + closeStreams(); + + myWaitFor.detach(); + notifyProcessDetached(); + } + }; + + executeOnPooledThread(runnable); + } + + private void closeStreams() { + try { + myProcess.getOutputStream().close(); + } + catch (IOException e) { + LOG.error(e); + } + } + + public boolean detachIsDefault() { + return false; + } + + public OutputStream getProcessInput() { + return myProcess.getOutputStream(); + } + + // todo: to remove + public String getCommandLine() { + return myCommandLine; + } + + + public Charset getCharset() { + return EncodingManager.getInstance().getDefaultCharset(); + } + + private abstract static class ReadProcessThread implements Runnable { + private final Reader myReader; + private boolean skipLF = false; + + private boolean myIsProcessTerminated = false; + private final char[] myBuffer = new char[8192]; + + public ReadProcessThread(final Reader reader) { + myReader = reader; + } + + public synchronized void setProcessTerminated(boolean isProcessTerminated) { + myIsProcessTerminated = isProcessTerminated; + } + + public void run() { + try { + while (true) { + final int rc = readAvailable(); + if (rc == DONE) break; + Thread.sleep(rc == READ_SOME ? 1L : 50L); + } + } + catch (InterruptedException ignore) { + } + catch (IOException e) { + LOG.info(e); + } + catch (Exception e) { + LOG.error(e); + } + } + + private static final int DONE = 0; + private static final int READ_SOME = 1; + private static final int READ_NONE = 2; + + private synchronized int readAvailable() throws IOException { + char[] buffer = myBuffer; + StringBuilder token = new StringBuilder(); + int rc = READ_NONE; + while (myReader.ready()) { + int n = myReader.read(buffer); + if (n <= 0) break; + rc = READ_SOME; + + for (int i = 0; i < n; i++) { + char c = buffer[i]; + if (skipLF && c != '\n') { + token.append('\r'); + } + + if (c == '\r') { + skipLF = true; + } + else { + skipLF = false; + token.append(c); + } + + if (c == '\n') { + textAvailable(token.toString()); + token.setLength(0); + } + } + } + + if (token.length() != 0) { + textAvailable(token.toString()); + token.setLength(0); + } + + if (myIsProcessTerminated) { + try { + myReader.close(); + } + catch (IOException e1) { + // supressed + } + + return DONE; + } + + return rc; + } + + protected abstract void textAvailable(final String s); + } +} diff --git a/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.form b/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.form index 381f42f0b8b0..05fabe029d3a 100644 --- a/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.form +++ b/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.form @@ -1,34 +1,34 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.java b/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.java index e7cada58c133..270104ca54e2 100644 --- a/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.java +++ b/platform/platform-api/src/com/intellij/execution/util/ExecutableDialog.java @@ -1,77 +1,77 @@ -// Copyright 2008-2010 Victor Iacoban -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -// either express or implied. See the License for the specific language governing permissions and -// limitations under the License. -package com.intellij.execution.util; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.ui.DocumentAdapter; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -public class ExecutableDialog extends DialogWrapper { - private JPanel myCenterPanel; - private ExecutablePathPanel myExecutablePath; - private JLabel myInfoLabel; - private final ExecutableValidator myExecutableValidator; - private String myErrorNotValidText; - - public ExecutableDialog(Project project, ExecutableValidator executableValidator) { - super(project, false); - myExecutableValidator = executableValidator; - myErrorNotValidText = executableValidator.getDialogErrorText(); - init(); - updateUI(); - setTitle(myExecutableValidator.getDialogTitle()); - myInfoLabel.setText(myExecutableValidator.getDialogDescription()); - } - - @Override - protected JComponent createCenterPanel() { - return myCenterPanel; - } - - // disable OK button if current executable is not valid - private void updateUI() { - final String path = myExecutablePath.getText(); - if (myExecutableValidator.isExecutableValid(path)) { - setErrorText(null); - getOKAction().setEnabled(true); - } else { - setErrorText(myErrorNotValidText); - getOKAction().setEnabled(false); - } - } - - private void createUIComponents() { - myExecutablePath = new ExecutablePathPanel(myExecutableValidator); - myExecutablePath.addOKListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - setErrorText(null); - updateUI(); - } - }); - myExecutablePath.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { - @Override - protected void textChanged(DocumentEvent e) { - updateUI(); - } - }); - } - - public String getPath() { - return myExecutablePath.getText(); - } +// Copyright 2008-2010 Victor Iacoban +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing permissions and +// limitations under the License. +package com.intellij.execution.util; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.ui.DocumentAdapter; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ExecutableDialog extends DialogWrapper { + private JPanel myCenterPanel; + private ExecutablePathPanel myExecutablePath; + private JLabel myInfoLabel; + private final ExecutableValidator myExecutableValidator; + private String myErrorNotValidText; + + public ExecutableDialog(Project project, ExecutableValidator executableValidator) { + super(project, false); + myExecutableValidator = executableValidator; + myErrorNotValidText = executableValidator.getDialogErrorText(); + init(); + updateUI(); + setTitle(myExecutableValidator.getDialogTitle()); + myInfoLabel.setText(myExecutableValidator.getDialogDescription()); + } + + @Override + protected JComponent createCenterPanel() { + return myCenterPanel; + } + + // disable OK button if current executable is not valid + private void updateUI() { + final String path = myExecutablePath.getText(); + if (myExecutableValidator.isExecutableValid(path)) { + setErrorText(null); + getOKAction().setEnabled(true); + } else { + setErrorText(myErrorNotValidText); + getOKAction().setEnabled(false); + } + } + + private void createUIComponents() { + myExecutablePath = new ExecutablePathPanel(myExecutableValidator); + myExecutablePath.addOKListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + setErrorText(null); + updateUI(); + } + }); + myExecutablePath.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + updateUI(); + } + }); + } + + public String getPath() { + return myExecutablePath.getText(); + } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/execution/util/ExecutablePathPanel.java b/platform/platform-api/src/com/intellij/execution/util/ExecutablePathPanel.java index 79bf0b466f82..d030a85746da 100644 --- a/platform/platform-api/src/com/intellij/execution/util/ExecutablePathPanel.java +++ b/platform/platform-api/src/com/intellij/execution/util/ExecutablePathPanel.java @@ -1,60 +1,60 @@ -/* - * 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.execution.util; - -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.intellij.openapi.vfs.VirtualFile; - -import java.awt.event.ActionListener; -import java.util.Collection; -import java.util.HashSet; - -/** - * HgSetExecutablePathPanel is a {@link com.intellij.openapi.ui.TextFieldWithBrowseButton}, which opens a file chooser - * to set path for executable and checks the validity of the selected file to be this executable. - * and checks validity of the selected file to be an executable. - */ -class ExecutablePathPanel extends TextFieldWithBrowseButton { - - private final Collection myOkListeners = new HashSet(); - - ExecutablePathPanel(final ExecutableValidator executableValidator) { - setText(executableValidator.getCurrentExecutable()); - FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) { - @Override - public boolean isFileSelectable(VirtualFile file) { - String path = file.getPath(); - if (!executableValidator.isExecutableValid(path)) { - return false; - } - for (ActionListener okListener : myOkListeners) { - okListener.actionPerformed(null); - } - return true; - } - }; - addBrowseFolderListener(executableValidator.getFileChooserTitle(), executableValidator.getFileChooserDescription(), null, descriptor); - } - - /** - * Adds a listener which will be called when file chooser dialog is closed successfully. - */ - void addOKListener(ActionListener listener) { - myOkListeners.add(listener); - } - -} +/* + * 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.execution.util; + +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vfs.VirtualFile; + +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.HashSet; + +/** + * HgSetExecutablePathPanel is a {@link com.intellij.openapi.ui.TextFieldWithBrowseButton}, which opens a file chooser + * to set path for executable and checks the validity of the selected file to be this executable. + * and checks validity of the selected file to be an executable. + */ +class ExecutablePathPanel extends TextFieldWithBrowseButton { + + private final Collection myOkListeners = new HashSet(); + + ExecutablePathPanel(final ExecutableValidator executableValidator) { + setText(executableValidator.getCurrentExecutable()); + FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) { + @Override + public boolean isFileSelectable(VirtualFile file) { + String path = file.getPath(); + if (!executableValidator.isExecutableValid(path)) { + return false; + } + for (ActionListener okListener : myOkListeners) { + okListener.actionPerformed(null); + } + return true; + } + }; + addBrowseFolderListener(executableValidator.getFileChooserTitle(), executableValidator.getFileChooserDescription(), null, descriptor); + } + + /** + * Adds a listener which will be called when file chooser dialog is closed successfully. + */ + void addOKListener(ActionListener listener) { + myOkListeners.add(listener); + } + +} diff --git a/platform/platform-api/src/com/intellij/execution/util/ExecutableValidator.java b/platform/platform-api/src/com/intellij/execution/util/ExecutableValidator.java index 08b872082b78..24eb05210eed 100644 --- a/platform/platform-api/src/com/intellij/execution/util/ExecutableValidator.java +++ b/platform/platform-api/src/com/intellij/execution/util/ExecutableValidator.java @@ -1,217 +1,217 @@ -/* - * 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.execution.util; - -import com.intellij.execution.configurations.GeneralCommandLine; -import com.intellij.execution.process.CapturingProcessHandler; -import com.intellij.execution.process.ProcessOutput; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ShowSettingsUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.event.HyperlinkEvent; - -/** - * Validates the supplied external executable. - * Shows notification or a dialog to fix it. - * @author Kirill Likhodedov - */ -public abstract class ExecutableValidator { - - private Notification myNotification; - protected final Project myProject; - private final String myNotificationGroupId; - private Configurable myConfigurable; - - private String myNotificationErrorTitle = "Executable not valid"; - private String myNotificationErrorDescription = "You haven't configured a valid executable. Fix"; - private String myDialogTitle = "Executable"; - private String myDialogDescription = "Specify the full path to the executable"; - private String myDialogErrorText = "It doesn't appear to be a valid executable"; - private String myFileChooserTitle = "Executable"; - private String myFileChooserDescription = "Specify the full path to the executable"; - - public ExecutableValidator(Project project, String notificationGroupId, @Nullable Configurable settingsConfigurable) { - myProject = project; - myNotificationGroupId = notificationGroupId; - myConfigurable = settingsConfigurable; - } - - /** - * Configures notification and dialog by setting text messages and titles specific to the whoever uses the validator. - * @param notificationErrorTitle title of the notification about not valid executable. - * @param notificationErrorDescription description of this notification with a link to fix it (link action is defined by - * {@link #notificationHyperlinkUpdate(com.intellij.notification.Notification, javax.swing.event.HyperlinkEvent)} - * @param dialogTitle - * @param dialogDescription - * @param dialogErrorText - * @param fileChooserTitle - */ - public void setMessagesAndTitles(String notificationErrorTitle, String notificationErrorDescription, - String dialogTitle, String dialogDescription, String dialogErrorText, - String fileChooserTitle, String fileChooserDescription) { - myNotificationErrorTitle = notificationErrorTitle; - myNotificationErrorDescription = notificationErrorDescription; - myDialogTitle = dialogTitle; - myDialogDescription = dialogDescription; - myDialogErrorText = dialogErrorText; - myFileChooserTitle = fileChooserTitle; - myFileChooserDescription = fileChooserDescription; - } - - /** - * Returns current executable persisted in the settings or elsewhere. - * @return Path to current executable. - */ - protected abstract String getCurrentExecutable(); - - /** - * Override this to save new (correct) executable path entered in the dialog. - * @param executable - */ - protected void saveCurrentExecutable(String executable) { - } - - /** - * Returns true if the supplied executable is valid. - * Default implementation: try to execute the given executable and test if output returned errors. - * Try to make this method fast, because it is used in the filechooser to disable files which are not valid. - * (default implementation is thus not fast). - * @param executable Path to executable. - * @return true if process with the supplied executable completed without errors and with exit code 0. - */ - @SuppressWarnings({"MethodMayBeStatic"}) - public boolean isExecutableValid(String executable) { - try { - GeneralCommandLine commandLine = new GeneralCommandLine(); - commandLine.setExePath(executable); - CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset()); - ProcessOutput result = handler.runProcess(60 * 1000); - return !result.isTimeout() && (result.getExitCode() != 0) && result.getStderr().isEmpty(); - } catch (Throwable e) { - return false; - } - } - - public boolean showDialog() { - ExecutableDialog dialog = new ExecutableDialog(myProject, this); - dialog.show(); - if (dialog.isOK()) { - saveCurrentExecutable(dialog.getPath()); - return true; - } else { // user pressed cancel - showExecutableNotConfiguredNotification(); - return false; - } - } - - public boolean checkExecutableAndShowDialogIfNeeded() { - if (!isExecutableValid(getCurrentExecutable())) { - return showDialog(); - } - return true; - } - - /** - * Shows a notification about not configured executable with a link to the Settings to fix it. - * Expires the notification if user fixes the path from the opened Settings dialog. - * Makes sure that there is always only one notification about the problem in the stack of notifications. - */ - public void showExecutableNotConfiguredNotification() { - if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) { - return; - } - - final Notification newNotification = new Notification(myNotificationGroupId, myNotificationErrorTitle, - myNotificationErrorDescription, NotificationType.ERROR, - new NotificationListener() { - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - notificationHyperlinkUpdate(notification, event); - } - }); - - // expire() needs to be called from AWT thread. - // we also want to be sure that previous notification expires before new one is shown (and assigned to myNotification). - UIUtil.invokeLaterIfNeeded(new Runnable() { - @Override - public void run() { - if (myNotification != null && !myNotification.isExpired()) { - // don't store this notification twice, but redisplay it again so that popup appears. - myNotification.expire(); - } - myNotification = newNotification; - Notifications.Bus.notify(myNotification, myProject); - } - }); - } - - /** - * The action which will be executed when the user presses "Fix" link in the notification description. - * By default it opens the Settings dialog on the page correspondent to the supplied configurable (e.g. Git configurable) - * and expires the notification after dialog is closed if executable was fixed in that dialog. - * One may override the method. - * Parameters are the same as in {@link com.intellij.notification.NotificationListener#hyperlinkUpdate(com.intellij.notification.Notification, javax.swing.event.HyperlinkEvent)} - */ - protected void notificationHyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - if (myConfigurable != null) { - ShowSettingsUtil.getInstance().showSettingsDialog(myProject, myConfigurable); - if (isExecutableValid(getCurrentExecutable())) { - notification.expire(); - } - } - } - - /** - * Checks if executable is valid and displays the notification if not. - * @return true if executable was valid, false - if not valid (and notification is shown in that case). - */ - public boolean checkExecutableAndNotifyIfNeeded() { - if (!isExecutableValid(getCurrentExecutable())) { - showExecutableNotConfiguredNotification(); - return false; - } - return true; - } - - String getDialogTitle() { - return myDialogTitle; - } - - String getDialogErrorText() { - return myDialogErrorText; - } - - String getFileChooserDescription() { - return myFileChooserDescription; - } - - String getFileChooserTitle() { - return myFileChooserTitle; - } - - public String getDialogDescription() { - return myDialogDescription; - } -} +/* + * 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.execution.util; + +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.process.CapturingProcessHandler; +import com.intellij.execution.process.ProcessOutput; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.event.HyperlinkEvent; + +/** + * Validates the supplied external executable. + * Shows notification or a dialog to fix it. + * @author Kirill Likhodedov + */ +public abstract class ExecutableValidator { + + private Notification myNotification; + protected final Project myProject; + private final String myNotificationGroupId; + private Configurable myConfigurable; + + private String myNotificationErrorTitle = "Executable not valid"; + private String myNotificationErrorDescription = "You haven't configured a valid executable. Fix"; + private String myDialogTitle = "Executable"; + private String myDialogDescription = "Specify the full path to the executable"; + private String myDialogErrorText = "It doesn't appear to be a valid executable"; + private String myFileChooserTitle = "Executable"; + private String myFileChooserDescription = "Specify the full path to the executable"; + + public ExecutableValidator(Project project, String notificationGroupId, @Nullable Configurable settingsConfigurable) { + myProject = project; + myNotificationGroupId = notificationGroupId; + myConfigurable = settingsConfigurable; + } + + /** + * Configures notification and dialog by setting text messages and titles specific to the whoever uses the validator. + * @param notificationErrorTitle title of the notification about not valid executable. + * @param notificationErrorDescription description of this notification with a link to fix it (link action is defined by + * {@link #notificationHyperlinkUpdate(com.intellij.notification.Notification, javax.swing.event.HyperlinkEvent)} + * @param dialogTitle + * @param dialogDescription + * @param dialogErrorText + * @param fileChooserTitle + */ + public void setMessagesAndTitles(String notificationErrorTitle, String notificationErrorDescription, + String dialogTitle, String dialogDescription, String dialogErrorText, + String fileChooserTitle, String fileChooserDescription) { + myNotificationErrorTitle = notificationErrorTitle; + myNotificationErrorDescription = notificationErrorDescription; + myDialogTitle = dialogTitle; + myDialogDescription = dialogDescription; + myDialogErrorText = dialogErrorText; + myFileChooserTitle = fileChooserTitle; + myFileChooserDescription = fileChooserDescription; + } + + /** + * Returns current executable persisted in the settings or elsewhere. + * @return Path to current executable. + */ + protected abstract String getCurrentExecutable(); + + /** + * Override this to save new (correct) executable path entered in the dialog. + * @param executable + */ + protected void saveCurrentExecutable(String executable) { + } + + /** + * Returns true if the supplied executable is valid. + * Default implementation: try to execute the given executable and test if output returned errors. + * Try to make this method fast, because it is used in the filechooser to disable files which are not valid. + * (default implementation is thus not fast). + * @param executable Path to executable. + * @return true if process with the supplied executable completed without errors and with exit code 0. + */ + @SuppressWarnings({"MethodMayBeStatic"}) + public boolean isExecutableValid(String executable) { + try { + GeneralCommandLine commandLine = new GeneralCommandLine(); + commandLine.setExePath(executable); + CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset()); + ProcessOutput result = handler.runProcess(60 * 1000); + return !result.isTimeout() && (result.getExitCode() != 0) && result.getStderr().isEmpty(); + } catch (Throwable e) { + return false; + } + } + + public boolean showDialog() { + ExecutableDialog dialog = new ExecutableDialog(myProject, this); + dialog.show(); + if (dialog.isOK()) { + saveCurrentExecutable(dialog.getPath()); + return true; + } else { // user pressed cancel + showExecutableNotConfiguredNotification(); + return false; + } + } + + public boolean checkExecutableAndShowDialogIfNeeded() { + if (!isExecutableValid(getCurrentExecutable())) { + return showDialog(); + } + return true; + } + + /** + * Shows a notification about not configured executable with a link to the Settings to fix it. + * Expires the notification if user fixes the path from the opened Settings dialog. + * Makes sure that there is always only one notification about the problem in the stack of notifications. + */ + public void showExecutableNotConfiguredNotification() { + if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) { + return; + } + + final Notification newNotification = new Notification(myNotificationGroupId, myNotificationErrorTitle, + myNotificationErrorDescription, NotificationType.ERROR, + new NotificationListener() { + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + notificationHyperlinkUpdate(notification, event); + } + }); + + // expire() needs to be called from AWT thread. + // we also want to be sure that previous notification expires before new one is shown (and assigned to myNotification). + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + if (myNotification != null && !myNotification.isExpired()) { + // don't store this notification twice, but redisplay it again so that popup appears. + myNotification.expire(); + } + myNotification = newNotification; + Notifications.Bus.notify(myNotification, myProject); + } + }); + } + + /** + * The action which will be executed when the user presses "Fix" link in the notification description. + * By default it opens the Settings dialog on the page correspondent to the supplied configurable (e.g. Git configurable) + * and expires the notification after dialog is closed if executable was fixed in that dialog. + * One may override the method. + * Parameters are the same as in {@link com.intellij.notification.NotificationListener#hyperlinkUpdate(com.intellij.notification.Notification, javax.swing.event.HyperlinkEvent)} + */ + protected void notificationHyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + if (myConfigurable != null) { + ShowSettingsUtil.getInstance().showSettingsDialog(myProject, myConfigurable); + if (isExecutableValid(getCurrentExecutable())) { + notification.expire(); + } + } + } + + /** + * Checks if executable is valid and displays the notification if not. + * @return true if executable was valid, false - if not valid (and notification is shown in that case). + */ + public boolean checkExecutableAndNotifyIfNeeded() { + if (!isExecutableValid(getCurrentExecutable())) { + showExecutableNotConfiguredNotification(); + return false; + } + return true; + } + + String getDialogTitle() { + return myDialogTitle; + } + + String getDialogErrorText() { + return myDialogErrorText; + } + + String getFileChooserDescription() { + return myFileChooserDescription; + } + + String getFileChooserTitle() { + return myFileChooserTitle; + } + + public String getDialogDescription() { + return myDialogDescription; + } +} diff --git a/platform/platform-api/src/com/intellij/ide/BrowserUtil.java b/platform/platform-api/src/com/intellij/ide/BrowserUtil.java index 1c5cc2ac4b34..9342135852d1 100644 --- a/platform/platform-api/src/com/intellij/ide/BrowserUtil.java +++ b/platform/platform-api/src/com/intellij/ide/BrowserUtil.java @@ -1,468 +1,468 @@ -/* - * 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.ide; - -import com.intellij.CommonBundle; -import com.intellij.openapi.application.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.ui.GuiUtils; -import com.intellij.util.io.ZipUtil; -import com.intellij.util.ui.OptionsDialog; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -public class BrowserUtil { - private static final Logger LOG = Logger.getInstance("#" + BrowserUtil.class.getName()); - - // The pattern for 'scheme' mainly according to RFC1738. - // We have to violate the RFC since we need to distinguish - // real schemes from local Windows paths; The only difference - // with RFC is that we do not allow schemes with length=1 (in other case - // local paths like "C:/temp/index.html" whould be erroneously interpreted as - // external URLs.) - @NonNls private static final Pattern ourExternalPrefix = Pattern.compile("^[\\w\\+\\.\\-]{2,}:"); - private static final Pattern ourAnchorsuffix = Pattern.compile("#(.*)$"); - - private BrowserUtil() { - } - - public static boolean isAbsoluteURL(String url) { - return ourExternalPrefix.matcher(url.toLowerCase()).find(); - } - - public static String getDocURL(String url) { - Matcher anchorMatcher = ourAnchorsuffix.matcher(url); - - if (anchorMatcher.find()) { - return anchorMatcher.reset().replaceAll(""); - } - - return url; - } - - @Nullable - public static URL getURL(String url) throws java.net.MalformedURLException { - if (!isAbsoluteURL(url)) { - return new URL("file", "", url); - } - - return VfsUtil.convertToURL(url); - } - - private static void launchBrowser(final String url, String[] command) { - try { - URL curl = getURL(url); - - if (curl != null) { - final String urlString = curl.toString(); - String[] commandLine; - if (SystemInfo.isWindows && isUseDefaultBrowser()) { - commandLine = new String[command.length + 2]; - System.arraycopy(command, 0, commandLine, 0, command.length); - commandLine[commandLine.length - 2] = "\"\""; - commandLine[commandLine.length - 1] = "\"" + redirectUrl(url, urlString) + "\""; - } - else { - commandLine = new String[command.length + 1]; - System.arraycopy(command, 0, commandLine, 0, command.length); - commandLine[commandLine.length - 1] = escapeUrl(urlString); - } - Runtime.getRuntime().exec(commandLine); - if (LOG.isDebugEnabled()) { - LOG.debug("Browser launched with command line: " + Arrays.toString(commandLine)); - } - } - else { - showErrorMessage(IdeBundle.message("error.malformed.url", url), CommonBundle.getErrorTitle()); - } - } - catch (final IOException e) { - showErrorMessage(IdeBundle.message("error.cannot.start.browser", e.getMessage()), - CommonBundle.getErrorTitle()); - } - } - - /** - * This method works around Windows 'start' command behaivor of dropping anchors from the url for local urls. - */ - private static String redirectUrl(String url, @NonNls String urlString) throws IOException { - if (url.indexOf('&') == -1 && (!urlString.startsWith("file:") || urlString.indexOf("#") == -1)) return urlString; - - File redirect = FileUtil.createTempFile("redirect", ".html"); - redirect.deleteOnExit(); - FileWriter writer = new FileWriter(redirect); - writer.write(""); - writer.close(); - return VfsUtil.pathToUrl(redirect.getAbsolutePath()); - } - - private static boolean isUseDefaultBrowser() { - Application application = ApplicationManager.getApplication(); - if (application == null) { - return true; - } - else { - return getGeneralSettingsInstance().isUseDefaultBrowser(); - } - } - - private static void showErrorMessage(final String message, final String title) { - final Application app = ApplicationManager.getApplication(); - if (app == null) { - return; // Not started yet. Not able to show message up. (Could happen in License panel under Linux). - } - - Runnable runnable = new Runnable() { - public void run() { - Messages.showMessageDialog(message, - title, - Messages.getErrorIcon()); - } - }; - - if (app.isDispatchThread()) { - runnable.run(); - } - else { - app.invokeLater(runnable, ModalityState.NON_MODAL); - } - } - - private static void launchBrowserUsingStandardWay(final String url) { - String[] command; - try { - String browserPath = getGeneralSettingsInstance().getBrowserPath(); - if (browserPath == null || browserPath.trim().length() == 0) { - showErrorMessage(IdeBundle.message("error.please.specify.path.to.web.browser"), - IdeBundle.message("title.browser.not.found")); - return; - } - - command = getOpenBrowserCommand(browserPath, url); - } - catch (NullPointerException e) { - // todo: fix the possible problem on startup, see SCR #35066 - command = getDefaultBrowserCommand(); - if (command == null) { - showErrorMessage(IdeBundle.message("error.please.open.url.manually", url, ApplicationNamesInfo.getInstance().getProductName()), - IdeBundle.message("title.browser.path.not.found")); - return; - } - } - // We do not need to check browserPath under Win32 - - launchBrowser(url, command); - } - - private static GeneralSettings getGeneralSettingsInstance() { - final GeneralSettings settings = GeneralSettings.getInstance(); - if (settings != null) return settings; - return new GeneralSettings(); - } - - private static boolean launchDefaultBrowserUsingJdk6Api(String sUrl) { - try { - URL url = getURL(sUrl); - if (url == null) return false; - Desktop.getDesktop().browse(url.toURI()); - LOG.debug("Browser launched using JDK 1.6 API"); - return true; - } - catch (Exception e) { - return false; - } - } - - @NotNull - public static String escapeUrl(@NotNull @NonNls String url) { - if (SystemInfo.isWindows) { - return (url.indexOf(' ') > 0 || url.indexOf('&') > -1) ? "\"" + url + "\"" : url; - } - else { - return url.replaceAll(" ", "%20"); - } - } - - private static String extractFiles(String url) { - try { - int sharpPos = url.indexOf('#'); - String anchor = ""; - if (sharpPos != -1) { - anchor = url.substring(sharpPos); - url = url.substring(0, sharpPos); - } - - VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url); - if (file == null || !(file.getFileSystem() instanceof JarFileSystem)) return null; - - JarFileSystem jarFileSystem = (JarFileSystem)file.getFileSystem(); - VirtualFile jarVirtualFile = jarFileSystem.getVirtualFileForJar(file); - if (jarVirtualFile == null) return null; - - String targetFilePath = file.getPath(); - String targetFileRelativePath = StringUtil.substringAfter(targetFilePath, JarFileSystem.JAR_SEPARATOR); - - String jarVirtualFileLocationHash = jarVirtualFile.getName() + Integer.toHexString(jarVirtualFile.getUrl().hashCode()); - final File outputDir = new File(getExtractedFilesDir(), jarVirtualFileLocationHash); - - final String currentTimestamp = String.valueOf(new File(jarVirtualFile.getPath()).lastModified()); - final File timestampFile = new File(outputDir, ".idea.timestamp"); - - String previousTimestamp = null; - if (timestampFile.exists()) { - previousTimestamp = new String(FileUtil.loadFileText(timestampFile)); - } - - if (!currentTimestamp.equals(previousTimestamp)) { - final Ref extract = new Ref(); - Runnable r = new Runnable() { - public void run() { - final ConfirmExtractDialog dialog = new ConfirmExtractDialog(); - if (dialog.isToBeShown()) { - dialog.show(); - extract.set(dialog.isOK()); - } else { - extract.set(true); - } - } - }; - - try { - GuiUtils.runOrInvokeAndWait(r); - } - catch (InvocationTargetException e) { - extract.set(false); - } - catch (InterruptedException e) { - extract.set(false); - } - - if (!extract.get()) return null; - - final ZipFile jarFile = jarFileSystem.getJarFile(file); - ZipEntry entry = jarFile.getEntry(targetFileRelativePath); - if (entry == null) return null; - InputStream is = jarFile.getInputStream(entry); - try { - ZipUtil.extractEntry(entry, is, outputDir); - } - finally { - is.close(); - } - - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - new Task.Backgroundable(null, "Extracting files...", true) { - public void run(@NotNull final ProgressIndicator indicator) { - final int size = jarFile.size(); - final int[] counter = new int[]{0}; - - class MyFilter implements FilenameFilter { - private final Set myImportantDirs = new HashSet( - Arrays.asList(outputDir, new File(outputDir, "resources"))); - private final boolean myImportantOnly; - - private MyFilter(boolean importantOnly) { - myImportantOnly = importantOnly; - } - - public boolean accept(File dir, String name) { - indicator.checkCanceled(); - boolean result = myImportantOnly == myImportantDirs.contains(dir); - if (result) { - indicator.setFraction(((double)counter[0]) / size); - counter[0]++; - } - return result; - } - } - - try { - ZipUtil.extract(jarFile, outputDir, new MyFilter(true)); - ZipUtil.extract(jarFile, outputDir, new MyFilter(false)); - FileUtil.writeToFile(timestampFile, currentTimestamp.getBytes()); - } - catch (IOException ignore) { - } - } - }.queue(); - } - }); - } - - return VfsUtil.pathToUrl(FileUtil.toSystemIndependentName(new File(outputDir, targetFileRelativePath).getPath())) + anchor; - } - catch (IOException e) { - LOG.warn(e); - Messages.showErrorDialog("Cannot extract files: " + e.getMessage(), "Error"); - return null; - } - } - - public static void clearExtractedFiles() { - FileUtil.delete(getExtractedFilesDir()); - } - - private static File getExtractedFilesDir() { - return new File(PathManager.getSystemPath(), "ExtractedFiles"); - } - - public static void launchBrowser(@NonNls String url) { - LOG.debug("Launch browser: " + url); - if (url.startsWith("jar:")) { - url = extractFiles(url); - if (url == null) return; - } - if (canStartDefaultBrowser() && isUseDefaultBrowser()) { - if (SystemInfo.isLinux && launchDefaultBrowserUsingJdk6Api(url)) { - return; - } - - launchBrowser(url, getDefaultBrowserCommand()); - } - else { - launchBrowserUsingStandardWay(url); - } - } - - @NonNls - private static String[] getDefaultBrowserCommand() { - if (SystemInfo.isWindows9x) { - return new String[]{"command.com", "/c", "start"}; - } - else if (SystemInfo.isWindows) { - return new String[]{"cmd.exe", "/c", "start"}; - } - else if (SystemInfo.isMac) { - return new String[]{"open"}; - } - else if (SystemInfo.isUnix) { - return new String[]{"mozilla"}; - } - else { - return null; - } - } - - public static boolean canStartDefaultBrowser() { - if (SystemInfo.isMac || SystemInfo.isWindows) { - return true; - } - - if (SystemInfo.isLinux && Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - return true; - } - - return false; - } - - public static String[] getOpenBrowserCommand(final @NonNls @NotNull String browserPath, final String... parameters) { - String[] command; - if (SystemInfo.isMac) { - if (parameters != null && parameters.length > 1) { - //open -a command doesn't support additional parameters - command = new String[] {browserPath}; - } - else { - command = new String[]{"open", "-a", browserPath}; - } - } - else if (SystemInfo.isWindows9x) { - if (browserPath.indexOf(File.separatorChar) != -1) { - command = new String[]{browserPath}; - } - else { - command = new String[]{"command.com", "/c", "start", browserPath}; - } - } - else if (SystemInfo.isWindows) { - if (browserPath.indexOf(File.separatorChar) != -1) { - command = new String[]{browserPath}; - } - else { - command = new String[]{"cmd.exe", "/c", "start", browserPath}; - } - } - else { - command = new String[]{browserPath}; - } - return command; - } - - private static class ConfirmExtractDialog extends OptionsDialog { - private ConfirmExtractDialog() { - super(null); - setTitle("Confirmation"); - init(); - } - - protected boolean isToBeShown() { - return getGeneralSettingsInstance().isConfirmExtractFiles(); - } - - protected void setToBeShown(boolean value, boolean onOk) { - getGeneralSettingsInstance().setConfirmExtractFiles(value); - } - - protected boolean shouldSaveOptionsOnCancel() { - return true; - } - - protected Action[] createActions() { - setOKButtonText(CommonBundle.getYesButtonText()); - return new Action[]{getOKAction(), getCancelAction()}; - } - - protected JComponent createCenterPanel() { - JPanel panel = new JPanel(new BorderLayout()); - String message = "The files are inside an archive, do you want them to be extracted?"; - JLabel label = new JLabel(message); - - label.setIconTextGap(10); - label.setIcon(Messages.getQuestionIcon()); - - panel.add(label, BorderLayout.CENTER); - panel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH); - - return panel; - } - } -} +/* + * 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.ide; + +import com.intellij.CommonBundle; +import com.intellij.openapi.application.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.ui.GuiUtils; +import com.intellij.util.io.ZipUtil; +import com.intellij.util.ui.OptionsDialog; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class BrowserUtil { + private static final Logger LOG = Logger.getInstance("#" + BrowserUtil.class.getName()); + + // The pattern for 'scheme' mainly according to RFC1738. + // We have to violate the RFC since we need to distinguish + // real schemes from local Windows paths; The only difference + // with RFC is that we do not allow schemes with length=1 (in other case + // local paths like "C:/temp/index.html" whould be erroneously interpreted as + // external URLs.) + @NonNls private static final Pattern ourExternalPrefix = Pattern.compile("^[\\w\\+\\.\\-]{2,}:"); + private static final Pattern ourAnchorsuffix = Pattern.compile("#(.*)$"); + + private BrowserUtil() { + } + + public static boolean isAbsoluteURL(String url) { + return ourExternalPrefix.matcher(url.toLowerCase()).find(); + } + + public static String getDocURL(String url) { + Matcher anchorMatcher = ourAnchorsuffix.matcher(url); + + if (anchorMatcher.find()) { + return anchorMatcher.reset().replaceAll(""); + } + + return url; + } + + @Nullable + public static URL getURL(String url) throws java.net.MalformedURLException { + if (!isAbsoluteURL(url)) { + return new URL("file", "", url); + } + + return VfsUtil.convertToURL(url); + } + + private static void launchBrowser(final String url, String[] command) { + try { + URL curl = getURL(url); + + if (curl != null) { + final String urlString = curl.toString(); + String[] commandLine; + if (SystemInfo.isWindows && isUseDefaultBrowser()) { + commandLine = new String[command.length + 2]; + System.arraycopy(command, 0, commandLine, 0, command.length); + commandLine[commandLine.length - 2] = "\"\""; + commandLine[commandLine.length - 1] = "\"" + redirectUrl(url, urlString) + "\""; + } + else { + commandLine = new String[command.length + 1]; + System.arraycopy(command, 0, commandLine, 0, command.length); + commandLine[commandLine.length - 1] = escapeUrl(urlString); + } + Runtime.getRuntime().exec(commandLine); + if (LOG.isDebugEnabled()) { + LOG.debug("Browser launched with command line: " + Arrays.toString(commandLine)); + } + } + else { + showErrorMessage(IdeBundle.message("error.malformed.url", url), CommonBundle.getErrorTitle()); + } + } + catch (final IOException e) { + showErrorMessage(IdeBundle.message("error.cannot.start.browser", e.getMessage()), + CommonBundle.getErrorTitle()); + } + } + + /** + * This method works around Windows 'start' command behaivor of dropping anchors from the url for local urls. + */ + private static String redirectUrl(String url, @NonNls String urlString) throws IOException { + if (url.indexOf('&') == -1 && (!urlString.startsWith("file:") || urlString.indexOf("#") == -1)) return urlString; + + File redirect = FileUtil.createTempFile("redirect", ".html"); + redirect.deleteOnExit(); + FileWriter writer = new FileWriter(redirect); + writer.write(""); + writer.close(); + return VfsUtil.pathToUrl(redirect.getAbsolutePath()); + } + + private static boolean isUseDefaultBrowser() { + Application application = ApplicationManager.getApplication(); + if (application == null) { + return true; + } + else { + return getGeneralSettingsInstance().isUseDefaultBrowser(); + } + } + + private static void showErrorMessage(final String message, final String title) { + final Application app = ApplicationManager.getApplication(); + if (app == null) { + return; // Not started yet. Not able to show message up. (Could happen in License panel under Linux). + } + + Runnable runnable = new Runnable() { + public void run() { + Messages.showMessageDialog(message, + title, + Messages.getErrorIcon()); + } + }; + + if (app.isDispatchThread()) { + runnable.run(); + } + else { + app.invokeLater(runnable, ModalityState.NON_MODAL); + } + } + + private static void launchBrowserUsingStandardWay(final String url) { + String[] command; + try { + String browserPath = getGeneralSettingsInstance().getBrowserPath(); + if (browserPath == null || browserPath.trim().length() == 0) { + showErrorMessage(IdeBundle.message("error.please.specify.path.to.web.browser"), + IdeBundle.message("title.browser.not.found")); + return; + } + + command = getOpenBrowserCommand(browserPath, url); + } + catch (NullPointerException e) { + // todo: fix the possible problem on startup, see SCR #35066 + command = getDefaultBrowserCommand(); + if (command == null) { + showErrorMessage(IdeBundle.message("error.please.open.url.manually", url, ApplicationNamesInfo.getInstance().getProductName()), + IdeBundle.message("title.browser.path.not.found")); + return; + } + } + // We do not need to check browserPath under Win32 + + launchBrowser(url, command); + } + + private static GeneralSettings getGeneralSettingsInstance() { + final GeneralSettings settings = GeneralSettings.getInstance(); + if (settings != null) return settings; + return new GeneralSettings(); + } + + private static boolean launchDefaultBrowserUsingJdk6Api(String sUrl) { + try { + URL url = getURL(sUrl); + if (url == null) return false; + Desktop.getDesktop().browse(url.toURI()); + LOG.debug("Browser launched using JDK 1.6 API"); + return true; + } + catch (Exception e) { + return false; + } + } + + @NotNull + public static String escapeUrl(@NotNull @NonNls String url) { + if (SystemInfo.isWindows) { + return (url.indexOf(' ') > 0 || url.indexOf('&') > -1) ? "\"" + url + "\"" : url; + } + else { + return url.replaceAll(" ", "%20"); + } + } + + private static String extractFiles(String url) { + try { + int sharpPos = url.indexOf('#'); + String anchor = ""; + if (sharpPos != -1) { + anchor = url.substring(sharpPos); + url = url.substring(0, sharpPos); + } + + VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url); + if (file == null || !(file.getFileSystem() instanceof JarFileSystem)) return null; + + JarFileSystem jarFileSystem = (JarFileSystem)file.getFileSystem(); + VirtualFile jarVirtualFile = jarFileSystem.getVirtualFileForJar(file); + if (jarVirtualFile == null) return null; + + String targetFilePath = file.getPath(); + String targetFileRelativePath = StringUtil.substringAfter(targetFilePath, JarFileSystem.JAR_SEPARATOR); + + String jarVirtualFileLocationHash = jarVirtualFile.getName() + Integer.toHexString(jarVirtualFile.getUrl().hashCode()); + final File outputDir = new File(getExtractedFilesDir(), jarVirtualFileLocationHash); + + final String currentTimestamp = String.valueOf(new File(jarVirtualFile.getPath()).lastModified()); + final File timestampFile = new File(outputDir, ".idea.timestamp"); + + String previousTimestamp = null; + if (timestampFile.exists()) { + previousTimestamp = new String(FileUtil.loadFileText(timestampFile)); + } + + if (!currentTimestamp.equals(previousTimestamp)) { + final Ref extract = new Ref(); + Runnable r = new Runnable() { + public void run() { + final ConfirmExtractDialog dialog = new ConfirmExtractDialog(); + if (dialog.isToBeShown()) { + dialog.show(); + extract.set(dialog.isOK()); + } else { + extract.set(true); + } + } + }; + + try { + GuiUtils.runOrInvokeAndWait(r); + } + catch (InvocationTargetException e) { + extract.set(false); + } + catch (InterruptedException e) { + extract.set(false); + } + + if (!extract.get()) return null; + + final ZipFile jarFile = jarFileSystem.getJarFile(file); + ZipEntry entry = jarFile.getEntry(targetFileRelativePath); + if (entry == null) return null; + InputStream is = jarFile.getInputStream(entry); + try { + ZipUtil.extractEntry(entry, is, outputDir); + } + finally { + is.close(); + } + + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + new Task.Backgroundable(null, "Extracting files...", true) { + public void run(@NotNull final ProgressIndicator indicator) { + final int size = jarFile.size(); + final int[] counter = new int[]{0}; + + class MyFilter implements FilenameFilter { + private final Set myImportantDirs = new HashSet( + Arrays.asList(outputDir, new File(outputDir, "resources"))); + private final boolean myImportantOnly; + + private MyFilter(boolean importantOnly) { + myImportantOnly = importantOnly; + } + + public boolean accept(File dir, String name) { + indicator.checkCanceled(); + boolean result = myImportantOnly == myImportantDirs.contains(dir); + if (result) { + indicator.setFraction(((double)counter[0]) / size); + counter[0]++; + } + return result; + } + } + + try { + ZipUtil.extract(jarFile, outputDir, new MyFilter(true)); + ZipUtil.extract(jarFile, outputDir, new MyFilter(false)); + FileUtil.writeToFile(timestampFile, currentTimestamp.getBytes()); + } + catch (IOException ignore) { + } + } + }.queue(); + } + }); + } + + return VfsUtil.pathToUrl(FileUtil.toSystemIndependentName(new File(outputDir, targetFileRelativePath).getPath())) + anchor; + } + catch (IOException e) { + LOG.warn(e); + Messages.showErrorDialog("Cannot extract files: " + e.getMessage(), "Error"); + return null; + } + } + + public static void clearExtractedFiles() { + FileUtil.delete(getExtractedFilesDir()); + } + + private static File getExtractedFilesDir() { + return new File(PathManager.getSystemPath(), "ExtractedFiles"); + } + + public static void launchBrowser(@NonNls String url) { + LOG.debug("Launch browser: " + url); + if (url.startsWith("jar:")) { + url = extractFiles(url); + if (url == null) return; + } + if (canStartDefaultBrowser() && isUseDefaultBrowser()) { + if (SystemInfo.isLinux && launchDefaultBrowserUsingJdk6Api(url)) { + return; + } + + launchBrowser(url, getDefaultBrowserCommand()); + } + else { + launchBrowserUsingStandardWay(url); + } + } + + @NonNls + private static String[] getDefaultBrowserCommand() { + if (SystemInfo.isWindows9x) { + return new String[]{"command.com", "/c", "start"}; + } + else if (SystemInfo.isWindows) { + return new String[]{"cmd.exe", "/c", "start"}; + } + else if (SystemInfo.isMac) { + return new String[]{"open"}; + } + else if (SystemInfo.isUnix) { + return new String[]{"mozilla"}; + } + else { + return null; + } + } + + public static boolean canStartDefaultBrowser() { + if (SystemInfo.isMac || SystemInfo.isWindows) { + return true; + } + + if (SystemInfo.isLinux && Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + return true; + } + + return false; + } + + public static String[] getOpenBrowserCommand(final @NonNls @NotNull String browserPath, final String... parameters) { + String[] command; + if (SystemInfo.isMac) { + if (parameters != null && parameters.length > 1) { + //open -a command doesn't support additional parameters + command = new String[] {browserPath}; + } + else { + command = new String[]{"open", "-a", browserPath}; + } + } + else if (SystemInfo.isWindows9x) { + if (browserPath.indexOf(File.separatorChar) != -1) { + command = new String[]{browserPath}; + } + else { + command = new String[]{"command.com", "/c", "start", browserPath}; + } + } + else if (SystemInfo.isWindows) { + if (browserPath.indexOf(File.separatorChar) != -1) { + command = new String[]{browserPath}; + } + else { + command = new String[]{"cmd.exe", "/c", "start", browserPath}; + } + } + else { + command = new String[]{browserPath}; + } + return command; + } + + private static class ConfirmExtractDialog extends OptionsDialog { + private ConfirmExtractDialog() { + super(null); + setTitle("Confirmation"); + init(); + } + + protected boolean isToBeShown() { + return getGeneralSettingsInstance().isConfirmExtractFiles(); + } + + protected void setToBeShown(boolean value, boolean onOk) { + getGeneralSettingsInstance().setConfirmExtractFiles(value); + } + + protected boolean shouldSaveOptionsOnCancel() { + return true; + } + + protected Action[] createActions() { + setOKButtonText(CommonBundle.getYesButtonText()); + return new Action[]{getOKAction(), getCancelAction()}; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout()); + String message = "The files are inside an archive, do you want them to be extracted?"; + JLabel label = new JLabel(message); + + label.setIconTextGap(10); + label.setIcon(Messages.getQuestionIcon()); + + panel.add(label, BorderLayout.CENTER); + panel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH); + + return panel; + } + } +} diff --git a/platform/platform-api/src/com/intellij/ide/GeneralSettings.java b/platform/platform-api/src/com/intellij/ide/GeneralSettings.java index 7e9d7457717c..142faa3a4637 100644 --- a/platform/platform-api/src/com/intellij/ide/GeneralSettings.java +++ b/platform/platform-api/src/com/intellij/ide/GeneralSettings.java @@ -1,513 +1,513 @@ -/* - * 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.ide; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.components.ExportableApplicationComponent; -import com.intellij.openapi.util.NamedJDOMExternalizable; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.vfs.encoding.EncodingManager; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; -import java.io.File; -import java.nio.charset.Charset; -import java.util.List; - -public class GeneralSettings implements NamedJDOMExternalizable, ExportableApplicationComponent { - @NonNls private static final String OPTION_INACTIVE_TIMEOUT = "inactiveTimeout"; - @NonNls public static final String PROP_INACTIVE_TIMEOUT = OPTION_INACTIVE_TIMEOUT; - private static final int DEFAULT_INACTIVE_TIMEOUT = 15; - - @NonNls private String myBrowserPath; - private boolean myShowTipsOnStartup = true; - private int myLastTip = 0; - private boolean myShowOccupiedMemory = false; - private boolean myReopenLastProject = true; - private boolean mySyncOnFrameActivation = true; - private boolean mySaveOnFrameDeactivation = true; - private boolean myAutoSaveIfInactive = false; // If true the IDEA automatically saves files if it is inactive for some seconds - private int myInactiveTimeout; // Number of seconds of inactivity after which IDEA automatically saves all files - private final PropertyChangeSupport myPropertyChangeSupport; - private boolean myUseDefaultBrowser = true; - private boolean myConfirmExtractFiles = true; - private String myLastProjectLocation; - private boolean mySearchInBackground; - private boolean myConfirmExit = true; - private int myConfirmOpenNewProject = -1; - @NonNls private static final String ELEMENT_OPTION = "option"; - @NonNls private static final String ATTRIBUTE_NAME = "name"; - @NonNls private static final String ATTRIBUTE_VALUE = "value"; - @NonNls private static final String OPTION_BROWSER_PATH = "browserPath"; - @NonNls private static final String OPTION_LAST_TIP = "lastTip"; - @NonNls private static final String OPTION_SHOW_TIPS_ON_STARTUP = "showTipsOnStartup"; - @NonNls private static final String OPTION_SHOW_OCCUPIED_MEMORY = "showOccupiedMemory"; - @NonNls private static final String OPTION_REOPEN_LAST_PROJECT = "reopenLastProject"; - @NonNls private static final String OPTION_AUTO_SYNC_FILES = "autoSyncFiles"; - @NonNls private static final String OPTION_AUTO_SAVE_FILES = "autoSaveFiles"; - @NonNls private static final String OPTION_AUTO_SAVE_IF_INACTIVE = "autoSaveIfInactive"; - @Deprecated - @NonNls private static final String OPTION_CHARSET = "charset"; - @Deprecated - @NonNls private static final String OPTION_UTFGUESSING = "UTFGuessing"; - - @NonNls private static final String OPTION_USE_DEFAULT_BROWSER = "useDefaultBrowser"; - @NonNls private static final String OPTION_CONFIRM_EXTRACT_FILES = "confirmExtractFiles"; - @NonNls private static final String OPTION_USE_CYCLIC_BUFFER = "useCyclicBuffer"; - @NonNls private static final String OPTION_SEARCH_IN_BACKGROUND = "searchInBackground"; - @NonNls private static final String OPTION_CONFIRM_EXIT = "confirmExit"; - @NonNls private static final String OPTION_CONFIRM_OPEN_NEW_PROJECT = "confirmOpenNewProject"; - @NonNls private static final String OPTION_CYCLIC_BUFFER_SIZE = "cyclicBufferSize"; - @NonNls private static final String OPTION_LAST_PROJECT_LOCATION = "lastProjectLocation"; - @Deprecated - private Charset myCharset; - @Deprecated - private boolean myUseUTFGuessing; - @Deprecated - private boolean oldCharsetSettingsHaveBeenRead; - - public static GeneralSettings getInstance(){ - return ApplicationManager.getApplication().getComponent(GeneralSettings.class); - } - - public GeneralSettings() { - myInactiveTimeout=DEFAULT_INACTIVE_TIMEOUT; - - if (SystemInfo.isWindows) { - myBrowserPath = "C:\\Program Files\\Internet Explorer\\IExplore.exe"; - } - else if (SystemInfo.isMac) { - myBrowserPath = "open"; - } - else { - myBrowserPath = ""; - } - - myPropertyChangeSupport = new PropertyChangeSupport(this); - } - - public void addPropertyChangeListener(PropertyChangeListener listener){ - myPropertyChangeSupport.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(PropertyChangeListener listener){ - myPropertyChangeSupport.removePropertyChangeListener(listener); - } - - public void initComponent() { } - - public void disposeComponent() { } - - public String getBrowserPath() { - return myBrowserPath; - } - - /** - * @return a path pointing to a directory where the last project was created or null if not available - */ - public String getLastProjectLocation() { - return myLastProjectLocation; - } - - public void setLastProjectLocation(String lastProjectLocation) { - myLastProjectLocation = lastProjectLocation; - } - - public void setBrowserPath(String browserPath) { - myBrowserPath = browserPath; - } - - public boolean showTipsOnStartup() { - return myShowTipsOnStartup; - } - - public void setShowTipsOnStartup(boolean b) { - myShowTipsOnStartup = b; - } - - public int getLastTip() { - return myLastTip; - } - - public void setLastTip(int i) { - myLastTip = i; - } - - public boolean isShowOccupiedMemory() { - return myShowOccupiedMemory; - } - - public boolean isReopenLastProject() { - return myReopenLastProject; - } - - public void setReopenLastProject(boolean reopenLastProject) { - myReopenLastProject = reopenLastProject; - } - - public boolean isSyncOnFrameActivation() { - return mySyncOnFrameActivation; - } - - public void setSyncOnFrameActivation(boolean syncOnFrameActivation) { - mySyncOnFrameActivation = syncOnFrameActivation; - } - - public boolean isSaveOnFrameDeactivation() { - return mySaveOnFrameDeactivation; - } - - public void setSaveOnFrameDeactivation(boolean saveOnFrameDeactivation) { - mySaveOnFrameDeactivation = saveOnFrameDeactivation; - } - - /** - * @return true if IDEA saves all files after "idle" timeout. - */ - public boolean isAutoSaveIfInactive(){ - return myAutoSaveIfInactive; - } - - public void setAutoSaveIfInactive(boolean autoSaveIfInactive) { - myAutoSaveIfInactive = autoSaveIfInactive; - } - - /** - * @return timeout in seconds after which IDEA saves all files if there was no user activity. - * The method always return non positive (more then zero) value. - */ - public int getInactiveTimeout(){ - return myInactiveTimeout; - } - - public void setInactiveTimeout(int inactiveTimeout) { - int oldInactiveTimeout = myInactiveTimeout; - - myInactiveTimeout = inactiveTimeout; - myPropertyChangeSupport.firePropertyChange( - PROP_INACTIVE_TIMEOUT, Integer.valueOf(oldInactiveTimeout), Integer.valueOf(inactiveTimeout) - ); - } - - //todo use DefaultExternalizer - public void readExternal(Element parentNode) { - List children = parentNode.getChildren(ELEMENT_OPTION); - for (final Object aChildren : children) { - Element element = (Element)aChildren; - - String name = element.getAttributeValue(ATTRIBUTE_NAME); - String value = element.getAttributeValue(ATTRIBUTE_VALUE); - - if (OPTION_BROWSER_PATH.equals(name)) { - myBrowserPath = value; - } - if (OPTION_LAST_TIP.equals(name)) { - try { - myLastTip = Integer.parseInt(value); - } - catch (NumberFormatException ex) { - myLastTip = 0; - } - } - if (OPTION_SHOW_TIPS_ON_STARTUP.equals(name)) { - try { - myShowTipsOnStartup = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - myShowTipsOnStartup = true; - } - } - if (OPTION_SHOW_OCCUPIED_MEMORY.equals(name)) { - try { - myShowOccupiedMemory = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - myShowOccupiedMemory = false; - } - } - if (OPTION_REOPEN_LAST_PROJECT.equals(name)) { - try { - myReopenLastProject = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - myReopenLastProject = true; - } - } - if (OPTION_AUTO_SYNC_FILES.equals(name)) { - try { - mySyncOnFrameActivation = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - mySyncOnFrameActivation = true; - } - } - if (OPTION_AUTO_SAVE_FILES.equals(name)) { - try { - mySaveOnFrameDeactivation = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - mySaveOnFrameDeactivation = true; - } - } - if (OPTION_AUTO_SAVE_IF_INACTIVE.equals(name) && value != null) { - myAutoSaveIfInactive = Boolean.valueOf(value).booleanValue(); - } - if (OPTION_INACTIVE_TIMEOUT.equals(name)) { - try { - int inactiveTimeout = Integer.parseInt(value); - if (inactiveTimeout > 0) { - myInactiveTimeout = inactiveTimeout; - } - } - catch (Exception ignored) { - } - } - if (OPTION_CHARSET.equals(name)) { - //for migration - myCharset = CharsetToolkit.forName(value); - oldCharsetSettingsHaveBeenRead = true; - } - if (OPTION_UTFGUESSING.equals(name)) { - myUseUTFGuessing = Boolean.valueOf(value).booleanValue(); - oldCharsetSettingsHaveBeenRead = true; - } - - if (OPTION_USE_DEFAULT_BROWSER.equals(name)) { - try { - myUseDefaultBrowser = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - myUseDefaultBrowser = true; - } - } - - if (OPTION_CONFIRM_EXTRACT_FILES.equals(name)) { - try { - myConfirmExtractFiles = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - myConfirmExtractFiles = true; - } - } - - if (OPTION_SEARCH_IN_BACKGROUND.equals(name)) { - try { - mySearchInBackground = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - mySearchInBackground = false; - } - } - - if (OPTION_CONFIRM_EXIT.equals(name)) { - try { - myConfirmExit = Boolean.valueOf(value).booleanValue(); - } - catch (Exception ex) { - myConfirmExit = false; - } - } - - if (OPTION_CONFIRM_OPEN_NEW_PROJECT.equals(name)) { - try { - myConfirmOpenNewProject = Integer.valueOf(value).intValue(); - } - catch (Exception ex) { - myConfirmOpenNewProject = -1; - } - } - - if (OPTION_LAST_PROJECT_LOCATION.equals(name)) { - try { - myLastProjectLocation = value; - } - catch (Exception ex) { - myLastProjectLocation = null; - } - } - } - } - - public void writeExternal(Element parentNode) { - if (myBrowserPath != null) { - Element element = new Element(ELEMENT_OPTION); - element.setAttribute(ATTRIBUTE_NAME, OPTION_BROWSER_PATH); - element.setAttribute(ATTRIBUTE_VALUE, myBrowserPath); - parentNode.addContent(element); - } - - Element optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_LAST_TIP); - optionElement.setAttribute(ATTRIBUTE_VALUE, Integer.toString(myLastTip)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_SHOW_TIPS_ON_STARTUP); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myShowTipsOnStartup)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_SHOW_OCCUPIED_MEMORY); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myShowOccupiedMemory)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_REOPEN_LAST_PROJECT); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myReopenLastProject)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_AUTO_SYNC_FILES); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(mySyncOnFrameActivation)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_AUTO_SAVE_FILES); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(mySaveOnFrameDeactivation)); - parentNode.addContent(optionElement); - - // AutoSave if inactive - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME,OPTION_AUTO_SAVE_IF_INACTIVE); - optionElement.setAttribute(ATTRIBUTE_VALUE,(myAutoSaveIfInactive?Boolean.TRUE:Boolean.FALSE).toString()); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME,OPTION_INACTIVE_TIMEOUT); - optionElement.setAttribute(ATTRIBUTE_VALUE,Integer.toString(myInactiveTimeout)); - parentNode.addContent(optionElement); - - // - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_USE_DEFAULT_BROWSER); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myUseDefaultBrowser)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_CONFIRM_EXTRACT_FILES); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myConfirmExtractFiles)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_SEARCH_IN_BACKGROUND); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(mySearchInBackground)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_CONFIRM_EXIT); - optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myConfirmExit)); - parentNode.addContent(optionElement); - - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_CONFIRM_OPEN_NEW_PROJECT); - optionElement.setAttribute(ATTRIBUTE_VALUE, Integer.toString(myConfirmOpenNewProject)); - parentNode.addContent(optionElement); - - if (myLastProjectLocation != null) { - optionElement = new Element(ELEMENT_OPTION); - optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_LAST_PROJECT_LOCATION); - optionElement.setAttribute(ATTRIBUTE_VALUE, myLastProjectLocation); - parentNode.addContent(optionElement); - } - } - - public String getExternalFileName() { - return "ide.general"; - } - - @NotNull - public File[] getExportFiles() { - return new File[]{PathManager.getOptionsFile(this)}; - } - - @NotNull - public String getPresentableName() { - return IdeBundle.message("general.settings"); - } - - @NotNull - public String getComponentName() { - return "GeneralSettings"; - } - - public boolean isUseDefaultBrowser() { - return myUseDefaultBrowser; - } - - public void setUseDefaultBrowser(boolean value) { - myUseDefaultBrowser = value; - } - - public boolean isConfirmExtractFiles() { - return myConfirmExtractFiles; - } - - public void setConfirmExtractFiles(boolean value) { - myConfirmExtractFiles = value; - } - - public boolean isConfirmExit() { - return myConfirmExit; - } - - public void setConfirmExit(boolean confirmExit) { - myConfirmExit = confirmExit; - } - - /** - * @return - *
    - *
  • 0 if new project should be opened in new window - *
  • 1 if new project should be opened in same window - *
  • -1 if a confirmation dialog should be shown - *
- */ - public int getConfirmOpenNewProject() { - return myConfirmOpenNewProject; - } - - public void setConfirmOpenNewProject(int confirmOpenNewProject) { - myConfirmOpenNewProject = confirmOpenNewProject; - } - - public static final int OPEN_PROJECT_ASK = -1; - public static final int OPEN_PROJECT_NEW_WINDOW = 0; - public static final int OPEN_PROJECT_SAME_WINDOW = 1; - - public boolean isSearchInBackground() { - return mySearchInBackground; - } - - public void setSearchInBackground(final boolean searchInBackground) { - mySearchInBackground = searchInBackground; - } - - // returns true if something has been migrated - public boolean migrateCharsetSettingsTo(EncodingManager encodingProjectManager) { - if (oldCharsetSettingsHaveBeenRead) { - encodingProjectManager.setEncoding(null, myCharset); - encodingProjectManager.setUseUTFGuessing(null, myUseUTFGuessing); - } - return oldCharsetSettingsHaveBeenRead; - } +/* + * 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.ide; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.components.ExportableApplicationComponent; +import com.intellij.openapi.util.NamedJDOMExternalizable; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.nio.charset.Charset; +import java.util.List; + +public class GeneralSettings implements NamedJDOMExternalizable, ExportableApplicationComponent { + @NonNls private static final String OPTION_INACTIVE_TIMEOUT = "inactiveTimeout"; + @NonNls public static final String PROP_INACTIVE_TIMEOUT = OPTION_INACTIVE_TIMEOUT; + private static final int DEFAULT_INACTIVE_TIMEOUT = 15; + + @NonNls private String myBrowserPath; + private boolean myShowTipsOnStartup = true; + private int myLastTip = 0; + private boolean myShowOccupiedMemory = false; + private boolean myReopenLastProject = true; + private boolean mySyncOnFrameActivation = true; + private boolean mySaveOnFrameDeactivation = true; + private boolean myAutoSaveIfInactive = false; // If true the IDEA automatically saves files if it is inactive for some seconds + private int myInactiveTimeout; // Number of seconds of inactivity after which IDEA automatically saves all files + private final PropertyChangeSupport myPropertyChangeSupport; + private boolean myUseDefaultBrowser = true; + private boolean myConfirmExtractFiles = true; + private String myLastProjectLocation; + private boolean mySearchInBackground; + private boolean myConfirmExit = true; + private int myConfirmOpenNewProject = -1; + @NonNls private static final String ELEMENT_OPTION = "option"; + @NonNls private static final String ATTRIBUTE_NAME = "name"; + @NonNls private static final String ATTRIBUTE_VALUE = "value"; + @NonNls private static final String OPTION_BROWSER_PATH = "browserPath"; + @NonNls private static final String OPTION_LAST_TIP = "lastTip"; + @NonNls private static final String OPTION_SHOW_TIPS_ON_STARTUP = "showTipsOnStartup"; + @NonNls private static final String OPTION_SHOW_OCCUPIED_MEMORY = "showOccupiedMemory"; + @NonNls private static final String OPTION_REOPEN_LAST_PROJECT = "reopenLastProject"; + @NonNls private static final String OPTION_AUTO_SYNC_FILES = "autoSyncFiles"; + @NonNls private static final String OPTION_AUTO_SAVE_FILES = "autoSaveFiles"; + @NonNls private static final String OPTION_AUTO_SAVE_IF_INACTIVE = "autoSaveIfInactive"; + @Deprecated + @NonNls private static final String OPTION_CHARSET = "charset"; + @Deprecated + @NonNls private static final String OPTION_UTFGUESSING = "UTFGuessing"; + + @NonNls private static final String OPTION_USE_DEFAULT_BROWSER = "useDefaultBrowser"; + @NonNls private static final String OPTION_CONFIRM_EXTRACT_FILES = "confirmExtractFiles"; + @NonNls private static final String OPTION_USE_CYCLIC_BUFFER = "useCyclicBuffer"; + @NonNls private static final String OPTION_SEARCH_IN_BACKGROUND = "searchInBackground"; + @NonNls private static final String OPTION_CONFIRM_EXIT = "confirmExit"; + @NonNls private static final String OPTION_CONFIRM_OPEN_NEW_PROJECT = "confirmOpenNewProject"; + @NonNls private static final String OPTION_CYCLIC_BUFFER_SIZE = "cyclicBufferSize"; + @NonNls private static final String OPTION_LAST_PROJECT_LOCATION = "lastProjectLocation"; + @Deprecated + private Charset myCharset; + @Deprecated + private boolean myUseUTFGuessing; + @Deprecated + private boolean oldCharsetSettingsHaveBeenRead; + + public static GeneralSettings getInstance(){ + return ApplicationManager.getApplication().getComponent(GeneralSettings.class); + } + + public GeneralSettings() { + myInactiveTimeout=DEFAULT_INACTIVE_TIMEOUT; + + if (SystemInfo.isWindows) { + myBrowserPath = "C:\\Program Files\\Internet Explorer\\IExplore.exe"; + } + else if (SystemInfo.isMac) { + myBrowserPath = "open"; + } + else { + myBrowserPath = ""; + } + + myPropertyChangeSupport = new PropertyChangeSupport(this); + } + + public void addPropertyChangeListener(PropertyChangeListener listener){ + myPropertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener){ + myPropertyChangeSupport.removePropertyChangeListener(listener); + } + + public void initComponent() { } + + public void disposeComponent() { } + + public String getBrowserPath() { + return myBrowserPath; + } + + /** + * @return a path pointing to a directory where the last project was created or null if not available + */ + public String getLastProjectLocation() { + return myLastProjectLocation; + } + + public void setLastProjectLocation(String lastProjectLocation) { + myLastProjectLocation = lastProjectLocation; + } + + public void setBrowserPath(String browserPath) { + myBrowserPath = browserPath; + } + + public boolean showTipsOnStartup() { + return myShowTipsOnStartup; + } + + public void setShowTipsOnStartup(boolean b) { + myShowTipsOnStartup = b; + } + + public int getLastTip() { + return myLastTip; + } + + public void setLastTip(int i) { + myLastTip = i; + } + + public boolean isShowOccupiedMemory() { + return myShowOccupiedMemory; + } + + public boolean isReopenLastProject() { + return myReopenLastProject; + } + + public void setReopenLastProject(boolean reopenLastProject) { + myReopenLastProject = reopenLastProject; + } + + public boolean isSyncOnFrameActivation() { + return mySyncOnFrameActivation; + } + + public void setSyncOnFrameActivation(boolean syncOnFrameActivation) { + mySyncOnFrameActivation = syncOnFrameActivation; + } + + public boolean isSaveOnFrameDeactivation() { + return mySaveOnFrameDeactivation; + } + + public void setSaveOnFrameDeactivation(boolean saveOnFrameDeactivation) { + mySaveOnFrameDeactivation = saveOnFrameDeactivation; + } + + /** + * @return true if IDEA saves all files after "idle" timeout. + */ + public boolean isAutoSaveIfInactive(){ + return myAutoSaveIfInactive; + } + + public void setAutoSaveIfInactive(boolean autoSaveIfInactive) { + myAutoSaveIfInactive = autoSaveIfInactive; + } + + /** + * @return timeout in seconds after which IDEA saves all files if there was no user activity. + * The method always return non positive (more then zero) value. + */ + public int getInactiveTimeout(){ + return myInactiveTimeout; + } + + public void setInactiveTimeout(int inactiveTimeout) { + int oldInactiveTimeout = myInactiveTimeout; + + myInactiveTimeout = inactiveTimeout; + myPropertyChangeSupport.firePropertyChange( + PROP_INACTIVE_TIMEOUT, Integer.valueOf(oldInactiveTimeout), Integer.valueOf(inactiveTimeout) + ); + } + + //todo use DefaultExternalizer + public void readExternal(Element parentNode) { + List children = parentNode.getChildren(ELEMENT_OPTION); + for (final Object aChildren : children) { + Element element = (Element)aChildren; + + String name = element.getAttributeValue(ATTRIBUTE_NAME); + String value = element.getAttributeValue(ATTRIBUTE_VALUE); + + if (OPTION_BROWSER_PATH.equals(name)) { + myBrowserPath = value; + } + if (OPTION_LAST_TIP.equals(name)) { + try { + myLastTip = Integer.parseInt(value); + } + catch (NumberFormatException ex) { + myLastTip = 0; + } + } + if (OPTION_SHOW_TIPS_ON_STARTUP.equals(name)) { + try { + myShowTipsOnStartup = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + myShowTipsOnStartup = true; + } + } + if (OPTION_SHOW_OCCUPIED_MEMORY.equals(name)) { + try { + myShowOccupiedMemory = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + myShowOccupiedMemory = false; + } + } + if (OPTION_REOPEN_LAST_PROJECT.equals(name)) { + try { + myReopenLastProject = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + myReopenLastProject = true; + } + } + if (OPTION_AUTO_SYNC_FILES.equals(name)) { + try { + mySyncOnFrameActivation = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + mySyncOnFrameActivation = true; + } + } + if (OPTION_AUTO_SAVE_FILES.equals(name)) { + try { + mySaveOnFrameDeactivation = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + mySaveOnFrameDeactivation = true; + } + } + if (OPTION_AUTO_SAVE_IF_INACTIVE.equals(name) && value != null) { + myAutoSaveIfInactive = Boolean.valueOf(value).booleanValue(); + } + if (OPTION_INACTIVE_TIMEOUT.equals(name)) { + try { + int inactiveTimeout = Integer.parseInt(value); + if (inactiveTimeout > 0) { + myInactiveTimeout = inactiveTimeout; + } + } + catch (Exception ignored) { + } + } + if (OPTION_CHARSET.equals(name)) { + //for migration + myCharset = CharsetToolkit.forName(value); + oldCharsetSettingsHaveBeenRead = true; + } + if (OPTION_UTFGUESSING.equals(name)) { + myUseUTFGuessing = Boolean.valueOf(value).booleanValue(); + oldCharsetSettingsHaveBeenRead = true; + } + + if (OPTION_USE_DEFAULT_BROWSER.equals(name)) { + try { + myUseDefaultBrowser = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + myUseDefaultBrowser = true; + } + } + + if (OPTION_CONFIRM_EXTRACT_FILES.equals(name)) { + try { + myConfirmExtractFiles = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + myConfirmExtractFiles = true; + } + } + + if (OPTION_SEARCH_IN_BACKGROUND.equals(name)) { + try { + mySearchInBackground = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + mySearchInBackground = false; + } + } + + if (OPTION_CONFIRM_EXIT.equals(name)) { + try { + myConfirmExit = Boolean.valueOf(value).booleanValue(); + } + catch (Exception ex) { + myConfirmExit = false; + } + } + + if (OPTION_CONFIRM_OPEN_NEW_PROJECT.equals(name)) { + try { + myConfirmOpenNewProject = Integer.valueOf(value).intValue(); + } + catch (Exception ex) { + myConfirmOpenNewProject = -1; + } + } + + if (OPTION_LAST_PROJECT_LOCATION.equals(name)) { + try { + myLastProjectLocation = value; + } + catch (Exception ex) { + myLastProjectLocation = null; + } + } + } + } + + public void writeExternal(Element parentNode) { + if (myBrowserPath != null) { + Element element = new Element(ELEMENT_OPTION); + element.setAttribute(ATTRIBUTE_NAME, OPTION_BROWSER_PATH); + element.setAttribute(ATTRIBUTE_VALUE, myBrowserPath); + parentNode.addContent(element); + } + + Element optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_LAST_TIP); + optionElement.setAttribute(ATTRIBUTE_VALUE, Integer.toString(myLastTip)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_SHOW_TIPS_ON_STARTUP); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myShowTipsOnStartup)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_SHOW_OCCUPIED_MEMORY); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myShowOccupiedMemory)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_REOPEN_LAST_PROJECT); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myReopenLastProject)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_AUTO_SYNC_FILES); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(mySyncOnFrameActivation)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_AUTO_SAVE_FILES); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(mySaveOnFrameDeactivation)); + parentNode.addContent(optionElement); + + // AutoSave if inactive + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME,OPTION_AUTO_SAVE_IF_INACTIVE); + optionElement.setAttribute(ATTRIBUTE_VALUE,(myAutoSaveIfInactive?Boolean.TRUE:Boolean.FALSE).toString()); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME,OPTION_INACTIVE_TIMEOUT); + optionElement.setAttribute(ATTRIBUTE_VALUE,Integer.toString(myInactiveTimeout)); + parentNode.addContent(optionElement); + + // + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_USE_DEFAULT_BROWSER); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myUseDefaultBrowser)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_CONFIRM_EXTRACT_FILES); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myConfirmExtractFiles)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_SEARCH_IN_BACKGROUND); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(mySearchInBackground)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_CONFIRM_EXIT); + optionElement.setAttribute(ATTRIBUTE_VALUE, Boolean.toString(myConfirmExit)); + parentNode.addContent(optionElement); + + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_CONFIRM_OPEN_NEW_PROJECT); + optionElement.setAttribute(ATTRIBUTE_VALUE, Integer.toString(myConfirmOpenNewProject)); + parentNode.addContent(optionElement); + + if (myLastProjectLocation != null) { + optionElement = new Element(ELEMENT_OPTION); + optionElement.setAttribute(ATTRIBUTE_NAME, OPTION_LAST_PROJECT_LOCATION); + optionElement.setAttribute(ATTRIBUTE_VALUE, myLastProjectLocation); + parentNode.addContent(optionElement); + } + } + + public String getExternalFileName() { + return "ide.general"; + } + + @NotNull + public File[] getExportFiles() { + return new File[]{PathManager.getOptionsFile(this)}; + } + + @NotNull + public String getPresentableName() { + return IdeBundle.message("general.settings"); + } + + @NotNull + public String getComponentName() { + return "GeneralSettings"; + } + + public boolean isUseDefaultBrowser() { + return myUseDefaultBrowser; + } + + public void setUseDefaultBrowser(boolean value) { + myUseDefaultBrowser = value; + } + + public boolean isConfirmExtractFiles() { + return myConfirmExtractFiles; + } + + public void setConfirmExtractFiles(boolean value) { + myConfirmExtractFiles = value; + } + + public boolean isConfirmExit() { + return myConfirmExit; + } + + public void setConfirmExit(boolean confirmExit) { + myConfirmExit = confirmExit; + } + + /** + * @return + *
    + *
  • 0 if new project should be opened in new window + *
  • 1 if new project should be opened in same window + *
  • -1 if a confirmation dialog should be shown + *
+ */ + public int getConfirmOpenNewProject() { + return myConfirmOpenNewProject; + } + + public void setConfirmOpenNewProject(int confirmOpenNewProject) { + myConfirmOpenNewProject = confirmOpenNewProject; + } + + public static final int OPEN_PROJECT_ASK = -1; + public static final int OPEN_PROJECT_NEW_WINDOW = 0; + public static final int OPEN_PROJECT_SAME_WINDOW = 1; + + public boolean isSearchInBackground() { + return mySearchInBackground; + } + + public void setSearchInBackground(final boolean searchInBackground) { + mySearchInBackground = searchInBackground; + } + + // returns true if something has been migrated + public boolean migrateCharsetSettingsTo(EncodingManager encodingProjectManager) { + if (oldCharsetSettingsHaveBeenRead) { + encodingProjectManager.setEncoding(null, myCharset); + encodingProjectManager.setUseUTFGuessing(null, myUseUTFGuessing); + } + return oldCharsetSettingsHaveBeenRead; + } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/ide/caches/FileContent.java b/platform/platform-api/src/com/intellij/ide/caches/FileContent.java index 7e85ebc51201..980970fc1955 100644 --- a/platform/platform-api/src/com/intellij/ide/caches/FileContent.java +++ b/platform/platform-api/src/com/intellij/ide/caches/FileContent.java @@ -1,84 +1,84 @@ -/* - * 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.ide.caches; - -import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.openapi.vfs.VirtualFile; - -import java.io.IOException; - -/** - * @author max - */ -public class FileContent extends UserDataHolderBase { - private static final byte[] EMPTY_CONTENT = new byte[0]; - - private final VirtualFile myVirtualFile; - private byte[] myCachedBytes; - private long myCachedLength = -1; - private long myCachedTimeStamp = -1; - private Boolean myCachedWritable; - - public FileContent(VirtualFile virtualFile) { - myVirtualFile = virtualFile; - } - - public void cache() throws IOException { - getBytes(); - getLength(); - getTimeStamp(); - isWritable(); - } - - public byte[] getBytes() throws IOException { - if (myCachedBytes == null) { - myCachedBytes = myVirtualFile.contentsToByteArray(false); - } - - return myCachedBytes; - } - - public void setEmptyContent() { - myCachedBytes = EMPTY_CONTENT; - myCachedLength = 0; - } - - public VirtualFile getVirtualFile() { - return myVirtualFile; - } - - public long getLength() { - if (myCachedLength == -1) { - myCachedLength = myVirtualFile.getLength(); - } - return myCachedLength; - } - - public long getTimeStamp() { - if (myCachedTimeStamp == -1) { - myCachedTimeStamp = myVirtualFile.getTimeStamp(); - } - return myCachedTimeStamp; - } - - public boolean isWritable() { - if (myCachedWritable == null) { - myCachedWritable = myVirtualFile.isWritable(); - } - return myCachedWritable == Boolean.TRUE; - } -} +/* + * 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.ide.caches; + +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.IOException; + +/** + * @author max + */ +public class FileContent extends UserDataHolderBase { + private static final byte[] EMPTY_CONTENT = new byte[0]; + + private final VirtualFile myVirtualFile; + private byte[] myCachedBytes; + private long myCachedLength = -1; + private long myCachedTimeStamp = -1; + private Boolean myCachedWritable; + + public FileContent(VirtualFile virtualFile) { + myVirtualFile = virtualFile; + } + + public void cache() throws IOException { + getBytes(); + getLength(); + getTimeStamp(); + isWritable(); + } + + public byte[] getBytes() throws IOException { + if (myCachedBytes == null) { + myCachedBytes = myVirtualFile.contentsToByteArray(false); + } + + return myCachedBytes; + } + + public void setEmptyContent() { + myCachedBytes = EMPTY_CONTENT; + myCachedLength = 0; + } + + public VirtualFile getVirtualFile() { + return myVirtualFile; + } + + public long getLength() { + if (myCachedLength == -1) { + myCachedLength = myVirtualFile.getLength(); + } + return myCachedLength; + } + + public long getTimeStamp() { + if (myCachedTimeStamp == -1) { + myCachedTimeStamp = myVirtualFile.getTimeStamp(); + } + return myCachedTimeStamp; + } + + public boolean isWritable() { + if (myCachedWritable == null) { + myCachedWritable = myVirtualFile.isWritable(); + } + return myCachedWritable == Boolean.TRUE; + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/components/ComponentManager.java b/platform/platform-api/src/com/intellij/openapi/components/ComponentManager.java index a92fc754bd93..12c201ac33f0 100644 --- a/platform/platform-api/src/com/intellij/openapi/components/ComponentManager.java +++ b/platform/platform-api/src/com/intellij/openapi/components/ComponentManager.java @@ -1,109 +1,109 @@ -/* - * 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.components; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.UserDataHolder; -import com.intellij.util.messages.MessageBus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.picocontainer.PicoContainer; - -/** - * Provides access to components. Servers as a base interface for {@link com.intellij.openapi.application.Application} - * and {@link com.intellij.openapi.project.Project}. - * - * @see ApplicationComponent - * @see ProjectComponent - * @see com.intellij.openapi.application.Application - * @see com.intellij.openapi.project.Project - */ -public interface ComponentManager extends UserDataHolder, Disposable { - /** - * Gets the component by its name - * - * @param name the name of the component - * @return component that matches interface class or null if there is no such component - */ - BaseComponent getComponent(String name); - - /** - * Gets the component by its interface class. - * - * @param interfaceClass the interface class of the component - * @return component that matches interface class or null if there is no such component - */ - T getComponent(Class interfaceClass); - - /** - * Gets the component by its interface class but returns a specified default implementation - * if the actualt component doesn't exist in the container. - * - * @param interfaceClass the interface class of the component - * @param defaultImplementationIfAbsent the default implementation - * @return component that matches interface class or default if there is no such component - */ - T getComponent(Class interfaceClass, T defaultImplementationIfAbsent); - - /** - * @deprecated Use {@link #getComponents} instead. - */ - @NotNull Class[] getComponentInterfaces(); - - /** - * Checks whether there is a component with the specified interface class. - * - * @param interfaceClass interface class of component to be checked - * @return true if there is a component with the specified interface class; - * false otherwise - */ - boolean hasComponent(@NotNull Class interfaceClass); - - /** - * Gets all components whose implementation class is derived from baseClass. - * - * @param baseClass - * @return array of components - * @deprecated use extension points instead - */ - @NotNull T[] getComponents(Class baseClass); - - @NotNull - PicoContainer getPicoContainer(); - - MessageBus getMessageBus(); - - boolean isDisposed(); - - @NotNull - ComponentConfig[] getComponentConfigurations(); - - @Nullable - Object getComponent(final ComponentConfig componentConfig); - - T[] getExtensions(ExtensionPointName extensionPointName); - - ComponentConfig getConfig(Class componentImplementation); - - /** - * @return condition for this component being disposed. - * see {@link com.intellij.openapi.application.Application#invokeLater(Runnable, Condition)} for the usage example. - */ - @NotNull - Condition getDisposed(); -} +/* + * 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.components; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.UserDataHolder; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.picocontainer.PicoContainer; + +/** + * Provides access to components. Servers as a base interface for {@link com.intellij.openapi.application.Application} + * and {@link com.intellij.openapi.project.Project}. + * + * @see ApplicationComponent + * @see ProjectComponent + * @see com.intellij.openapi.application.Application + * @see com.intellij.openapi.project.Project + */ +public interface ComponentManager extends UserDataHolder, Disposable { + /** + * Gets the component by its name + * + * @param name the name of the component + * @return component that matches interface class or null if there is no such component + */ + BaseComponent getComponent(String name); + + /** + * Gets the component by its interface class. + * + * @param interfaceClass the interface class of the component + * @return component that matches interface class or null if there is no such component + */ + T getComponent(Class interfaceClass); + + /** + * Gets the component by its interface class but returns a specified default implementation + * if the actualt component doesn't exist in the container. + * + * @param interfaceClass the interface class of the component + * @param defaultImplementationIfAbsent the default implementation + * @return component that matches interface class or default if there is no such component + */ + T getComponent(Class interfaceClass, T defaultImplementationIfAbsent); + + /** + * @deprecated Use {@link #getComponents} instead. + */ + @NotNull Class[] getComponentInterfaces(); + + /** + * Checks whether there is a component with the specified interface class. + * + * @param interfaceClass interface class of component to be checked + * @return true if there is a component with the specified interface class; + * false otherwise + */ + boolean hasComponent(@NotNull Class interfaceClass); + + /** + * Gets all components whose implementation class is derived from baseClass. + * + * @param baseClass + * @return array of components + * @deprecated use extension points instead + */ + @NotNull T[] getComponents(Class baseClass); + + @NotNull + PicoContainer getPicoContainer(); + + MessageBus getMessageBus(); + + boolean isDisposed(); + + @NotNull + ComponentConfig[] getComponentConfigurations(); + + @Nullable + Object getComponent(final ComponentConfig componentConfig); + + T[] getExtensions(ExtensionPointName extensionPointName); + + ComponentConfig getConfig(Class componentImplementation); + + /** + * @return condition for this component being disposed. + * see {@link com.intellij.openapi.application.Application#invokeLater(Runnable, Condition)} for the usage example. + */ + @NotNull + Condition getDisposed(); +} diff --git a/platform/platform-api/src/com/intellij/openapi/editor/actions/ContentChooser.java b/platform/platform-api/src/com/intellij/openapi/editor/actions/ContentChooser.java index 22d3fe2c228a..875f73d67e17 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/actions/ContentChooser.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/actions/ContentChooser.java @@ -1,241 +1,241 @@ -/* - * 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.editor.actions; - -import com.intellij.CommonBundle; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.components.JBList; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; - -public abstract class ContentChooser extends DialogWrapper { - - private static final Icon textIcon = IconLoader.getIcon("/fileTypes/text.png"); - - private JList myList; - private List myAllContents; - - private Editor myViewer; - private final boolean myUseIdeaEditor; - - private Splitter mySplitter; - private final Project myProject; - - public ContentChooser(Project project, String title, boolean useIdeaEditor) { - super(project, true); - myProject = project; - myUseIdeaEditor = useIdeaEditor; - - setOKButtonText(CommonBundle.getOkButtonText()); - setTitle(title); - - init(); - } - - public JComponent getPreferredFocusedComponent() { - return myList; - } - - protected JComponent createCenterPanel() { - myList = new JBList(); - myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - rebuildListContent(); - - myList.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (e.isConsumed() || e.getClickCount() != 2 || e.isPopupTrigger()) return; - close(OK_EXIT_CODE); - } - }); - - myList.setCellRenderer(new MyListCellRenderer()); - - if (myAllContents.size() > 0) { - myList.setSelectedIndex(0); - } - - myList.addKeyListener(new KeyAdapter() { - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_DELETE) { - int selectedIndex = getSelectedIndex(); - int size = myAllContents.size(); - removeContentAt(myAllContents.get(selectedIndex)); - rebuildListContent(); - if (size == 1) { - close(CANCEL_EXIT_CODE); - return; - } - myList.setSelectedIndex(Math.min(selectedIndex, myAllContents.size() - 1)); - } - else if (e.getKeyCode() == KeyEvent.VK_ENTER) { - close(OK_EXIT_CODE); - } - else { - final char aChar = e.getKeyChar(); - if (aChar >= '0' && aChar <= '9') { - int idx = aChar == '0' ? 9 : aChar - '1'; - if (idx < myAllContents.size()) { - myList.setSelectedIndex(idx); - } - } - } - } - }); - - mySplitter = new Splitter(true); - mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myList)); - mySplitter.setSecondComponent(new JPanel()); - updateViewerForSelection(); - - myList.addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - updateViewerForSelection(); - } - }); - - mySplitter.setPreferredSize(new Dimension(500, 500)); - - return mySplitter; - } - - protected abstract void removeContentAt(final Data Content); - - protected String getDimensionServiceKey() { - return "#com.intellij.openapi.editor.actions.MultiplePasteAction.Chooser"; - } - - private void updateViewerForSelection() { - if (myAllContents.size() == 0) return; - String fullString = getStringRepresentationFor(myAllContents.get(getSelectedIndex())); - fullString = StringUtil.convertLineSeparators(fullString); - - if (myViewer != null) { - EditorFactory.getInstance().releaseEditor(myViewer); - } - - if (myUseIdeaEditor) { - Document doc = EditorFactory.getInstance().createDocument(fullString); - myViewer = EditorFactory.getInstance().createViewer(doc, myProject); - myViewer.getComponent().setPreferredSize(new Dimension(300, 500)); - myViewer.getSettings().setFoldingOutlineShown(false); - myViewer.getSettings().setLineNumbersShown(false); - myViewer.getSettings().setLineMarkerAreaShown(false); - myViewer.getSettings().setIndentGuidesShown(false); - mySplitter.setSecondComponent(myViewer.getComponent()); - } else { - final JTextArea textArea = new JTextArea(fullString); - textArea.setRows(3); - textArea.setWrapStyleWord(true); - textArea.setLineWrap(true); - textArea.setSelectionStart(0); - textArea.setSelectionEnd(textArea.getText().length()); - textArea.setEditable(false); - mySplitter.setSecondComponent(ScrollPaneFactory.createScrollPane(textArea)); - } - mySplitter.revalidate(); - } - - public void dispose() { - super.dispose(); - if (myViewer != null) { - EditorFactory.getInstance().releaseEditor(myViewer); - myViewer = null; - } - } - - private void rebuildListContent() { - List allContents = new ArrayList(getContents()); - ArrayList shortened = new ArrayList(); - for (Data content : allContents) { - String fullString = getStringRepresentationFor(content); - if (fullString != null) { - fullString = StringUtil.convertLineSeparators(fullString); - int newLineIdx = fullString.indexOf('\n'); - if (newLineIdx == -1) { - shortened.add(fullString.trim()); - } - else { - int lastLooked = 0; - do { - int nextLineIdx = fullString.indexOf("\n", lastLooked); - if (nextLineIdx > lastLooked) { - shortened.add(fullString.substring(lastLooked, nextLineIdx).trim() + " ..."); - break; - } - else if (nextLineIdx == -1) { - shortened.add(" ..."); - break; - } - lastLooked = nextLineIdx + 1; - } while (true); - } - } - } - - myAllContents = allContents; - myList.setListData(ArrayUtil.toStringArray(shortened)); - } - - protected abstract String getStringRepresentationFor(final Data content); - - protected abstract List getContents(); - - public int getSelectedIndex() { - if (myList.getSelectedIndex() == -1) return 0; - return myList.getSelectedIndex(); - } - - public List getAllContents() { - return myAllContents; - } - - private static class MyListCellRenderer extends ColoredListCellRenderer { - protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { - // Fix GTK background - if (UIUtil.isUnderGTKLookAndFeel()){ - final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground(); - UIUtil.changeBackGround(this, background); - } - setIcon(textIcon); - if (index <= 9) { - append(String.valueOf((index + 1) % 10) + " ", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } - append((String) value, SimpleTextAttributes.REGULAR_ATTRIBUTES); - } - } -} +/* + * 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.editor.actions; + +import com.intellij.CommonBundle; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBList; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +public abstract class ContentChooser extends DialogWrapper { + + private static final Icon textIcon = IconLoader.getIcon("/fileTypes/text.png"); + + private JList myList; + private List myAllContents; + + private Editor myViewer; + private final boolean myUseIdeaEditor; + + private Splitter mySplitter; + private final Project myProject; + + public ContentChooser(Project project, String title, boolean useIdeaEditor) { + super(project, true); + myProject = project; + myUseIdeaEditor = useIdeaEditor; + + setOKButtonText(CommonBundle.getOkButtonText()); + setTitle(title); + + init(); + } + + public JComponent getPreferredFocusedComponent() { + return myList; + } + + protected JComponent createCenterPanel() { + myList = new JBList(); + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + rebuildListContent(); + + myList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.isConsumed() || e.getClickCount() != 2 || e.isPopupTrigger()) return; + close(OK_EXIT_CODE); + } + }); + + myList.setCellRenderer(new MyListCellRenderer()); + + if (myAllContents.size() > 0) { + myList.setSelectedIndex(0); + } + + myList.addKeyListener(new KeyAdapter() { + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_DELETE) { + int selectedIndex = getSelectedIndex(); + int size = myAllContents.size(); + removeContentAt(myAllContents.get(selectedIndex)); + rebuildListContent(); + if (size == 1) { + close(CANCEL_EXIT_CODE); + return; + } + myList.setSelectedIndex(Math.min(selectedIndex, myAllContents.size() - 1)); + } + else if (e.getKeyCode() == KeyEvent.VK_ENTER) { + close(OK_EXIT_CODE); + } + else { + final char aChar = e.getKeyChar(); + if (aChar >= '0' && aChar <= '9') { + int idx = aChar == '0' ? 9 : aChar - '1'; + if (idx < myAllContents.size()) { + myList.setSelectedIndex(idx); + } + } + } + } + }); + + mySplitter = new Splitter(true); + mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myList)); + mySplitter.setSecondComponent(new JPanel()); + updateViewerForSelection(); + + myList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + updateViewerForSelection(); + } + }); + + mySplitter.setPreferredSize(new Dimension(500, 500)); + + return mySplitter; + } + + protected abstract void removeContentAt(final Data Content); + + protected String getDimensionServiceKey() { + return "#com.intellij.openapi.editor.actions.MultiplePasteAction.Chooser"; + } + + private void updateViewerForSelection() { + if (myAllContents.size() == 0) return; + String fullString = getStringRepresentationFor(myAllContents.get(getSelectedIndex())); + fullString = StringUtil.convertLineSeparators(fullString); + + if (myViewer != null) { + EditorFactory.getInstance().releaseEditor(myViewer); + } + + if (myUseIdeaEditor) { + Document doc = EditorFactory.getInstance().createDocument(fullString); + myViewer = EditorFactory.getInstance().createViewer(doc, myProject); + myViewer.getComponent().setPreferredSize(new Dimension(300, 500)); + myViewer.getSettings().setFoldingOutlineShown(false); + myViewer.getSettings().setLineNumbersShown(false); + myViewer.getSettings().setLineMarkerAreaShown(false); + myViewer.getSettings().setIndentGuidesShown(false); + mySplitter.setSecondComponent(myViewer.getComponent()); + } else { + final JTextArea textArea = new JTextArea(fullString); + textArea.setRows(3); + textArea.setWrapStyleWord(true); + textArea.setLineWrap(true); + textArea.setSelectionStart(0); + textArea.setSelectionEnd(textArea.getText().length()); + textArea.setEditable(false); + mySplitter.setSecondComponent(ScrollPaneFactory.createScrollPane(textArea)); + } + mySplitter.revalidate(); + } + + public void dispose() { + super.dispose(); + if (myViewer != null) { + EditorFactory.getInstance().releaseEditor(myViewer); + myViewer = null; + } + } + + private void rebuildListContent() { + List allContents = new ArrayList(getContents()); + ArrayList shortened = new ArrayList(); + for (Data content : allContents) { + String fullString = getStringRepresentationFor(content); + if (fullString != null) { + fullString = StringUtil.convertLineSeparators(fullString); + int newLineIdx = fullString.indexOf('\n'); + if (newLineIdx == -1) { + shortened.add(fullString.trim()); + } + else { + int lastLooked = 0; + do { + int nextLineIdx = fullString.indexOf("\n", lastLooked); + if (nextLineIdx > lastLooked) { + shortened.add(fullString.substring(lastLooked, nextLineIdx).trim() + " ..."); + break; + } + else if (nextLineIdx == -1) { + shortened.add(" ..."); + break; + } + lastLooked = nextLineIdx + 1; + } while (true); + } + } + } + + myAllContents = allContents; + myList.setListData(ArrayUtil.toStringArray(shortened)); + } + + protected abstract String getStringRepresentationFor(final Data content); + + protected abstract List getContents(); + + public int getSelectedIndex() { + if (myList.getSelectedIndex() == -1) return 0; + return myList.getSelectedIndex(); + } + + public List getAllContents() { + return myAllContents; + } + + private static class MyListCellRenderer extends ColoredListCellRenderer { + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + // Fix GTK background + if (UIUtil.isUnderGTKLookAndFeel()){ + final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground(); + UIUtil.changeBackGround(this, background); + } + setIcon(textIcon); + if (index <= 9) { + append(String.valueOf((index + 1) % 10) + " ", SimpleTextAttributes.GRAYED_ATTRIBUTES); + } + append((String) value, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/project/Project.java b/platform/platform-api/src/com/intellij/openapi/project/Project.java index 5009f962440e..b05098b9c086 100644 --- a/platform/platform-api/src/com/intellij/openapi/project/Project.java +++ b/platform/platform-api/src/com/intellij/openapi/project/Project.java @@ -1,68 +1,68 @@ -/* - * 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.project; - -import com.intellij.openapi.components.ComponentManager; -import com.intellij.openapi.extensions.AreaInstance; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - - -/** - * Project interface class. - */ -public interface Project extends ComponentManager, AreaInstance { - @NonNls String DIRECTORY_STORE_FOLDER = ".idea"; - - @Nullable - VirtualFile getProjectFile(); - - @Nullable - VirtualFile getWorkspaceFile(); - - @NotNull - String getProjectFilePath(); - - @Nullable - VirtualFile getBaseDir(); - - @NotNull - @NonNls - String getName(); - - @Nullable - @NonNls - String getPresentableUrl(); - - @NotNull - @NonNls - String getLocationHash(); - - - @Nullable - @NonNls - String getLocation(); - - void save(); - - boolean isOpen(); - - boolean isInitialized(); - - boolean isDefault(); -} +/* + * 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.project; + +import com.intellij.openapi.components.ComponentManager; +import com.intellij.openapi.extensions.AreaInstance; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +/** + * Project interface class. + */ +public interface Project extends ComponentManager, AreaInstance { + @NonNls String DIRECTORY_STORE_FOLDER = ".idea"; + + @Nullable + VirtualFile getProjectFile(); + + @Nullable + VirtualFile getWorkspaceFile(); + + @NotNull + String getProjectFilePath(); + + @Nullable + VirtualFile getBaseDir(); + + @NotNull + @NonNls + String getName(); + + @Nullable + @NonNls + String getPresentableUrl(); + + @NotNull + @NonNls + String getLocationHash(); + + + @Nullable + @NonNls + String getLocation(); + + void save(); + + boolean isOpen(); + + boolean isInitialized(); + + boolean isDefault(); +} diff --git a/platform/platform-api/src/com/intellij/openapi/roots/ui/util/CompositeAppearance.java b/platform/platform-api/src/com/intellij/openapi/roots/ui/util/CompositeAppearance.java index 8ac633cd9c2c..9cd5cf46da8e 100644 --- a/platform/platform-api/src/com/intellij/openapi/roots/ui/util/CompositeAppearance.java +++ b/platform/platform-api/src/com/intellij/openapi/roots/ui/util/CompositeAppearance.java @@ -1,217 +1,217 @@ -/* - * 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.roots.ui.util; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.ui.SimpleColoredComponent; -import com.intellij.ui.SimpleTextAttributes; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.Iterator; - -public class CompositeAppearance implements ModifiableCellAppearance { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.ui.util.CompositeAppearance"); - - private Icon myIcon; - private final ArrayList mySections = new ArrayList(); - private int myInsertionIndex = 0; - - public synchronized void customize(SimpleColoredComponent component) { - for (TextSection section : mySections) { - TextAttributes attributes = section.ATTRIBUTES; - component.append(section.TEXT, SimpleTextAttributes.fromTextAttributes(attributes)); - } - component.setIcon(myIcon); - } - - public Icon getIcon() { - return myIcon; - } - - public void setIcon(Icon icon) { - myIcon = icon; - } - - public synchronized String getText() { - StringBuilder buffer = new StringBuilder(); - for (TextSection section : mySections) { - buffer.append(section.TEXT); - } - return buffer.toString(); - } - - public synchronized boolean equals(Object obj) { - if (!(obj instanceof CompositeAppearance)) return false; - CompositeAppearance appearance = (CompositeAppearance)obj; - if (SwingUtilities.isEventDispatchThread()) { - return appearance.mySections.equals(mySections); - } - else { - return new ArrayList(appearance.mySections).equals(new ArrayList(mySections)); - } - } - - public int hashCode() { - return getText().hashCode(); - } - - protected void addSectionAt(int index, @NotNull TextSection section) { - synchronized (this) { - mySections.add(index, section); - for (Iterator iterator = mySections.iterator(); iterator.hasNext();) { - TextSection textSection = iterator.next(); - if (textSection == null) { - LOG.error("index: " + index + " size: " + mySections.size()); - iterator.remove(); - } - } - } - } - - public DequeEnd getBeginning() { - return new DequeBeginning(); - } - - public DequeEnd getEnding() { - return new DequeEnding(); - } - - public DequeEnd getSuffix() { - return new DequeSuffix(); - } - - public static CompositeAppearance textComment(String text, String comment) { - DequeEnd ending = new CompositeAppearance().getEnding(); - ending.addText(text); - ending.addComment(comment); - return ending.getAppearance(); - } - - public static CompositeAppearance single(String text, SimpleTextAttributes textAttributes) { - CompositeAppearance result = new CompositeAppearance(); - result.getEnding().addText(text, textAttributes); - return result; - } - - public static CompositeAppearance single(String text) { - return single(text, SimpleTextAttributes.REGULAR_ATTRIBUTES); - } - - public static CompositeAppearance invalid(String absolutePath) { - CompositeAppearance appearance = new CompositeAppearance(); - appearance.setIcon(CellAppearanceUtils.INVALID_ICON); - appearance.getEnding().addText(absolutePath, SimpleTextAttributes.ERROR_ATTRIBUTES); - return appearance; - } - - public Iterator getSectionsIterator() { - return mySections.iterator(); - } - - public static class TextSection { - private static final TextAttributes DEFAULT_TEXT_ATTRIBUTES = new TextAttributes(null, null, null, null, Font.PLAIN); - private static final String DEFAULT_TEXT = ""; - private String TEXT; - private TextAttributes ATTRIBUTES; - - public TextSection(String text, TextAttributes attributes) { - ATTRIBUTES = attributes == null ? DEFAULT_TEXT_ATTRIBUTES : attributes; - TEXT = text == null ? DEFAULT_TEXT : text; - } - - public String getText() { - return TEXT; - } - - public TextAttributes getTextAttributes() { - return ATTRIBUTES; - } - - public boolean equals(Object obj) { - if (!(obj instanceof TextSection)) return false; - TextSection section = (TextSection)obj; - return section.ATTRIBUTES.equals(ATTRIBUTES) && section.TEXT.equals(TEXT); - } - - public int hashCode() { - return TEXT.hashCode(); - } - } - - public abstract class DequeEnd { - public void addText(String text, SimpleTextAttributes textAttributes) { - addText(text, textAttributes.toTextAttributes()); - } - - public void addText(String text) { - addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES); - } - - public abstract void addSection(TextSection section); - - public void addText(String text, TextAttributes attributes) { - addSection(new TextSection(text, attributes)); - } - - public void addSurrounded(String text, String prefix, String suffix, SimpleTextAttributes textAttributes) { - if (text != null && text.trim().length() > 0) { - addText(prefix + text + suffix, textAttributes); - } - } - - public CompositeAppearance getAppearance() { - return CompositeAppearance.this; - } - - public void addComment(String comment, SimpleTextAttributes commentAttributes) { - addSurrounded(comment, " (", ")", commentAttributes); - } - - public void addComment(String comment) { - addComment(comment, SimpleTextAttributes.GRAY_ATTRIBUTES); - } - } - - private class DequeBeginning extends DequeEnd { - public void addSection(TextSection section) { - synchronized (CompositeAppearance.this) { - addSectionAt(0, section); - myInsertionIndex++; - } - } - } - - private class DequeEnding extends DequeEnd { - public void addSection(TextSection section) { - synchronized (CompositeAppearance.this) { - addSectionAt(myInsertionIndex, section); - myInsertionIndex++; - } - } - } - - private class DequeSuffix extends DequeEnd { - public void addSection(TextSection section) { - synchronized (CompositeAppearance.this) { - addSectionAt(mySections.size(), section); - } - } - } -} +/* + * 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.roots.ui.util; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.ui.SimpleColoredComponent; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Iterator; + +public class CompositeAppearance implements ModifiableCellAppearance { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.ui.util.CompositeAppearance"); + + private Icon myIcon; + private final ArrayList mySections = new ArrayList(); + private int myInsertionIndex = 0; + + public synchronized void customize(SimpleColoredComponent component) { + for (TextSection section : mySections) { + TextAttributes attributes = section.ATTRIBUTES; + component.append(section.TEXT, SimpleTextAttributes.fromTextAttributes(attributes)); + } + component.setIcon(myIcon); + } + + public Icon getIcon() { + return myIcon; + } + + public void setIcon(Icon icon) { + myIcon = icon; + } + + public synchronized String getText() { + StringBuilder buffer = new StringBuilder(); + for (TextSection section : mySections) { + buffer.append(section.TEXT); + } + return buffer.toString(); + } + + public synchronized boolean equals(Object obj) { + if (!(obj instanceof CompositeAppearance)) return false; + CompositeAppearance appearance = (CompositeAppearance)obj; + if (SwingUtilities.isEventDispatchThread()) { + return appearance.mySections.equals(mySections); + } + else { + return new ArrayList(appearance.mySections).equals(new ArrayList(mySections)); + } + } + + public int hashCode() { + return getText().hashCode(); + } + + protected void addSectionAt(int index, @NotNull TextSection section) { + synchronized (this) { + mySections.add(index, section); + for (Iterator iterator = mySections.iterator(); iterator.hasNext();) { + TextSection textSection = iterator.next(); + if (textSection == null) { + LOG.error("index: " + index + " size: " + mySections.size()); + iterator.remove(); + } + } + } + } + + public DequeEnd getBeginning() { + return new DequeBeginning(); + } + + public DequeEnd getEnding() { + return new DequeEnding(); + } + + public DequeEnd getSuffix() { + return new DequeSuffix(); + } + + public static CompositeAppearance textComment(String text, String comment) { + DequeEnd ending = new CompositeAppearance().getEnding(); + ending.addText(text); + ending.addComment(comment); + return ending.getAppearance(); + } + + public static CompositeAppearance single(String text, SimpleTextAttributes textAttributes) { + CompositeAppearance result = new CompositeAppearance(); + result.getEnding().addText(text, textAttributes); + return result; + } + + public static CompositeAppearance single(String text) { + return single(text, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + public static CompositeAppearance invalid(String absolutePath) { + CompositeAppearance appearance = new CompositeAppearance(); + appearance.setIcon(CellAppearanceUtils.INVALID_ICON); + appearance.getEnding().addText(absolutePath, SimpleTextAttributes.ERROR_ATTRIBUTES); + return appearance; + } + + public Iterator getSectionsIterator() { + return mySections.iterator(); + } + + public static class TextSection { + private static final TextAttributes DEFAULT_TEXT_ATTRIBUTES = new TextAttributes(null, null, null, null, Font.PLAIN); + private static final String DEFAULT_TEXT = ""; + private String TEXT; + private TextAttributes ATTRIBUTES; + + public TextSection(String text, TextAttributes attributes) { + ATTRIBUTES = attributes == null ? DEFAULT_TEXT_ATTRIBUTES : attributes; + TEXT = text == null ? DEFAULT_TEXT : text; + } + + public String getText() { + return TEXT; + } + + public TextAttributes getTextAttributes() { + return ATTRIBUTES; + } + + public boolean equals(Object obj) { + if (!(obj instanceof TextSection)) return false; + TextSection section = (TextSection)obj; + return section.ATTRIBUTES.equals(ATTRIBUTES) && section.TEXT.equals(TEXT); + } + + public int hashCode() { + return TEXT.hashCode(); + } + } + + public abstract class DequeEnd { + public void addText(String text, SimpleTextAttributes textAttributes) { + addText(text, textAttributes.toTextAttributes()); + } + + public void addText(String text) { + addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + public abstract void addSection(TextSection section); + + public void addText(String text, TextAttributes attributes) { + addSection(new TextSection(text, attributes)); + } + + public void addSurrounded(String text, String prefix, String suffix, SimpleTextAttributes textAttributes) { + if (text != null && text.trim().length() > 0) { + addText(prefix + text + suffix, textAttributes); + } + } + + public CompositeAppearance getAppearance() { + return CompositeAppearance.this; + } + + public void addComment(String comment, SimpleTextAttributes commentAttributes) { + addSurrounded(comment, " (", ")", commentAttributes); + } + + public void addComment(String comment) { + addComment(comment, SimpleTextAttributes.GRAY_ATTRIBUTES); + } + } + + private class DequeBeginning extends DequeEnd { + public void addSection(TextSection section) { + synchronized (CompositeAppearance.this) { + addSectionAt(0, section); + myInsertionIndex++; + } + } + } + + private class DequeEnding extends DequeEnd { + public void addSection(TextSection section) { + synchronized (CompositeAppearance.this) { + addSectionAt(myInsertionIndex, section); + myInsertionIndex++; + } + } + } + + private class DequeSuffix extends DequeEnd { + public void addSection(TextSection section) { + synchronized (CompositeAppearance.this) { + addSectionAt(mySections.size(), section); + } + } + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/roots/ui/util/ModifiableCellAppearance.java b/platform/platform-api/src/com/intellij/openapi/roots/ui/util/ModifiableCellAppearance.java index 8904a48fee17..6b47416e9278 100644 --- a/platform/platform-api/src/com/intellij/openapi/roots/ui/util/ModifiableCellAppearance.java +++ b/platform/platform-api/src/com/intellij/openapi/roots/ui/util/ModifiableCellAppearance.java @@ -1,26 +1,26 @@ -/* - * 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.roots.ui.util; - -import javax.swing.*; - -/** - * @author dyoma - */ -public interface ModifiableCellAppearance extends CellAppearance { - Icon getIcon(); - void setIcon(Icon icon); -} +/* + * 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.roots.ui.util; + +import javax.swing.*; + +/** + * @author dyoma + */ +public interface ModifiableCellAppearance extends CellAppearance { + Icon getIcon(); + void setIcon(Icon icon); +} diff --git a/platform/platform-api/src/com/intellij/openapi/roots/ui/util/SimpleTextCellAppearance.java b/platform/platform-api/src/com/intellij/openapi/roots/ui/util/SimpleTextCellAppearance.java index 0bc28ef3a052..f5b350c0c8e4 100644 --- a/platform/platform-api/src/com/intellij/openapi/roots/ui/util/SimpleTextCellAppearance.java +++ b/platform/platform-api/src/com/intellij/openapi/roots/ui/util/SimpleTextCellAppearance.java @@ -1,69 +1,69 @@ -/* - * 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.roots.ui.util; - -import com.intellij.ui.SimpleColoredComponent; -import com.intellij.ui.SimpleTextAttributes; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -public class SimpleTextCellAppearance implements ModifiableCellAppearance { - private Icon myIcon; - private final SimpleTextAttributes myTextAttributes; - private final String myText; - - public static SimpleTextCellAppearance invalid(String text, Icon icon) { - return new SimpleTextCellAppearance(text, icon, SimpleTextAttributes.ERROR_ATTRIBUTES); - } - - public static CellAppearance normal(String text, Icon icon) { - CompositeAppearance result = CompositeAppearance.single(text); - result.setIcon(icon); - return result; - } - - public static SimpleTextCellAppearance synthetic(String text, Icon icon) { - return new SimpleTextCellAppearance(text, icon, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - } - - public SimpleTextCellAppearance(@NotNull String text, Icon icon, SimpleTextAttributes textAttributes) { - myIcon = icon; - myTextAttributes = textAttributes; - myText = text; - } - - public void customize(SimpleColoredComponent component) { - component.setIcon(myIcon); - component.append(myText, myTextAttributes); - } - - public String getText() { - return myText; - } - - public SimpleTextAttributes getTextAttributes() { - return myTextAttributes; - } - - public Icon getIcon() { - return myIcon; - } - - public void setIcon(Icon icon) { - myIcon = icon; - } -} +/* + * 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.roots.ui.util; + +import com.intellij.ui.SimpleColoredComponent; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +public class SimpleTextCellAppearance implements ModifiableCellAppearance { + private Icon myIcon; + private final SimpleTextAttributes myTextAttributes; + private final String myText; + + public static SimpleTextCellAppearance invalid(String text, Icon icon) { + return new SimpleTextCellAppearance(text, icon, SimpleTextAttributes.ERROR_ATTRIBUTES); + } + + public static CellAppearance normal(String text, Icon icon) { + CompositeAppearance result = CompositeAppearance.single(text); + result.setIcon(icon); + return result; + } + + public static SimpleTextCellAppearance synthetic(String text, Icon icon) { + return new SimpleTextCellAppearance(text, icon, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + } + + public SimpleTextCellAppearance(@NotNull String text, Icon icon, SimpleTextAttributes textAttributes) { + myIcon = icon; + myTextAttributes = textAttributes; + myText = text; + } + + public void customize(SimpleColoredComponent component) { + component.setIcon(myIcon); + component.append(myText, myTextAttributes); + } + + public String getText() { + return myText; + } + + public SimpleTextAttributes getTextAttributes() { + return myTextAttributes; + } + + public Icon getIcon() { + return myIcon; + } + + public void setIcon(Icon icon) { + myIcon = icon; + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ComboBoxWithWidePopup.java b/platform/platform-api/src/com/intellij/openapi/ui/ComboBoxWithWidePopup.java index 2e9c9f9d82a3..9f211b15429e 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/ComboBoxWithWidePopup.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/ComboBoxWithWidePopup.java @@ -1,72 +1,72 @@ -/* - * 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.ui; - -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.util.Vector; -import java.awt.*; - -public class ComboBoxWithWidePopup extends JComboBox { - - private boolean myLayingOut = false; - - public ComboBoxWithWidePopup(final ComboBoxModel aModel) { - super(aModel); - - if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); - } - - public ComboBoxWithWidePopup(final Object[] items) { - super(items); - - if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); - } - - public ComboBoxWithWidePopup(final Vector items) { - super(items); - - if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); - } - - public ComboBoxWithWidePopup() { - } - - public void doLayout() { - try { - myLayingOut = true; - super.doLayout(); - } - finally { - myLayingOut = false; - } - } - - public Dimension getSize() { - Dimension size = super.getSize(); - if (!myLayingOut) { - size.width = Math.max(size.width, getOriginalPreferredSize().width); - } - return size; - } - - protected Dimension getOriginalPreferredSize() { - return getPreferredSize(); - } -} +/* + * 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.ui; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.util.Vector; +import java.awt.*; + +public class ComboBoxWithWidePopup extends JComboBox { + + private boolean myLayingOut = false; + + public ComboBoxWithWidePopup(final ComboBoxModel aModel) { + super(aModel); + + if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); + } + + public ComboBoxWithWidePopup(final Object[] items) { + super(items); + + if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); + } + + public ComboBoxWithWidePopup(final Vector items) { + super(items); + + if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); + } + + public ComboBoxWithWidePopup() { + } + + public void doLayout() { + try { + myLayingOut = true; + super.doLayout(); + } + finally { + myLayingOut = false; + } + } + + public Dimension getSize() { + Dimension size = super.getSize(); + if (!myLayingOut) { + size.width = Math.max(size.width, getOriginalPreferredSize().width); + } + return size; + } + + protected Dimension getOriginalPreferredSize() { + return getPreferredSize(); + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DescriptionLabel.java b/platform/platform-api/src/com/intellij/openapi/ui/DescriptionLabel.java index 34a8a789328a..8bd8f5fe2fa7 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DescriptionLabel.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DescriptionLabel.java @@ -1,39 +1,39 @@ -/* - * 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.ui; - -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class DescriptionLabel extends JLabel { - - public DescriptionLabel(@Nullable String text) { - setText(text); - } - - @Override - public void updateUI() { - super.updateUI(); - setForeground(UIUtil.getPanelBackground().darker()); - int size = getFont().getSize(); - if (size >= 12) { - size -= 2; - } - setFont(getFont().deriveFont((float)size)); - } +/* + * 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.ui; + +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class DescriptionLabel extends JLabel { + + public DescriptionLabel(@Nullable String text) { + setText(text); + } + + @Override + public void updateUI() { + super.updateUI(); + setForeground(UIUtil.getPanelBackground().darker()); + int size = getFont().getSize(); + if (size >= 12) { + size -= 2; + } + setFont(getFont().deriveFont((float)size)); + } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/ui/FixedComboBoxEditor.java b/platform/platform-api/src/com/intellij/openapi/ui/FixedComboBoxEditor.java index 6944220f7448..47a77cddeaeb 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/FixedComboBoxEditor.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/FixedComboBoxEditor.java @@ -1,328 +1,328 @@ -/* - * 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.ui; - -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.plaf.ComboBoxUI; -import javax.swing.plaf.basic.BasicComboBoxUI; -import javax.swing.plaf.basic.ComboPopup; -import java.awt.*; -import java.awt.event.ActionListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * Use this editor if you wish your combobox editor to look good on Macs. - * - * User: spLeaner - */ -public class FixedComboBoxEditor implements ComboBoxEditor { - public static final Border EDITOR_BORDER = new MacComboBoxEditorBorder(false); - public static final Border DISABLED_EDITOR_BORDER = new MacComboBoxEditorBorder(true); - - private JTextField myField; - private Object oldValue; - - public FixedComboBoxEditor() { - if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) { - myField = new MacComboBoxTextField(); - } else { - myField = new JTextField(); - myField.setBorder(null); - } - } - - protected JTextField getField() { - return myField; - } - - @Override - public Component getEditorComponent() { - return myField; - } - - public void setItem(Object anObject) { - if (anObject != null) { - myField.setText(anObject.toString()); - oldValue = anObject; - } - else { - myField.setText(""); - } - } - - public Object getItem() { - Object newValue = myField.getText(); - if (oldValue != null && !(oldValue instanceof String)) { - // The original value is not a string. Should return the value in it's - // original type. - if (newValue.equals(oldValue.toString())) { - return oldValue; - } - else { - // Must take the value from the editor and get the value and cast it to the new type. - Class cls = oldValue.getClass(); - try { - Method method = cls.getMethod("valueOf", new Class[]{String.class}); - newValue = method.invoke(oldValue, new Object[]{myField.getText()}); - } - catch (Exception ex) { - // Fail silently and return the newValue (a String object) - } - } - } - return newValue; - } - - public void selectAll() { - myField.selectAll(); - myField.requestFocus(); - } - - @Override - public void addActionListener(ActionListener l) { - } - - @Override - public void removeActionListener(ActionListener l) { - } - - @Nullable - private static ComboPopup getComboboxPopup(final JComboBox comboBox) { - final ComboBoxUI ui = comboBox.getUI(); - ComboPopup popup = null; - if (ui instanceof BasicComboBoxUI) { - try { - final Field popupField = BasicComboBoxUI.class.getDeclaredField("popup"); - popupField.setAccessible(true); - popup = (ComboPopup)popupField.get(ui); - } - catch (NoSuchFieldException e1) { - popup = null; - } - catch (IllegalAccessException e1) { - popup = null; - } - } - - return popup; - } - - private class MacComboBoxTextField extends JTextField implements DocumentListener, FocusListener { - private boolean myRepaintingParent; - - private MacComboBoxTextField() { - setBorder(isEnabled() ? EDITOR_BORDER : DISABLED_EDITOR_BORDER); - //setFont(UIUtil.getListFont()); - - final InputMap inputMap = getInputMap(); - - inputMap.put(KeyStroke.getKeyStroke("DOWN"), "aquaSelectNext"); - inputMap.put(KeyStroke.getKeyStroke("KP_DOWN"), "aquaSelectNext"); - inputMap.put(KeyStroke.getKeyStroke("UP"), "aquaSelectPrevious"); - inputMap.put(KeyStroke.getKeyStroke("KP_UP"), "aquaSelectPrevious"); - - inputMap.put(KeyStroke.getKeyStroke("HOME"), "aquaSelectHome"); - inputMap.put(KeyStroke.getKeyStroke("END"), "aquaSelectEnd"); - inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), "aquaSelectPageUp"); - inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), "aquaSelectPageDown"); - - inputMap.put(KeyStroke.getKeyStroke("ENTER"), "aquaEnterPressed"); - inputMap.put(KeyStroke.getKeyStroke("SPACE"), "aquaSpacePressed"); - - //getActionMap().put("macEnterPressed", macEnterPressedAction); - //getDocument().addDocumentListener(this); - - addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if ("enabled".equals(evt.getPropertyName())) { - setBorder(Boolean.TRUE.equals(evt.getNewValue()) ? EDITOR_BORDER : DISABLED_EDITOR_BORDER); - repaint(); - } - } - }); - - addFocusListener(this); - } - - @Override - public boolean hasFocus() { - if (myRepaintingParent) { - return false; // to disable focus painting around combobox button - } - return super.hasFocus(); - } - - @Override - public void focusGained(FocusEvent e) { - repaintCombobox(); - } - - private void repaintCombobox() { - final Container parent = getParent(); - if (parent == null) return; - if (parent instanceof JComponent && Boolean.TRUE == ((JComponent)parent).getClientProperty("JComboBox.isTableCellEditor")) return; - final Container grandParent = parent.getParent(); - if (grandParent != null) { - myRepaintingParent = true; - grandParent.repaint(); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - myRepaintingParent = false; - } - }); - } - } - - @Override - public void focusLost(FocusEvent e) { - repaintCombobox(); - } - - @Override - public Dimension getMinimumSize() { - final Dimension minimumSize = super.getMinimumSize(); - return new Dimension(minimumSize.width, minimumSize.height + 2); - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public void setBounds(final int x, final int y, final int width, final int height) { - UIUtil.setComboBoxEditorBounds(x, y, width, height, this); - } - - @Override - public void insertUpdate(DocumentEvent e) { - textChanged(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - textChanged(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - textChanged(); - } - - private void textChanged() { - final Container ancestor = SwingUtilities.getAncestorOfClass(JComboBox.class, this); - if (ancestor == null || !ancestor.isVisible()) return; - - final JComboBox comboBox = (JComboBox)ancestor; - if (!comboBox.isPopupVisible()) return; - - final ComboPopup popup = getComboboxPopup(comboBox); - if (popup == null) return; - - String s = myField.getText(); - - final ListModel listmodel = comboBox.getModel(); - int i = listmodel.getSize(); - if (s.length() > 0) { - for (int j = 0; j < i; j++) { - Object obj = listmodel.getElementAt(j); - if (obj == null) continue; - - String s1 = obj.toString(); - if (s1 != null && (s1.startsWith(s) || s1.equals(s))) { - popup.getList().setSelectedIndex(j); - return; - } - } - } - - popup.getList().clearSelection(); - } - } - - public static class MacComboBoxEditorBorder implements Border { - - private boolean myDisabled; - - public MacComboBoxEditorBorder(final boolean disabled) { - myDisabled = disabled; - } - - @Override - public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { - Color topColor; - Color secondTopColor; - Color leftRightColor; - Color bottomColor; - - if (myDisabled) { - topColor = new Color(200, 200, 200); - secondTopColor = new Color(250, 250, 250); - leftRightColor = new Color(205, 205, 205); - bottomColor = new Color(220, 220, 220); - } - else { - topColor = new Color(150, 150, 150); - secondTopColor = new Color(230, 230, 230); - leftRightColor = new Color(175, 175, 175); - bottomColor = new Color(200, 200, 200); - } - - g.setColor(topColor); - g.drawLine(x + 3, y + 3, x + width - 1, y + 3); - - g.setColor(secondTopColor); - g.drawLine(x + 3, y + 4, x + width - 1, y + 4); - - g.setColor(leftRightColor); - g.drawLine(x + 3, y + 4, x + 3, y + height - 4); - g.drawLine(x + width - 1, y + 4, x + width - 1, y + height - 4); - - g.setColor(bottomColor); - g.drawLine(x + 4, y + height - 4, x + width - 2, y + height - 4); - - g.setColor(UIUtil.getPanelBackground()); - - g.fillRect(x, y, width, 3); - g.fillRect(x, y, 3, height); - g.fillRect(x, y + height - 3, width, 3); - } - - @Override - public Insets getBorderInsets(final Component c) { - return new Insets(6, 6, 4, 3); - } - - @Override - public boolean isBorderOpaque() { - return true; - } - } +/* + * 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.ui; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.plaf.ComboBoxUI; +import javax.swing.plaf.basic.BasicComboBoxUI; +import javax.swing.plaf.basic.ComboPopup; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Use this editor if you wish your combobox editor to look good on Macs. + * + * User: spLeaner + */ +public class FixedComboBoxEditor implements ComboBoxEditor { + public static final Border EDITOR_BORDER = new MacComboBoxEditorBorder(false); + public static final Border DISABLED_EDITOR_BORDER = new MacComboBoxEditorBorder(true); + + private JTextField myField; + private Object oldValue; + + public FixedComboBoxEditor() { + if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) { + myField = new MacComboBoxTextField(); + } else { + myField = new JTextField(); + myField.setBorder(null); + } + } + + protected JTextField getField() { + return myField; + } + + @Override + public Component getEditorComponent() { + return myField; + } + + public void setItem(Object anObject) { + if (anObject != null) { + myField.setText(anObject.toString()); + oldValue = anObject; + } + else { + myField.setText(""); + } + } + + public Object getItem() { + Object newValue = myField.getText(); + if (oldValue != null && !(oldValue instanceof String)) { + // The original value is not a string. Should return the value in it's + // original type. + if (newValue.equals(oldValue.toString())) { + return oldValue; + } + else { + // Must take the value from the editor and get the value and cast it to the new type. + Class cls = oldValue.getClass(); + try { + Method method = cls.getMethod("valueOf", new Class[]{String.class}); + newValue = method.invoke(oldValue, new Object[]{myField.getText()}); + } + catch (Exception ex) { + // Fail silently and return the newValue (a String object) + } + } + } + return newValue; + } + + public void selectAll() { + myField.selectAll(); + myField.requestFocus(); + } + + @Override + public void addActionListener(ActionListener l) { + } + + @Override + public void removeActionListener(ActionListener l) { + } + + @Nullable + private static ComboPopup getComboboxPopup(final JComboBox comboBox) { + final ComboBoxUI ui = comboBox.getUI(); + ComboPopup popup = null; + if (ui instanceof BasicComboBoxUI) { + try { + final Field popupField = BasicComboBoxUI.class.getDeclaredField("popup"); + popupField.setAccessible(true); + popup = (ComboPopup)popupField.get(ui); + } + catch (NoSuchFieldException e1) { + popup = null; + } + catch (IllegalAccessException e1) { + popup = null; + } + } + + return popup; + } + + private class MacComboBoxTextField extends JTextField implements DocumentListener, FocusListener { + private boolean myRepaintingParent; + + private MacComboBoxTextField() { + setBorder(isEnabled() ? EDITOR_BORDER : DISABLED_EDITOR_BORDER); + //setFont(UIUtil.getListFont()); + + final InputMap inputMap = getInputMap(); + + inputMap.put(KeyStroke.getKeyStroke("DOWN"), "aquaSelectNext"); + inputMap.put(KeyStroke.getKeyStroke("KP_DOWN"), "aquaSelectNext"); + inputMap.put(KeyStroke.getKeyStroke("UP"), "aquaSelectPrevious"); + inputMap.put(KeyStroke.getKeyStroke("KP_UP"), "aquaSelectPrevious"); + + inputMap.put(KeyStroke.getKeyStroke("HOME"), "aquaSelectHome"); + inputMap.put(KeyStroke.getKeyStroke("END"), "aquaSelectEnd"); + inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), "aquaSelectPageUp"); + inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), "aquaSelectPageDown"); + + inputMap.put(KeyStroke.getKeyStroke("ENTER"), "aquaEnterPressed"); + inputMap.put(KeyStroke.getKeyStroke("SPACE"), "aquaSpacePressed"); + + //getActionMap().put("macEnterPressed", macEnterPressedAction); + //getDocument().addDocumentListener(this); + + addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if ("enabled".equals(evt.getPropertyName())) { + setBorder(Boolean.TRUE.equals(evt.getNewValue()) ? EDITOR_BORDER : DISABLED_EDITOR_BORDER); + repaint(); + } + } + }); + + addFocusListener(this); + } + + @Override + public boolean hasFocus() { + if (myRepaintingParent) { + return false; // to disable focus painting around combobox button + } + return super.hasFocus(); + } + + @Override + public void focusGained(FocusEvent e) { + repaintCombobox(); + } + + private void repaintCombobox() { + final Container parent = getParent(); + if (parent == null) return; + if (parent instanceof JComponent && Boolean.TRUE == ((JComponent)parent).getClientProperty("JComboBox.isTableCellEditor")) return; + final Container grandParent = parent.getParent(); + if (grandParent != null) { + myRepaintingParent = true; + grandParent.repaint(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + myRepaintingParent = false; + } + }); + } + } + + @Override + public void focusLost(FocusEvent e) { + repaintCombobox(); + } + + @Override + public Dimension getMinimumSize() { + final Dimension minimumSize = super.getMinimumSize(); + return new Dimension(minimumSize.width, minimumSize.height + 2); + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public void setBounds(final int x, final int y, final int width, final int height) { + UIUtil.setComboBoxEditorBounds(x, y, width, height, this); + } + + @Override + public void insertUpdate(DocumentEvent e) { + textChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + textChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + textChanged(); + } + + private void textChanged() { + final Container ancestor = SwingUtilities.getAncestorOfClass(JComboBox.class, this); + if (ancestor == null || !ancestor.isVisible()) return; + + final JComboBox comboBox = (JComboBox)ancestor; + if (!comboBox.isPopupVisible()) return; + + final ComboPopup popup = getComboboxPopup(comboBox); + if (popup == null) return; + + String s = myField.getText(); + + final ListModel listmodel = comboBox.getModel(); + int i = listmodel.getSize(); + if (s.length() > 0) { + for (int j = 0; j < i; j++) { + Object obj = listmodel.getElementAt(j); + if (obj == null) continue; + + String s1 = obj.toString(); + if (s1 != null && (s1.startsWith(s) || s1.equals(s))) { + popup.getList().setSelectedIndex(j); + return; + } + } + } + + popup.getList().clearSelection(); + } + } + + public static class MacComboBoxEditorBorder implements Border { + + private boolean myDisabled; + + public MacComboBoxEditorBorder(final boolean disabled) { + myDisabled = disabled; + } + + @Override + public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { + Color topColor; + Color secondTopColor; + Color leftRightColor; + Color bottomColor; + + if (myDisabled) { + topColor = new Color(200, 200, 200); + secondTopColor = new Color(250, 250, 250); + leftRightColor = new Color(205, 205, 205); + bottomColor = new Color(220, 220, 220); + } + else { + topColor = new Color(150, 150, 150); + secondTopColor = new Color(230, 230, 230); + leftRightColor = new Color(175, 175, 175); + bottomColor = new Color(200, 200, 200); + } + + g.setColor(topColor); + g.drawLine(x + 3, y + 3, x + width - 1, y + 3); + + g.setColor(secondTopColor); + g.drawLine(x + 3, y + 4, x + width - 1, y + 4); + + g.setColor(leftRightColor); + g.drawLine(x + 3, y + 4, x + 3, y + height - 4); + g.drawLine(x + width - 1, y + 4, x + width - 1, y + height - 4); + + g.setColor(bottomColor); + g.drawLine(x + 4, y + height - 4, x + width - 2, y + height - 4); + + g.setColor(UIUtil.getPanelBackground()); + + g.fillRect(x, y, width, 3); + g.fillRect(x, y, 3, height); + g.fillRect(x, y + height - 3, width, 3); + } + + @Override + public Insets getBorderInsets(final Component c) { + return new Insets(6, 6, 4, 3); + } + + @Override + public boolean isBorderOpaque() { + return true; + } + } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java b/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java index d3fc5b1583dd..a4012a2abedb 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java @@ -1,272 +1,272 @@ -/* - * 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.ui; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.ui.components.panels.NonOpaquePanel; -import com.intellij.util.Alarm; -import com.intellij.util.ui.Animator; -import com.intellij.util.ui.AsyncProcessIcon; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.image.BufferedImage; - -public class LoadingDecorator { - - JLayeredPane myPane = new MyLayeredPane(); - - LoadingLayer myLoadingLayer; - Animator myFadeOutAnimator; - - int myDelay; - Alarm myStartAlarm; - boolean myStartRequest; - - - public LoadingDecorator(JComponent content, Disposable parent, int startDelayMs) { - myLoadingLayer = new LoadingLayer(); - myDelay = startDelayMs; - myStartAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD, parent); - - setLoadingText("Loading..."); - - - myFadeOutAnimator = new Animator("Loading", 10, 500, false, 0, 1) { - public void paintNow(final float frame, final float totalFrames, final float cycle) { - myLoadingLayer.setAlpha(1f - frame / totalFrames); - } - - @Override - protected void paintCycleEnd() { - myLoadingLayer.setVisible(false); - myLoadingLayer.setAlpha(-1); - } - }; - Disposer.register(parent, myFadeOutAnimator); - - - myPane.add(content, 0, JLayeredPane.DEFAULT_LAYER); - myPane.add(myLoadingLayer, 1, JLayeredPane.DRAG_LAYER); - - Disposer.register(parent, myLoadingLayer.myProgress); - } - - public JComponent getComponent() { - return myPane; - } - - public void startLoading(final boolean takeSnapshot) { - if (isLoading() || myStartRequest) return; - - myStartRequest = true; - if (myDelay > 0) { - myStartAlarm.addRequest(new Runnable() { - public void run() { - UIUtil.invokeLaterIfNeeded(new Runnable() { - public void run() { - if (!myStartRequest) return; - _startLoading(takeSnapshot); - } - }); - } - }, myDelay); - } else { - _startLoading(takeSnapshot); - } - } - - private void _startLoading(final boolean takeSnapshot) { - myLoadingLayer.setVisible(true, takeSnapshot); - } - - public void stopLoading() { - myStartRequest = false; - myStartAlarm.cancelAllRequests(); - - if (!isLoading()) return; - - myLoadingLayer.setVisible(false, false); - } - - - public String getLoadingText() { - return myLoadingLayer.myText.getText(); - } - - public void setLoadingText(final String loadingText) { - myLoadingLayer.myText.setText(loadingText); - } - - public boolean isLoading() { - return myLoadingLayer.isVisible(); - } - - private class LoadingLayer extends JPanel { - - private final JLabel myText; - - private BufferedImage mySnapshot; - private Color mySnapshotBg; - - private final AsyncProcessIcon myProgress = new AsyncProcessIcon.Big("Loading"); - - private boolean myVisible; - - private float myCurrentAlpha; - private final NonOpaquePanel myTextComponent; - - private LoadingLayer() { - setOpaque(false); - setVisible(false); - setLayout(new GridBagLayout()); - - myText = new JLabel("", JLabel.CENTER); - final Font font = myText.getFont(); - myText.setFont(font.deriveFont(font.getStyle(), font.getSize() + 8)); - myText.setForeground(Color.black); - myProgress.setOpaque(false); - - - final int gap = new JLabel().getIconTextGap(); - myTextComponent = new NonOpaquePanel(new FlowLayout(FlowLayout.CENTER, gap * 3, 0)); - myTextComponent.add(myProgress); - myTextComponent.add(myText); - - add(myTextComponent); - - myProgress.suspend(); - } - - public void setVisible(final boolean visible, boolean takeSnapshot) { - if (myVisible == visible) return; - - if (myVisible && !visible && myCurrentAlpha != -1) return; - - myVisible = visible; - if (myVisible) { - setVisible(myVisible); - myCurrentAlpha = -1; - } - - if (myVisible) { - if (takeSnapshot && getWidth() > 0 && getHeight() > 0) { - mySnapshot = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); - final Graphics2D g = mySnapshot.createGraphics(); - myPane.paint(g); - final Component opaque = UIUtil.findNearestOpaque(this); - mySnapshotBg = opaque != null ? opaque.getBackground() : UIUtil.getPanelBackground(); - g.dispose(); - } - myProgress.resume(); - } else { - disposeSnapshot(); - myProgress.suspend(); - - myFadeOutAnimator.reset(); - myFadeOutAnimator.resume(); - } - } - - private void disposeSnapshot() { - if (mySnapshot != null) { - mySnapshot.flush(); - mySnapshot = null; - } - } - - @Override - protected void paintComponent(final Graphics g) { - if (mySnapshot != null) { - if (mySnapshot.getWidth() == getWidth() && mySnapshot.getHeight() == getHeight()) { - g.drawImage(mySnapshot, 0, 0, getWidth(), getHeight(), null); - g.setColor(new Color(200, 200, 200, 240)); - g.fillRect(0, 0, getWidth(), getHeight()); - return; - } else { - disposeSnapshot(); - } - } - - if (mySnapshotBg != null) { - g.setColor(mySnapshotBg); - g.fillRect(0, 0, getWidth(), getHeight()); - } - } - - public void setAlpha(final float alpha) { - myCurrentAlpha = alpha; - paintImmediately(myTextComponent.getBounds()); - } - - @Override - protected void paintChildren(final Graphics g) { - if (myCurrentAlpha != -1) { - ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, myCurrentAlpha)); - } - super.paintChildren(g); - } - } - - public static void main(String[] args) { - IconLoader.activate(); - - final JFrame frame = new JFrame(); - frame.getContentPane().setLayout(new BorderLayout()); - - final JPanel content = new JPanel(new BorderLayout()); - - final LoadingDecorator loadingTree = new LoadingDecorator(new JComboBox(), Disposer.newDisposable(), -1); - - content.add(loadingTree.getComponent(), BorderLayout.CENTER); - - final JCheckBox loadingCheckBox = new JCheckBox("Loading"); - loadingCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(final ActionEvent e) { - if (loadingTree.isLoading()) { - loadingTree.stopLoading(); - } else { - loadingTree.startLoading(false); - } - } - }); - - content.add(loadingCheckBox, BorderLayout.SOUTH); - - - frame.getContentPane().add(content, BorderLayout.CENTER); - - frame.setBounds(300, 300, 300, 300); - frame.show(); - } - - - private static class MyLayeredPane extends JLayeredPane { - @Override - public void doLayout() { - super.doLayout(); - for (int i = 0; i < getComponentCount(); i++) { - final Component each = getComponent(i); - each.setBounds(0, 0, getWidth(), getHeight()); - } - } - } -} +/* + * 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.ui; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.util.Alarm; +import com.intellij.util.ui.Animator; +import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; + +public class LoadingDecorator { + + JLayeredPane myPane = new MyLayeredPane(); + + LoadingLayer myLoadingLayer; + Animator myFadeOutAnimator; + + int myDelay; + Alarm myStartAlarm; + boolean myStartRequest; + + + public LoadingDecorator(JComponent content, Disposable parent, int startDelayMs) { + myLoadingLayer = new LoadingLayer(); + myDelay = startDelayMs; + myStartAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD, parent); + + setLoadingText("Loading..."); + + + myFadeOutAnimator = new Animator("Loading", 10, 500, false, 0, 1) { + public void paintNow(final float frame, final float totalFrames, final float cycle) { + myLoadingLayer.setAlpha(1f - frame / totalFrames); + } + + @Override + protected void paintCycleEnd() { + myLoadingLayer.setVisible(false); + myLoadingLayer.setAlpha(-1); + } + }; + Disposer.register(parent, myFadeOutAnimator); + + + myPane.add(content, 0, JLayeredPane.DEFAULT_LAYER); + myPane.add(myLoadingLayer, 1, JLayeredPane.DRAG_LAYER); + + Disposer.register(parent, myLoadingLayer.myProgress); + } + + public JComponent getComponent() { + return myPane; + } + + public void startLoading(final boolean takeSnapshot) { + if (isLoading() || myStartRequest) return; + + myStartRequest = true; + if (myDelay > 0) { + myStartAlarm.addRequest(new Runnable() { + public void run() { + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + if (!myStartRequest) return; + _startLoading(takeSnapshot); + } + }); + } + }, myDelay); + } else { + _startLoading(takeSnapshot); + } + } + + private void _startLoading(final boolean takeSnapshot) { + myLoadingLayer.setVisible(true, takeSnapshot); + } + + public void stopLoading() { + myStartRequest = false; + myStartAlarm.cancelAllRequests(); + + if (!isLoading()) return; + + myLoadingLayer.setVisible(false, false); + } + + + public String getLoadingText() { + return myLoadingLayer.myText.getText(); + } + + public void setLoadingText(final String loadingText) { + myLoadingLayer.myText.setText(loadingText); + } + + public boolean isLoading() { + return myLoadingLayer.isVisible(); + } + + private class LoadingLayer extends JPanel { + + private final JLabel myText; + + private BufferedImage mySnapshot; + private Color mySnapshotBg; + + private final AsyncProcessIcon myProgress = new AsyncProcessIcon.Big("Loading"); + + private boolean myVisible; + + private float myCurrentAlpha; + private final NonOpaquePanel myTextComponent; + + private LoadingLayer() { + setOpaque(false); + setVisible(false); + setLayout(new GridBagLayout()); + + myText = new JLabel("", JLabel.CENTER); + final Font font = myText.getFont(); + myText.setFont(font.deriveFont(font.getStyle(), font.getSize() + 8)); + myText.setForeground(Color.black); + myProgress.setOpaque(false); + + + final int gap = new JLabel().getIconTextGap(); + myTextComponent = new NonOpaquePanel(new FlowLayout(FlowLayout.CENTER, gap * 3, 0)); + myTextComponent.add(myProgress); + myTextComponent.add(myText); + + add(myTextComponent); + + myProgress.suspend(); + } + + public void setVisible(final boolean visible, boolean takeSnapshot) { + if (myVisible == visible) return; + + if (myVisible && !visible && myCurrentAlpha != -1) return; + + myVisible = visible; + if (myVisible) { + setVisible(myVisible); + myCurrentAlpha = -1; + } + + if (myVisible) { + if (takeSnapshot && getWidth() > 0 && getHeight() > 0) { + mySnapshot = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + final Graphics2D g = mySnapshot.createGraphics(); + myPane.paint(g); + final Component opaque = UIUtil.findNearestOpaque(this); + mySnapshotBg = opaque != null ? opaque.getBackground() : UIUtil.getPanelBackground(); + g.dispose(); + } + myProgress.resume(); + } else { + disposeSnapshot(); + myProgress.suspend(); + + myFadeOutAnimator.reset(); + myFadeOutAnimator.resume(); + } + } + + private void disposeSnapshot() { + if (mySnapshot != null) { + mySnapshot.flush(); + mySnapshot = null; + } + } + + @Override + protected void paintComponent(final Graphics g) { + if (mySnapshot != null) { + if (mySnapshot.getWidth() == getWidth() && mySnapshot.getHeight() == getHeight()) { + g.drawImage(mySnapshot, 0, 0, getWidth(), getHeight(), null); + g.setColor(new Color(200, 200, 200, 240)); + g.fillRect(0, 0, getWidth(), getHeight()); + return; + } else { + disposeSnapshot(); + } + } + + if (mySnapshotBg != null) { + g.setColor(mySnapshotBg); + g.fillRect(0, 0, getWidth(), getHeight()); + } + } + + public void setAlpha(final float alpha) { + myCurrentAlpha = alpha; + paintImmediately(myTextComponent.getBounds()); + } + + @Override + protected void paintChildren(final Graphics g) { + if (myCurrentAlpha != -1) { + ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, myCurrentAlpha)); + } + super.paintChildren(g); + } + } + + public static void main(String[] args) { + IconLoader.activate(); + + final JFrame frame = new JFrame(); + frame.getContentPane().setLayout(new BorderLayout()); + + final JPanel content = new JPanel(new BorderLayout()); + + final LoadingDecorator loadingTree = new LoadingDecorator(new JComboBox(), Disposer.newDisposable(), -1); + + content.add(loadingTree.getComponent(), BorderLayout.CENTER); + + final JCheckBox loadingCheckBox = new JCheckBox("Loading"); + loadingCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + if (loadingTree.isLoading()) { + loadingTree.stopLoading(); + } else { + loadingTree.startLoading(false); + } + } + }); + + content.add(loadingCheckBox, BorderLayout.SOUTH); + + + frame.getContentPane().add(content, BorderLayout.CENTER); + + frame.setBounds(300, 300, 300, 300); + frame.show(); + } + + + private static class MyLayeredPane extends JLayeredPane { + @Override + public void doLayout() { + super.doLayout(); + for (int i = 0; i < getComponentCount(); i++) { + final Component each = getComponent(i); + each.setBounds(0, 0, getWidth(), getHeight()); + } + } + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/ui/NamedItemsListEditor.java b/platform/platform-api/src/com/intellij/openapi/ui/NamedItemsListEditor.java index 9b491118614f..a31e681be63d 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/NamedItemsListEditor.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/NamedItemsListEditor.java @@ -1,327 +1,327 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.openapi.ui; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonShortcuts; -import com.intellij.openapi.actionSystem.CustomShortcutSet; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.UnnamedConfigurable; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.util.*; -import com.intellij.util.Icons; -import com.intellij.util.ui.tree.TreeUtil; -import gnu.trove.Equality; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.awt.event.KeyEvent; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public abstract class NamedItemsListEditor extends MasterDetailsComponent { - private final Namer myNamer; - private final Factory myFactory; - private final Cloner myCloner; - private final List myItems = new ArrayList(); - private final Equality myComparer; - private List myResultItems; - private final List myOriginalItems; - - protected NamedItemsListEditor(Namer namer, - Factory factory, - Cloner cloner, - Equality comparer, - List items) { - this(namer, factory, cloner, comparer, items, true); - } - - protected NamedItemsListEditor(Namer namer, - Factory factory, - Cloner cloner, - Equality comparer, - List items, - boolean initInConstructor) { - myNamer = namer; - myFactory = factory; - myCloner = cloner; - myComparer = comparer; - - myOriginalItems = items; - myResultItems = items; - if (initInConstructor) { - reset(); - initTree(); - } - } - - public void reset() { - myResultItems = myOriginalItems; - myItems.clear(); - - myRoot.removeAllChildren(); - for (T item : myOriginalItems) { - addNewNode(myCloner.cloneOf(item)); - } - - super.reset(); - } - - protected void processRemovedItems() { - } - - protected boolean wasObjectStored(Object editableObject) { - return true; - } - - protected String subjDisplayName() { - return "item"; - } - - @Nullable - public String askForProfileName(String titlePattern) { - String title = MessageFormat.format(titlePattern, subjDisplayName()); - return Messages.showInputDialog("New " + subjDisplayName() + " name:", title, Messages.getQuestionIcon(), "", new InputValidator() { - public boolean checkInput(String s) { - return s.length() > 0 && findByName(s) == null; - } - - public boolean canClose(String s) { - return checkInput(s); - } - }); - } - - @Nullable - private T findByName(String name) { - for (T item : myItems) { - if (Comparing.equal(name, myNamer.getName(item))) return item; - } - - return null; - } - - @Nullable - protected ArrayList createActions(boolean fromPopup) { - ArrayList result = new ArrayList(); - result.add(new AddAction()); - - result.add(new MyDeleteAction(new Condition() { - @SuppressWarnings({"unchecked"}) - public boolean value(Object o) { - return canDelete((T) ((MyNode) o).getConfigurable().getEditableObject()); - } - })); - - result.add(new CopyAction()); - - return result; - } - - private void addNewNode(T item) { - addNode(new MyNode(new ItemConfigurable(item)), myRoot); - myItems.add(item); - } - - protected boolean canDelete(T item) { - return true; - } - - protected abstract UnnamedConfigurable createConfigurable(T item); - - @Override - protected void onItemDeleted(Object item) { - myItems.remove((T)item); - } - - protected void setDisplayName(T item, String name) { - myNamer.setName(item, name); - } - - @Nullable - protected UnnamedConfigurable getItemConfigurable(final T item) { - final Ref result = new Ref(); - TreeUtil.traverse((TreeNode)myTree.getModel().getRoot(), new TreeUtil.Traverse() { - public boolean accept(Object node) { - final NamedConfigurable configurable = (NamedConfigurable)((DefaultMutableTreeNode)node).getUserObject(); - if (configurable.getEditableObject() == item) { - result.set(((ItemConfigurable)configurable).myConfigurable); - return false; - } - else { - return true; - } - } - }); - return result.get(); - } - - private class ItemConfigurable extends NamedConfigurable { - private final T myItem; - private final UnnamedConfigurable myConfigurable; - - public ItemConfigurable(T item) { - super(myNamer.canRename(item), TREE_UPDATER); - myItem = item; - myConfigurable = createConfigurable(item); - } - - public void setDisplayName(String name) { - NamedItemsListEditor.this.setDisplayName(myItem, name); - } - - public Object getEditableObject() { - return myItem; - } - - public String getBannerSlogan() { - return myNamer.getName(myItem); - } - - public JComponent createOptionsPanel() { - return myConfigurable.createComponent(); - } - - public String getDisplayName() { - return myNamer.getName(myItem); - } - - public Icon getIcon() { - if (myConfigurable instanceof Iconable) { - return ((Iconable)myConfigurable).getIcon(0); - } - return null; - } - - public String getHelpTopic() { - return null; - } - - public boolean isModified() { - return myConfigurable.isModified(); - } - - public void apply() throws ConfigurationException { - myConfigurable.apply(); - } - - public void reset() { - myConfigurable.reset(); - } - - public void disposeUIResources() { - myConfigurable.disposeUIResources(); - } - } - - @Override - public boolean isModified() { - if (myResultItems.size() != myItems.size()) return true; - - for (int i = 0; i < myItems.size(); i++) { - if (!myComparer.equals(myItems.get(i), myResultItems.get(i))) return true; - } - - return super.isModified(); - } - - public void apply() throws ConfigurationException { - super.apply(); - myResultItems = myItems; - } - - protected List getCurrentItems() { - return Collections.unmodifiableList(myItems); - } - - public List getItems() { - return myResultItems; - } - - public T getSelectedItem() { - return (T) getSelectedObject(); - } - - - private class CopyAction extends DumbAwareAction { - public CopyAction() { - super("Copy", "Copy", MasterDetailsComponent.COPY_ICON); - registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK)), myTree); - } - - public void actionPerformed(AnActionEvent event) { - final String profileName = askForProfileName("Copy {0}"); - if (profileName == null) return; - - final T clone = myCloner.copyOf((T) getSelectedObject()); - myNamer.setName(clone, profileName); - addNewNode(clone); - selectNodeInTree(clone); - onItemCloned(clone); - } - - - public void update(AnActionEvent event) { - super.update(event); - event.getPresentation().setEnabled(getSelectedObject() != null); - } - } - - protected void onItemCloned(T clone) { - } - - private class AddAction extends DumbAwareAction { - public AddAction() { - super("Add", "Add", Icons.ADD_ICON); - registerCustomShortcutSet(CommonShortcuts.INSERT, myTree); - } - - public void actionPerformed(AnActionEvent event) { - final T newItem = createItem(); - if (newItem != null) { - onItemCreated(newItem); - } - } - } - - public void selectItem(T item) { - selectNodeInTree(findByName(myNamer.getName(item))); - } - - @Nullable - protected T createItem() { - final String name = askForProfileName("Create new {0}"); - if (name == null) return null; - final T newItem = myFactory.create(); - myNamer.setName(newItem, name); - return newItem; - } - - protected void onItemCreated(T newItem) { - addNewNode(newItem); - selectNodeInTree(newItem); - } - -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.openapi.ui; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonShortcuts; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.UnnamedConfigurable; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.util.*; +import com.intellij.util.Icons; +import com.intellij.util.ui.tree.TreeUtil; +import gnu.trove.Equality; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.awt.event.KeyEvent; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class NamedItemsListEditor extends MasterDetailsComponent { + private final Namer myNamer; + private final Factory myFactory; + private final Cloner myCloner; + private final List myItems = new ArrayList(); + private final Equality myComparer; + private List myResultItems; + private final List myOriginalItems; + + protected NamedItemsListEditor(Namer namer, + Factory factory, + Cloner cloner, + Equality comparer, + List items) { + this(namer, factory, cloner, comparer, items, true); + } + + protected NamedItemsListEditor(Namer namer, + Factory factory, + Cloner cloner, + Equality comparer, + List items, + boolean initInConstructor) { + myNamer = namer; + myFactory = factory; + myCloner = cloner; + myComparer = comparer; + + myOriginalItems = items; + myResultItems = items; + if (initInConstructor) { + reset(); + initTree(); + } + } + + public void reset() { + myResultItems = myOriginalItems; + myItems.clear(); + + myRoot.removeAllChildren(); + for (T item : myOriginalItems) { + addNewNode(myCloner.cloneOf(item)); + } + + super.reset(); + } + + protected void processRemovedItems() { + } + + protected boolean wasObjectStored(Object editableObject) { + return true; + } + + protected String subjDisplayName() { + return "item"; + } + + @Nullable + public String askForProfileName(String titlePattern) { + String title = MessageFormat.format(titlePattern, subjDisplayName()); + return Messages.showInputDialog("New " + subjDisplayName() + " name:", title, Messages.getQuestionIcon(), "", new InputValidator() { + public boolean checkInput(String s) { + return s.length() > 0 && findByName(s) == null; + } + + public boolean canClose(String s) { + return checkInput(s); + } + }); + } + + @Nullable + private T findByName(String name) { + for (T item : myItems) { + if (Comparing.equal(name, myNamer.getName(item))) return item; + } + + return null; + } + + @Nullable + protected ArrayList createActions(boolean fromPopup) { + ArrayList result = new ArrayList(); + result.add(new AddAction()); + + result.add(new MyDeleteAction(new Condition() { + @SuppressWarnings({"unchecked"}) + public boolean value(Object o) { + return canDelete((T) ((MyNode) o).getConfigurable().getEditableObject()); + } + })); + + result.add(new CopyAction()); + + return result; + } + + private void addNewNode(T item) { + addNode(new MyNode(new ItemConfigurable(item)), myRoot); + myItems.add(item); + } + + protected boolean canDelete(T item) { + return true; + } + + protected abstract UnnamedConfigurable createConfigurable(T item); + + @Override + protected void onItemDeleted(Object item) { + myItems.remove((T)item); + } + + protected void setDisplayName(T item, String name) { + myNamer.setName(item, name); + } + + @Nullable + protected UnnamedConfigurable getItemConfigurable(final T item) { + final Ref result = new Ref(); + TreeUtil.traverse((TreeNode)myTree.getModel().getRoot(), new TreeUtil.Traverse() { + public boolean accept(Object node) { + final NamedConfigurable configurable = (NamedConfigurable)((DefaultMutableTreeNode)node).getUserObject(); + if (configurable.getEditableObject() == item) { + result.set(((ItemConfigurable)configurable).myConfigurable); + return false; + } + else { + return true; + } + } + }); + return result.get(); + } + + private class ItemConfigurable extends NamedConfigurable { + private final T myItem; + private final UnnamedConfigurable myConfigurable; + + public ItemConfigurable(T item) { + super(myNamer.canRename(item), TREE_UPDATER); + myItem = item; + myConfigurable = createConfigurable(item); + } + + public void setDisplayName(String name) { + NamedItemsListEditor.this.setDisplayName(myItem, name); + } + + public Object getEditableObject() { + return myItem; + } + + public String getBannerSlogan() { + return myNamer.getName(myItem); + } + + public JComponent createOptionsPanel() { + return myConfigurable.createComponent(); + } + + public String getDisplayName() { + return myNamer.getName(myItem); + } + + public Icon getIcon() { + if (myConfigurable instanceof Iconable) { + return ((Iconable)myConfigurable).getIcon(0); + } + return null; + } + + public String getHelpTopic() { + return null; + } + + public boolean isModified() { + return myConfigurable.isModified(); + } + + public void apply() throws ConfigurationException { + myConfigurable.apply(); + } + + public void reset() { + myConfigurable.reset(); + } + + public void disposeUIResources() { + myConfigurable.disposeUIResources(); + } + } + + @Override + public boolean isModified() { + if (myResultItems.size() != myItems.size()) return true; + + for (int i = 0; i < myItems.size(); i++) { + if (!myComparer.equals(myItems.get(i), myResultItems.get(i))) return true; + } + + return super.isModified(); + } + + public void apply() throws ConfigurationException { + super.apply(); + myResultItems = myItems; + } + + protected List getCurrentItems() { + return Collections.unmodifiableList(myItems); + } + + public List getItems() { + return myResultItems; + } + + public T getSelectedItem() { + return (T) getSelectedObject(); + } + + + private class CopyAction extends DumbAwareAction { + public CopyAction() { + super("Copy", "Copy", MasterDetailsComponent.COPY_ICON); + registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK)), myTree); + } + + public void actionPerformed(AnActionEvent event) { + final String profileName = askForProfileName("Copy {0}"); + if (profileName == null) return; + + final T clone = myCloner.copyOf((T) getSelectedObject()); + myNamer.setName(clone, profileName); + addNewNode(clone); + selectNodeInTree(clone); + onItemCloned(clone); + } + + + public void update(AnActionEvent event) { + super.update(event); + event.getPresentation().setEnabled(getSelectedObject() != null); + } + } + + protected void onItemCloned(T clone) { + } + + private class AddAction extends DumbAwareAction { + public AddAction() { + super("Add", "Add", Icons.ADD_ICON); + registerCustomShortcutSet(CommonShortcuts.INSERT, myTree); + } + + public void actionPerformed(AnActionEvent event) { + final T newItem = createItem(); + if (newItem != null) { + onItemCreated(newItem); + } + } + } + + public void selectItem(T item) { + selectNodeInTree(findByName(myNamer.getName(item))); + } + + @Nullable + protected T createItem() { + final String name = askForProfileName("Create new {0}"); + if (name == null) return null; + final T newItem = myFactory.create(); + myNamer.setName(newItem, name); + return newItem; + } + + protected void onItemCreated(T newItem) { + addNewNode(newItem); + selectNodeInTree(newItem); + } + +} diff --git a/platform/platform-api/src/com/intellij/openapi/ui/TreeComboBox.java b/platform/platform-api/src/com/intellij/openapi/ui/TreeComboBox.java index fb528d6dc3e5..c52d8450478d 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/TreeComboBox.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/TreeComboBox.java @@ -1,233 +1,233 @@ -/* - * 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.ui; - -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.containers.Stack; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; -import java.awt.*; -import java.util.Collections; -import java.util.Enumeration; - -/** - * User: spLeaner - */ -public class TreeComboBox extends JComboBox { - final static int INDENT = UIUtil.getTreeLeftChildIndent(); - private TreeModel myTreeModel; - - public TreeComboBox(@NotNull final TreeModel model) { - myTreeModel = model; - setModel(new TreeModelWrapper(myTreeModel)); - setRenderer(new TreeListCellRenderer(this, model)); - if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); - } - - public TreeModel getTreeModel() { - return myTreeModel; - } - - private static class TreeListCellRenderer extends JLabel implements ListCellRenderer { - private static final Border SELECTION_PAINTER = (Border)UIManager.get("MenuItem.selectedBackgroundPainter"); - - private TreeModel myTreeModel; - private boolean mySelected; - private boolean myInList; - private JComboBox myComboBox; - private boolean myChecked; - private boolean myEditable; - private int myLevel = 0; - private JTree myTree; - private boolean myUnderAquaLookAndFeel; - - private TreeListCellRenderer(@NotNull final JComboBox comboBox, @NotNull final TreeModel model) { - myComboBox = comboBox; - myTreeModel = model; - myTree = new JTree(myTreeModel); - TreeUtil.expandAll(myTree); - myTree.setRootVisible(true); - myUnderAquaLookAndFeel = UIUtil.isUnderAquaLookAndFeel(); - setOpaque(!myUnderAquaLookAndFeel); - } - - @Override - public Component getListCellRendererComponent(final JList list, - final Object value, - final int index, - final boolean isSelected, - final boolean cellHasFocus) { - myInList = index >= 0; - if (index >= 0) { - Object obj1 = myComboBox.getItemAt(index); - myChecked = obj1 != null && obj1.equals(myComboBox.getSelectedItem()); - } - else { - myChecked = false; - } - - if (value instanceof Iconable) { - setIcon(((Iconable)value).getIcon(Iconable.ICON_FLAG_OPEN)); - } - - if (myInList) { - final TreePath path = myTree.getPathForRow(index); - myLevel = path == null ? 0 : path.getPathCount() - 1; - } else { - myLevel = 0; - } - - myEditable = myComboBox.isEditable(); - - setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); - if (!myUnderAquaLookAndFeel) setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); - - setText(value.toString()); - setSelected(isSelected); - setFont(list.getFont()); - return this; - } - - private void setSelected(final boolean selected) { - mySelected = selected; - } - - public Insets getInsets(Insets insets) { - if (insets == null) insets = new Insets(0, 0, 0, 0); - insets.top = 1; - insets.bottom = 1; - insets.right = 5; - insets.left = !myInList || myEditable ? (myUnderAquaLookAndFeel ? 0 : 5) : (myUnderAquaLookAndFeel ? 23 : 5) + (INDENT * myLevel); - return insets; - } - - @Override - protected void paintComponent(final Graphics g) { - if (myUnderAquaLookAndFeel) { - if (mySelected) { - SELECTION_PAINTER.paintBorder(this, g, 0, 0, getWidth(), getHeight()); - } - - if (SystemInfo.isMac && myChecked && !myEditable) { - int i = getHeight() - 4; - g.setColor(getForeground()); - g.drawString("\u2713", 6, i); - } - } - - super.paintComponent(g); - } - } - - private static class TreeModelWrapper extends AbstractListModel implements ComboBoxModel { - private TreeModel myTreeModel; - private Object mySelectedItem; - - private TreeModelWrapper(@NotNull final TreeModel treeModel) { - myTreeModel = treeModel; - } - - public TreeModel getTreeModel() { - return myTreeModel; - } - - @Override - public void setSelectedItem(final Object obj) { - if (mySelectedItem != null && !mySelectedItem.equals(obj) || mySelectedItem == null && obj != null) { - mySelectedItem = obj; - fireContentsChanged(this, -1, -1); - } - } - - @Override - public Object getSelectedItem() { - return mySelectedItem; - } - - public int getSize() { - int count = 0; - Enumeration e = new PreorderEnumeration(myTreeModel); - while (e.hasMoreElements()) { - e.nextElement(); - count++; - } - return count; - } - - public Object getElementAt(int index) { - Enumeration e = new PreorderEnumeration(myTreeModel); - for (int i = 0; i < index; i++) { - e.nextElement(); - } - return e.nextElement(); - } - } - - private static class ChildrenEnumeration implements Enumeration { - private final TreeModel myTreeModel; - private final Object myNode; - private int myIndex = -1; - - public ChildrenEnumeration(@NotNull final TreeModel treeModel, @NotNull final Object node) { - myTreeModel = treeModel; - myNode = node; - } - - public boolean hasMoreElements() { - return myIndex < myTreeModel.getChildCount(myNode) - 1; - } - - public Object nextElement() { - return myTreeModel.getChild(myNode, ++myIndex); - } - } - - private static class PreorderEnumeration implements Enumeration { - private final TreeModel myTreeModel; - private final Stack myStack; - - public PreorderEnumeration(@NotNull final TreeModel treeModel) { - myTreeModel = treeModel; - myStack = new Stack(); - myStack.push(Collections.enumeration(Collections.singleton(treeModel.getRoot()))); - } - - public boolean hasMoreElements() { - return (!myStack.empty() && - myStack.peek().hasMoreElements()); - } - - public Object nextElement() { - Enumeration e = myStack.peek(); - Object node = e.nextElement(); - if (!e.hasMoreElements()) { - myStack.pop(); - } - Enumeration children = new ChildrenEnumeration(myTreeModel, node); - if (children.hasMoreElements()) { - myStack.push(children); - } - return node; - } - } -} +/* + * 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.ui; + +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.containers.Stack; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.util.Collections; +import java.util.Enumeration; + +/** + * User: spLeaner + */ +public class TreeComboBox extends JComboBox { + final static int INDENT = UIUtil.getTreeLeftChildIndent(); + private TreeModel myTreeModel; + + public TreeComboBox(@NotNull final TreeModel model) { + myTreeModel = model; + setModel(new TreeModelWrapper(myTreeModel)); + setRenderer(new TreeListCellRenderer(this, model)); + if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) setMaximumRowCount(25); + } + + public TreeModel getTreeModel() { + return myTreeModel; + } + + private static class TreeListCellRenderer extends JLabel implements ListCellRenderer { + private static final Border SELECTION_PAINTER = (Border)UIManager.get("MenuItem.selectedBackgroundPainter"); + + private TreeModel myTreeModel; + private boolean mySelected; + private boolean myInList; + private JComboBox myComboBox; + private boolean myChecked; + private boolean myEditable; + private int myLevel = 0; + private JTree myTree; + private boolean myUnderAquaLookAndFeel; + + private TreeListCellRenderer(@NotNull final JComboBox comboBox, @NotNull final TreeModel model) { + myComboBox = comboBox; + myTreeModel = model; + myTree = new JTree(myTreeModel); + TreeUtil.expandAll(myTree); + myTree.setRootVisible(true); + myUnderAquaLookAndFeel = UIUtil.isUnderAquaLookAndFeel(); + setOpaque(!myUnderAquaLookAndFeel); + } + + @Override + public Component getListCellRendererComponent(final JList list, + final Object value, + final int index, + final boolean isSelected, + final boolean cellHasFocus) { + myInList = index >= 0; + if (index >= 0) { + Object obj1 = myComboBox.getItemAt(index); + myChecked = obj1 != null && obj1.equals(myComboBox.getSelectedItem()); + } + else { + myChecked = false; + } + + if (value instanceof Iconable) { + setIcon(((Iconable)value).getIcon(Iconable.ICON_FLAG_OPEN)); + } + + if (myInList) { + final TreePath path = myTree.getPathForRow(index); + myLevel = path == null ? 0 : path.getPathCount() - 1; + } else { + myLevel = 0; + } + + myEditable = myComboBox.isEditable(); + + setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + if (!myUnderAquaLookAndFeel) setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); + + setText(value.toString()); + setSelected(isSelected); + setFont(list.getFont()); + return this; + } + + private void setSelected(final boolean selected) { + mySelected = selected; + } + + public Insets getInsets(Insets insets) { + if (insets == null) insets = new Insets(0, 0, 0, 0); + insets.top = 1; + insets.bottom = 1; + insets.right = 5; + insets.left = !myInList || myEditable ? (myUnderAquaLookAndFeel ? 0 : 5) : (myUnderAquaLookAndFeel ? 23 : 5) + (INDENT * myLevel); + return insets; + } + + @Override + protected void paintComponent(final Graphics g) { + if (myUnderAquaLookAndFeel) { + if (mySelected) { + SELECTION_PAINTER.paintBorder(this, g, 0, 0, getWidth(), getHeight()); + } + + if (SystemInfo.isMac && myChecked && !myEditable) { + int i = getHeight() - 4; + g.setColor(getForeground()); + g.drawString("\u2713", 6, i); + } + } + + super.paintComponent(g); + } + } + + private static class TreeModelWrapper extends AbstractListModel implements ComboBoxModel { + private TreeModel myTreeModel; + private Object mySelectedItem; + + private TreeModelWrapper(@NotNull final TreeModel treeModel) { + myTreeModel = treeModel; + } + + public TreeModel getTreeModel() { + return myTreeModel; + } + + @Override + public void setSelectedItem(final Object obj) { + if (mySelectedItem != null && !mySelectedItem.equals(obj) || mySelectedItem == null && obj != null) { + mySelectedItem = obj; + fireContentsChanged(this, -1, -1); + } + } + + @Override + public Object getSelectedItem() { + return mySelectedItem; + } + + public int getSize() { + int count = 0; + Enumeration e = new PreorderEnumeration(myTreeModel); + while (e.hasMoreElements()) { + e.nextElement(); + count++; + } + return count; + } + + public Object getElementAt(int index) { + Enumeration e = new PreorderEnumeration(myTreeModel); + for (int i = 0; i < index; i++) { + e.nextElement(); + } + return e.nextElement(); + } + } + + private static class ChildrenEnumeration implements Enumeration { + private final TreeModel myTreeModel; + private final Object myNode; + private int myIndex = -1; + + public ChildrenEnumeration(@NotNull final TreeModel treeModel, @NotNull final Object node) { + myTreeModel = treeModel; + myNode = node; + } + + public boolean hasMoreElements() { + return myIndex < myTreeModel.getChildCount(myNode) - 1; + } + + public Object nextElement() { + return myTreeModel.getChild(myNode, ++myIndex); + } + } + + private static class PreorderEnumeration implements Enumeration { + private final TreeModel myTreeModel; + private final Stack myStack; + + public PreorderEnumeration(@NotNull final TreeModel treeModel) { + myTreeModel = treeModel; + myStack = new Stack(); + myStack.push(Collections.enumeration(Collections.singleton(treeModel.getRoot()))); + } + + public boolean hasMoreElements() { + return (!myStack.empty() && + myStack.peek().hasMoreElements()); + } + + public Object nextElement() { + Enumeration e = myStack.peek(); + Object node = e.nextElement(); + if (!e.hasMoreElements()) { + myStack.pop(); + } + Enumeration children = new ChildrenEnumeration(myTreeModel, node); + if (children.hasMoreElements()) { + myStack.push(children); + } + return node; + } + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/VirtualFile.java b/platform/platform-api/src/com/intellij/openapi/vfs/VirtualFile.java index cba9a671ceca..914982e98ce1 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/VirtualFile.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/VirtualFile.java @@ -1,641 +1,641 @@ -/* - * 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.vfs; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.encoding.EncodingManager; -import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; -import com.intellij.util.Icons; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; - -/** - * Represents a file in {@link VirtualFileSystem}. A particular file is represented by the same - * VirtualFile instance for the entire lifetime of the IntelliJ IDEA process, unless the file - * is deleted, in which case {@link #isValid()} for the instance will return false. - *

- * If an in-memory implementation of VirtualFile is required, LightVirtualFile from the com.intellij.testFramework - * package (Extended API) can be used. - * - * @see VirtualFileSystem - * @see VirtualFileManager - */ -public abstract class VirtualFile extends UserDataHolderBase implements ModificationTracker { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VirtualFile"); - public static final Key REQUESTOR_MARKER = new Key("REQUESTOR_MARKER"); - private static final Key BOM_KEY = new Key("BOM"); - private static final Key CHARSET_KEY = new Key("CHARSET"); - public static final VirtualFile[] EMPTY_ARRAY = new VirtualFile[0]; - - protected VirtualFile() { - } - - /** - * Gets the name of this file. - * - * @return file name - */ - @NotNull - @NonNls - public abstract String getName(); - - /** - * Gets the {@link VirtualFileSystem} this file belongs to. - * - * @return the {@link VirtualFileSystem} - */ - @NotNull - public abstract VirtualFileSystem getFileSystem(); - - /** - * Gets the path of this file. Path is a string which uniquely identifies file within given - * {@link VirtualFileSystem}. Format of the path depends on the concrete file system. - * For {@link LocalFileSystem} it is an absoulute file path with file separator characters - * (File.separatorChar) replaced to the forward slash ('/'). - * - * @return the path - */ - public abstract String getPath(); - - /** - * Gets the URL of this file. The URL is a string which uniquely identifies file in all file systems. - * It has the following format: <protocol>://<path>. - *

- * File can be found by its URL using {@link VirtualFileManager#findFileByUrl} method. - * - * @return the URL consisting of protocol and path - * @see VirtualFileManager#findFileByUrl - * @see VirtualFile#getPath - * @see VirtualFileSystem#getProtocol - */ - @NotNull - public abstract String getUrl(); - - /** - * Fetches "presentable URL" of this file. "Presentable URL" is a string to be used for displaying this - * file in the UI. - * - * @return the presentable URL. - * @see VirtualFileSystem#extractPresentableUrl - */ - public final String getPresentableUrl() { - return getFileSystem().extractPresentableUrl(getPath()); - } - - /** - * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the name of a - * {@link VirtualFile} changes. - * - * @see VirtualFileListener#propertyChanged - * @see VirtualFilePropertyEvent#getPropertyName - */ - @NonNls public static final String PROP_NAME = "name"; - - /** - * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the encoding of a - * {@link VirtualFile} changes. - * - * @see VirtualFileListener#propertyChanged - * @see VirtualFilePropertyEvent#getPropertyName - */ - @NonNls public static final String PROP_ENCODING = "encoding"; - - /** - * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the write permission of a - * {@link VirtualFile} changes. - * - * @see VirtualFileListener#propertyChanged - * @see VirtualFilePropertyEvent#getPropertyName - */ - @NonNls public static final String PROP_WRITABLE = "writable"; - - /** - * Gets the extension of this file. If file name contains '.' extension is the substring from the last '.' - * to the end of the name, otherwise extension is null. - * - * @return the extension or null if file name doesn't contain '.' - */ - @Nullable - @NonNls - public String getExtension() { - String name = getName(); - int index = name.lastIndexOf('.'); - if (index < 0) return null; - return name.substring(index + 1); - } - - /** - * Gets the file name without the extension. If file name contains '.' the substring till the last '.' is returned. - * Otherwise the same value as {@link #getName} method returns is returned. - * - * @return the name without extension - * if there is no '.' in it - */ - @NonNls - @NotNull - public String getNameWithoutExtension() { - String name = getName(); - int index = name.lastIndexOf('.'); - if (index < 0) return name; - return name.substring(0, index); - } - - - /** - * Renames this file to the newName.

- * This method should be only called within write-action. - * See {@link Application#runWriteAction}. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @param newName the new file name - * @throws IOException if file failed to be renamed - */ - public void rename(Object requestor, @NotNull @NonNls String newName) throws IOException { - if (getName().equals(newName)) return; - if (!VfsUtil.isValidName(newName)) { - throw new IOException(VfsBundle.message("file.invalid.name.error", newName)); - } - - getFileSystem().renameFile(requestor, this, newName); - } - - /** - * Checks whether this file has write permission. Note that this value may be cached and may differ from - * the write permission of the physical file. - * - * @return true if this file is writable, false otherwise - */ - public abstract boolean isWritable(); - - /** - * Checks whether this file is a directory. - * - * @return true if this file is a directory, fasle otherwise - */ - public abstract boolean isDirectory(); - - /** - * Checks whether this VirtualFile is valid. File can be invalidated either by deleting it or one of its - * parents with {@link #delete} method or by an external change. - * If file is not valid only {@link #equals}, {@link #hashCode} and methods from - * {@link UserDataHolder} can be called for it. Using any other methods for an invalid {@link VirtualFile} instance - * produce inpredictable results. - * - * @return true if this is a valid file, fasle otherwise - */ - public abstract boolean isValid(); - - /** - * Gets the parent VirtualFile. - * - * @return the parent file or null if this file is a root directory - */ - public abstract VirtualFile getParent(); - - /** - * Gets the child files. - * - * @return array of the child files or null if this file is not a directory - */ - public abstract VirtualFile[] getChildren(); - - /** - * Finds child of this file with the given name. - * - * @param name the file name to search by - * @return the file if found any, null otherwise - */ - @Nullable - public VirtualFile findChild(@NotNull @NonNls String name) { - VirtualFile[] children = getChildren(); - if (children == null) return null; - for (VirtualFile child : children) { - if (child.nameEquals(name)) { - return child; - } - } - return null; - } - - @Nullable - public VirtualFile findOrCreateChildData(Object requestor, @NotNull @NonNls String name) throws IOException { - final VirtualFile child = findChild(name); - if (child != null) return child; - return createChildData(requestor, name); - } - - public Icon getIcon() { - if (isDirectory() && isInLocalFileSystem()) { - return Icons.FOLDER_ICON; - } - return getFileType().getIcon(); - } - - /** - * @return the {@link FileType} of this file. - * When IDEA has no idea what the file type is (i.e. file type is not registered via {@link FileTypeManager}), - * it returns {@link com.intellij.openapi.fileTypes.FileTypes#UNKNOWN} - */ - @NotNull - public FileType getFileType() { - return FileTypeManager.getInstance().getFileTypeByFile(this); - } - - /** - * Finds file by path relative to this file. - * - * @param relPath the relative path to search by - * @return the file if found any, null otherwise - */ - @Nullable - public VirtualFile findFileByRelativePath(@NotNull @NonNls String relPath) { - if (relPath.length() == 0) return this; - relPath = StringUtil.trimStart(relPath, "/"); - - int index = relPath.indexOf('/'); - if (index < 0) index = relPath.length(); - String name = relPath.substring(0, index); - - VirtualFile child; - if (name.equals(".")) { - child = this; - } - else if (name.equals("..")) { - child = getParent(); - } - else { - child = findChild(name); - } - - if (child == null) return null; - - if (index < relPath.length()) { - return child.findFileByRelativePath(relPath.substring(index + 1)); - } - else { - return child; - } - } - - /** - * Creates a subdirectory in this directory. This method should be only called within write-action. - * See {@link com.intellij.openapi.application.Application#runWriteAction}. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @param name directory name - * @return VirtualFile representing the created directory - * @throws java.io.IOException if directory failed to be created - */ - public VirtualFile createChildDirectory(Object requestor, @NonNls String name) throws IOException { - if (!isDirectory()) { - throw new IOException(VfsBundle.message("directory.create.wrong.parent.error")); - } - - if (!isValid()) { - throw new IOException(VfsBundle.message("invalid.directory.create.files")); - } - - if (!VfsUtil.isValidName(name)) { - throw new IOException(VfsBundle.message("directory.invalid.name.error", name)); - } - - if (findChild(name) != null) { - throw new IOException(VfsBundle.message("file.create.already.exists.error", getUrl(), name)); - } - - return getFileSystem().createChildDirectory(requestor, this, name); - } - - /** - * Creates a new file in this directory. This method should be only called within write-action. - * See {@link com.intellij.openapi.application.Application#runWriteAction}. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @return VirtualFile representing the created file - * @throws IOException if file failed to be created - */ - public VirtualFile createChildData(Object requestor, @NotNull @NonNls String name) throws IOException { - if (!isDirectory()) { - throw new IOException(VfsBundle.message("file.create.wrong.parent.error")); - } - - if (!isValid()) { - throw new IOException(VfsBundle.message("invalid.directory.create.files")); - } - - if (!VfsUtil.isValidName(name)) { - throw new IOException(VfsBundle.message("file.invalid.name.error", name)); - } - - if (findChild(name) != null) { - throw new IOException(VfsBundle.message("file.create.already.exists.error", getUrl(), name)); - } - - return getFileSystem().createChildFile(requestor, this, name); - } - - /** - * Deletes this file. This method should be only called within write-action. - * See {@link com.intellij.openapi.application.Application#runWriteAction}. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @throws IOException if file failed to be deleted - */ - public void delete(Object requestor) throws IOException { - LOG.assertTrue(isValid(), "Deleting invalid file"); - getFileSystem().deleteFile(requestor, this); - } - - /** - * Moves this file to another directory. This method should be only called within write-action. - * See {@link com.intellij.openapi.application.Application#runWriteAction}. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @param newParent the directory to move this file to - * @throws IOException if file failed to be moved - */ - public void move(final Object requestor, final VirtualFile newParent) throws IOException { - if (getFileSystem() != newParent.getFileSystem()) { - throw new IOException(VfsBundle.message("file.move.error", newParent.getPresentableUrl())); - } - - VfsUtil.doActionAndRestoreEncoding(this, new ThrowableComputable() { - public VirtualFile compute() throws IOException { - getFileSystem().moveFile(requestor, VirtualFile.this, newParent); - return VirtualFile.this; - } - }); - } - - public VirtualFile copy(final Object requestor, final VirtualFile newParent, final String copyName) throws IOException { - if (getFileSystem() != newParent.getFileSystem()) { - throw new IOException(VfsBundle.message("file.copy.error", newParent.getPresentableUrl())); - } - - if (!newParent.isDirectory()) { - throw new IOException(VfsBundle.message("file.copy.target.must.be.directory")); - } - - return VfsUtil.doActionAndRestoreEncoding(this, new ThrowableComputable() { - public VirtualFile compute() throws IOException { - return getFileSystem().copyFile(requestor, VirtualFile.this, newParent, copyName); - } - }); - } - - - public final void setBinaryContent(byte[] content) throws IOException { - setBinaryContent(content, -1, -1); - } - - /** - * @return Retreive the charset file have been loaded with (if loaded) and would be saved with (if would). - */ - public Charset getCharset() { - Charset charset = getUserData(CHARSET_KEY); - if (charset == null) { - charset = EncodingManager.getInstance().getDefaultCharset(); - setCharset(charset); - } - return charset; - } - - public void setCharset(final Charset charset) { - final Charset old = getUserData(CHARSET_KEY); - putUserData(CHARSET_KEY, charset); - byte[] bom = charset == null ? null : CharsetToolkit.getBom(charset); - setBOM(bom); - - if (old != null && !old.equals(charset)) { //do not send on detect - final Application application = ApplicationManager.getApplication(); - application.invokeLater(new Runnable() { - public void run() { - if (isValid() && !application.isDisposed()) { - application.runWriteAction(new Runnable(){ - public void run() { - List events = Collections.singletonList(new VFilePropertyChangeEvent(this, VirtualFile.this, PROP_ENCODING, old, charset, false)); - BulkFileListener listener = application.getMessageBus().syncPublisher(VirtualFileManager.VFS_CHANGES); - listener.before(events); - listener.after(events); - } - }); - } - } - }, ModalityState.NON_MODAL); - } - } - - protected boolean isCharsetSet() { - return getUserData(CHARSET_KEY) != null; - } - - public void setBinaryContent(final byte[] content, long newModificationStamp, long newTimeStamp) throws IOException { - setBinaryContent(content, newModificationStamp, newTimeStamp, this); - } - public void setBinaryContent(final byte[] content, long newModificationStamp, long newTimeStamp, Object requestor) throws IOException { - OutputStream outputStream = null; - try { - outputStream = getOutputStream(requestor, newModificationStamp, newTimeStamp); - outputStream.write(content); - outputStream.flush(); - } - finally { - if (outputStream != null) outputStream.close(); - } - } - - /** - * Gets the OutputStream for this file. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @return OutputStream - * @throws IOException if an I/O error occurs - */ - public final OutputStream getOutputStream(Object requestor) throws IOException { - return getOutputStream(requestor, -1, -1); - } - - /** - * Gets the OutputStream for this file and sets modification stamp and time stamp to the specified values - * after closing the stream.

- *

- * Normally you should not use this method. - * - * @param requestor any object to control who called this method. Note that - * it is considered to be an external change if requestor is null. - * See {@link VirtualFileEvent#getRequestor} - * @param newModificationStamp new modification stamp or -1 if no special value should be set - * @param newTimeStamp new time stamp or -1 if no special value should be set - * @return OutputStream - * @throws IOException if an I/O error occurs - * @see #getModificationStamp() - */ - @NotNull - public abstract OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) throws IOException; - - /** - * Returns file content as an array of bytes. - * Has the same effect as contentsToByteArray(true). - * - * @return file content - * @throws IOException if an I/O error occurs - * @see #contentsToByteArray(boolean) - * @see #getInputStream() - */ - @NotNull - public abstract byte[] contentsToByteArray() throws IOException; - - /** - * Returns file content as an array of bytes. - * - * @param cacheContent set true to - * @return file content - * @throws IOException if an I/O error occurs - * @see #contentsToByteArray() - */ - @NotNull - public byte[] contentsToByteArray(boolean cacheContent) throws IOException { - return contentsToByteArray(); - } - - - /** - * Gets modification stamp value. Modification stamp is a value changed by any modification - * of the content of the file. Note that it is not related to the file modification time. - * - * @return modification stamp - * @see #getTimeStamp() - */ - public long getModificationStamp() { - throw new UnsupportedOperationException(); - } - - /** - * Gets the timestamp for this file. Note that this value may be cached and may differ from - * the timestamp of the physical file. - * - * @return timestamp - * @see java.io.File#lastModified - */ - public abstract long getTimeStamp(); - - /** - * File length in bytes. - * - * @return the length of this file. - */ - public abstract long getLength(); - - /** - * Refreshes the cached file information from the physical file system. If this file is not a directory - * the timestamp value is refreshed and contentsChanged event is fired if it is changed.

- * If this file is a directory the set of its children is refreshed. If recursive value is true all - * children are refreshed recursively. - *

- * This method should be only called within write-action. - * See {@link com.intellij.openapi.application.Application#runWriteAction}. - * - * @param asynchronous if true then the operation will be performed in a separate thread, - * otherwise will be performed immediately - * @param recursive whether to refresh all the files in this directory recursively - */ - public void refresh(boolean asynchronous, boolean recursive) { - refresh(asynchronous, recursive, null); - } - - /** - * The same as {@link #refresh(boolean asynchronous, boolean recursive)} but also runs postRunnable - * after the operation is completed. - */ - public abstract void refresh(boolean asynchronous, boolean recursive, Runnable postRunnable); - - public String getPresentableName() { - return getName(); - } - - public long getModificationCount() { - return isValid() ? getTimeStamp() : -1; - } - - /** - * @param name - * @return whether file name equals to this name - * result depends on the filesystem specifics - */ - protected boolean nameEquals(@NotNull @NonNls String name) { - return getName().equals(name); - } - - /** - * Gets the InputStream for this file. - * - * @return InputStream - * @throws IOException if an I/O error occurs - * @see #contentsToByteArray - */ - public abstract InputStream getInputStream() throws IOException; - - @Nullable - public byte[] getBOM() { - return getUserData(BOM_KEY); - } - - public void setBOM(@Nullable byte[] BOM) { - putUserData(BOM_KEY, BOM); - } - - @NonNls - public String toString() { - return "VirtualFile: " + getPresentableUrl(); - } - - public boolean exists() { - return isValid(); - } - - public boolean isInLocalFileSystem() { - return false; - } -} +/* + * 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.vfs; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; +import com.intellij.util.Icons; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; + +/** + * Represents a file in {@link VirtualFileSystem}. A particular file is represented by the same + * VirtualFile instance for the entire lifetime of the IntelliJ IDEA process, unless the file + * is deleted, in which case {@link #isValid()} for the instance will return false. + *

+ * If an in-memory implementation of VirtualFile is required, LightVirtualFile from the com.intellij.testFramework + * package (Extended API) can be used. + * + * @see VirtualFileSystem + * @see VirtualFileManager + */ +public abstract class VirtualFile extends UserDataHolderBase implements ModificationTracker { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VirtualFile"); + public static final Key REQUESTOR_MARKER = new Key("REQUESTOR_MARKER"); + private static final Key BOM_KEY = new Key("BOM"); + private static final Key CHARSET_KEY = new Key("CHARSET"); + public static final VirtualFile[] EMPTY_ARRAY = new VirtualFile[0]; + + protected VirtualFile() { + } + + /** + * Gets the name of this file. + * + * @return file name + */ + @NotNull + @NonNls + public abstract String getName(); + + /** + * Gets the {@link VirtualFileSystem} this file belongs to. + * + * @return the {@link VirtualFileSystem} + */ + @NotNull + public abstract VirtualFileSystem getFileSystem(); + + /** + * Gets the path of this file. Path is a string which uniquely identifies file within given + * {@link VirtualFileSystem}. Format of the path depends on the concrete file system. + * For {@link LocalFileSystem} it is an absoulute file path with file separator characters + * (File.separatorChar) replaced to the forward slash ('/'). + * + * @return the path + */ + public abstract String getPath(); + + /** + * Gets the URL of this file. The URL is a string which uniquely identifies file in all file systems. + * It has the following format: <protocol>://<path>. + *

+ * File can be found by its URL using {@link VirtualFileManager#findFileByUrl} method. + * + * @return the URL consisting of protocol and path + * @see VirtualFileManager#findFileByUrl + * @see VirtualFile#getPath + * @see VirtualFileSystem#getProtocol + */ + @NotNull + public abstract String getUrl(); + + /** + * Fetches "presentable URL" of this file. "Presentable URL" is a string to be used for displaying this + * file in the UI. + * + * @return the presentable URL. + * @see VirtualFileSystem#extractPresentableUrl + */ + public final String getPresentableUrl() { + return getFileSystem().extractPresentableUrl(getPath()); + } + + /** + * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the name of a + * {@link VirtualFile} changes. + * + * @see VirtualFileListener#propertyChanged + * @see VirtualFilePropertyEvent#getPropertyName + */ + @NonNls public static final String PROP_NAME = "name"; + + /** + * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the encoding of a + * {@link VirtualFile} changes. + * + * @see VirtualFileListener#propertyChanged + * @see VirtualFilePropertyEvent#getPropertyName + */ + @NonNls public static final String PROP_ENCODING = "encoding"; + + /** + * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the write permission of a + * {@link VirtualFile} changes. + * + * @see VirtualFileListener#propertyChanged + * @see VirtualFilePropertyEvent#getPropertyName + */ + @NonNls public static final String PROP_WRITABLE = "writable"; + + /** + * Gets the extension of this file. If file name contains '.' extension is the substring from the last '.' + * to the end of the name, otherwise extension is null. + * + * @return the extension or null if file name doesn't contain '.' + */ + @Nullable + @NonNls + public String getExtension() { + String name = getName(); + int index = name.lastIndexOf('.'); + if (index < 0) return null; + return name.substring(index + 1); + } + + /** + * Gets the file name without the extension. If file name contains '.' the substring till the last '.' is returned. + * Otherwise the same value as {@link #getName} method returns is returned. + * + * @return the name without extension + * if there is no '.' in it + */ + @NonNls + @NotNull + public String getNameWithoutExtension() { + String name = getName(); + int index = name.lastIndexOf('.'); + if (index < 0) return name; + return name.substring(0, index); + } + + + /** + * Renames this file to the newName.

+ * This method should be only called within write-action. + * See {@link Application#runWriteAction}. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @param newName the new file name + * @throws IOException if file failed to be renamed + */ + public void rename(Object requestor, @NotNull @NonNls String newName) throws IOException { + if (getName().equals(newName)) return; + if (!VfsUtil.isValidName(newName)) { + throw new IOException(VfsBundle.message("file.invalid.name.error", newName)); + } + + getFileSystem().renameFile(requestor, this, newName); + } + + /** + * Checks whether this file has write permission. Note that this value may be cached and may differ from + * the write permission of the physical file. + * + * @return true if this file is writable, false otherwise + */ + public abstract boolean isWritable(); + + /** + * Checks whether this file is a directory. + * + * @return true if this file is a directory, fasle otherwise + */ + public abstract boolean isDirectory(); + + /** + * Checks whether this VirtualFile is valid. File can be invalidated either by deleting it or one of its + * parents with {@link #delete} method or by an external change. + * If file is not valid only {@link #equals}, {@link #hashCode} and methods from + * {@link UserDataHolder} can be called for it. Using any other methods for an invalid {@link VirtualFile} instance + * produce inpredictable results. + * + * @return true if this is a valid file, fasle otherwise + */ + public abstract boolean isValid(); + + /** + * Gets the parent VirtualFile. + * + * @return the parent file or null if this file is a root directory + */ + public abstract VirtualFile getParent(); + + /** + * Gets the child files. + * + * @return array of the child files or null if this file is not a directory + */ + public abstract VirtualFile[] getChildren(); + + /** + * Finds child of this file with the given name. + * + * @param name the file name to search by + * @return the file if found any, null otherwise + */ + @Nullable + public VirtualFile findChild(@NotNull @NonNls String name) { + VirtualFile[] children = getChildren(); + if (children == null) return null; + for (VirtualFile child : children) { + if (child.nameEquals(name)) { + return child; + } + } + return null; + } + + @Nullable + public VirtualFile findOrCreateChildData(Object requestor, @NotNull @NonNls String name) throws IOException { + final VirtualFile child = findChild(name); + if (child != null) return child; + return createChildData(requestor, name); + } + + public Icon getIcon() { + if (isDirectory() && isInLocalFileSystem()) { + return Icons.FOLDER_ICON; + } + return getFileType().getIcon(); + } + + /** + * @return the {@link FileType} of this file. + * When IDEA has no idea what the file type is (i.e. file type is not registered via {@link FileTypeManager}), + * it returns {@link com.intellij.openapi.fileTypes.FileTypes#UNKNOWN} + */ + @NotNull + public FileType getFileType() { + return FileTypeManager.getInstance().getFileTypeByFile(this); + } + + /** + * Finds file by path relative to this file. + * + * @param relPath the relative path to search by + * @return the file if found any, null otherwise + */ + @Nullable + public VirtualFile findFileByRelativePath(@NotNull @NonNls String relPath) { + if (relPath.length() == 0) return this; + relPath = StringUtil.trimStart(relPath, "/"); + + int index = relPath.indexOf('/'); + if (index < 0) index = relPath.length(); + String name = relPath.substring(0, index); + + VirtualFile child; + if (name.equals(".")) { + child = this; + } + else if (name.equals("..")) { + child = getParent(); + } + else { + child = findChild(name); + } + + if (child == null) return null; + + if (index < relPath.length()) { + return child.findFileByRelativePath(relPath.substring(index + 1)); + } + else { + return child; + } + } + + /** + * Creates a subdirectory in this directory. This method should be only called within write-action. + * See {@link com.intellij.openapi.application.Application#runWriteAction}. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @param name directory name + * @return VirtualFile representing the created directory + * @throws java.io.IOException if directory failed to be created + */ + public VirtualFile createChildDirectory(Object requestor, @NonNls String name) throws IOException { + if (!isDirectory()) { + throw new IOException(VfsBundle.message("directory.create.wrong.parent.error")); + } + + if (!isValid()) { + throw new IOException(VfsBundle.message("invalid.directory.create.files")); + } + + if (!VfsUtil.isValidName(name)) { + throw new IOException(VfsBundle.message("directory.invalid.name.error", name)); + } + + if (findChild(name) != null) { + throw new IOException(VfsBundle.message("file.create.already.exists.error", getUrl(), name)); + } + + return getFileSystem().createChildDirectory(requestor, this, name); + } + + /** + * Creates a new file in this directory. This method should be only called within write-action. + * See {@link com.intellij.openapi.application.Application#runWriteAction}. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @return VirtualFile representing the created file + * @throws IOException if file failed to be created + */ + public VirtualFile createChildData(Object requestor, @NotNull @NonNls String name) throws IOException { + if (!isDirectory()) { + throw new IOException(VfsBundle.message("file.create.wrong.parent.error")); + } + + if (!isValid()) { + throw new IOException(VfsBundle.message("invalid.directory.create.files")); + } + + if (!VfsUtil.isValidName(name)) { + throw new IOException(VfsBundle.message("file.invalid.name.error", name)); + } + + if (findChild(name) != null) { + throw new IOException(VfsBundle.message("file.create.already.exists.error", getUrl(), name)); + } + + return getFileSystem().createChildFile(requestor, this, name); + } + + /** + * Deletes this file. This method should be only called within write-action. + * See {@link com.intellij.openapi.application.Application#runWriteAction}. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @throws IOException if file failed to be deleted + */ + public void delete(Object requestor) throws IOException { + LOG.assertTrue(isValid(), "Deleting invalid file"); + getFileSystem().deleteFile(requestor, this); + } + + /** + * Moves this file to another directory. This method should be only called within write-action. + * See {@link com.intellij.openapi.application.Application#runWriteAction}. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @param newParent the directory to move this file to + * @throws IOException if file failed to be moved + */ + public void move(final Object requestor, final VirtualFile newParent) throws IOException { + if (getFileSystem() != newParent.getFileSystem()) { + throw new IOException(VfsBundle.message("file.move.error", newParent.getPresentableUrl())); + } + + VfsUtil.doActionAndRestoreEncoding(this, new ThrowableComputable() { + public VirtualFile compute() throws IOException { + getFileSystem().moveFile(requestor, VirtualFile.this, newParent); + return VirtualFile.this; + } + }); + } + + public VirtualFile copy(final Object requestor, final VirtualFile newParent, final String copyName) throws IOException { + if (getFileSystem() != newParent.getFileSystem()) { + throw new IOException(VfsBundle.message("file.copy.error", newParent.getPresentableUrl())); + } + + if (!newParent.isDirectory()) { + throw new IOException(VfsBundle.message("file.copy.target.must.be.directory")); + } + + return VfsUtil.doActionAndRestoreEncoding(this, new ThrowableComputable() { + public VirtualFile compute() throws IOException { + return getFileSystem().copyFile(requestor, VirtualFile.this, newParent, copyName); + } + }); + } + + + public final void setBinaryContent(byte[] content) throws IOException { + setBinaryContent(content, -1, -1); + } + + /** + * @return Retreive the charset file have been loaded with (if loaded) and would be saved with (if would). + */ + public Charset getCharset() { + Charset charset = getUserData(CHARSET_KEY); + if (charset == null) { + charset = EncodingManager.getInstance().getDefaultCharset(); + setCharset(charset); + } + return charset; + } + + public void setCharset(final Charset charset) { + final Charset old = getUserData(CHARSET_KEY); + putUserData(CHARSET_KEY, charset); + byte[] bom = charset == null ? null : CharsetToolkit.getBom(charset); + setBOM(bom); + + if (old != null && !old.equals(charset)) { //do not send on detect + final Application application = ApplicationManager.getApplication(); + application.invokeLater(new Runnable() { + public void run() { + if (isValid() && !application.isDisposed()) { + application.runWriteAction(new Runnable(){ + public void run() { + List events = Collections.singletonList(new VFilePropertyChangeEvent(this, VirtualFile.this, PROP_ENCODING, old, charset, false)); + BulkFileListener listener = application.getMessageBus().syncPublisher(VirtualFileManager.VFS_CHANGES); + listener.before(events); + listener.after(events); + } + }); + } + } + }, ModalityState.NON_MODAL); + } + } + + protected boolean isCharsetSet() { + return getUserData(CHARSET_KEY) != null; + } + + public void setBinaryContent(final byte[] content, long newModificationStamp, long newTimeStamp) throws IOException { + setBinaryContent(content, newModificationStamp, newTimeStamp, this); + } + public void setBinaryContent(final byte[] content, long newModificationStamp, long newTimeStamp, Object requestor) throws IOException { + OutputStream outputStream = null; + try { + outputStream = getOutputStream(requestor, newModificationStamp, newTimeStamp); + outputStream.write(content); + outputStream.flush(); + } + finally { + if (outputStream != null) outputStream.close(); + } + } + + /** + * Gets the OutputStream for this file. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @return OutputStream + * @throws IOException if an I/O error occurs + */ + public final OutputStream getOutputStream(Object requestor) throws IOException { + return getOutputStream(requestor, -1, -1); + } + + /** + * Gets the OutputStream for this file and sets modification stamp and time stamp to the specified values + * after closing the stream.

+ *

+ * Normally you should not use this method. + * + * @param requestor any object to control who called this method. Note that + * it is considered to be an external change if requestor is null. + * See {@link VirtualFileEvent#getRequestor} + * @param newModificationStamp new modification stamp or -1 if no special value should be set + * @param newTimeStamp new time stamp or -1 if no special value should be set + * @return OutputStream + * @throws IOException if an I/O error occurs + * @see #getModificationStamp() + */ + @NotNull + public abstract OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) throws IOException; + + /** + * Returns file content as an array of bytes. + * Has the same effect as contentsToByteArray(true). + * + * @return file content + * @throws IOException if an I/O error occurs + * @see #contentsToByteArray(boolean) + * @see #getInputStream() + */ + @NotNull + public abstract byte[] contentsToByteArray() throws IOException; + + /** + * Returns file content as an array of bytes. + * + * @param cacheContent set true to + * @return file content + * @throws IOException if an I/O error occurs + * @see #contentsToByteArray() + */ + @NotNull + public byte[] contentsToByteArray(boolean cacheContent) throws IOException { + return contentsToByteArray(); + } + + + /** + * Gets modification stamp value. Modification stamp is a value changed by any modification + * of the content of the file. Note that it is not related to the file modification time. + * + * @return modification stamp + * @see #getTimeStamp() + */ + public long getModificationStamp() { + throw new UnsupportedOperationException(); + } + + /** + * Gets the timestamp for this file. Note that this value may be cached and may differ from + * the timestamp of the physical file. + * + * @return timestamp + * @see java.io.File#lastModified + */ + public abstract long getTimeStamp(); + + /** + * File length in bytes. + * + * @return the length of this file. + */ + public abstract long getLength(); + + /** + * Refreshes the cached file information from the physical file system. If this file is not a directory + * the timestamp value is refreshed and contentsChanged event is fired if it is changed.

+ * If this file is a directory the set of its children is refreshed. If recursive value is true all + * children are refreshed recursively. + *

+ * This method should be only called within write-action. + * See {@link com.intellij.openapi.application.Application#runWriteAction}. + * + * @param asynchronous if true then the operation will be performed in a separate thread, + * otherwise will be performed immediately + * @param recursive whether to refresh all the files in this directory recursively + */ + public void refresh(boolean asynchronous, boolean recursive) { + refresh(asynchronous, recursive, null); + } + + /** + * The same as {@link #refresh(boolean asynchronous, boolean recursive)} but also runs postRunnable + * after the operation is completed. + */ + public abstract void refresh(boolean asynchronous, boolean recursive, Runnable postRunnable); + + public String getPresentableName() { + return getName(); + } + + public long getModificationCount() { + return isValid() ? getTimeStamp() : -1; + } + + /** + * @param name + * @return whether file name equals to this name + * result depends on the filesystem specifics + */ + protected boolean nameEquals(@NotNull @NonNls String name) { + return getName().equals(name); + } + + /** + * Gets the InputStream for this file. + * + * @return InputStream + * @throws IOException if an I/O error occurs + * @see #contentsToByteArray + */ + public abstract InputStream getInputStream() throws IOException; + + @Nullable + public byte[] getBOM() { + return getUserData(BOM_KEY); + } + + public void setBOM(@Nullable byte[] BOM) { + putUserData(BOM_KEY, BOM); + } + + @NonNls + public String toString() { + return "VirtualFile: " + getPresentableUrl(); + } + + public boolean exists() { + return isValid(); + } + + public boolean isInLocalFileSystem() { + return false; + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/FileAttribute.java b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/FileAttribute.java index 6d8c853df511..9f53d95039a0 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/FileAttribute.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/FileAttribute.java @@ -1,121 +1,121 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.openapi.vfs.newvfs; - -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -public class FileAttribute { - private static final Set ourRegisteredIds = new HashSet(); - private final String myId; - private final int myVersion; - private final boolean myFixedSize; - - /** - * @deprecated - * @see #FileAttribute(String, int, boolean) - */ - public FileAttribute(@NonNls final String id, int version) { - this(id, version, false); - } - - public FileAttribute(@NonNls final String id, int version, boolean fixedSize) { - myId = id; - myVersion = version; - myFixedSize = fixedSize; - boolean added = ourRegisteredIds.add(id); - assert added : "Attribute id='" + id+ "' is not unique"; - } - - @Nullable - public DataInputStream readAttribute(VirtualFile file) { - DataInputStream stream = ManagingFS.getInstance().readAttribute(file, this); - if (stream != null) { - try { - int actualVersion = stream.readInt(); - if (actualVersion != myVersion) { - stream.close(); - return null; - } - } - catch (IOException e) { - return null; - } - } - return stream; - } - - public DataOutputStream writeAttribute(VirtualFile file) { - final DataOutputStream stream = ManagingFS.getInstance().writeAttribute(file, this); - try { - stream.writeInt(myVersion); - } - catch (IOException e) { - throw new RuntimeException(e); - } - - return stream; - } - - @Nullable - public byte[] readAttributeBytes(VirtualFile file) throws IOException { - final DataInputStream stream = readAttribute(file); - if (stream == null) return null; - - try { - int len = stream.readInt(); - return FileUtil.loadBytes(stream, len); - } - finally { - stream.close(); - } - } - - public void writeAttributeBytes(VirtualFile file, byte[] bytes) throws IOException { - writeAttributeBytes(file, bytes, 0, bytes.length); - } - - public void writeAttributeBytes(VirtualFile file, byte[] bytes, int offset, int len) throws IOException { - final DataOutputStream stream = writeAttribute(file); - try { - stream.writeInt(len); - stream.write(bytes, offset, len); - } - finally { - stream.close(); - } - } - - public String getId() { - return myId; - } - - public boolean isFixedSize() { - return myFixedSize; - } -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.openapi.vfs.newvfs; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class FileAttribute { + private static final Set ourRegisteredIds = new HashSet(); + private final String myId; + private final int myVersion; + private final boolean myFixedSize; + + /** + * @deprecated + * @see #FileAttribute(String, int, boolean) + */ + public FileAttribute(@NonNls final String id, int version) { + this(id, version, false); + } + + public FileAttribute(@NonNls final String id, int version, boolean fixedSize) { + myId = id; + myVersion = version; + myFixedSize = fixedSize; + boolean added = ourRegisteredIds.add(id); + assert added : "Attribute id='" + id+ "' is not unique"; + } + + @Nullable + public DataInputStream readAttribute(VirtualFile file) { + DataInputStream stream = ManagingFS.getInstance().readAttribute(file, this); + if (stream != null) { + try { + int actualVersion = stream.readInt(); + if (actualVersion != myVersion) { + stream.close(); + return null; + } + } + catch (IOException e) { + return null; + } + } + return stream; + } + + public DataOutputStream writeAttribute(VirtualFile file) { + final DataOutputStream stream = ManagingFS.getInstance().writeAttribute(file, this); + try { + stream.writeInt(myVersion); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + return stream; + } + + @Nullable + public byte[] readAttributeBytes(VirtualFile file) throws IOException { + final DataInputStream stream = readAttribute(file); + if (stream == null) return null; + + try { + int len = stream.readInt(); + return FileUtil.loadBytes(stream, len); + } + finally { + stream.close(); + } + } + + public void writeAttributeBytes(VirtualFile file, byte[] bytes) throws IOException { + writeAttributeBytes(file, bytes, 0, bytes.length); + } + + public void writeAttributeBytes(VirtualFile file, byte[] bytes, int offset, int len) throws IOException { + final DataOutputStream stream = writeAttribute(file); + try { + stream.writeInt(len); + stream.write(bytes, offset, len); + } + finally { + stream.close(); + } + } + + public String getId() { + return myId; + } + + public boolean isFixedSize() { + return myFixedSize; + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/wm/StatusBar.java b/platform/platform-api/src/com/intellij/openapi/wm/StatusBar.java index 4da68b0a1411..100573473fda 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/StatusBar.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/StatusBar.java @@ -1,100 +1,100 @@ -/* - * 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.wm; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.Topic; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; - -/** - * @author spleaner - */ -public interface StatusBar extends StatusBarInfo, Disposable { - - @SuppressWarnings({"AbstractClassNeverImplemented"}) - abstract class Info implements StatusBarInfo { - public static final Topic TOPIC = Topic.create("IdeStatusBar.Text", StatusBarInfo.class); - - private Info() { - } - - public static void set(@Nullable final String text, @Nullable final Project project) { - set(text, project, null); - } - - public static void set(@Nullable final String text, @Nullable final Project project, @Nullable final String requestor) { - if (project != null) { - if (project.isDisposed()) return; - if (!project.isInitialized()) { - StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() { - public void run() { - project.getMessageBus().syncPublisher(TOPIC).setInfo(text, requestor); - } - }); - return; - } - } - - final MessageBus bus = project == null ? ApplicationManager.getApplication().getMessageBus() : project.getMessageBus(); - bus.syncPublisher(TOPIC).setInfo(text, requestor); - } - } - - void addWidget(@NotNull StatusBarWidget widget); - - void addWidget(@NotNull StatusBarWidget widget, @NotNull String anchor); - - void addWidget(@NotNull StatusBarWidget widget, @NotNull Disposable parentDisposable); - - void addWidget(@NotNull StatusBarWidget widget, @NotNull String anchor, @NotNull Disposable parentDisposable); - - /** - * @deprecated use addWidget instead - */ - @Deprecated - void addCustomIndicationComponent(@NotNull JComponent c); - - /** - * @deprecated use removeWidget instead - */ - @Deprecated - void removeCustomIndicationComponent(@NotNull JComponent c); - - void removeWidget(@NotNull String id); - - void updateWidget(@NotNull String id); - - void fireNotificationPopup(@NotNull JComponent content, Color backgroundColor); - - StatusBar createChild(); - - JComponent getComponent(); - - StatusBar findChild(Component c); - - IdeFrame getFrame(); - - void install(IdeFrame frame); - -} +/* + * 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.wm; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +/** + * @author spleaner + */ +public interface StatusBar extends StatusBarInfo, Disposable { + + @SuppressWarnings({"AbstractClassNeverImplemented"}) + abstract class Info implements StatusBarInfo { + public static final Topic TOPIC = Topic.create("IdeStatusBar.Text", StatusBarInfo.class); + + private Info() { + } + + public static void set(@Nullable final String text, @Nullable final Project project) { + set(text, project, null); + } + + public static void set(@Nullable final String text, @Nullable final Project project, @Nullable final String requestor) { + if (project != null) { + if (project.isDisposed()) return; + if (!project.isInitialized()) { + StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() { + public void run() { + project.getMessageBus().syncPublisher(TOPIC).setInfo(text, requestor); + } + }); + return; + } + } + + final MessageBus bus = project == null ? ApplicationManager.getApplication().getMessageBus() : project.getMessageBus(); + bus.syncPublisher(TOPIC).setInfo(text, requestor); + } + } + + void addWidget(@NotNull StatusBarWidget widget); + + void addWidget(@NotNull StatusBarWidget widget, @NotNull String anchor); + + void addWidget(@NotNull StatusBarWidget widget, @NotNull Disposable parentDisposable); + + void addWidget(@NotNull StatusBarWidget widget, @NotNull String anchor, @NotNull Disposable parentDisposable); + + /** + * @deprecated use addWidget instead + */ + @Deprecated + void addCustomIndicationComponent(@NotNull JComponent c); + + /** + * @deprecated use removeWidget instead + */ + @Deprecated + void removeCustomIndicationComponent(@NotNull JComponent c); + + void removeWidget(@NotNull String id); + + void updateWidget(@NotNull String id); + + void fireNotificationPopup(@NotNull JComponent content, Color backgroundColor); + + StatusBar createChild(); + + JComponent getComponent(); + + StatusBar findChild(Component c); + + IdeFrame getFrame(); + + void install(IdeFrame frame); + +} diff --git a/platform/platform-api/src/com/intellij/openapi/wm/StatusBarWidget.java b/platform/platform-api/src/com/intellij/openapi/wm/StatusBarWidget.java index a0fbedb2e7a8..2701dfb2ecd8 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/StatusBarWidget.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/StatusBarWidget.java @@ -1,139 +1,139 @@ -/* - * 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.wm; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.ui.popup.ListPopup; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.Consumer; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import java.awt.*; -import java.awt.event.MouseEvent; - -/** - * User: spLeaner - */ -public interface StatusBarWidget extends Disposable { - - enum PlatformType { - DEFAULT, MAC - } - - @NotNull - String ID(); - - @Nullable - WidgetPresentation getPresentation(@NotNull PlatformType type); - - void install(@NotNull final StatusBar statusBar); - - interface Multiframe extends StatusBarWidget { - - StatusBarWidget copy(); - - } - - interface WidgetPresentation { - @Nullable - String getTooltipText(); - - @Nullable - Consumer getClickConsumer(); - } - - interface IconPresentation extends WidgetPresentation { - @NotNull - Icon getIcon(); - } - - interface TextPresentation extends WidgetPresentation { - @NotNull - String getText(); - - @NotNull - String getMaxPossibleText(); - - float getAlignment(); - } - - interface MultipleTextValuesPresentation extends WidgetPresentation { - @NotNull - ListPopup getPopupStep(); - - @Nullable - String getSelectedValue(); - - @NotNull - String getMaxValue(); - } - - class WidgetBorder implements Border { - public static final WidgetBorder INSTANCE = new WidgetBorder(); - - private static final Color TOP = new Color(227, 227, 227); - private static final Color LEFT1_FROM = new Color(161, 161, 161); - private static final Color LEFT1_TO = new Color(133, 133, 133); - private static final Color LEFT2_FROM = new Color(220, 220, 220); - private static final Color LEFT2_TO = new Color(184, 184, 184); - private static final Color LEFT1_FROM_INACTIVE = new Color(190, 190, 190); - private static final Color PIXEL = LEFT1_FROM_INACTIVE; - private static final Color LEFT1_TO_INACTIVE = new Color(180, 180, 180); - - private static final Color SEPARATOR_COLOR = UIUtil.getPanelBackground().darker(); - - public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { - final Graphics2D g2 = (Graphics2D)g.create(); - if (SystemInfo.isMac) { - final Window window = SwingUtilities.getWindowAncestor(c); - if (window != null && window.isActive()) { - g2.setPaint(new GradientPaint(0, 0, LEFT1_FROM, 0, height, LEFT1_TO)); - g2.drawLine(x, y, x, y + height); - - g2.setPaint(new GradientPaint(0, 0, LEFT2_FROM, 0, height, LEFT2_TO)); - g2.drawLine(x + 1, y, x + 1, y + height); - - g2.setColor(PIXEL); - g2.drawLine(x, y, x, y); - - g2.setColor(TOP); - g2.drawLine(x + 2, y, x + width - 2, y); - } - else { - g2.setPaint(new GradientPaint(0, 0, LEFT1_FROM_INACTIVE, 0, height, LEFT1_TO_INACTIVE)); - g2.drawLine(x, y, x, y + height); - } - } else { - g2.setColor(SEPARATOR_COLOR); - g2.drawLine(x, y, x, y + height); - } - - g2.dispose(); - } - - public Insets getBorderInsets(Component c) { - return new Insets(2, SystemInfo.isMac ? 4 : 4, 2, 2); - } - - public boolean isBorderOpaque() { - return false; - } - } -} +/* + * 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.wm; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.Consumer; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.event.MouseEvent; + +/** + * User: spLeaner + */ +public interface StatusBarWidget extends Disposable { + + enum PlatformType { + DEFAULT, MAC + } + + @NotNull + String ID(); + + @Nullable + WidgetPresentation getPresentation(@NotNull PlatformType type); + + void install(@NotNull final StatusBar statusBar); + + interface Multiframe extends StatusBarWidget { + + StatusBarWidget copy(); + + } + + interface WidgetPresentation { + @Nullable + String getTooltipText(); + + @Nullable + Consumer getClickConsumer(); + } + + interface IconPresentation extends WidgetPresentation { + @NotNull + Icon getIcon(); + } + + interface TextPresentation extends WidgetPresentation { + @NotNull + String getText(); + + @NotNull + String getMaxPossibleText(); + + float getAlignment(); + } + + interface MultipleTextValuesPresentation extends WidgetPresentation { + @NotNull + ListPopup getPopupStep(); + + @Nullable + String getSelectedValue(); + + @NotNull + String getMaxValue(); + } + + class WidgetBorder implements Border { + public static final WidgetBorder INSTANCE = new WidgetBorder(); + + private static final Color TOP = new Color(227, 227, 227); + private static final Color LEFT1_FROM = new Color(161, 161, 161); + private static final Color LEFT1_TO = new Color(133, 133, 133); + private static final Color LEFT2_FROM = new Color(220, 220, 220); + private static final Color LEFT2_TO = new Color(184, 184, 184); + private static final Color LEFT1_FROM_INACTIVE = new Color(190, 190, 190); + private static final Color PIXEL = LEFT1_FROM_INACTIVE; + private static final Color LEFT1_TO_INACTIVE = new Color(180, 180, 180); + + private static final Color SEPARATOR_COLOR = UIUtil.getPanelBackground().darker(); + + public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { + final Graphics2D g2 = (Graphics2D)g.create(); + if (SystemInfo.isMac) { + final Window window = SwingUtilities.getWindowAncestor(c); + if (window != null && window.isActive()) { + g2.setPaint(new GradientPaint(0, 0, LEFT1_FROM, 0, height, LEFT1_TO)); + g2.drawLine(x, y, x, y + height); + + g2.setPaint(new GradientPaint(0, 0, LEFT2_FROM, 0, height, LEFT2_TO)); + g2.drawLine(x + 1, y, x + 1, y + height); + + g2.setColor(PIXEL); + g2.drawLine(x, y, x, y); + + g2.setColor(TOP); + g2.drawLine(x + 2, y, x + width - 2, y); + } + else { + g2.setPaint(new GradientPaint(0, 0, LEFT1_FROM_INACTIVE, 0, height, LEFT1_TO_INACTIVE)); + g2.drawLine(x, y, x, y + height); + } + } else { + g2.setColor(SEPARATOR_COLOR); + g2.drawLine(x, y, x, y + height); + } + + g2.dispose(); + } + + public Insets getBorderInsets(Component c) { + return new Insets(2, SystemInfo.isMac ? 4 : 4, 2, 2); + } + + public boolean isBorderOpaque() { + return false; + } + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java b/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java index 94e0e9f18e7b..1398215ceba8 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java @@ -1,79 +1,79 @@ -/* - * 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.wm; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; - -public abstract class WindowManager { - public static WindowManager getInstance(){ - return ApplicationManager.getApplication().getComponent(WindowManager.class); - } - - /** - */ - public abstract void doNotSuggestAsParent(Window window); - - /** - * Gets first window (starting from the active one) that can be parent for other windows. - * Note, that this method returns only subclasses of dialog or frame. - * @return null if there is no currently active window or there are any window - * that can be parent. - */ - public abstract Window suggestParentWindow(Project project); - - /** - * Get the status bar for the project's main frame - * @param project - * @return - */ - public abstract StatusBar getStatusBar(Project project); - - /** - * Get the status bar for the component, it may be either the main status bar or the status bar for an undocked window - * @param c - * @return - */ - public abstract StatusBar getStatusBar(@NotNull Component c); - - public abstract JFrame getFrame(Project project); - - public abstract IdeFrame getIdeFrame(Project project); - - /** - * Tests whether the specified rectangle is inside of screen bounds. Method uses its own heuristic test. - * Test passes if intersection of screen bounds and specified rectangle isn't empty and its height and - * width are not less then some value. Note, that all parameters are in screen coordinate system. - * The method properly works in mutlimonitor configuration. - */ - public abstract boolean isInsideScreenBounds(int x, int y, int width); - - /** - * Tests whether the specified point is inside of screen bounds. Note, that - * all parameters are in screen coordinate system. - * The method properly works in mutlimonitor configuration. - */ - public abstract boolean isInsideScreenBounds(int x,int y); - - public abstract IdeFrame[] getAllFrames(); - - public abstract void addListener(WindowManagerListener listener); - public abstract void removeListener(WindowManagerListener listener); -} +/* + * 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.wm; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; + +public abstract class WindowManager { + public static WindowManager getInstance(){ + return ApplicationManager.getApplication().getComponent(WindowManager.class); + } + + /** + */ + public abstract void doNotSuggestAsParent(Window window); + + /** + * Gets first window (starting from the active one) that can be parent for other windows. + * Note, that this method returns only subclasses of dialog or frame. + * @return null if there is no currently active window or there are any window + * that can be parent. + */ + public abstract Window suggestParentWindow(Project project); + + /** + * Get the status bar for the project's main frame + * @param project + * @return + */ + public abstract StatusBar getStatusBar(Project project); + + /** + * Get the status bar for the component, it may be either the main status bar or the status bar for an undocked window + * @param c + * @return + */ + public abstract StatusBar getStatusBar(@NotNull Component c); + + public abstract JFrame getFrame(Project project); + + public abstract IdeFrame getIdeFrame(Project project); + + /** + * Tests whether the specified rectangle is inside of screen bounds. Method uses its own heuristic test. + * Test passes if intersection of screen bounds and specified rectangle isn't empty and its height and + * width are not less then some value. Note, that all parameters are in screen coordinate system. + * The method properly works in mutlimonitor configuration. + */ + public abstract boolean isInsideScreenBounds(int x, int y, int width); + + /** + * Tests whether the specified point is inside of screen bounds. Note, that + * all parameters are in screen coordinate system. + * The method properly works in mutlimonitor configuration. + */ + public abstract boolean isInsideScreenBounds(int x,int y); + + public abstract IdeFrame[] getAllFrames(); + + public abstract void addListener(WindowManagerListener listener); + public abstract void removeListener(WindowManagerListener listener); +} diff --git a/platform/platform-api/src/com/intellij/pom/NavigatableWithText.java b/platform/platform-api/src/com/intellij/pom/NavigatableWithText.java index a2cb4a47d9f4..a9968e592fef 100644 --- a/platform/platform-api/src/com/intellij/pom/NavigatableWithText.java +++ b/platform/platform-api/src/com/intellij/pom/NavigatableWithText.java @@ -1,26 +1,26 @@ -/* - * 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.pom; - -import org.jetbrains.annotations.Nullable; - -/** - * @author yole - */ -public interface NavigatableWithText extends Navigatable { - @Nullable - String getNavigateActionText(boolean focusEditor); -} +/* + * 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.pom; + +import org.jetbrains.annotations.Nullable; + +/** + * @author yole + */ +public interface NavigatableWithText extends Navigatable { + @Nullable + String getNavigateActionText(boolean focusEditor); +} diff --git a/platform/platform-api/src/com/intellij/ui/ColorPanel.java b/platform/platform-api/src/com/intellij/ui/ColorPanel.java index e644b0901f69..b2e378f6681c 100644 --- a/platform/platform-api/src/com/intellij/ui/ColorPanel.java +++ b/platform/platform-api/src/com/intellij/ui/ColorPanel.java @@ -1,330 +1,330 @@ -/* - * 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.ui; - -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class ColorPanel extends JPanel { - public static final Color[] fixedColors; - public static final Color DARK_MAGENTA = new Color(128, 0, 128); - public static final Color DARK_BLUE = new Color(0, 0, 128); - public static final Color DARK_GREEN = new Color(0, 128, 0); - public static final Color BLUE_GREEN = new Color(0, 128, 128); - public static final Color DARK_YELLOW = new Color(128, 128, 0); - public static final Color DARK_RED = new Color(128, 0, 0); - public static final Color DISABLED_COLOR = UIUtil.getPanelBackground(); - private static Color[] myCustomColors; - @NonNls private String myActionCommand = "colorPanelChanged"; - private boolean isFiringEvent = false; - private int myBoxSize; - private GridLayout myCustomGridLayout; - private GridLayout myFixedGridLayout; - private ColorBox[] myFgFixedColorBoxes; - private ColorBox[] myFgCustomColorBoxes; - private ColorBox myFgSelectedColorBox; - - static { - fixedColors = new Color[]{Color.white, Color.lightGray, Color.red, Color.yellow, Color.green, Color.cyan, Color.blue, Color.magenta, - Color.black, Color.gray, DARK_RED, DARK_YELLOW, DARK_GREEN, BLUE_GREEN, DARK_BLUE, DARK_MAGENTA}; - } - - public ColorPanel() { - this(null, 10); - } - - public ColorPanel(Color[] colors, int boxSize) { - myBoxSize = boxSize; - myFgSelectedColorBox = new ColorBox(null, (myBoxSize + 2) * 2, true); - myFgSelectedColorBox.setSelectColorAction( - new Runnable() { - public void run() { - fireActionEvent(); - } - } - ); - - myFgFixedColorBoxes = new ColorBox[16]; - myFixedGridLayout = new GridLayout(2, 0, 2, 2); - myCustomGridLayout = new GridLayout(2, 0, 2, 2); - myFgCustomColorBoxes = new ColorBox[8]; - if (colors == null){ - if (myCustomColors == null){ - myCustomColors = - new Color[]{ - Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray - }; - } - } - else{ - myCustomColors = colors; - } - - JPanel selectedColorPanel = new JPanel(new GridBagLayout()); - selectedColorPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); - myFgSelectedColorBox.setBorder(BorderFactory.createEtchedBorder()); - selectedColorPanel.add(myFgSelectedColorBox, new GridBagConstraints()); - - initializeColorBoxes(); - JPanel fixedColorsPanel = new JPanel(myFixedGridLayout); - for(int i = 0; i < myFgFixedColorBoxes.length; i++){ - JPanel jpanel2 = new JPanel(new BorderLayout()); - jpanel2.setBorder(BorderFactory.createEtchedBorder()); - jpanel2.add(myFgFixedColorBoxes[i], BorderLayout.CENTER); - fixedColorsPanel.add(jpanel2); - } - - JPanel customColorsPanel = new JPanel(myCustomGridLayout); - customColorsPanel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(SideBorder.LEFT), - BorderFactory.createEmptyBorder(0, 2, 0, 0))); - for(int k = 0; k < myFgCustomColorBoxes.length; k++){ - JPanel jpanel4 = new JPanel(new BorderLayout()); - jpanel4.setBorder(BorderFactory.createEtchedBorder()); - jpanel4.add(myFgCustomColorBoxes[k], BorderLayout.CENTER); - customColorsPanel.add(jpanel4); - } - - JPanel allColorsPanel = new JPanel(new GridBagLayout()); - allColorsPanel.setBorder(BorderFactory.createEtchedBorder()); - allColorsPanel.add(fixedColorsPanel, new GridBagConstraints(1, 0, 1, 1, 0.1D, 0.1D, 10, 0, new Insets(2, 2, 2, 1), 0, 0)); - allColorsPanel.add(customColorsPanel, new GridBagConstraints(2, 0, 1, 1, 0.1D, 0.1D, 17, 0, new Insets(2, 2, 2, 2), 0, 0)); - setLayout(new BorderLayout()); - add(selectedColorPanel, BorderLayout.WEST); - add(allColorsPanel, BorderLayout.CENTER); - } - - public void setEnabled(boolean enabled) { - for(int i = 0; i < myFgFixedColorBoxes.length; i++){ - ColorBox colorBox = myFgFixedColorBoxes[i]; - colorBox.setEnabled(enabled); - } - for(int i = 0; i < myFgCustomColorBoxes.length; i++){ - ColorBox colorBox = myFgCustomColorBoxes[i]; - colorBox.setEnabled(enabled); - } - myFgSelectedColorBox.setEnabled(enabled); - super.setEnabled(enabled); - repaint(); - } - - void updateColorBoxes() { - for(int i = 0; i < myFgCustomColorBoxes.length; i++){ - myFgCustomColorBoxes[i].setColor(myCustomColors[i]); - } - } - - protected void initializeColorBoxes() { - for(int i = 0; i < myFgFixedColorBoxes.length; i++){ - myFgFixedColorBoxes[i] = new ColorBox(fixedColors[i], myBoxSize, false); - } - for(int i = 0; i < myFgCustomColorBoxes.length; i++){ - myFgCustomColorBoxes[i] = new ColorBox(Color.orange, myBoxSize, true); - final int customColorIndex = i; - final ColorBox customColorBox = myFgCustomColorBoxes[i]; - myFgCustomColorBoxes[i].setSelectColorAction( - new Runnable() { - public void run() { - myCustomColors[customColorIndex] = customColorBox.getColor(); - } - } - ); - } - updateColorBoxes(); - } - - public void setActionCommand(String actionCommand) { - myActionCommand = actionCommand; - } - - public String getActionCommand() { - return myActionCommand; - } - - private void fireActionEvent() { - if (!isFiringEvent){ - isFiringEvent = true; - ActionEvent actionevent = null; - Object[] listeners = listenerList.getListenerList(); - for(int i = listeners.length - 2; i >= 0; i -= 2){ - if (listeners[i] != ActionListener.class) continue; - if (actionevent == null){ - actionevent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand()); - } - ((ActionListener)listeners[i + 1]).actionPerformed(actionevent); - } - isFiringEvent = false; - } - } - - public void removeActionListener(ActionListener actionlistener) { - listenerList.remove(ActionListener.class, actionlistener); - } - - public void addActionListener(ActionListener actionlistener) { - listenerList.add(ActionListener.class, actionlistener); - } - - public void setCustomColor(int index, Color color1) { - myCustomColors[index - myFgFixedColorBoxes.length] = color1; - } - - public Color getSelectedColor() { - return myFgSelectedColorBox.getColor(); - } - - public void setSelectedColor(Color color) { - myFgSelectedColorBox.setColor(color); - } - - public Color[] getCustomColors() { - return myCustomColors; - } - - public void setPanelGridHeight(int i) { - myFixedGridLayout.setRows(i); - myCustomGridLayout.setRows(i); - doLayout(); - } - - public void setCustomColors(Color[] colors) { - myCustomColors = colors; - if (myCustomColors == null){ - myCustomColors = fixedColors; - } - if (colors != null){ - updateColorBoxes(); - } - } - - private class ColorBox extends JComponent { - private final Dimension mySize; - private final boolean isSelectable; - private Runnable mySelectColorAction = null; - private Color myColor; - @NonNls public static final String RGB = "RGB"; - - public ColorBox(Color color, int size, boolean isSelectable) { - mySize = new Dimension(size, size); - this.isSelectable = isSelectable; - myColor = color; - updateToolTip(); - //TODO[anton,vova] investigate - addMouseListener(new MouseAdapter(){ - public void mouseReleased(MouseEvent mouseevent) { - if (!isEnabled()){ - return; - } - if (mouseevent.isPopupTrigger()){ - selectColor(); - } - } - - public void mousePressed(MouseEvent mouseevent) { - if (!isEnabled()){ - return; - } - if (mouseevent.getClickCount() == 2){ - selectColor(); - } - else{ - if (SwingUtilities.isLeftMouseButton(mouseevent)){ - setSelectedColor(myColor); - fireActionEvent(); - } - else{ - if (mouseevent.isPopupTrigger()){ - selectColor(); - } - } - } - } - }); - } - - public void setSelectColorAction(Runnable selectColorAction) { - mySelectColorAction = selectColorAction; - } - - private void selectColor() { - if (isSelectable){ - Color color = ColorChooser.chooseColor(ColorPanel.this, UIBundle.message("color.panel.select.color.dialog.description"), myColor); - if (color != null){ - setColor(color); - if (mySelectColorAction != null){ - mySelectColorAction.run(); - } - } - } - } - - public Dimension getMinimumSize() { - return mySize; - } - - public Dimension getMaximumSize() { - return mySize; - } - - public Dimension getPreferredSize() { - return mySize; - } - - public void paintComponent(Graphics g) { - if (isEnabled()){ - g.setColor(myColor); - } - else{ - g.setColor(DISABLED_COLOR); - } - g.fillRect(0, 0, getWidth(), getHeight()); - } - - private void updateToolTip() { - if (myColor == null){ - return; - } - StringBuffer buffer = new StringBuffer(64); - buffer.append(RGB + ": "); - buffer.append(myColor.getRed()); - buffer.append(", "); - buffer.append(myColor.getGreen()); - buffer.append(", "); - buffer.append(myColor.getBlue()); - - if (isSelectable) { - buffer.append(" (" + UIBundle.message("color.panel.right.click.to.customize.tooltip.suffix") + ")"); - } - setToolTipText(buffer.toString()); - } - - public void setColor(Color color) { - myColor = color; - updateToolTip(); - repaint(); - } - - public Color getColor() { - return myColor; - } - } -} +/* + * 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.ui; + +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class ColorPanel extends JPanel { + public static final Color[] fixedColors; + public static final Color DARK_MAGENTA = new Color(128, 0, 128); + public static final Color DARK_BLUE = new Color(0, 0, 128); + public static final Color DARK_GREEN = new Color(0, 128, 0); + public static final Color BLUE_GREEN = new Color(0, 128, 128); + public static final Color DARK_YELLOW = new Color(128, 128, 0); + public static final Color DARK_RED = new Color(128, 0, 0); + public static final Color DISABLED_COLOR = UIUtil.getPanelBackground(); + private static Color[] myCustomColors; + @NonNls private String myActionCommand = "colorPanelChanged"; + private boolean isFiringEvent = false; + private int myBoxSize; + private GridLayout myCustomGridLayout; + private GridLayout myFixedGridLayout; + private ColorBox[] myFgFixedColorBoxes; + private ColorBox[] myFgCustomColorBoxes; + private ColorBox myFgSelectedColorBox; + + static { + fixedColors = new Color[]{Color.white, Color.lightGray, Color.red, Color.yellow, Color.green, Color.cyan, Color.blue, Color.magenta, + Color.black, Color.gray, DARK_RED, DARK_YELLOW, DARK_GREEN, BLUE_GREEN, DARK_BLUE, DARK_MAGENTA}; + } + + public ColorPanel() { + this(null, 10); + } + + public ColorPanel(Color[] colors, int boxSize) { + myBoxSize = boxSize; + myFgSelectedColorBox = new ColorBox(null, (myBoxSize + 2) * 2, true); + myFgSelectedColorBox.setSelectColorAction( + new Runnable() { + public void run() { + fireActionEvent(); + } + } + ); + + myFgFixedColorBoxes = new ColorBox[16]; + myFixedGridLayout = new GridLayout(2, 0, 2, 2); + myCustomGridLayout = new GridLayout(2, 0, 2, 2); + myFgCustomColorBoxes = new ColorBox[8]; + if (colors == null){ + if (myCustomColors == null){ + myCustomColors = + new Color[]{ + Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray, Color.lightGray + }; + } + } + else{ + myCustomColors = colors; + } + + JPanel selectedColorPanel = new JPanel(new GridBagLayout()); + selectedColorPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); + myFgSelectedColorBox.setBorder(BorderFactory.createEtchedBorder()); + selectedColorPanel.add(myFgSelectedColorBox, new GridBagConstraints()); + + initializeColorBoxes(); + JPanel fixedColorsPanel = new JPanel(myFixedGridLayout); + for(int i = 0; i < myFgFixedColorBoxes.length; i++){ + JPanel jpanel2 = new JPanel(new BorderLayout()); + jpanel2.setBorder(BorderFactory.createEtchedBorder()); + jpanel2.add(myFgFixedColorBoxes[i], BorderLayout.CENTER); + fixedColorsPanel.add(jpanel2); + } + + JPanel customColorsPanel = new JPanel(myCustomGridLayout); + customColorsPanel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(SideBorder.LEFT), + BorderFactory.createEmptyBorder(0, 2, 0, 0))); + for(int k = 0; k < myFgCustomColorBoxes.length; k++){ + JPanel jpanel4 = new JPanel(new BorderLayout()); + jpanel4.setBorder(BorderFactory.createEtchedBorder()); + jpanel4.add(myFgCustomColorBoxes[k], BorderLayout.CENTER); + customColorsPanel.add(jpanel4); + } + + JPanel allColorsPanel = new JPanel(new GridBagLayout()); + allColorsPanel.setBorder(BorderFactory.createEtchedBorder()); + allColorsPanel.add(fixedColorsPanel, new GridBagConstraints(1, 0, 1, 1, 0.1D, 0.1D, 10, 0, new Insets(2, 2, 2, 1), 0, 0)); + allColorsPanel.add(customColorsPanel, new GridBagConstraints(2, 0, 1, 1, 0.1D, 0.1D, 17, 0, new Insets(2, 2, 2, 2), 0, 0)); + setLayout(new BorderLayout()); + add(selectedColorPanel, BorderLayout.WEST); + add(allColorsPanel, BorderLayout.CENTER); + } + + public void setEnabled(boolean enabled) { + for(int i = 0; i < myFgFixedColorBoxes.length; i++){ + ColorBox colorBox = myFgFixedColorBoxes[i]; + colorBox.setEnabled(enabled); + } + for(int i = 0; i < myFgCustomColorBoxes.length; i++){ + ColorBox colorBox = myFgCustomColorBoxes[i]; + colorBox.setEnabled(enabled); + } + myFgSelectedColorBox.setEnabled(enabled); + super.setEnabled(enabled); + repaint(); + } + + void updateColorBoxes() { + for(int i = 0; i < myFgCustomColorBoxes.length; i++){ + myFgCustomColorBoxes[i].setColor(myCustomColors[i]); + } + } + + protected void initializeColorBoxes() { + for(int i = 0; i < myFgFixedColorBoxes.length; i++){ + myFgFixedColorBoxes[i] = new ColorBox(fixedColors[i], myBoxSize, false); + } + for(int i = 0; i < myFgCustomColorBoxes.length; i++){ + myFgCustomColorBoxes[i] = new ColorBox(Color.orange, myBoxSize, true); + final int customColorIndex = i; + final ColorBox customColorBox = myFgCustomColorBoxes[i]; + myFgCustomColorBoxes[i].setSelectColorAction( + new Runnable() { + public void run() { + myCustomColors[customColorIndex] = customColorBox.getColor(); + } + } + ); + } + updateColorBoxes(); + } + + public void setActionCommand(String actionCommand) { + myActionCommand = actionCommand; + } + + public String getActionCommand() { + return myActionCommand; + } + + private void fireActionEvent() { + if (!isFiringEvent){ + isFiringEvent = true; + ActionEvent actionevent = null; + Object[] listeners = listenerList.getListenerList(); + for(int i = listeners.length - 2; i >= 0; i -= 2){ + if (listeners[i] != ActionListener.class) continue; + if (actionevent == null){ + actionevent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand()); + } + ((ActionListener)listeners[i + 1]).actionPerformed(actionevent); + } + isFiringEvent = false; + } + } + + public void removeActionListener(ActionListener actionlistener) { + listenerList.remove(ActionListener.class, actionlistener); + } + + public void addActionListener(ActionListener actionlistener) { + listenerList.add(ActionListener.class, actionlistener); + } + + public void setCustomColor(int index, Color color1) { + myCustomColors[index - myFgFixedColorBoxes.length] = color1; + } + + public Color getSelectedColor() { + return myFgSelectedColorBox.getColor(); + } + + public void setSelectedColor(Color color) { + myFgSelectedColorBox.setColor(color); + } + + public Color[] getCustomColors() { + return myCustomColors; + } + + public void setPanelGridHeight(int i) { + myFixedGridLayout.setRows(i); + myCustomGridLayout.setRows(i); + doLayout(); + } + + public void setCustomColors(Color[] colors) { + myCustomColors = colors; + if (myCustomColors == null){ + myCustomColors = fixedColors; + } + if (colors != null){ + updateColorBoxes(); + } + } + + private class ColorBox extends JComponent { + private final Dimension mySize; + private final boolean isSelectable; + private Runnable mySelectColorAction = null; + private Color myColor; + @NonNls public static final String RGB = "RGB"; + + public ColorBox(Color color, int size, boolean isSelectable) { + mySize = new Dimension(size, size); + this.isSelectable = isSelectable; + myColor = color; + updateToolTip(); + //TODO[anton,vova] investigate + addMouseListener(new MouseAdapter(){ + public void mouseReleased(MouseEvent mouseevent) { + if (!isEnabled()){ + return; + } + if (mouseevent.isPopupTrigger()){ + selectColor(); + } + } + + public void mousePressed(MouseEvent mouseevent) { + if (!isEnabled()){ + return; + } + if (mouseevent.getClickCount() == 2){ + selectColor(); + } + else{ + if (SwingUtilities.isLeftMouseButton(mouseevent)){ + setSelectedColor(myColor); + fireActionEvent(); + } + else{ + if (mouseevent.isPopupTrigger()){ + selectColor(); + } + } + } + } + }); + } + + public void setSelectColorAction(Runnable selectColorAction) { + mySelectColorAction = selectColorAction; + } + + private void selectColor() { + if (isSelectable){ + Color color = ColorChooser.chooseColor(ColorPanel.this, UIBundle.message("color.panel.select.color.dialog.description"), myColor); + if (color != null){ + setColor(color); + if (mySelectColorAction != null){ + mySelectColorAction.run(); + } + } + } + } + + public Dimension getMinimumSize() { + return mySize; + } + + public Dimension getMaximumSize() { + return mySize; + } + + public Dimension getPreferredSize() { + return mySize; + } + + public void paintComponent(Graphics g) { + if (isEnabled()){ + g.setColor(myColor); + } + else{ + g.setColor(DISABLED_COLOR); + } + g.fillRect(0, 0, getWidth(), getHeight()); + } + + private void updateToolTip() { + if (myColor == null){ + return; + } + StringBuffer buffer = new StringBuffer(64); + buffer.append(RGB + ": "); + buffer.append(myColor.getRed()); + buffer.append(", "); + buffer.append(myColor.getGreen()); + buffer.append(", "); + buffer.append(myColor.getBlue()); + + if (isSelectable) { + buffer.append(" (" + UIBundle.message("color.panel.right.click.to.customize.tooltip.suffix") + ")"); + } + setToolTipText(buffer.toString()); + } + + public void setColor(Color color) { + myColor = color; + updateToolTip(); + repaint(); + } + + public Color getColor() { + return myColor; + } + } +} diff --git a/platform/platform-api/src/com/intellij/ui/GuiUtils.java b/platform/platform-api/src/com/intellij/ui/GuiUtils.java index b7932228279e..f93831c2c605 100644 --- a/platform/platform-api/src/com/intellij/ui/GuiUtils.java +++ b/platform/platform-api/src/com/intellij/ui/GuiUtils.java @@ -1,383 +1,383 @@ -/* - * 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.ui; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.ui.FixedSizeButton; -import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.CharFilter; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Consumer; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.TitledBorder; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumnModel; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; - -public class GuiUtils { - - private static final Insets paddingFromDialogBoundaries = new Insets(7, 5, 7, 5); - private static final Insets paddingInsideDialog = new Insets(5, 5, 5, 5); - - public static final int lengthForFileField = 25; - private static final CharFilter NOT_MNEMONIC_CHAR_FILTER = new CharFilter() { - public boolean accept(char ch) { - return ch != '&' && ch != UIUtil.MNEMONIC; - } - }; - - public static JPanel constructFieldWithBrowseButton(JComponent aComponent, ActionListener aActionListener) { - return constructFieldWithBrowseButton(aComponent, aActionListener, 0); - } - - public static JPanel constructFieldWithBrowseButton(TextFieldWithHistory aComponent, ActionListener aActionListener) { - return constructFieldWithBrowseButton(aComponent, aActionListener, 0); - } - - private static JPanel constructFieldWithBrowseButton(final JComponent aComponent, final ActionListener aActionListener, int delta) { - JPanel result = new JPanel(new GridBagLayout()); - result.add(aComponent, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,0)); - FixedSizeButton browseButton = new FixedSizeButton(aComponent.getPreferredSize().height - delta);//ignore border in case of browse button - TextFieldWithBrowseButton.MyDoClickAction.addTo(browseButton, aComponent); - result.add(browseButton, new GridBagConstraints(1, 0, 1, 1, 0, 1, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0,0,0,0), 0,0)); - browseButton.addActionListener(aActionListener); - - return result; - } - - public static JPanel constructDirectoryBrowserField(final JTextField aTextField, final String aSearchedObjectName) { - return constructFieldWithBrowseButton(aTextField, new ActionListener() { - public void actionPerformed(ActionEvent e) { - FileChooserDescriptor descriptor = FileChooserDescriptorFactory.getDirectoryChooserDescriptor(aSearchedObjectName); - VirtualFile[] files = FileChooser.chooseFiles(aTextField, descriptor); - if (files.length != 0) { - aTextField.setText(FileUtil.toSystemDependentName(files[0].getPath())); - aTextField.postActionEvent(); - } - } - }); - } - - public static JPanel constructFileURLBrowserField(final TextFieldWithHistory aFieldWithHistory, - final String aSearchedObjectName) { - return constructFieldWithBrowseButton(aFieldWithHistory, new ActionListener() { - public void actionPerformed(ActionEvent e) { - FileChooserDescriptor descriptor = FileChooserDescriptorFactory.getFileChooserDescriptor(aSearchedObjectName); - VirtualFile[] files = FileChooser.chooseFiles(aFieldWithHistory, descriptor); - if (files.length != 0) { - try { - aFieldWithHistory.setText(VfsUtil.virtualToIoFile(files[0]).toURL().toString()); - } - catch (MalformedURLException e1) { - aFieldWithHistory.setText(""); - } - } - } - }); - } - - public static JComponent constructLabeledComponent(String aLabelText, JComponent aComponent, int aAxis) { - JPanel result = new JPanel(); - BoxLayout boxLayout = new BoxLayout(result, aAxis); - result.setLayout(boxLayout); - - result.add(new JLabel(aLabelText)); - result.add(aComponent); - - return result; - } - - public static JPanel makeDialogPanel(JPanel aPanel) { - JPanel emptyBordered = makePaddedPanel(aPanel, paddingFromDialogBoundaries); - return wrapWithBorder(emptyBordered, IdeBorderFactory.createRoundedBorder()); - } - - public static JPanel makeTitledPanel(JComponent aComponent, String aTitle) { - JPanel result = makePaddedPanel(aComponent, false, true, false, true); - return wrapWithBorder(result, IdeBorderFactory.createTitledBorder(aTitle)); - } - - - private static JPanel wrapWithBorder(JComponent aPanel, Border aBorder) { - JPanel wrapper = new JPanel(new BorderLayout()); - wrapper.add(aPanel, BorderLayout.CENTER); - wrapper.setBorder(aBorder); - return wrapper; - } - - - public static BorderLayout createBorderLayout() { - return new BorderLayout(paddingInsideDialog.left, paddingInsideDialog.top); - } - - public static GridLayout createGridLayout(int aRows, int aColumns) { - return new GridLayout(aRows, aColumns, paddingInsideDialog.left, paddingInsideDialog.top); - } - - public static Component createVerticalStrut() { - return Box.createRigidArea(new Dimension(0, paddingInsideDialog.top)); - } - - public static Component createHorisontalStrut() { - return Box.createRigidArea(new Dimension(paddingInsideDialog.left, 0)); - } - - private static JPanel makePaddedPanel(JComponent aComponent, Insets aInsets) { - return wrapWithBorder(aComponent, BorderFactory.createEmptyBorder( - aInsets.top, - aInsets.left, - aInsets.bottom, - aInsets.right - )); - } - - private static JPanel makePaddedPanel(JComponent aComponent, - boolean aTop, - boolean aLeft, - boolean aBottom, - boolean aRight) { - return wrapWithBorder(aComponent, BorderFactory.createEmptyBorder( - aTop ? paddingInsideDialog.top : 0, - aLeft ? paddingInsideDialog.left : 0, - aBottom ? paddingInsideDialog.bottom : 0, - aRight ? paddingInsideDialog.right : 0)); - } - - public static void setAdditionalIcon(JRadioButton button, Icon icon) { - final Icon defaultIcon = UIUtil.getRadioButtonIcon(); - LayeredIcon deficon = new LayeredIcon(2); - deficon.setIcon(defaultIcon, 0); - deficon.setIcon(icon, 1, defaultIcon.getIconWidth() + 5, 0); - button.setIcon(deficon); - - LayeredIcon pressed = new LayeredIcon(2); - pressed.setIcon(defaultIcon, 0); - pressed.setIcon(icon, 1, defaultIcon.getIconWidth() + 5, 0); - button.setPressedIcon(pressed); - - LayeredIcon selected = new LayeredIcon(2); - selected.setIcon(defaultIcon, 0); - selected.setIcon(icon, 1, defaultIcon.getIconWidth() + 5, 0); - button.setSelectedIcon(selected); - } - - public static String getTextWithoutMnemonicEscaping(String text) { - return StringUtil.strip(text, NOT_MNEMONIC_CHAR_FILTER); - } - - public static char getDisplayedMnemonic(String text) { - final int i = getDisplayedMnemonicIndex(text); - return i == -1 ? (char)-1 : text.charAt(i + 1); - } - - public static int getDisplayedMnemonicIndex(String text) { - return text.indexOf("&"); - } - - public static void packParentDialog(Component component) { - while (component != null) { - if (component instanceof JDialog) { - component.setVisible(true); - break; - } - component = component.getParent(); - } - } - - public static void replaceJSplitPaneWithIDEASplitter(JComponent root) { - final Container parent = root.getParent(); - if (root instanceof JSplitPane) { - // we can painlessly replace only splitter which is the only child in container - if (parent.getComponents().length != 1 && !(parent instanceof Splitter)) { - return; - } - final JSplitPane pane = (JSplitPane)root; - final Component component1 = pane.getTopComponent(); - final Component component2 = pane.getBottomComponent(); - final int orientation = pane.getOrientation(); - final Splitter splitter = new Splitter(orientation == JSplitPane.VERTICAL_SPLIT); - splitter.setFirstComponent((JComponent) component1); - splitter.setSecondComponent((JComponent) component2); - splitter.setShowDividerControls(pane.isOneTouchExpandable()); - splitter.setHonorComponentsMinimumSize(true); - - if (pane.getDividerLocation() > 0) { -// let the component chance to resize itself - SwingUtilities.invokeLater(new Runnable() { - public void run() { - double proportion; - if (pane.getOrientation() == JSplitPane.VERTICAL_SPLIT) { - proportion = pane.getDividerLocation() / (double)(parent.getHeight() - pane.getDividerSize()); - } - else { - proportion = pane.getDividerLocation() / (double)(parent.getWidth() - pane.getDividerSize()); - } - if (proportion > 0 && proportion < 1) { - splitter.setProportion((float)proportion); - } - } - }); - } - - if (parent instanceof Splitter) { - final Splitter psplitter = (Splitter) parent; - if (psplitter.getFirstComponent() == root) - psplitter.setFirstComponent(splitter); - else - psplitter.setSecondComponent(splitter); - } - else { - parent.remove(0); - parent.setLayout(new BorderLayout()); - parent.add(splitter, BorderLayout.CENTER); - } - replaceJSplitPaneWithIDEASplitter((JComponent) component1); - replaceJSplitPaneWithIDEASplitter((JComponent) component2); - } - else { - final Component[] components = root.getComponents(); - for (Component component : components) { - if (component instanceof JComponent) { - replaceJSplitPaneWithIDEASplitter((JComponent)component); - } - } - } - } - - public static void iterateChildren(Component container, Consumer consumer, JComponent... excludeComponents) { - if (excludeComponents != null && ArrayUtil.find(excludeComponents, container) != -1) return; - consumer.consume(container); - if (container instanceof Container) { - final Component[] components = ((Container)container).getComponents(); - for (Component child : components) { - iterateChildren(child, consumer, excludeComponents); - } - } - } - - public static void iterateChildren(Consumer consumer, Component... components) { - for (final Component component : components) { - iterateChildren(component, consumer); - } - } - - public static void enableChildren(final boolean enabled, Component... components) { - for (final Component component : components) { - enableChildren(component, enabled); - } - } - - public static void showComponents(final boolean visible, Component... components) { - for (final Component component : components) { - component.setVisible(visible); - } - } - - public static void enableChildren(Component container, final boolean enabled, JComponent... excludeComponents) { - iterateChildren(container, new Consumer() { - public void consume(final Component t) { - enableComponent(t, enabled); - } - }, excludeComponents); - } - - private static void enableComponent(Component component, boolean enabled) { - if (component.isEnabled() == enabled) return; - component.setEnabled(enabled); - if (component instanceof JPanel) { - final Border border = ((JPanel)component).getBorder(); - if (border instanceof TitledBorder) { - Color color = enabled ? component.getForeground() : UIUtil.getInactiveTextColor(); - ((TitledBorder)border).setTitleColor(color); - } - } - else if (component instanceof JLabel) { - Color color = UIUtil.getInactiveTextColor(); - if (color == null) color = component.getForeground(); - @NonNls String changeColorString = ""; - final JLabel label = (JLabel)component; - @NonNls String text = label.getText(); - if (text != null && text.startsWith("")) { - if (StringUtil.startsWithConcatenationOf(text, "", changeColorString) && enabled) { - text = ""+text.substring((""+changeColorString).length()); - } - else if (!StringUtil.startsWithConcatenationOf(text, "", changeColorString) && !enabled) { - text = ""+changeColorString+text.substring("".length()); - } - label.setText(text); - } - } - else if (component instanceof JTable) { - TableColumnModel columnModel = ((JTable)component).getColumnModel(); - for (int i=0; i 0) { +// let the component chance to resize itself + SwingUtilities.invokeLater(new Runnable() { + public void run() { + double proportion; + if (pane.getOrientation() == JSplitPane.VERTICAL_SPLIT) { + proportion = pane.getDividerLocation() / (double)(parent.getHeight() - pane.getDividerSize()); + } + else { + proportion = pane.getDividerLocation() / (double)(parent.getWidth() - pane.getDividerSize()); + } + if (proportion > 0 && proportion < 1) { + splitter.setProportion((float)proportion); + } + } + }); + } + + if (parent instanceof Splitter) { + final Splitter psplitter = (Splitter) parent; + if (psplitter.getFirstComponent() == root) + psplitter.setFirstComponent(splitter); + else + psplitter.setSecondComponent(splitter); + } + else { + parent.remove(0); + parent.setLayout(new BorderLayout()); + parent.add(splitter, BorderLayout.CENTER); + } + replaceJSplitPaneWithIDEASplitter((JComponent) component1); + replaceJSplitPaneWithIDEASplitter((JComponent) component2); + } + else { + final Component[] components = root.getComponents(); + for (Component component : components) { + if (component instanceof JComponent) { + replaceJSplitPaneWithIDEASplitter((JComponent)component); + } + } + } + } + + public static void iterateChildren(Component container, Consumer consumer, JComponent... excludeComponents) { + if (excludeComponents != null && ArrayUtil.find(excludeComponents, container) != -1) return; + consumer.consume(container); + if (container instanceof Container) { + final Component[] components = ((Container)container).getComponents(); + for (Component child : components) { + iterateChildren(child, consumer, excludeComponents); + } + } + } + + public static void iterateChildren(Consumer consumer, Component... components) { + for (final Component component : components) { + iterateChildren(component, consumer); + } + } + + public static void enableChildren(final boolean enabled, Component... components) { + for (final Component component : components) { + enableChildren(component, enabled); + } + } + + public static void showComponents(final boolean visible, Component... components) { + for (final Component component : components) { + component.setVisible(visible); + } + } + + public static void enableChildren(Component container, final boolean enabled, JComponent... excludeComponents) { + iterateChildren(container, new Consumer() { + public void consume(final Component t) { + enableComponent(t, enabled); + } + }, excludeComponents); + } + + private static void enableComponent(Component component, boolean enabled) { + if (component.isEnabled() == enabled) return; + component.setEnabled(enabled); + if (component instanceof JPanel) { + final Border border = ((JPanel)component).getBorder(); + if (border instanceof TitledBorder) { + Color color = enabled ? component.getForeground() : UIUtil.getInactiveTextColor(); + ((TitledBorder)border).setTitleColor(color); + } + } + else if (component instanceof JLabel) { + Color color = UIUtil.getInactiveTextColor(); + if (color == null) color = component.getForeground(); + @NonNls String changeColorString = ""; + final JLabel label = (JLabel)component; + @NonNls String text = label.getText(); + if (text != null && text.startsWith("")) { + if (StringUtil.startsWithConcatenationOf(text, "", changeColorString) && enabled) { + text = ""+text.substring((""+changeColorString).length()); + } + else if (!StringUtil.startsWithConcatenationOf(text, "", changeColorString) && !enabled) { + text = ""+changeColorString+text.substring("".length()); + } + label.setText(text); + } + } + else if (component instanceof JTable) { + TableColumnModel columnModel = ((JTable)component).getColumnModel(); + for (int i=0; iReplacement for {@link ColoredListCellRenderer}, L&F friendly. - * - *

From {@link #customize(javax.swing.JList, Object, int, boolean, boolean)}, - * text fragments should be added via {@link #append(String, SimpleTextAttributes)}.
- * Note that not all styles from SimpleTextAttributes are supported, see method code for details. - * - *

Alternatively, item can be rendered from {@link com.intellij.openapi.roots.ui.util.ModifiableCellAppearance} - * by {@link #render(com.intellij.openapi.roots.ui.util.ModifiableCellAppearance)}. - * - * @param type of list items. - */ -public abstract class HtmlListCellRenderer extends ListCellRendererWrapper { - private StringBuilder myText; - - /** - * Default JComboBox cell renderer should be passed here. - * @param listCellRenderer Default cell renderer ({@link javax.swing.JComboBox#getRenderer()}). - */ - public HtmlListCellRenderer(final ListCellRenderer listCellRenderer) { - super(listCellRenderer); - } - - @Override - public final void customize(final JList list, final T value, final int index, final boolean selected, final boolean hasFocus) { - myText = StringBuilderSpinAllocator.alloc(); - try { - doCustomize(list, value, index, selected, hasFocus); - - if (myText.length() == 0) { - setText(null); - } - else { - myText.insert(0, ""); - myText.append(""); - setText(myText.toString()); - } - } - finally { - StringBuilderSpinAllocator.dispose(myText); - myText = null; - } - } - - protected abstract void doCustomize(final JList list, final T value, final int index, final boolean selected, final boolean hasFocus); - - protected void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes) { - final Color fgColor = attributes.getFgColor(); - final Color bgColor = attributes.getBgColor(); - final int style = attributes.getStyle(); - - myText.append("").append(StringUtil.escapeXml(fragment)).append(""); - } - - protected void render(@NotNull final ModifiableCellAppearance appearance) { - if (appearance instanceof SimpleTextCellAppearance) { - final SimpleTextCellAppearance simple = (SimpleTextCellAppearance)appearance; - append(simple.getText(), simple.getTextAttributes()); - } - else if (appearance instanceof CompositeAppearance) { - final Iterator iterator = ((CompositeAppearance)appearance).getSectionsIterator(); - while (iterator.hasNext()) { - final CompositeAppearance.TextSection section = iterator.next(); - append(section.getText(), SimpleTextAttributes.fromTextAttributes(section.getTextAttributes())); - } - } - else { - throw new IllegalArgumentException("Unknown class: " + appearance.getClass()); - } - setIcon(appearance.getIcon()); - } -} +/* + * 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.ui; + +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.openapi.roots.ui.util.CompositeAppearance; +import com.intellij.openapi.roots.ui.util.ModifiableCellAppearance; +import com.intellij.openapi.roots.ui.util.SimpleTextCellAppearance; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.StringBuilderSpinAllocator; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.util.Iterator; + +/** + *

Replacement for {@link ColoredListCellRenderer}, L&F friendly. + * + *

From {@link #customize(javax.swing.JList, Object, int, boolean, boolean)}, + * text fragments should be added via {@link #append(String, SimpleTextAttributes)}.
+ * Note that not all styles from SimpleTextAttributes are supported, see method code for details. + * + *

Alternatively, item can be rendered from {@link com.intellij.openapi.roots.ui.util.ModifiableCellAppearance} + * by {@link #render(com.intellij.openapi.roots.ui.util.ModifiableCellAppearance)}. + * + * @param type of list items. + */ +public abstract class HtmlListCellRenderer extends ListCellRendererWrapper { + private StringBuilder myText; + + /** + * Default JComboBox cell renderer should be passed here. + * @param listCellRenderer Default cell renderer ({@link javax.swing.JComboBox#getRenderer()}). + */ + public HtmlListCellRenderer(final ListCellRenderer listCellRenderer) { + super(listCellRenderer); + } + + @Override + public final void customize(final JList list, final T value, final int index, final boolean selected, final boolean hasFocus) { + myText = StringBuilderSpinAllocator.alloc(); + try { + doCustomize(list, value, index, selected, hasFocus); + + if (myText.length() == 0) { + setText(null); + } + else { + myText.insert(0, ""); + myText.append(""); + setText(myText.toString()); + } + } + finally { + StringBuilderSpinAllocator.dispose(myText); + myText = null; + } + } + + protected abstract void doCustomize(final JList list, final T value, final int index, final boolean selected, final boolean hasFocus); + + protected void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes) { + final Color fgColor = attributes.getFgColor(); + final Color bgColor = attributes.getBgColor(); + final int style = attributes.getStyle(); + + myText.append("").append(StringUtil.escapeXml(fragment)).append(""); + } + + protected void render(@NotNull final ModifiableCellAppearance appearance) { + if (appearance instanceof SimpleTextCellAppearance) { + final SimpleTextCellAppearance simple = (SimpleTextCellAppearance)appearance; + append(simple.getText(), simple.getTextAttributes()); + } + else if (appearance instanceof CompositeAppearance) { + final Iterator iterator = ((CompositeAppearance)appearance).getSectionsIterator(); + while (iterator.hasNext()) { + final CompositeAppearance.TextSection section = iterator.next(); + append(section.getText(), SimpleTextAttributes.fromTextAttributes(section.getTextAttributes())); + } + } + else { + throw new IllegalArgumentException("Unknown class: " + appearance.getClass()); + } + setIcon(appearance.getIcon()); + } +} diff --git a/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java b/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java index 0f42f5e55574..763c2ff99a10 100644 --- a/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java +++ b/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java @@ -1,96 +1,96 @@ -/* - * 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.ui; - -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import java.awt.*; - -public class SeparatorWithText extends JComponent { - - private static final int VGAP = 3; - private static final int HGAP = 3; - - private String myCaption = ""; - private int myPrefWidth; - - public SeparatorWithText() { - setBorder(BorderFactory.createEmptyBorder(VGAP, 0, VGAP, 0)); - setFont(UIUtil.getLabelFont()); - setFont(getFont().deriveFont(Font.BOLD)); - } - - public Dimension getPreferredSize() { - final Dimension size = getPreferredFontSize(); - size.width = myPrefWidth == -1? size.width : myPrefWidth; - return size; - } - - public Dimension getPreferredFontSize() { - if (hasCaption()) { - FontMetrics fm = getFontMetrics(getFont()); - int preferredHeight = fm.getHeight(); - int preferredWidth = getPreferredWidth(fm); - - return new Dimension(preferredWidth, preferredHeight + VGAP * 2); - } - - return new Dimension(0, VGAP * 2 + 1); - } - - private int getPreferredWidth(FontMetrics fm) { - return fm.stringWidth(myCaption) + 2 * HGAP; - } - - private boolean hasCaption() { - return myCaption != null && !"".equals(myCaption.trim()); - } - - public Dimension getMinimumSize() { - return getPreferredSize(); - } - - public void setMinimumWidth(int width) { - myPrefWidth = width; - } - - protected void paintComponent(Graphics g) { - g.setColor(GroupedElementsRenderer.POPUP_SEPARATOR_FOREGROUND); - - if (hasCaption()) { - FontMetrics fm = getFontMetrics(getFont()); - int baseline = VGAP + fm.getAscent(); - - final int fontWidth = getPreferredFontSize().width; - final int lineX = (getWidth() - fontWidth) / 2; - final int lineY = VGAP + fm.getHeight() / 2; - - g.drawLine(0, lineY, lineX, lineY); - g.drawString(myCaption, lineX + HGAP, baseline); - g.drawLine(lineX + fontWidth, lineY, getWidth() - 1, lineY); - } - else { - g.drawLine(0, VGAP, getWidth() - 1, VGAP); - } - } - - public void setCaption(String captionAboveOf) { - myCaption = captionAboveOf; - } - +/* + * 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.ui; + +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import java.awt.*; + +public class SeparatorWithText extends JComponent { + + private static final int VGAP = 3; + private static final int HGAP = 3; + + private String myCaption = ""; + private int myPrefWidth; + + public SeparatorWithText() { + setBorder(BorderFactory.createEmptyBorder(VGAP, 0, VGAP, 0)); + setFont(UIUtil.getLabelFont()); + setFont(getFont().deriveFont(Font.BOLD)); + } + + public Dimension getPreferredSize() { + final Dimension size = getPreferredFontSize(); + size.width = myPrefWidth == -1? size.width : myPrefWidth; + return size; + } + + public Dimension getPreferredFontSize() { + if (hasCaption()) { + FontMetrics fm = getFontMetrics(getFont()); + int preferredHeight = fm.getHeight(); + int preferredWidth = getPreferredWidth(fm); + + return new Dimension(preferredWidth, preferredHeight + VGAP * 2); + } + + return new Dimension(0, VGAP * 2 + 1); + } + + private int getPreferredWidth(FontMetrics fm) { + return fm.stringWidth(myCaption) + 2 * HGAP; + } + + private boolean hasCaption() { + return myCaption != null && !"".equals(myCaption.trim()); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public void setMinimumWidth(int width) { + myPrefWidth = width; + } + + protected void paintComponent(Graphics g) { + g.setColor(GroupedElementsRenderer.POPUP_SEPARATOR_FOREGROUND); + + if (hasCaption()) { + FontMetrics fm = getFontMetrics(getFont()); + int baseline = VGAP + fm.getAscent(); + + final int fontWidth = getPreferredFontSize().width; + final int lineX = (getWidth() - fontWidth) / 2; + final int lineY = VGAP + fm.getHeight() / 2; + + g.drawLine(0, lineY, lineX, lineY); + g.drawString(myCaption, lineX + HGAP, baseline); + g.drawLine(lineX + fontWidth, lineY, getWidth() - 1, lineY); + } + else { + g.drawLine(0, VGAP, getWidth() - 1, VGAP); + } + } + + public void setCaption(String captionAboveOf) { + myCaption = captionAboveOf; + } + } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/ui/SimpleColoredComponent.java b/platform/platform-api/src/com/intellij/ui/SimpleColoredComponent.java index 0c6c74e695dc..aff4263398ee 100644 --- a/platform/platform-api/src/com/intellij/ui/SimpleColoredComponent.java +++ b/platform/platform-api/src/com/intellij/ui/SimpleColoredComponent.java @@ -1,615 +1,615 @@ -/* - * 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.ui; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; - -import javax.accessibility.Accessible; -import javax.accessibility.AccessibleContext; -import javax.accessibility.AccessibleRole; -import javax.accessibility.AccessibleStateSet; -import javax.swing.*; -import javax.swing.border.Border; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * This is high performance Swing component which represents an icon - * with a colored text. The text consists of fragments. Each - * text fragment has its own color (foreground) and font style. - * - * @author Vladimir Kondratyev - */ -public class SimpleColoredComponent extends JComponent implements Accessible { - private static final Logger LOG = Logger.getInstance("#com.intellij.ui.SimpleColoredComponent"); - - private final ArrayList myFragments; - private final ArrayList myAttributes; - private ArrayList myFragmentTags = null; - - /** - * Component's icon. It can be null. - */ - private Icon myIcon; - /** - * Internal padding - */ - private Insets myIpad; - /** - * Gap between icon and text. It is used only if icon is defined. - */ - private int myIconTextGap; - /** - * Defines whether the focus border around the text is painted or not. - * For example, text can have a border if the component represents a selected item - * in focused JList. - */ - private boolean myPaintFocusBorder; - /** - * Defines whether the focus border around the text extends to icon or not - */ - private boolean myFocusBorderAroundIcon; - /** - * This is the border around the text. For example, text can have a border - * if the component represents a selected item in a focused JList. - * Border can be null. - */ - private final MyBorder myBorder; - - private int myMainTextLastIndex = -1; - - private final Map myAligns; - - private boolean myIconOpaque = true; - - private boolean myAutoInvalidate = true; - - private AccessibleContext myContext = new MyAccessibleContext(); - - public SimpleColoredComponent() { - myFragments = new ArrayList(3); - myAttributes = new ArrayList(3); - myIpad = new Insets(1, 2, 1, 2); - myIconTextGap = 2; - myBorder = new MyBorder(); - myAligns = new HashMap(10); - setOpaque(true); - } - - public final void append(@NotNull String fragment) { - append(fragment, SimpleTextAttributes.REGULAR_ATTRIBUTES); - } - - /** - * Appends string fragments to existing ones. Appended string - * will have specified attributes. - */ - public final void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes) { - append(fragment, attributes, myMainTextLastIndex < 0); - } - - /** - * Appends string fragments to existing ones. Appended string - * will have specified attributes. - */ - public void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes, boolean isMainText) { - synchronized (this) { - myFragments.add(fragment); - myAttributes.add(attributes); - if (isMainText) { - myMainTextLastIndex = myFragments.size() - 1; - } - } - revalidateAndRepaint(); - } - - private void revalidateAndRepaint() { - if (myAutoInvalidate) { - revalidate(); - } - - repaint(); - } - - public void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes, Object tag) { - synchronized (this) { - append(fragment, attributes); - if (myFragmentTags == null) { - myFragmentTags = new ArrayList(); - } - while(myFragmentTags.size() < myFragments.size()-1) { - myFragmentTags.add(null); - } - myFragmentTags.add(tag); - } - revalidateAndRepaint(); - } - - public synchronized void appendAlign(int alignWidth) { - final int alignIndex = myFragments.size()-1; - myAligns.put(alignIndex, alignWidth); - } - - /** - * Clear all special attributes of SimpleColoredComponent. - * They are icon, text fragments and their attributes, "paint focus border". - */ - public void clear() { - synchronized (this) { - myIcon = null; - myPaintFocusBorder = false; - myFragments.clear(); - myAttributes.clear(); - myFragmentTags = null; - myMainTextLastIndex = -1; - myAligns.clear(); - } - revalidateAndRepaint(); - } - - /** - * @return component's icon. This method returns null - * if there is no icon. - */ - public final Icon getIcon() { - return myIcon; - } - - /** - * Sets a new component icon - */ - public final void setIcon(final Icon icon) { - myIcon = icon; - revalidateAndRepaint(); - } - - /** - * @return "leave" (internal) internal paddings of the component - */ - public Insets getIpad() { - return myIpad; - } - - /** - * Sets specified internal paddings - */ - public void setIpad(final Insets ipad) { - myIpad = ipad; - - revalidateAndRepaint(); - } - - /** - * @return gap between icon and text - */ - public int getIconTextGap() { - return myIconTextGap; - } - - /** - * Sets a new gap between icon and text - * - * @throws java.lang.IllegalArgumentException - * if the iconTextGap - * has a negative value - */ - public void setIconTextGap(final int iconTextGap) { - if (iconTextGap < 0) { - throw new IllegalArgumentException("wrong iconTextGap: " + iconTextGap); - } - myIconTextGap = iconTextGap; - - revalidateAndRepaint(); - } - - /** - * Sets whether focus border is painted or not - */ - protected final void setPaintFocusBorder(final boolean paintFocusBorder) { - myPaintFocusBorder = paintFocusBorder; - - repaint(); - } - - /** - * Sets whether focus border extends to icon or not. If so then - * component also extends the selection. - */ - protected final void setFocusBorderAroundIcon(final boolean focusBorderAroundIcon) { - myFocusBorderAroundIcon = focusBorderAroundIcon; - - repaint(); - } - - public boolean isIconOpaque() { - return myIconOpaque; - } - - public void setIconOpaque(final boolean iconOpaque) { - myIconOpaque = iconOpaque; - - repaint(); - } - - public Dimension getPreferredSize() { - return computePreferredSize(false); - - } - - public synchronized Object getFragmentTag(int index) { - if (myFragmentTags != null && index < myFragmentTags.size()) { - return myFragmentTags.get(index); - } - return null; - } - - public final synchronized Dimension computePreferredSize(final boolean mainTextOnly) { - // Calculate width - int width = myIpad.left; - - if (myIcon != null) { - width += myIcon.getIconWidth() + myIconTextGap; - } - - final Insets borderInsets = myBorder.getBorderInsets(this); - width += borderInsets.left; - - Font font = getFont(); - if (font == null) { - font = UIUtil.getLabelFont(); - } - - LOG.assertTrue(font != null); - - for (int i = 0; i < myAttributes.size(); i++) { - SimpleTextAttributes attributes = myAttributes.get(i); - if (font.getStyle() != attributes.getFontStyle()) { // derive font only if it is necessary - font = font.deriveFont(attributes.getFontStyle()); - } - final FontMetrics metrics = getFontMetrics(font); - width += metrics.stringWidth(myFragments.get(i)); - - final Integer fixedWidth = myAligns.get(i); - if (fixedWidth != null && width < fixedWidth.intValue()) { - width = fixedWidth.intValue(); - } - - if (mainTextOnly && myMainTextLastIndex >= 0 && i == myMainTextLastIndex) break; - } - width += myIpad.right + borderInsets.right; - - // Calculate height - int height = myIpad.top + myIpad.bottom; - - final FontMetrics metrics = getFontMetrics(font); - int textHeight = metrics.getHeight(); - textHeight += borderInsets.top + borderInsets.bottom; - - if (myIcon != null) { - height += Math.max(myIcon.getIconHeight(), textHeight); - } - else { - height += textHeight; - } - - // Take into accound that the component itself can have a border - final Insets insets = getInsets(); - width += insets.left + insets.right; - height += insets.top + insets.bottom; - - return new Dimension(width, height); - } - - public int findFragmentAt(int x) { - int curX = myIpad.left; - if (myIcon != null) { - curX += myIcon.getIconWidth() + myIconTextGap; - } - - Font font = getFont(); - LOG.assertTrue(font != null); - - for (int i = 0; i < myAttributes.size(); i++) { - SimpleTextAttributes attributes = myAttributes.get(i); - if (font.getStyle() != attributes.getFontStyle()) { // derive font only if it is necessary - font = font.deriveFont(attributes.getFontStyle()); - } - final FontMetrics metrics = getFontMetrics(font); - final int curWidth = metrics.stringWidth(myFragments.get(i)); - if (x >= curX && x < curX + curWidth) { - return i; - } - curX += curWidth; - final Integer fixedWidth = myAligns.get(i); - if (fixedWidth != null && curX < fixedWidth.intValue()) { - curX = fixedWidth.intValue(); - } - } - return -1; - } - - protected void paintComponent(final Graphics g) { - try { - doPaint(g); - } - catch (RuntimeException e) { - LOG.error(logSwingPath(), e); - throw e; - } - } - - protected synchronized void doPaint(final Graphics g) { - checkCanPaint(g); - int xOffset = 0; - - // Paint icon and its background - final Icon icon = myIcon; // guard against concurrent modification (IDEADEV-12635) - if (icon != null) { - final Container parent = getParent(); - Color iconBackgroundColor = null; - if (isIconOpaque()) { - if (parent != null && !myFocusBorderAroundIcon && !UIUtil.isFullRowSelectionLAF()) { - iconBackgroundColor = parent.getBackground(); - } - else { - iconBackgroundColor = getBackground(); - } - } - - if (iconBackgroundColor != null) { - g.setColor(iconBackgroundColor); - g.fillRect(0, 0, icon.getIconWidth() + myIpad.left + myIconTextGap, getHeight()); - } - - icon.paintIcon(this, g, myIpad.left, (getHeight() - icon.getIconHeight()) / 2); - - xOffset += myIpad.left + icon.getIconWidth() + myIconTextGap; - } - - if (isOpaque()) { - // Paint text background - g.setColor(getBackground()); - g.fillRect(xOffset, 0, getWidth() - xOffset, getHeight()); - } - - // If there is no icon, then we have to add left internal padding - if (xOffset == 0) { - xOffset = myIpad.left; - } - - int textStart = xOffset; - xOffset += myBorder.getBorderInsets(this).left; - - final List searchMatches = new ArrayList(); - - // Paint text - UIUtil.applyRenderingHints(g); - for (int i = 0; i < myFragments.size(); i++) { - final SimpleTextAttributes attributes = myAttributes.get(i); - Font font = g.getFont(); - if (font.getStyle() != attributes.getFontStyle()) { // derive font only if it is necessary - font = font.deriveFont(attributes.getFontStyle()); - } - g.setFont(font); - final FontMetrics metrics = g.getFontMetrics(font); - - final String fragment = myFragments.get(i); - final int fragmentWidth = metrics.stringWidth(fragment); - - final Color bgColor = attributes.getBgColor(); - if (isOpaque() && bgColor != null) { - g.setColor(bgColor); - g.fillRect(xOffset, 0, fragmentWidth, getHeight()); - } - - Color color = attributes.getFgColor(); - if (color == null) { // in case if color is not defined we have to get foreground color from Swing hierarchy - color = getForeground(); - } - if (!isEnabled()) { - color = UIUtil.getInactiveTextColor(); - } - g.setColor(color); - - final int textBaseline = (getHeight() - metrics.getHeight()) / 2 + metrics.getAscent(); - - if (!attributes.isSearchMatch()) g.drawString(fragment, xOffset, textBaseline); - - // 1. Strikeout effect - if (attributes.isStrikeout()) { - final int strikeOutAt = textBaseline + (metrics.getDescent() - metrics.getAscent()) / 2; - UIUtil.drawLine(g, xOffset, strikeOutAt, xOffset + fragmentWidth, strikeOutAt); - } - // 2. Waved effect - if (attributes.isWaved()) { - if (attributes.getWaveColor() != null) { - g.setColor(attributes.getWaveColor()); - } - final int wavedAt = textBaseline + 1; - for (int x = xOffset; x <= xOffset + fragmentWidth; x += 4) { - UIUtil.drawLine(g, x, wavedAt, x + 2, wavedAt + 2); - UIUtil.drawLine(g, x + 3, wavedAt + 1, x + 4, wavedAt); - } - } - // 3. Underline - if (attributes.isUnderline()) { - final int underlineAt = textBaseline + 1; - UIUtil.drawLine(g, xOffset, underlineAt, xOffset + fragmentWidth, underlineAt); - } - // 4. Bold Dotted Line - if (attributes.isBoldDottedLine()) { - final int dottedAt = SystemInfo.isMac ? textBaseline : textBaseline + 1; - final Color lineColor = attributes.getWaveColor(); - UIUtil.drawBoldDottedLine((Graphics2D)g, xOffset, xOffset + fragmentWidth, dottedAt, bgColor, lineColor, isOpaque()); - } - - if (attributes.isSearchMatch()) { - searchMatches.add(new Object[] {xOffset, xOffset + fragmentWidth, textBaseline, fragment, g.getFont()}); - } - - final Integer fixedWidth = myAligns.get(i); - if (fixedWidth != null && fragmentWidth < fixedWidth.intValue()) { - //if (fixedWidth != null) { - xOffset += fixedWidth.intValue(); - } else { - xOffset += fragmentWidth; - } - } - - // Paint focus border around the text and icon (if necessary) - if (myPaintFocusBorder) { - if (myFocusBorderAroundIcon || icon == null) { - myBorder.paintBorder(this, g, 0, 0, getWidth(), getHeight()); - } - else { - myBorder.paintBorder(this, g, textStart, 0, getWidth() - textStart, getHeight()); - } - } - - // draw search matches after all - for (final Object[] info: searchMatches) { - UIUtil.drawSearchMatch((Graphics2D)g, (Integer) info[0], (Integer) info[1], getHeight()); - g.setFont((Font) info[4]); - g.setColor(new Color(50, 50, 50)); - g.drawString((String) info[3], (Integer) info[0], (Integer) info[2]); - } - } - - private static void checkCanPaint(Graphics g) { - if (UIUtil.isPrinting(g)) return; - - /* wtf?? - if (!isDisplayable()) { - LOG.assertTrue(false, logSwingPath()); - } - */ - final Application application = ApplicationManager.getApplication(); - if (application != null) { - application.assertIsDispatchThread(); - } - else if (!SwingUtilities.isEventDispatchThread()) { - throw new RuntimeException(Thread.currentThread().toString()); - } - } - - private String logSwingPath() { - //noinspection HardCodedStringLiteral - final StringBuilder buffer = new StringBuilder("Components hierarchy:\n"); - for (Container c = this; c != null; c = c.getParent()) { - buffer.append('\n'); - buffer.append(c.toString()); - } - return buffer.toString(); - } - - protected void setBorderInsets(Insets insets) { - myBorder.setInsets(insets); - - revalidateAndRepaint(); - } - - private static final class MyBorder implements Border { - private Insets myInsets; - - public MyBorder() { - myInsets = new Insets(1, 1, 1, 1); - } - - public void setInsets(final Insets insets) { - myInsets = insets; - } - - public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { - g.setColor(Color.BLACK); - UIUtil.drawDottedRectangle(g, x, y, x + width - 1, y + height - 1); - } - - public Insets getBorderInsets(final Component c) { - return myInsets; - } - - public boolean isBorderOpaque() { - return true; - } - } - - @Override - public String toString() { - StringBuffer result = new StringBuffer(); - for (String each : myFragments) { - result.append(each); - } - - return result.toString(); - } - - - public void change(@NotNull Runnable runnable, boolean autoInvalidate) { - boolean old = myAutoInvalidate; - myAutoInvalidate = autoInvalidate; - try { - runnable.run(); - } finally { - myAutoInvalidate = old; - } - } - - @Override - public AccessibleContext getAccessibleContext() { - return myContext; - } - - private static class MyAccessibleContext extends AccessibleContext { - @Override - public AccessibleRole getAccessibleRole() { - return AccessibleRole.AWT_COMPONENT; - } - - @Override - public AccessibleStateSet getAccessibleStateSet() { - return new AccessibleStateSet(); - } - - @Override - public int getAccessibleIndexInParent() { - return 0; - } - - @Override - public int getAccessibleChildrenCount() { - return 0; - } - - @Override - public Accessible getAccessibleChild(int i) { - return null; - } - - @Override - public Locale getLocale() throws IllegalComponentStateException { - return Locale.getDefault(); - } - } -} +/* + * 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.ui; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.accessibility.Accessible; +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; +import javax.accessibility.AccessibleStateSet; +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * This is high performance Swing component which represents an icon + * with a colored text. The text consists of fragments. Each + * text fragment has its own color (foreground) and font style. + * + * @author Vladimir Kondratyev + */ +public class SimpleColoredComponent extends JComponent implements Accessible { + private static final Logger LOG = Logger.getInstance("#com.intellij.ui.SimpleColoredComponent"); + + private final ArrayList myFragments; + private final ArrayList myAttributes; + private ArrayList myFragmentTags = null; + + /** + * Component's icon. It can be null. + */ + private Icon myIcon; + /** + * Internal padding + */ + private Insets myIpad; + /** + * Gap between icon and text. It is used only if icon is defined. + */ + private int myIconTextGap; + /** + * Defines whether the focus border around the text is painted or not. + * For example, text can have a border if the component represents a selected item + * in focused JList. + */ + private boolean myPaintFocusBorder; + /** + * Defines whether the focus border around the text extends to icon or not + */ + private boolean myFocusBorderAroundIcon; + /** + * This is the border around the text. For example, text can have a border + * if the component represents a selected item in a focused JList. + * Border can be null. + */ + private final MyBorder myBorder; + + private int myMainTextLastIndex = -1; + + private final Map myAligns; + + private boolean myIconOpaque = true; + + private boolean myAutoInvalidate = true; + + private AccessibleContext myContext = new MyAccessibleContext(); + + public SimpleColoredComponent() { + myFragments = new ArrayList(3); + myAttributes = new ArrayList(3); + myIpad = new Insets(1, 2, 1, 2); + myIconTextGap = 2; + myBorder = new MyBorder(); + myAligns = new HashMap(10); + setOpaque(true); + } + + public final void append(@NotNull String fragment) { + append(fragment, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + /** + * Appends string fragments to existing ones. Appended string + * will have specified attributes. + */ + public final void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes) { + append(fragment, attributes, myMainTextLastIndex < 0); + } + + /** + * Appends string fragments to existing ones. Appended string + * will have specified attributes. + */ + public void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes, boolean isMainText) { + synchronized (this) { + myFragments.add(fragment); + myAttributes.add(attributes); + if (isMainText) { + myMainTextLastIndex = myFragments.size() - 1; + } + } + revalidateAndRepaint(); + } + + private void revalidateAndRepaint() { + if (myAutoInvalidate) { + revalidate(); + } + + repaint(); + } + + public void append(@NotNull final String fragment, @NotNull final SimpleTextAttributes attributes, Object tag) { + synchronized (this) { + append(fragment, attributes); + if (myFragmentTags == null) { + myFragmentTags = new ArrayList(); + } + while(myFragmentTags.size() < myFragments.size()-1) { + myFragmentTags.add(null); + } + myFragmentTags.add(tag); + } + revalidateAndRepaint(); + } + + public synchronized void appendAlign(int alignWidth) { + final int alignIndex = myFragments.size()-1; + myAligns.put(alignIndex, alignWidth); + } + + /** + * Clear all special attributes of SimpleColoredComponent. + * They are icon, text fragments and their attributes, "paint focus border". + */ + public void clear() { + synchronized (this) { + myIcon = null; + myPaintFocusBorder = false; + myFragments.clear(); + myAttributes.clear(); + myFragmentTags = null; + myMainTextLastIndex = -1; + myAligns.clear(); + } + revalidateAndRepaint(); + } + + /** + * @return component's icon. This method returns null + * if there is no icon. + */ + public final Icon getIcon() { + return myIcon; + } + + /** + * Sets a new component icon + */ + public final void setIcon(final Icon icon) { + myIcon = icon; + revalidateAndRepaint(); + } + + /** + * @return "leave" (internal) internal paddings of the component + */ + public Insets getIpad() { + return myIpad; + } + + /** + * Sets specified internal paddings + */ + public void setIpad(final Insets ipad) { + myIpad = ipad; + + revalidateAndRepaint(); + } + + /** + * @return gap between icon and text + */ + public int getIconTextGap() { + return myIconTextGap; + } + + /** + * Sets a new gap between icon and text + * + * @throws java.lang.IllegalArgumentException + * if the iconTextGap + * has a negative value + */ + public void setIconTextGap(final int iconTextGap) { + if (iconTextGap < 0) { + throw new IllegalArgumentException("wrong iconTextGap: " + iconTextGap); + } + myIconTextGap = iconTextGap; + + revalidateAndRepaint(); + } + + /** + * Sets whether focus border is painted or not + */ + protected final void setPaintFocusBorder(final boolean paintFocusBorder) { + myPaintFocusBorder = paintFocusBorder; + + repaint(); + } + + /** + * Sets whether focus border extends to icon or not. If so then + * component also extends the selection. + */ + protected final void setFocusBorderAroundIcon(final boolean focusBorderAroundIcon) { + myFocusBorderAroundIcon = focusBorderAroundIcon; + + repaint(); + } + + public boolean isIconOpaque() { + return myIconOpaque; + } + + public void setIconOpaque(final boolean iconOpaque) { + myIconOpaque = iconOpaque; + + repaint(); + } + + public Dimension getPreferredSize() { + return computePreferredSize(false); + + } + + public synchronized Object getFragmentTag(int index) { + if (myFragmentTags != null && index < myFragmentTags.size()) { + return myFragmentTags.get(index); + } + return null; + } + + public final synchronized Dimension computePreferredSize(final boolean mainTextOnly) { + // Calculate width + int width = myIpad.left; + + if (myIcon != null) { + width += myIcon.getIconWidth() + myIconTextGap; + } + + final Insets borderInsets = myBorder.getBorderInsets(this); + width += borderInsets.left; + + Font font = getFont(); + if (font == null) { + font = UIUtil.getLabelFont(); + } + + LOG.assertTrue(font != null); + + for (int i = 0; i < myAttributes.size(); i++) { + SimpleTextAttributes attributes = myAttributes.get(i); + if (font.getStyle() != attributes.getFontStyle()) { // derive font only if it is necessary + font = font.deriveFont(attributes.getFontStyle()); + } + final FontMetrics metrics = getFontMetrics(font); + width += metrics.stringWidth(myFragments.get(i)); + + final Integer fixedWidth = myAligns.get(i); + if (fixedWidth != null && width < fixedWidth.intValue()) { + width = fixedWidth.intValue(); + } + + if (mainTextOnly && myMainTextLastIndex >= 0 && i == myMainTextLastIndex) break; + } + width += myIpad.right + borderInsets.right; + + // Calculate height + int height = myIpad.top + myIpad.bottom; + + final FontMetrics metrics = getFontMetrics(font); + int textHeight = metrics.getHeight(); + textHeight += borderInsets.top + borderInsets.bottom; + + if (myIcon != null) { + height += Math.max(myIcon.getIconHeight(), textHeight); + } + else { + height += textHeight; + } + + // Take into accound that the component itself can have a border + final Insets insets = getInsets(); + width += insets.left + insets.right; + height += insets.top + insets.bottom; + + return new Dimension(width, height); + } + + public int findFragmentAt(int x) { + int curX = myIpad.left; + if (myIcon != null) { + curX += myIcon.getIconWidth() + myIconTextGap; + } + + Font font = getFont(); + LOG.assertTrue(font != null); + + for (int i = 0; i < myAttributes.size(); i++) { + SimpleTextAttributes attributes = myAttributes.get(i); + if (font.getStyle() != attributes.getFontStyle()) { // derive font only if it is necessary + font = font.deriveFont(attributes.getFontStyle()); + } + final FontMetrics metrics = getFontMetrics(font); + final int curWidth = metrics.stringWidth(myFragments.get(i)); + if (x >= curX && x < curX + curWidth) { + return i; + } + curX += curWidth; + final Integer fixedWidth = myAligns.get(i); + if (fixedWidth != null && curX < fixedWidth.intValue()) { + curX = fixedWidth.intValue(); + } + } + return -1; + } + + protected void paintComponent(final Graphics g) { + try { + doPaint(g); + } + catch (RuntimeException e) { + LOG.error(logSwingPath(), e); + throw e; + } + } + + protected synchronized void doPaint(final Graphics g) { + checkCanPaint(g); + int xOffset = 0; + + // Paint icon and its background + final Icon icon = myIcon; // guard against concurrent modification (IDEADEV-12635) + if (icon != null) { + final Container parent = getParent(); + Color iconBackgroundColor = null; + if (isIconOpaque()) { + if (parent != null && !myFocusBorderAroundIcon && !UIUtil.isFullRowSelectionLAF()) { + iconBackgroundColor = parent.getBackground(); + } + else { + iconBackgroundColor = getBackground(); + } + } + + if (iconBackgroundColor != null) { + g.setColor(iconBackgroundColor); + g.fillRect(0, 0, icon.getIconWidth() + myIpad.left + myIconTextGap, getHeight()); + } + + icon.paintIcon(this, g, myIpad.left, (getHeight() - icon.getIconHeight()) / 2); + + xOffset += myIpad.left + icon.getIconWidth() + myIconTextGap; + } + + if (isOpaque()) { + // Paint text background + g.setColor(getBackground()); + g.fillRect(xOffset, 0, getWidth() - xOffset, getHeight()); + } + + // If there is no icon, then we have to add left internal padding + if (xOffset == 0) { + xOffset = myIpad.left; + } + + int textStart = xOffset; + xOffset += myBorder.getBorderInsets(this).left; + + final List searchMatches = new ArrayList(); + + // Paint text + UIUtil.applyRenderingHints(g); + for (int i = 0; i < myFragments.size(); i++) { + final SimpleTextAttributes attributes = myAttributes.get(i); + Font font = g.getFont(); + if (font.getStyle() != attributes.getFontStyle()) { // derive font only if it is necessary + font = font.deriveFont(attributes.getFontStyle()); + } + g.setFont(font); + final FontMetrics metrics = g.getFontMetrics(font); + + final String fragment = myFragments.get(i); + final int fragmentWidth = metrics.stringWidth(fragment); + + final Color bgColor = attributes.getBgColor(); + if (isOpaque() && bgColor != null) { + g.setColor(bgColor); + g.fillRect(xOffset, 0, fragmentWidth, getHeight()); + } + + Color color = attributes.getFgColor(); + if (color == null) { // in case if color is not defined we have to get foreground color from Swing hierarchy + color = getForeground(); + } + if (!isEnabled()) { + color = UIUtil.getInactiveTextColor(); + } + g.setColor(color); + + final int textBaseline = (getHeight() - metrics.getHeight()) / 2 + metrics.getAscent(); + + if (!attributes.isSearchMatch()) g.drawString(fragment, xOffset, textBaseline); + + // 1. Strikeout effect + if (attributes.isStrikeout()) { + final int strikeOutAt = textBaseline + (metrics.getDescent() - metrics.getAscent()) / 2; + UIUtil.drawLine(g, xOffset, strikeOutAt, xOffset + fragmentWidth, strikeOutAt); + } + // 2. Waved effect + if (attributes.isWaved()) { + if (attributes.getWaveColor() != null) { + g.setColor(attributes.getWaveColor()); + } + final int wavedAt = textBaseline + 1; + for (int x = xOffset; x <= xOffset + fragmentWidth; x += 4) { + UIUtil.drawLine(g, x, wavedAt, x + 2, wavedAt + 2); + UIUtil.drawLine(g, x + 3, wavedAt + 1, x + 4, wavedAt); + } + } + // 3. Underline + if (attributes.isUnderline()) { + final int underlineAt = textBaseline + 1; + UIUtil.drawLine(g, xOffset, underlineAt, xOffset + fragmentWidth, underlineAt); + } + // 4. Bold Dotted Line + if (attributes.isBoldDottedLine()) { + final int dottedAt = SystemInfo.isMac ? textBaseline : textBaseline + 1; + final Color lineColor = attributes.getWaveColor(); + UIUtil.drawBoldDottedLine((Graphics2D)g, xOffset, xOffset + fragmentWidth, dottedAt, bgColor, lineColor, isOpaque()); + } + + if (attributes.isSearchMatch()) { + searchMatches.add(new Object[] {xOffset, xOffset + fragmentWidth, textBaseline, fragment, g.getFont()}); + } + + final Integer fixedWidth = myAligns.get(i); + if (fixedWidth != null && fragmentWidth < fixedWidth.intValue()) { + //if (fixedWidth != null) { + xOffset += fixedWidth.intValue(); + } else { + xOffset += fragmentWidth; + } + } + + // Paint focus border around the text and icon (if necessary) + if (myPaintFocusBorder) { + if (myFocusBorderAroundIcon || icon == null) { + myBorder.paintBorder(this, g, 0, 0, getWidth(), getHeight()); + } + else { + myBorder.paintBorder(this, g, textStart, 0, getWidth() - textStart, getHeight()); + } + } + + // draw search matches after all + for (final Object[] info: searchMatches) { + UIUtil.drawSearchMatch((Graphics2D)g, (Integer) info[0], (Integer) info[1], getHeight()); + g.setFont((Font) info[4]); + g.setColor(new Color(50, 50, 50)); + g.drawString((String) info[3], (Integer) info[0], (Integer) info[2]); + } + } + + private static void checkCanPaint(Graphics g) { + if (UIUtil.isPrinting(g)) return; + + /* wtf?? + if (!isDisplayable()) { + LOG.assertTrue(false, logSwingPath()); + } + */ + final Application application = ApplicationManager.getApplication(); + if (application != null) { + application.assertIsDispatchThread(); + } + else if (!SwingUtilities.isEventDispatchThread()) { + throw new RuntimeException(Thread.currentThread().toString()); + } + } + + private String logSwingPath() { + //noinspection HardCodedStringLiteral + final StringBuilder buffer = new StringBuilder("Components hierarchy:\n"); + for (Container c = this; c != null; c = c.getParent()) { + buffer.append('\n'); + buffer.append(c.toString()); + } + return buffer.toString(); + } + + protected void setBorderInsets(Insets insets) { + myBorder.setInsets(insets); + + revalidateAndRepaint(); + } + + private static final class MyBorder implements Border { + private Insets myInsets; + + public MyBorder() { + myInsets = new Insets(1, 1, 1, 1); + } + + public void setInsets(final Insets insets) { + myInsets = insets; + } + + public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { + g.setColor(Color.BLACK); + UIUtil.drawDottedRectangle(g, x, y, x + width - 1, y + height - 1); + } + + public Insets getBorderInsets(final Component c) { + return myInsets; + } + + public boolean isBorderOpaque() { + return true; + } + } + + @Override + public String toString() { + StringBuffer result = new StringBuffer(); + for (String each : myFragments) { + result.append(each); + } + + return result.toString(); + } + + + public void change(@NotNull Runnable runnable, boolean autoInvalidate) { + boolean old = myAutoInvalidate; + myAutoInvalidate = autoInvalidate; + try { + runnable.run(); + } finally { + myAutoInvalidate = old; + } + } + + @Override + public AccessibleContext getAccessibleContext() { + return myContext; + } + + private static class MyAccessibleContext extends AccessibleContext { + @Override + public AccessibleRole getAccessibleRole() { + return AccessibleRole.AWT_COMPONENT; + } + + @Override + public AccessibleStateSet getAccessibleStateSet() { + return new AccessibleStateSet(); + } + + @Override + public int getAccessibleIndexInParent() { + return 0; + } + + @Override + public int getAccessibleChildrenCount() { + return 0; + } + + @Override + public Accessible getAccessibleChild(int i) { + return null; + } + + @Override + public Locale getLocale() throws IllegalComponentStateException { + return Locale.getDefault(); + } + } +} diff --git a/platform/platform-api/src/com/intellij/ui/SimpleTextAttributes.java b/platform/platform-api/src/com/intellij/ui/SimpleTextAttributes.java index 2a51b0ff6c39..a7d90c4922f0 100644 --- a/platform/platform-api/src/com/intellij/ui/SimpleTextAttributes.java +++ b/platform/platform-api/src/com/intellij/ui/SimpleTextAttributes.java @@ -1,218 +1,218 @@ -/* - * 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.ui; - -import com.intellij.openapi.editor.markup.EffectType; -import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; - -/** - * @author Vladimir Kondratyev - */ -@SuppressWarnings({"PointlessBitwiseExpression"}) -public final class SimpleTextAttributes { - public static final int STYLE_PLAIN = Font.PLAIN; - public static final int STYLE_BOLD = Font.BOLD; - public static final int STYLE_ITALIC = Font.ITALIC; - public static final int FONT_MASK = STYLE_PLAIN | STYLE_BOLD | STYLE_ITALIC; - public static final int STYLE_STRIKEOUT = STYLE_ITALIC << 1; - public static final int STYLE_WAVED = STYLE_STRIKEOUT << 1; - public static final int STYLE_UNDERLINE = STYLE_WAVED << 1; - public static final int STYLE_BOLD_DOTTED_LINE = STYLE_UNDERLINE << 1; - public static final int STYLE_SEARCH_MATCH = STYLE_BOLD_DOTTED_LINE << 1; - - public static final SimpleTextAttributes REGULAR_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, null); - public static final SimpleTextAttributes REGULAR_BOLD_ATTRIBUTES = new SimpleTextAttributes(STYLE_BOLD, null); - public static final SimpleTextAttributes REGULAR_ITALIC_ATTRIBUTES = new SimpleTextAttributes(STYLE_ITALIC, null); - public static final SimpleTextAttributes ERROR_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, Color.red); - - public static final SimpleTextAttributes GRAYED_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, UIUtil.getInactiveTextColor()); - public static final SimpleTextAttributes GRAYED_BOLD_ATTRIBUTES = new SimpleTextAttributes(STYLE_BOLD, UIUtil.getInactiveTextColor()); - - public static final SimpleTextAttributes SYNTHETIC_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, Color.BLUE); - public static final SimpleTextAttributes GRAY_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, Color.GRAY); - public static final SimpleTextAttributes GRAY_ITALIC_ATTRIBUTES = new SimpleTextAttributes(STYLE_ITALIC, Color.GRAY); - public static final SimpleTextAttributes DARK_TEXT = new SimpleTextAttributes(STYLE_PLAIN, new Color(112, 112, 164)); - public static final SimpleTextAttributes SIMPLE_CELL_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, UIUtil.getListForeground()); - public static final SimpleTextAttributes SELECTED_SIMPLE_CELL_ATTRIBUTES = - new SimpleTextAttributes(STYLE_PLAIN, UIUtil.getListSelectionForeground()); - public static final SimpleTextAttributes EXCLUDED_ATTRIBUTES = new SimpleTextAttributes(STYLE_ITALIC, Color.GRAY); - - public static final SimpleTextAttributes LINK_ATTRIBUTES = new SimpleTextAttributes(STYLE_UNDERLINE, Color.blue); - public static final SimpleTextAttributes LINK_BOLD_ATTRIBUTES = new SimpleTextAttributes(STYLE_UNDERLINE | STYLE_BOLD, Color.blue); - - private final Color myBgColor; - private final Color myFgColor; - private final Color myWaveColor; - private final int myStyle; - - /** - * @param style style of the text fragment. - * @param fgColor color of the text fragment. color can be - * null. In that case SimpleColoredComponent will - * use its foreground to paint the text fragment. - */ - public SimpleTextAttributes(int style, Color fgColor) { - this(style, fgColor, null); - } - - public SimpleTextAttributes(int style, Color fgColor, Color waveColor) { - this(null, fgColor, waveColor, style); - } - - public SimpleTextAttributes(final Color bgColor, final Color fgColor, final Color waveColor, final int style) { - if ((~(STYLE_PLAIN | - STYLE_BOLD | - STYLE_ITALIC | - STYLE_STRIKEOUT | - STYLE_WAVED | - STYLE_UNDERLINE | - STYLE_BOLD_DOTTED_LINE | - STYLE_SEARCH_MATCH) & style) != 0) { - throw new IllegalArgumentException("Wrong style: " + style); - } - - myBgColor = bgColor; - myFgColor = fgColor; - myWaveColor = waveColor; - myStyle = style; - } - - /** - * @return foreground color - */ - public Color getFgColor() { - return myFgColor; - } - - - /** - * @return background color - */ - public Color getBgColor() { - return myBgColor; - } - - /** - * @return wave color. The method can return null. null - * means that color of wave is the same as foreground color. - */ - public Color getWaveColor() { - return myWaveColor; - } - - public int getStyle() { - return myStyle; - } - - /** - * @return whether text is struck out or not - */ - public boolean isStrikeout() { - return (myStyle & STYLE_STRIKEOUT) != 0; - } - - /** - * @return whether text is waved or not - */ - public boolean isWaved() { - return (myStyle & STYLE_WAVED) != 0; - } - - public boolean isUnderline() { - return (myStyle & STYLE_UNDERLINE) != 0; - } - - public boolean isBoldDottedLine() { - return (myStyle & STYLE_BOLD_DOTTED_LINE) != 0; - } - - public boolean isSearchMatch() { - return (myStyle & STYLE_SEARCH_MATCH) != 0; - } - - public static SimpleTextAttributes fromTextAttributes(TextAttributes attributes) { - if (attributes == null) return REGULAR_ATTRIBUTES; - - Color foregroundColor = attributes.getForegroundColor(); - if (foregroundColor == null) foregroundColor = REGULAR_ATTRIBUTES.getFgColor(); - - int style = attributes.getFontType(); - if (attributes.getEffectColor() != null) { - EffectType effectType = attributes.getEffectType(); - if (effectType == EffectType.STRIKEOUT) { - style |= STYLE_STRIKEOUT; - } - else if (effectType == EffectType.WAVE_UNDERSCORE) { - style |= STYLE_WAVED; - } - else if (effectType == EffectType.LINE_UNDERSCORE || - effectType == EffectType.BOLD_LINE_UNDERSCORE || - effectType == EffectType.BOLD_DOTTED_LINE) { - style |= STYLE_UNDERLINE; - } - else if (effectType == EffectType.SEARCH_MATCH) { - style |= STYLE_SEARCH_MATCH; - } - else { - // not supported - } - } - return new SimpleTextAttributes(attributes.getBackgroundColor(), foregroundColor, attributes.getEffectColor(), style); - } - - public int getFontStyle() { - return myStyle & FONT_MASK; - } - - public TextAttributes toTextAttributes() { - Color effectColor; - EffectType effectType; - if (isWaved()) { - effectColor = myWaveColor; - effectType = EffectType.WAVE_UNDERSCORE; - } - else if (isStrikeout()) { - effectColor = myWaveColor; - effectType = EffectType.STRIKEOUT; - } - else if (isUnderline()) { - effectColor = myWaveColor; - effectType = EffectType.LINE_UNDERSCORE; - } - else if (isBoldDottedLine()) { - effectColor = myWaveColor; - effectType = EffectType.BOLD_DOTTED_LINE; - } - else if (isSearchMatch()) { - effectColor = myWaveColor; - effectType = EffectType.SEARCH_MATCH; - } - else { - effectColor = null; - effectType = null; - } - return new TextAttributes(myFgColor, null, effectColor, effectType, myStyle & FONT_MASK); - } - - public SimpleTextAttributes derive(int style, @Nullable Color fg, @Nullable Color bg, @Nullable Color wave) { - return new SimpleTextAttributes(bg != null ? bg : getBgColor(), fg != null ? fg : getFgColor(), wave != null ? wave : getWaveColor(), - style == -1 ? getStyle() : style); - } -} +/* + * 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.ui; + +import com.intellij.openapi.editor.markup.EffectType; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +/** + * @author Vladimir Kondratyev + */ +@SuppressWarnings({"PointlessBitwiseExpression"}) +public final class SimpleTextAttributes { + public static final int STYLE_PLAIN = Font.PLAIN; + public static final int STYLE_BOLD = Font.BOLD; + public static final int STYLE_ITALIC = Font.ITALIC; + public static final int FONT_MASK = STYLE_PLAIN | STYLE_BOLD | STYLE_ITALIC; + public static final int STYLE_STRIKEOUT = STYLE_ITALIC << 1; + public static final int STYLE_WAVED = STYLE_STRIKEOUT << 1; + public static final int STYLE_UNDERLINE = STYLE_WAVED << 1; + public static final int STYLE_BOLD_DOTTED_LINE = STYLE_UNDERLINE << 1; + public static final int STYLE_SEARCH_MATCH = STYLE_BOLD_DOTTED_LINE << 1; + + public static final SimpleTextAttributes REGULAR_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, null); + public static final SimpleTextAttributes REGULAR_BOLD_ATTRIBUTES = new SimpleTextAttributes(STYLE_BOLD, null); + public static final SimpleTextAttributes REGULAR_ITALIC_ATTRIBUTES = new SimpleTextAttributes(STYLE_ITALIC, null); + public static final SimpleTextAttributes ERROR_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, Color.red); + + public static final SimpleTextAttributes GRAYED_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, UIUtil.getInactiveTextColor()); + public static final SimpleTextAttributes GRAYED_BOLD_ATTRIBUTES = new SimpleTextAttributes(STYLE_BOLD, UIUtil.getInactiveTextColor()); + + public static final SimpleTextAttributes SYNTHETIC_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, Color.BLUE); + public static final SimpleTextAttributes GRAY_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, Color.GRAY); + public static final SimpleTextAttributes GRAY_ITALIC_ATTRIBUTES = new SimpleTextAttributes(STYLE_ITALIC, Color.GRAY); + public static final SimpleTextAttributes DARK_TEXT = new SimpleTextAttributes(STYLE_PLAIN, new Color(112, 112, 164)); + public static final SimpleTextAttributes SIMPLE_CELL_ATTRIBUTES = new SimpleTextAttributes(STYLE_PLAIN, UIUtil.getListForeground()); + public static final SimpleTextAttributes SELECTED_SIMPLE_CELL_ATTRIBUTES = + new SimpleTextAttributes(STYLE_PLAIN, UIUtil.getListSelectionForeground()); + public static final SimpleTextAttributes EXCLUDED_ATTRIBUTES = new SimpleTextAttributes(STYLE_ITALIC, Color.GRAY); + + public static final SimpleTextAttributes LINK_ATTRIBUTES = new SimpleTextAttributes(STYLE_UNDERLINE, Color.blue); + public static final SimpleTextAttributes LINK_BOLD_ATTRIBUTES = new SimpleTextAttributes(STYLE_UNDERLINE | STYLE_BOLD, Color.blue); + + private final Color myBgColor; + private final Color myFgColor; + private final Color myWaveColor; + private final int myStyle; + + /** + * @param style style of the text fragment. + * @param fgColor color of the text fragment. color can be + * null. In that case SimpleColoredComponent will + * use its foreground to paint the text fragment. + */ + public SimpleTextAttributes(int style, Color fgColor) { + this(style, fgColor, null); + } + + public SimpleTextAttributes(int style, Color fgColor, Color waveColor) { + this(null, fgColor, waveColor, style); + } + + public SimpleTextAttributes(final Color bgColor, final Color fgColor, final Color waveColor, final int style) { + if ((~(STYLE_PLAIN | + STYLE_BOLD | + STYLE_ITALIC | + STYLE_STRIKEOUT | + STYLE_WAVED | + STYLE_UNDERLINE | + STYLE_BOLD_DOTTED_LINE | + STYLE_SEARCH_MATCH) & style) != 0) { + throw new IllegalArgumentException("Wrong style: " + style); + } + + myBgColor = bgColor; + myFgColor = fgColor; + myWaveColor = waveColor; + myStyle = style; + } + + /** + * @return foreground color + */ + public Color getFgColor() { + return myFgColor; + } + + + /** + * @return background color + */ + public Color getBgColor() { + return myBgColor; + } + + /** + * @return wave color. The method can return null. null + * means that color of wave is the same as foreground color. + */ + public Color getWaveColor() { + return myWaveColor; + } + + public int getStyle() { + return myStyle; + } + + /** + * @return whether text is struck out or not + */ + public boolean isStrikeout() { + return (myStyle & STYLE_STRIKEOUT) != 0; + } + + /** + * @return whether text is waved or not + */ + public boolean isWaved() { + return (myStyle & STYLE_WAVED) != 0; + } + + public boolean isUnderline() { + return (myStyle & STYLE_UNDERLINE) != 0; + } + + public boolean isBoldDottedLine() { + return (myStyle & STYLE_BOLD_DOTTED_LINE) != 0; + } + + public boolean isSearchMatch() { + return (myStyle & STYLE_SEARCH_MATCH) != 0; + } + + public static SimpleTextAttributes fromTextAttributes(TextAttributes attributes) { + if (attributes == null) return REGULAR_ATTRIBUTES; + + Color foregroundColor = attributes.getForegroundColor(); + if (foregroundColor == null) foregroundColor = REGULAR_ATTRIBUTES.getFgColor(); + + int style = attributes.getFontType(); + if (attributes.getEffectColor() != null) { + EffectType effectType = attributes.getEffectType(); + if (effectType == EffectType.STRIKEOUT) { + style |= STYLE_STRIKEOUT; + } + else if (effectType == EffectType.WAVE_UNDERSCORE) { + style |= STYLE_WAVED; + } + else if (effectType == EffectType.LINE_UNDERSCORE || + effectType == EffectType.BOLD_LINE_UNDERSCORE || + effectType == EffectType.BOLD_DOTTED_LINE) { + style |= STYLE_UNDERLINE; + } + else if (effectType == EffectType.SEARCH_MATCH) { + style |= STYLE_SEARCH_MATCH; + } + else { + // not supported + } + } + return new SimpleTextAttributes(attributes.getBackgroundColor(), foregroundColor, attributes.getEffectColor(), style); + } + + public int getFontStyle() { + return myStyle & FONT_MASK; + } + + public TextAttributes toTextAttributes() { + Color effectColor; + EffectType effectType; + if (isWaved()) { + effectColor = myWaveColor; + effectType = EffectType.WAVE_UNDERSCORE; + } + else if (isStrikeout()) { + effectColor = myWaveColor; + effectType = EffectType.STRIKEOUT; + } + else if (isUnderline()) { + effectColor = myWaveColor; + effectType = EffectType.LINE_UNDERSCORE; + } + else if (isBoldDottedLine()) { + effectColor = myWaveColor; + effectType = EffectType.BOLD_DOTTED_LINE; + } + else if (isSearchMatch()) { + effectColor = myWaveColor; + effectType = EffectType.SEARCH_MATCH; + } + else { + effectColor = null; + effectType = null; + } + return new TextAttributes(myFgColor, null, effectColor, effectType, myStyle & FONT_MASK); + } + + public SimpleTextAttributes derive(int style, @Nullable Color fg, @Nullable Color bg, @Nullable Color wave) { + return new SimpleTextAttributes(bg != null ? bg : getBgColor(), fg != null ? fg : getFgColor(), wave != null ? wave : getWaveColor(), + style == -1 ? getStyle() : style); + } +} diff --git a/platform/platform-api/src/com/intellij/ui/UI.java b/platform/platform-api/src/com/intellij/ui/UI.java index 44daa5b55447..5c73cfaef3bd 100644 --- a/platform/platform-api/src/com/intellij/ui/UI.java +++ b/platform/platform-api/src/com/intellij/ui/UI.java @@ -1,77 +1,77 @@ -/* - * 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.ui; - -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import java.awt.*; -import java.util.HashMap; -import java.util.Map; - -/** - * @author max - */ -@SuppressWarnings({"HardCodedStringLiteral"}) -public class UI { - private static final Map ourColors = new HashMap(); - - private UI() { - } - - static { - ourColors.put("panel.border.color", new Color(102, 101, 84)); - ourColors.put("panel.separator.color", new Color(180, 179, 169)); - - ourColors.put("panel.custom.background", new Color(250, 249, 245)); - - ourColors.put("link.foreground", new Color(82, 99, 155)); - ourColors.put("link.pressed.foreground", new Color(240, 0, 0)); - ourColors.put("link.visited.foreground", new Color(128, 0, 128)); - - ourColors.put("bar.separator.foreground", getColor("panel.separator.color")); - ourColors.put("bar.selected.separator.foreground", new Color(232, 231, 228)); - ourColors.put("bar.background", UIUtil.getPanelBackground()); - ourColors.put("bar.hover.background", UIUtil.getTreeSelectionBackground()); - ourColors.put("bar.selected.background", getColor("panel.custom.background")); - ourColors.put("bar.hover.frame.foreground", UIUtil.getTreeSelectionBackground().darker()); - - ourColors.put("popup.selected.background", UIUtil.getTreeSelectionBackground()); - ourColors.put("popup.selected.frame.foreground", UIUtil.getTreeSelectionBackground().darker()); - ourColors.put("popup.separator.foreground", Color.gray); - - ourColors.put("callout.background", Color.white); - ourColors.put("callout.frame.color", Color.red); - - ourColors.put("underline.error", Color.red); - ourColors.put("underline.warning", Color.yellow); - - ourColors.put("tooltip.error", Color.red); - ourColors.put("tooltip.warning", Color.yellow.darker()); - - ourColors.put("toolbar.background", UIUtil.getPanelBackground()); - ourColors.put("toolbar.hover.background", UIUtil.getTreeSelectionBackground()); - ourColors.put("toolbar.selected.background", getColor("panel.custom.background")); - ourColors.put("toolbar.hover.frame.foreground", UIUtil.getTreeSelectionBackground().darker()); - - ourColors.put("speedsearch.background", new Color(244, 249, 181)); - ourColors.put("speedsearch.foreground", Color.black); - } - - public static Color getColor(@NonNls String id) { - return ourColors.get(id); - } -} +/* + * 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.ui; + +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +/** + * @author max + */ +@SuppressWarnings({"HardCodedStringLiteral"}) +public class UI { + private static final Map ourColors = new HashMap(); + + private UI() { + } + + static { + ourColors.put("panel.border.color", new Color(102, 101, 84)); + ourColors.put("panel.separator.color", new Color(180, 179, 169)); + + ourColors.put("panel.custom.background", new Color(250, 249, 245)); + + ourColors.put("link.foreground", new Color(82, 99, 155)); + ourColors.put("link.pressed.foreground", new Color(240, 0, 0)); + ourColors.put("link.visited.foreground", new Color(128, 0, 128)); + + ourColors.put("bar.separator.foreground", getColor("panel.separator.color")); + ourColors.put("bar.selected.separator.foreground", new Color(232, 231, 228)); + ourColors.put("bar.background", UIUtil.getPanelBackground()); + ourColors.put("bar.hover.background", UIUtil.getTreeSelectionBackground()); + ourColors.put("bar.selected.background", getColor("panel.custom.background")); + ourColors.put("bar.hover.frame.foreground", UIUtil.getTreeSelectionBackground().darker()); + + ourColors.put("popup.selected.background", UIUtil.getTreeSelectionBackground()); + ourColors.put("popup.selected.frame.foreground", UIUtil.getTreeSelectionBackground().darker()); + ourColors.put("popup.separator.foreground", Color.gray); + + ourColors.put("callout.background", Color.white); + ourColors.put("callout.frame.color", Color.red); + + ourColors.put("underline.error", Color.red); + ourColors.put("underline.warning", Color.yellow); + + ourColors.put("tooltip.error", Color.red); + ourColors.put("tooltip.warning", Color.yellow.darker()); + + ourColors.put("toolbar.background", UIUtil.getPanelBackground()); + ourColors.put("toolbar.hover.background", UIUtil.getTreeSelectionBackground()); + ourColors.put("toolbar.selected.background", getColor("panel.custom.background")); + ourColors.put("toolbar.hover.frame.foreground", UIUtil.getTreeSelectionBackground().darker()); + + ourColors.put("speedsearch.background", new Color(244, 249, 181)); + ourColors.put("speedsearch.foreground", Color.black); + } + + public static Color getColor(@NonNls String id) { + return ourColors.get(id); + } +} diff --git a/platform/platform-api/src/com/intellij/ui/components/panels/ValidatingComponent.java b/platform/platform-api/src/com/intellij/ui/components/panels/ValidatingComponent.java index 806bf495698b..33ba62c1dc6f 100644 --- a/platform/platform-api/src/com/intellij/ui/components/panels/ValidatingComponent.java +++ b/platform/platform-api/src/com/intellij/ui/components/panels/ValidatingComponent.java @@ -1,88 +1,88 @@ -/* - * 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.ui.components.panels; - -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; - -/** - * @author kir - * - * A label with possible error text is placed under validated component. - */ -public abstract class ValidatingComponent extends NonOpaquePanel { - private static final Font ERROR_FONT = UIUtil.getLabelFont().deriveFont(Font.PLAIN, 10f); - - private JLabel myErrorLabel; - private T myMainComponent; - private JLabel myLabel; - - protected ValidatingComponent() { - setLayout(new BorderLayout()); - } - - public final void doInitialize() { - myErrorLabel = createErrorLabel(); - myMainComponent = createMainComponent(); - - add(myMainComponent, BorderLayout.CENTER); - add(myErrorLabel, BorderLayout.SOUTH); - } - - protected abstract T createMainComponent(); - - public void setErrorText(String errorText) { - if ("".equals(errorText) || errorText == null) { - errorText = " "; - } - myErrorLabel.setText(errorText); - } - - public JLabel getErrorLabel() { - return myErrorLabel; - } - - public T getMainComponent() { - return myMainComponent; - } - - public String getErrorText() { - final String text = myErrorLabel.getText(); - return " ".equals(text) ? "" : text; - } - - public void setLabel(JLabel label) { - myLabel = label; - myLabel.setLabelFor(myMainComponent); - add(label, BorderLayout.WEST); - } - - protected JLabel createErrorLabel() { - final JLabel label = new JLabel(" "); - label.setForeground(Color.red); - label.setFont(ERROR_FONT); - return label; - } - - public void doLayout() { - super.doLayout(); - if (myLabel != null) { - myErrorLabel.setBorder(BorderFactory.createEmptyBorder(0, myLabel.getSize().width, 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.ui.components.panels; + +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; + +/** + * @author kir + * + * A label with possible error text is placed under validated component. + */ +public abstract class ValidatingComponent extends NonOpaquePanel { + private static final Font ERROR_FONT = UIUtil.getLabelFont().deriveFont(Font.PLAIN, 10f); + + private JLabel myErrorLabel; + private T myMainComponent; + private JLabel myLabel; + + protected ValidatingComponent() { + setLayout(new BorderLayout()); + } + + public final void doInitialize() { + myErrorLabel = createErrorLabel(); + myMainComponent = createMainComponent(); + + add(myMainComponent, BorderLayout.CENTER); + add(myErrorLabel, BorderLayout.SOUTH); + } + + protected abstract T createMainComponent(); + + public void setErrorText(String errorText) { + if ("".equals(errorText) || errorText == null) { + errorText = " "; + } + myErrorLabel.setText(errorText); + } + + public JLabel getErrorLabel() { + return myErrorLabel; + } + + public T getMainComponent() { + return myMainComponent; + } + + public String getErrorText() { + final String text = myErrorLabel.getText(); + return " ".equals(text) ? "" : text; + } + + public void setLabel(JLabel label) { + myLabel = label; + myLabel.setLabelFor(myMainComponent); + add(label, BorderLayout.WEST); + } + + protected JLabel createErrorLabel() { + final JLabel label = new JLabel(" "); + label.setForeground(Color.red); + label.setFont(ERROR_FONT); + return label; + } + + public void doLayout() { + super.doLayout(); + if (myLabel != null) { + myErrorLabel.setBorder(BorderFactory.createEmptyBorder(0, myLabel.getSize().width, 0, 0)); + } + } +} diff --git a/platform/platform-api/src/com/intellij/ui/docking/DockManager.java b/platform/platform-api/src/com/intellij/ui/docking/DockManager.java index 85ca729b9946..cf417b5fc203 100644 --- a/platform/platform-api/src/com/intellij/ui/docking/DockManager.java +++ b/platform/platform-api/src/com/intellij/ui/docking/DockManager.java @@ -1,52 +1,52 @@ -/* - * 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.ui.docking; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.wm.IdeFrame; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; -import java.awt.event.MouseEvent; -import java.util.Set; - -public abstract class DockManager implements ProjectComponent { - - public abstract void register(DockContainer container); - public abstract void register(String id, DockContainerFactory factory); - - public static DockManager getInstance(Project project) { - return project.getComponent(DockManager.class); - } - - public abstract DragSession createDragSession(MouseEvent mouseEvent, DockableContent content); - - public abstract Set getContainers(); - - public abstract IdeFrame getIdeFrame(DockContainer container); - - public abstract String getDimensionKeyForFocus(@NotNull String key); - - @Nullable - public abstract DockContainer getContainerFor(Component c); -} +/* + * 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.ui.docking; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.wm.IdeFrame; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.Set; + +public abstract class DockManager implements ProjectComponent { + + public abstract void register(DockContainer container); + public abstract void register(String id, DockContainerFactory factory); + + public static DockManager getInstance(Project project) { + return project.getComponent(DockManager.class); + } + + public abstract DragSession createDragSession(MouseEvent mouseEvent, DockableContent content); + + public abstract Set getContainers(); + + public abstract IdeFrame getIdeFrame(DockContainer container); + + public abstract String getDimensionKeyForFocus(@NotNull String key); + + @Nullable + public abstract DockContainer getContainerFor(Component c); +} diff --git a/platform/platform-api/src/com/intellij/ui/table/JBTable.java b/platform/platform-api/src/com/intellij/ui/table/JBTable.java index 17b7950ec3a1..ad5dd5b426c1 100644 --- a/platform/platform-api/src/com/intellij/ui/table/JBTable.java +++ b/platform/platform-api/src/com/intellij/ui/table/JBTable.java @@ -1,437 +1,437 @@ -/* - * 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.ui.table; - -import com.intellij.Patches; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.ui.ComponentWithExpandableItems; -import com.intellij.ui.ExpandableItemsHandler; -import com.intellij.ui.ExpandableItemsHandlerFactory; -import com.intellij.ui.TableCell; -import com.intellij.util.ui.*; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.TableColumnModelEvent; -import javax.swing.event.TableColumnModelListener; -import javax.swing.table.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Arrays; -import java.util.Comparator; -import java.util.EventObject; - -public class JBTable extends JTable implements ComponentWithEmptyText, ComponentWithExpandableItems { - private StatusText myEmptyText; - private ExpandableItemsHandler myExpandableItemsHandler; - - private MyCellEditorRemover myEditorRemover; - private boolean myEnableAntialiasing; - - public JBTable() { - this(new DefaultTableModel()); - } - - public JBTable(final TableModel model) { - super(model); - myEmptyText = new StatusText(this) { - @Override - protected boolean isStatusVisible() { - return JBTable.this.isEmpty(); - } - }; - - myExpandableItemsHandler = ExpandableItemsHandlerFactory.install(this); - - setFillsViewportHeight(true); - - addMouseListener(new MyMouseListener()); - getColumnModel().addColumnModelListener(new TableColumnModelListener() { - public void columnMarginChanged(ChangeEvent e) { - if (cellEditor != null) { - cellEditor.stopCellEditing(); - } - } - - public void columnSelectionChanged(ListSelectionEvent e) { - } - - public void columnAdded(TableColumnModelEvent e) { - } - - public void columnMoved(TableColumnModelEvent e) { - } - - public void columnRemoved(TableColumnModelEvent e) { - } - }); - - //noinspection UnusedDeclaration - boolean marker = Patches.SUN_BUG_ID_4503845; // Don't remove. It's a marker for find usages - } - - @Override - protected JTableHeader createDefaultTableHeader() { - return new JTableHeader(columnModel) { - @Override - public void paint(Graphics g) { - if (myEnableAntialiasing) { - UISettings.setupAntialiasing(g); - } - super.paint(g); - } - }; - } - - public boolean isEmpty() { - return getRowCount() == 0; - } - - @Override - public void setModel(final TableModel model) { - super.setModel(model); - - if (model instanceof SortableColumnModel) { - final SortableColumnModel sortableModel = (SortableColumnModel)model; - if (sortableModel.isSortable()) { - final TableRowSorter rowSorter = createRowSorter(model); - rowSorter.setSortsOnUpdates(isSortOnUpdates()); - setRowSorter(rowSorter); - final RowSorter.SortKey sortKey = sortableModel.getDefaultSortKey(); - if (sortKey != null && sortKey.getColumn() >= 0 && sortKey.getColumn() < model.getColumnCount()) { - if (sortableModel.getColumnInfos()[sortKey.getColumn()].isSortable()) { - rowSorter.setSortKeys(Arrays.asList(sortKey)); - } - } - } - else { - final RowSorter rowSorter = getRowSorter(); - if (rowSorter instanceof DefaultColumnInfoBasedRowSorter) { - setRowSorter(null); - } - } - } - } - - protected boolean isSortOnUpdates() { - return true; - } - - @Override - protected void paintComponent(Graphics g) { - if (myEnableAntialiasing) { - UISettings.setupAntialiasing(g); - } - super.paintComponent(g); - myEmptyText.paint(this, g); - } - - @Override - protected void paintChildren(Graphics g) { - if (myEnableAntialiasing) { - UISettings.setupAntialiasing(g); - } - super.paintChildren(g); - } - - public void setEnableAntialiasing(boolean flag) { - myEnableAntialiasing = flag; - } - - public static DefaultCellEditor createBooleanEditor() { - return new DefaultCellEditor(new JCheckBox()) { - { - ((JCheckBox)getComponent()).setHorizontalAlignment(JCheckBox.CENTER); - } - - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { - Component component = super.getTableCellEditorComponent(table, value, isSelected, row, column); - component.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - return component; - } - }; - } - - public void resetDefaultFocusTraversalKeys() { - KeyboardFocusManager m = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - for (Integer each : Arrays.asList(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, - KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS)) { - setFocusTraversalKeys(each, m.getDefaultFocusTraversalKeys(each)); - } - } - - @Override - public StatusText getEmptyText() { - return myEmptyText; - } - - @Override - @NotNull - public ExpandableItemsHandler getExpandableItemsHandler() { - return myExpandableItemsHandler; - } - - public void removeNotify() { - final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - //noinspection HardCodedStringLiteral - keyboardFocusManager.removePropertyChangeListener("permanentFocusOwner", myEditorRemover); - //noinspection HardCodedStringLiteral - keyboardFocusManager.removePropertyChangeListener("focusOwner", myEditorRemover); - super.removeNotify(); - } - - public boolean editCellAt(final int row, final int column, final EventObject e) { - if (cellEditor != null && !cellEditor.stopCellEditing()) { - return false; - } - - if (row < 0 || row >= getRowCount() || column < 0 || column >= getColumnCount()) { - return false; - } - - if (!isCellEditable(row, column)) { - return false; - } - - if (myEditorRemover == null) { - final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - myEditorRemover = new MyCellEditorRemover(); - //noinspection HardCodedStringLiteral - keyboardFocusManager.addPropertyChangeListener("focusOwner", myEditorRemover); - //noinspection HardCodedStringLiteral - keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", myEditorRemover); - } - - final TableCellEditor editor = getCellEditor(row, column); - if (editor != null && editor.isCellEditable(e)) { - editorComp = prepareEditor(editor, row, column); - if (editorComp == null) { - removeEditor(); - return false; - } - editorComp.setBounds(getCellRect(row, column, false)); - add(editorComp); - editorComp.validate(); - - editorComp.requestFocusInWindow(); - - setCellEditor(editor); - setEditingRow(row); - setEditingColumn(column); - editor.addCellEditorListener(this); - - return true; - } - return false; - } - - @Override - public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { - final Component result = super.prepareRenderer(renderer, row, column); - final boolean selected = myExpandableItemsHandler.getExpandedItems().contains(new TableCell(row, column)); - // Fix GTK background - if (UIUtil.isUnderGTKLookAndFeel()){ - final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground(); - UIUtil.changeBackGround(this, background); - } - - if (!selected) return result; - - return new JComponent() { - { - add(result); - setOpaque(false); - setLayout(new AbstractLayoutManager() { - @Override - public Dimension preferredLayoutSize(Container parent) { - return result.getPreferredSize(); - } - - @Override - public void layoutContainer(Container parent) { - Dimension size = parent.getSize(); - Insets i = parent.getInsets(); - Dimension pref = result.getPreferredSize(); - result.setBounds(i.left, i.top, Math.max(pref.width, size.width - i.left - i.right), size.height - i.top - i.bottom); - } - }); - } - }; - } - - private final class MyCellEditorRemover implements PropertyChangeListener { - private final IdeFocusManager myFocusManager; - - public MyCellEditorRemover() { - myFocusManager = IdeFocusManager.findInstanceByComponent(JBTable.this); - } - - public void propertyChange(final PropertyChangeEvent e) { - if (!isEditing()) { - return; - } - - myFocusManager.doWhenFocusSettlesDown(new Runnable() { - @Override - public void run() { - if (!isEditing()) { - return; - } - Component c = myFocusManager.getFocusOwner(); - while (c != null) { - if (c instanceof JPopupMenu) { - c = ((JPopupMenu)c).getInvoker(); - } - if (c == JBTable.this) { - // focus remains inside the table - return; - } - else if (c instanceof Window) { - if (c == SwingUtilities.getWindowAncestor(JBTable.this)) { - getCellEditor().stopCellEditing(); - } - break; - } - c = c.getParent(); - } - } - }); - } - } - - private final class MyMouseListener extends MouseAdapter { - public void mousePressed(final MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - final int[] selectedRows = getSelectedRows(); - if (selectedRows.length < 2) { - final int row = rowAtPoint(e.getPoint()); - if (row != -1) { - getSelectionModel().setSelectionInterval(row, row); - } - } - } - } - } - - @SuppressWarnings({"MethodMayBeStatic", "unchecked"}) - protected TableRowSorter createRowSorter(final TableModel model) { - return new DefaultColumnInfoBasedRowSorter(model); - } - - protected static class DefaultColumnInfoBasedRowSorter extends TableRowSorter { - public DefaultColumnInfoBasedRowSorter(final TableModel model) { - super(model); - setModelWrapper(new TableRowSorterModelWrapper(model)); - setMaxSortKeys(1); - } - - @Override - public Comparator getComparator(final int column) { - final TableModel model = getModel(); - if (model instanceof SortableColumnModel) { - final ColumnInfo[] columnInfos = ((SortableColumnModel)model).getColumnInfos(); - if (column >= 0 && column < columnInfos.length) { - final Comparator comparator = columnInfos[column].getComparator(); - if (comparator != null) return comparator; - } - } - - return super.getComparator(column); - } - - protected boolean useToString(int column) { - return false; - } - - @Override - public boolean isSortable(final int column) { - final TableModel model = getModel(); - if (model instanceof SortableColumnModel) { - final ColumnInfo[] columnInfos = ((SortableColumnModel)model).getColumnInfos(); - if (column >= 0 && column < columnInfos.length) { - return columnInfos[column].isSortable() && columnInfos[column].getComparator() != null; - } - } - - return false; - } - - private class TableRowSorterModelWrapper extends ModelWrapper { - private TableModel myModel; - - private TableRowSorterModelWrapper(@NotNull final TableModel model) { - myModel = model; - } - - public TableModel getModel() { - return myModel; - } - - public int getColumnCount() { - return (myModel == null) ? 0 : myModel.getColumnCount(); - } - - public int getRowCount() { - return (myModel == null) ? 0 : myModel.getRowCount(); - } - - public Object getValueAt(int row, int column) { - if (myModel instanceof SortableColumnModel) { - return ((SortableColumnModel)myModel).getRowValue(row); - } - - return myModel.getValueAt(row, column); - } - - public String getStringValueAt(int row, int column) { - TableStringConverter converter = getStringConverter(); - if (converter != null) { - // Use the converter - String value = converter.toString( - myModel, row, column); - if (value != null) { - return value; - } - return ""; - } - - // No converter, use getValueAt followed by toString - Object o = getValueAt(row, column); - if (o == null) { - return ""; - } - String string = o.toString(); - if (string == null) { - return ""; - } - return string; - } - - public Integer getIdentifier(int index) { - return index; - } - } - } +/* + * 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.ui.table; + +import com.intellij.Patches; +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.ui.ComponentWithExpandableItems; +import com.intellij.ui.ExpandableItemsHandler; +import com.intellij.ui.ExpandableItemsHandlerFactory; +import com.intellij.ui.TableCell; +import com.intellij.util.ui.*; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.TableColumnModelEvent; +import javax.swing.event.TableColumnModelListener; +import javax.swing.table.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EventObject; + +public class JBTable extends JTable implements ComponentWithEmptyText, ComponentWithExpandableItems { + private StatusText myEmptyText; + private ExpandableItemsHandler myExpandableItemsHandler; + + private MyCellEditorRemover myEditorRemover; + private boolean myEnableAntialiasing; + + public JBTable() { + this(new DefaultTableModel()); + } + + public JBTable(final TableModel model) { + super(model); + myEmptyText = new StatusText(this) { + @Override + protected boolean isStatusVisible() { + return JBTable.this.isEmpty(); + } + }; + + myExpandableItemsHandler = ExpandableItemsHandlerFactory.install(this); + + setFillsViewportHeight(true); + + addMouseListener(new MyMouseListener()); + getColumnModel().addColumnModelListener(new TableColumnModelListener() { + public void columnMarginChanged(ChangeEvent e) { + if (cellEditor != null) { + cellEditor.stopCellEditing(); + } + } + + public void columnSelectionChanged(ListSelectionEvent e) { + } + + public void columnAdded(TableColumnModelEvent e) { + } + + public void columnMoved(TableColumnModelEvent e) { + } + + public void columnRemoved(TableColumnModelEvent e) { + } + }); + + //noinspection UnusedDeclaration + boolean marker = Patches.SUN_BUG_ID_4503845; // Don't remove. It's a marker for find usages + } + + @Override + protected JTableHeader createDefaultTableHeader() { + return new JTableHeader(columnModel) { + @Override + public void paint(Graphics g) { + if (myEnableAntialiasing) { + UISettings.setupAntialiasing(g); + } + super.paint(g); + } + }; + } + + public boolean isEmpty() { + return getRowCount() == 0; + } + + @Override + public void setModel(final TableModel model) { + super.setModel(model); + + if (model instanceof SortableColumnModel) { + final SortableColumnModel sortableModel = (SortableColumnModel)model; + if (sortableModel.isSortable()) { + final TableRowSorter rowSorter = createRowSorter(model); + rowSorter.setSortsOnUpdates(isSortOnUpdates()); + setRowSorter(rowSorter); + final RowSorter.SortKey sortKey = sortableModel.getDefaultSortKey(); + if (sortKey != null && sortKey.getColumn() >= 0 && sortKey.getColumn() < model.getColumnCount()) { + if (sortableModel.getColumnInfos()[sortKey.getColumn()].isSortable()) { + rowSorter.setSortKeys(Arrays.asList(sortKey)); + } + } + } + else { + final RowSorter rowSorter = getRowSorter(); + if (rowSorter instanceof DefaultColumnInfoBasedRowSorter) { + setRowSorter(null); + } + } + } + } + + protected boolean isSortOnUpdates() { + return true; + } + + @Override + protected void paintComponent(Graphics g) { + if (myEnableAntialiasing) { + UISettings.setupAntialiasing(g); + } + super.paintComponent(g); + myEmptyText.paint(this, g); + } + + @Override + protected void paintChildren(Graphics g) { + if (myEnableAntialiasing) { + UISettings.setupAntialiasing(g); + } + super.paintChildren(g); + } + + public void setEnableAntialiasing(boolean flag) { + myEnableAntialiasing = flag; + } + + public static DefaultCellEditor createBooleanEditor() { + return new DefaultCellEditor(new JCheckBox()) { + { + ((JCheckBox)getComponent()).setHorizontalAlignment(JCheckBox.CENTER); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + Component component = super.getTableCellEditorComponent(table, value, isSelected, row, column); + component.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); + return component; + } + }; + } + + public void resetDefaultFocusTraversalKeys() { + KeyboardFocusManager m = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + for (Integer each : Arrays.asList(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, + KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS)) { + setFocusTraversalKeys(each, m.getDefaultFocusTraversalKeys(each)); + } + } + + @Override + public StatusText getEmptyText() { + return myEmptyText; + } + + @Override + @NotNull + public ExpandableItemsHandler getExpandableItemsHandler() { + return myExpandableItemsHandler; + } + + public void removeNotify() { + final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + //noinspection HardCodedStringLiteral + keyboardFocusManager.removePropertyChangeListener("permanentFocusOwner", myEditorRemover); + //noinspection HardCodedStringLiteral + keyboardFocusManager.removePropertyChangeListener("focusOwner", myEditorRemover); + super.removeNotify(); + } + + public boolean editCellAt(final int row, final int column, final EventObject e) { + if (cellEditor != null && !cellEditor.stopCellEditing()) { + return false; + } + + if (row < 0 || row >= getRowCount() || column < 0 || column >= getColumnCount()) { + return false; + } + + if (!isCellEditable(row, column)) { + return false; + } + + if (myEditorRemover == null) { + final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + myEditorRemover = new MyCellEditorRemover(); + //noinspection HardCodedStringLiteral + keyboardFocusManager.addPropertyChangeListener("focusOwner", myEditorRemover); + //noinspection HardCodedStringLiteral + keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", myEditorRemover); + } + + final TableCellEditor editor = getCellEditor(row, column); + if (editor != null && editor.isCellEditable(e)) { + editorComp = prepareEditor(editor, row, column); + if (editorComp == null) { + removeEditor(); + return false; + } + editorComp.setBounds(getCellRect(row, column, false)); + add(editorComp); + editorComp.validate(); + + editorComp.requestFocusInWindow(); + + setCellEditor(editor); + setEditingRow(row); + setEditingColumn(column); + editor.addCellEditorListener(this); + + return true; + } + return false; + } + + @Override + public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { + final Component result = super.prepareRenderer(renderer, row, column); + final boolean selected = myExpandableItemsHandler.getExpandedItems().contains(new TableCell(row, column)); + // Fix GTK background + if (UIUtil.isUnderGTKLookAndFeel()){ + final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground(); + UIUtil.changeBackGround(this, background); + } + + if (!selected) return result; + + return new JComponent() { + { + add(result); + setOpaque(false); + setLayout(new AbstractLayoutManager() { + @Override + public Dimension preferredLayoutSize(Container parent) { + return result.getPreferredSize(); + } + + @Override + public void layoutContainer(Container parent) { + Dimension size = parent.getSize(); + Insets i = parent.getInsets(); + Dimension pref = result.getPreferredSize(); + result.setBounds(i.left, i.top, Math.max(pref.width, size.width - i.left - i.right), size.height - i.top - i.bottom); + } + }); + } + }; + } + + private final class MyCellEditorRemover implements PropertyChangeListener { + private final IdeFocusManager myFocusManager; + + public MyCellEditorRemover() { + myFocusManager = IdeFocusManager.findInstanceByComponent(JBTable.this); + } + + public void propertyChange(final PropertyChangeEvent e) { + if (!isEditing()) { + return; + } + + myFocusManager.doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + if (!isEditing()) { + return; + } + Component c = myFocusManager.getFocusOwner(); + while (c != null) { + if (c instanceof JPopupMenu) { + c = ((JPopupMenu)c).getInvoker(); + } + if (c == JBTable.this) { + // focus remains inside the table + return; + } + else if (c instanceof Window) { + if (c == SwingUtilities.getWindowAncestor(JBTable.this)) { + getCellEditor().stopCellEditing(); + } + break; + } + c = c.getParent(); + } + } + }); + } + } + + private final class MyMouseListener extends MouseAdapter { + public void mousePressed(final MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e)) { + final int[] selectedRows = getSelectedRows(); + if (selectedRows.length < 2) { + final int row = rowAtPoint(e.getPoint()); + if (row != -1) { + getSelectionModel().setSelectionInterval(row, row); + } + } + } + } + } + + @SuppressWarnings({"MethodMayBeStatic", "unchecked"}) + protected TableRowSorter createRowSorter(final TableModel model) { + return new DefaultColumnInfoBasedRowSorter(model); + } + + protected static class DefaultColumnInfoBasedRowSorter extends TableRowSorter { + public DefaultColumnInfoBasedRowSorter(final TableModel model) { + super(model); + setModelWrapper(new TableRowSorterModelWrapper(model)); + setMaxSortKeys(1); + } + + @Override + public Comparator getComparator(final int column) { + final TableModel model = getModel(); + if (model instanceof SortableColumnModel) { + final ColumnInfo[] columnInfos = ((SortableColumnModel)model).getColumnInfos(); + if (column >= 0 && column < columnInfos.length) { + final Comparator comparator = columnInfos[column].getComparator(); + if (comparator != null) return comparator; + } + } + + return super.getComparator(column); + } + + protected boolean useToString(int column) { + return false; + } + + @Override + public boolean isSortable(final int column) { + final TableModel model = getModel(); + if (model instanceof SortableColumnModel) { + final ColumnInfo[] columnInfos = ((SortableColumnModel)model).getColumnInfos(); + if (column >= 0 && column < columnInfos.length) { + return columnInfos[column].isSortable() && columnInfos[column].getComparator() != null; + } + } + + return false; + } + + private class TableRowSorterModelWrapper extends ModelWrapper { + private TableModel myModel; + + private TableRowSorterModelWrapper(@NotNull final TableModel model) { + myModel = model; + } + + public TableModel getModel() { + return myModel; + } + + public int getColumnCount() { + return (myModel == null) ? 0 : myModel.getColumnCount(); + } + + public int getRowCount() { + return (myModel == null) ? 0 : myModel.getRowCount(); + } + + public Object getValueAt(int row, int column) { + if (myModel instanceof SortableColumnModel) { + return ((SortableColumnModel)myModel).getRowValue(row); + } + + return myModel.getValueAt(row, column); + } + + public String getStringValueAt(int row, int column) { + TableStringConverter converter = getStringConverter(); + if (converter != null) { + // Use the converter + String value = converter.toString( + myModel, row, column); + if (value != null) { + return value; + } + return ""; + } + + // No converter, use getValueAt followed by toString + Object o = getValueAt(row, column); + if (o == null) { + return ""; + } + String string = o.toString(); + if (string == null) { + return ""; + } + return string; + } + + public Integer getIdentifier(int index) { + return index; + } + } + } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java b/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java index ad26e68d14e8..7386a462f722 100644 --- a/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java +++ b/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java @@ -1,3021 +1,3021 @@ -/* - * 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.ui.tabs.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Queryable; -import com.intellij.openapi.ui.ShadowAction; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.FocusCommand; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.IdeGlassPane; -import com.intellij.openapi.wm.IdeGlassPaneUtil; -import com.intellij.openapi.wm.impl.content.GraphicsConfig; -import com.intellij.ui.CaptionPanel; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.awt.RelativeRectangle; -import com.intellij.ui.switcher.QuickActionProvider; -import com.intellij.ui.switcher.SwitchProvider; -import com.intellij.ui.switcher.SwitchTarget; -import com.intellij.ui.tabs.*; -import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout; -import com.intellij.ui.tabs.impl.singleRow.SingleRowPassInfo; -import com.intellij.ui.tabs.impl.table.TableLayout; -import com.intellij.ui.tabs.impl.table.TablePassInfo; -import com.intellij.util.ui.Animator; -import com.intellij.util.ui.TimedDeadzone; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.update.ComparableObject; -import com.intellij.util.ui.update.LazyUiDisposable; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; -import javax.swing.plaf.ComponentUI; -import java.awt.*; -import java.awt.event.*; -import java.awt.geom.Line2D; -import java.awt.image.BufferedImage; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.*; -import java.util.List; - -public class JBTabsImpl extends JComponent - implements JBTabs, PropertyChangeListener, TimerListener, DataProvider, PopupMenuListener, Disposable, JBTabsPresentation, Queryable, QuickActionProvider { - - static DataKey NAVIGATION_ACTIONS_KEY = DataKey.create("JBTabs"); - - ActionManager myActionManager; - private final List myVisibleInfos = new ArrayList(); - private final Map myHiddenInfos = new HashMap(); - - private TabInfo mySelectedInfo; - public final Map myInfo2Label = new HashMap(); - public final Map myInfo2Toolbar = new HashMap(); - public Dimension myHeaderFitSize; - - private Insets myInnerInsets = new Insets(0, 0, 0, 0); - - private final List myTabMouseListeners = new ArrayList(); - private final List myTabListeners = new ArrayList(); - public boolean myFocused; - - private Getter myPopupGroup; - private String myPopupPlace; - - TabInfo myPopupInfo; - DefaultActionGroup myNavigationActions; - - PopupMenuListener myPopupListener; - JPopupMenu myActivePopup; - - public boolean myHorizontalSide = true; - - private boolean myStealthTabMode = false; - - private DataProvider myDataProvider; - - private final WeakHashMap myDeferredToRemove = new WeakHashMap(); - - private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this); - private final TableLayout myTableLayout = new TableLayout(this); - - - private TabLayout myLayout = mySingleRowLayout; - LayoutPassInfo myLastLayoutPass; - private TabInfo myLastPaintedSelection; - - public boolean myForcedRelayout; - - private UiDecorator myUiDecorator; - static final UiDecorator ourDefaultDecorator = new DefautDecorator(); - - private boolean myPaintFocus; - - private boolean myHideTabs = false; - @Nullable private Project myProject; - - private boolean myRequestFocusOnLastFocusedComponent = false; - private boolean myListenerAdded; - final Set myAttractions = new HashSet(); - private Animator myAnimator; - private List myAllTabs; - private boolean myPaintBlocked; - private BufferedImage myImage; - private IdeFocusManager myFocusManager; - private boolean myAdjustBorders = true; - - boolean myAddNavigationGroup = true; - - private boolean myGhostsAlwaysVisible = false; - private boolean myDisposed; - private boolean myToDrawBorderIfTabsHidden = true; - private Color myActiveTabFillIn; - - private boolean myTabLabelActionsAutoHide; - - private final TabActionsAutoHideListener myTabActionsAutoHideListener = new TabActionsAutoHideListener(); - private IdeGlassPane myGlassPane; - @NonNls private static final String LAYOUT_DONE = "Layout.done"; - @NonNls public static final String STRETCHED_BY_WIDTH = "Layout.stretchedByWidth"; - - private TimedDeadzone.Length myTabActionsMouseDeadzone = TimedDeadzone.DEFAULT; - - private long myRemoveDefferredRequest; - private boolean myTestMode; - - private JBTabsPosition myPosition = JBTabsPosition.top; - - private final TabsBorder myBorder = new TabsBorder(this); - private BaseNavigationAction myNextAction; - private BaseNavigationAction myPrevAction; - - private boolean myWasEverShown; - - private boolean myTabDraggingEnabled; - private DragHelper myDragHelper; - private boolean myNavigationActionsEnabled = true; - private boolean myUseBufferedPaint = true; - - private boolean myOwnSwitchProvider = true; - private SwitchProvider mySwitchDelegate; - private TabInfo myDropInfo; - private int myDropInfoIndex; - - public JBTabsImpl(@NotNull Project project) { - this(project, project); - } - - public JBTabsImpl(@NotNull Project project, @NotNull Disposable parent) { - this(project, ActionManager.getInstance(), IdeFocusManager.getInstance(project), parent); - } - - public JBTabsImpl(@Nullable Project project, IdeFocusManager focusManager, @NotNull Disposable parent) { - this(project, ActionManager.getInstance(), focusManager, parent); - } - - public JBTabsImpl(@Nullable Project project, ActionManager actionManager, IdeFocusManager focusManager, @NotNull Disposable parent) { - myProject = project; - myActionManager = actionManager; - myFocusManager = focusManager != null ? focusManager : IdeFocusManager.getGlobalInstance(); - - setOpaque(true); - setPaintBorder(-1, -1, -1, -1); - - Disposer.register(parent, this); - - myNavigationActions = new DefaultActionGroup(); - - if (myActionManager != null) { - myNextAction = new SelectNextAction(this, myActionManager); - myPrevAction = new SelectPreviousAction(this, myActionManager); - - myNavigationActions.add(myNextAction); - myNavigationActions.add(myPrevAction); - } - - setUiDecorator(null); - - myPopupListener = new PopupMenuListener() { - public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { - } - - public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { - disposePopupListener(); - } - - public void popupMenuCanceled(final PopupMenuEvent e) { - disposePopupListener(); - } - }; - - addMouseListener(new MouseAdapter() { - public void mousePressed(final MouseEvent e) { - if (mySingleRowLayout.myLastSingRowLayout != null && - mySingleRowLayout.myLastSingRowLayout.moreRect != null && - mySingleRowLayout.myLastSingRowLayout.moreRect.contains(e.getPoint())) { - showMorePopup(e); - } - } - }); - - myAnimator = new Animator("JBTabs Attractions", 2, 500, true, 0, -1) { - public void paintNow(final float frame, final float totalFrames, final float cycle) { - repaintAttractions(); - } - }; - myAnimator.setTakInitialDelay(false); - - setFocusCycleRoot(true); - setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { - public Component getDefaultComponent(final Container aContainer) { - return getToFocus(); - } - }); - - add(mySingleRowLayout.myLeftGhost); - add(mySingleRowLayout.myRightGhost); - - - new LazyUiDisposable(parent, this, this) { - protected void initialize(@NotNull Disposable parent, @NotNull JBTabsImpl child, @Nullable Project project) { - myProject = project; - - Disposer.register(child, myAnimator); - Disposer.register(child, new Disposable() { - public void dispose() { - removeTimerUpdate(); - } - }); - - if (!myTestMode) { - final IdeGlassPane gp = IdeGlassPaneUtil.find(child); - if (gp != null) { - gp.addMouseMotionPreprocessor(myTabActionsAutoHideListener, child); - myGlassPane = gp; - } - - UIUtil.addAwtListener(new AWTEventListener() { - public void eventDispatched(final AWTEvent event) { - if (mySingleRowLayout.myMorePopup != null) return; - processFocusChange(); - } - }, AWTEvent.FOCUS_EVENT_MASK, child); - - myDragHelper = new DragHelper(child); - myDragHelper.start(); - } - - if (myProject != null && myFocusManager == IdeFocusManager.getGlobalInstance()) { - myFocusManager = IdeFocusManager.getInstance(myProject); - } - } - }; - } - - - public JBTabs setNavigationActiondBinding(String prevActionId, String nextActionId) { - if (myNextAction != null) { - myNextAction.reconnect(nextActionId); - } - if (myPrevAction != null) { - myPrevAction.reconnect(prevActionId); - } - - return this; - } - - public JBTabs setNavigationActionsEnabled(boolean enabled) { - myNavigationActionsEnabled = enabled; - return this; - } - - public final boolean isDisposed() { - return myDisposed; - } - - public JBTabs setAdditinalSwitchProviderWhenOriginal(SwitchProvider delegate) { - mySwitchDelegate = delegate; - return this; - } - - @Override - public Image getComponentImage(TabInfo info) { - JComponent cmp = info.getComponent(); - - BufferedImage img; - if (cmp.isShowing()) { - img = new BufferedImage(cmp.getWidth(), cmp.getHeight(), BufferedImage.TYPE_INT_ARGB); - Graphics2D g = img.createGraphics(); - cmp.paint(g); - } else { - img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); - } - return img; - } - - public void dispose() { - myDisposed = true; - mySelectedInfo = null; - resetTabsCache(); - myAttractions.clear(); - myVisibleInfos.clear(); - myUiDecorator = null; - myImage = null; - myActivePopup = null; - myInfo2Label.clear(); - myInfo2Toolbar.clear(); - myTabListeners.clear(); - } - - void resetTabsCache() { - myAllTabs = null; - } - - private void processFocusChange() { - Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (owner == null) { - setFocused(false); - return; - } - - if (owner == this || SwingUtilities.isDescendingFrom(owner, this)) { - setFocused(true); - } - else { - setFocused(false); - } - } - - private void repaintAttractions() { - boolean needsUpdate = false; - for (TabInfo each : myVisibleInfos) { - TabLabel eachLabel = myInfo2Label.get(each); - needsUpdate |= eachLabel.repaintAttraction(); - } - - if (needsUpdate) { - relayout(true, false); - } - } - - public void addNotify() { - super.addNotify(); - addTimerUpdate(); - } - - public void removeNotify() { - super.removeNotify(); - - setFocused(false); - - removeTimerUpdate(); - - if (myGlassPane != null) { - myGlassPane.removeMouseMotionPreprocessor(myTabActionsAutoHideListener); - myGlassPane = null; - } - } - - private void addTimerUpdate() { - if (myActionManager != null && !myListenerAdded) { - myActionManager.addTimerListener(500, this); - myListenerAdded = true; - } - } - - private void removeTimerUpdate() { - if (myActionManager != null && myListenerAdded) { - myActionManager.removeTimerListener(this); - myListenerAdded = false; - } - } - - void setTestMode(final boolean testMode) { - myTestMode = testMode; - } - - public void layoutComp(SingleRowPassInfo data, int deltaX, int deltaY, int deltaWidth, int deltaHeight) { - if (data.hToolbar != null) { - final int toolbarHeight = data.hToolbar.getPreferredSize().height; - final Rectangle compRect = layoutComp(deltaX, toolbarHeight + deltaY, data.comp, deltaWidth, deltaHeight); - layout(data.hToolbar, compRect.x, compRect.y - toolbarHeight, compRect.width, toolbarHeight); - } - else if (data.vToolbar != null) { - final int toolbarWidth = data.vToolbar.getPreferredSize().width; - final Rectangle compRect = layoutComp(toolbarWidth + deltaX, deltaY, data.comp, deltaWidth, deltaHeight); - layout(data.vToolbar, compRect.x - toolbarWidth, compRect.y, toolbarWidth, compRect.height); - } - else { - layoutComp(deltaX, deltaY, data.comp, deltaWidth, deltaHeight); - } - } - - public boolean isDropTarget(TabInfo info) { - return myDropInfo != null && myDropInfo == info; - } - - public void setDropInfoIndex(int dropInfoIndex) { - myDropInfoIndex = dropInfoIndex; - } - - class TabActionsAutoHideListener extends MouseMotionAdapter { - - private TabLabel myCurrentOverLabel; - private Point myLastOverPoint; - - @Override - public void mouseMoved(final MouseEvent e) { - if (!myTabLabelActionsAutoHide) return; - - final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getX(), e.getY(), JBTabsImpl.this); - myLastOverPoint = point; - processMouseOver(); - } - - void processMouseOver() { - if (!myTabLabelActionsAutoHide) return; - - if (myLastOverPoint == null) return; - - if (myLastOverPoint.x >= 0 && myLastOverPoint.x < getWidth() && myLastOverPoint.y > 0 && myLastOverPoint.y < getHeight()) { - final TabLabel label = myInfo2Label.get(_findInfo(myLastOverPoint, true)); - if (label != null) { - if (myCurrentOverLabel != null) { - myCurrentOverLabel.toggleShowActions(false); - } - label.toggleShowActions(true); - myCurrentOverLabel = label; - return; - } - } - - if (myCurrentOverLabel != null) { - myCurrentOverLabel.toggleShowActions(false); - myCurrentOverLabel = null; - } - } - } - - - public ModalityState getModalityState() { - return ModalityState.stateForComponent(this); - } - - public void run() { - updateTabActions(false); - } - - public void updateTabActions(final boolean validateNow) { - final Ref changed = new Ref(Boolean.FALSE); - for (final TabInfo eachInfo : myInfo2Label.keySet()) { - updateTab(new Computable() { - public Boolean compute() { - final boolean changes = myInfo2Label.get(eachInfo).updateTabActions(); - changed.set(changed.get().booleanValue() || changes); - return changes; - } - }, eachInfo); - } - - if (changed.get().booleanValue()) { - if (validateNow) { - validate(); - paintImmediately(0, 0, getWidth(), getHeight()); - } - } - } - - private void showMorePopup(final MouseEvent e) { - mySingleRowLayout.myMorePopup = new JPopupMenu(); - for (final TabInfo each : myVisibleInfos) { - final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText()); - mySingleRowLayout.myMorePopup.add(item); - if (getSelectedInfo() == each) { - item.setSelected(true); - } - item.addActionListener(new ActionListener() { - public void actionPerformed(final ActionEvent e) { - select(each, true); - } - }); - } - - mySingleRowLayout.myMorePopup.addPopupMenuListener(new PopupMenuListener() { - public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { - } - - public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { - mySingleRowLayout.myMorePopup = null; - } - - public void popupMenuCanceled(final PopupMenuEvent e) { - mySingleRowLayout.myMorePopup = null; - } - }); - - mySingleRowLayout.myMorePopup.show(this, e.getX(), e.getY()); - } - - - private JComponent getToFocus() { - final TabInfo info = getSelectedInfo(); - - if (info == null) return null; - - JComponent toFocus = null; - - if (isRequestFocusOnLastFocusedComponent() && info.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) { - toFocus = info.getLastFocusOwner(); - } - - if (toFocus == null && info.getPreferredFocusableComponent() == null) { - return null; - } - - - if (toFocus == null) { - toFocus = info.getPreferredFocusableComponent(); - final JComponent policyToFocus = myFocusManager.getFocusTargetFor(toFocus); - if (policyToFocus != null) { - toFocus = policyToFocus; - } - } - - return toFocus; - } - - public void requestFocus() { - final JComponent toFocus = getToFocus(); - if (toFocus != null) { - toFocus.requestFocus(); - } - else { - super.requestFocus(); - } - } - - public boolean requestFocusInWindow() { - final JComponent toFocus = getToFocus(); - if (toFocus != null) { - return toFocus.requestFocusInWindow(); - } - else { - return super.requestFocusInWindow(); - } - } - - private JBTabsImpl findTabs(Component c) { - Component eachParent = c; - while (eachParent != null) { - if (eachParent instanceof JBTabsImpl) { - return (JBTabsImpl)eachParent; - } - eachParent = eachParent.getParent(); - } - - return null; - } - - - @NotNull - public TabInfo addTab(TabInfo info, int index) { - return addTab(info, index, false); - } - - private TabInfo addTab(TabInfo info, int index, boolean isDropTarget) { - if (!isDropTarget && getTabs().contains(info)) { - return getTabs().get(getTabs().indexOf(info)); - } - - info.getChangeSupport().addPropertyChangeListener(this); - final TabLabel label = new TabLabel(this, info); - myInfo2Label.put(info, label); - - if (!isDropTarget) { - if (index < 0) { - myVisibleInfos.add(info); - } - else if (index > myVisibleInfos.size() - 1) { - myVisibleInfos.add(info); - } - else { - myVisibleInfos.add(index, info); - } - } - - resetTabsCache(); - - - updateText(info); - updateIcon(info); - updateSideComponent(info); - updateTabActions(info); - - add(label); - - adjust(info); - - updateAll(false, false); - - if (info.isHidden()) { - updateHiding(); - } - - if (!isDropTarget) { - if (getTabCount() == 1) { - fireBeforeSelectionChanged(null, info); - fireSelectionChanged(null, info); - } - } - - revalidateAndRepaint(false); - - return info; - } - - - @NotNull - public TabInfo addTab(TabInfo info) { - return addTab(info, -1); - } - - public ActionGroup getPopupGroup() { - return myPopupGroup != null ? myPopupGroup.get() : null; - } - - public String getPopupPlace() { - return myPopupPlace; - } - - public JBTabs setPopupGroup(@NotNull final ActionGroup popupGroup, @NotNull String place, final boolean addNavigationGroup) { - return setPopupGroup(new Getter() { - public ActionGroup get() { - return popupGroup; - } - }, place, addNavigationGroup); - } - - public JBTabs setPopupGroup(@NotNull final Getter popupGroup, - @NotNull final String place, - final boolean addNavigationGroup) { - myPopupGroup = popupGroup; - myPopupPlace = place; - myAddNavigationGroup = addNavigationGroup; - return this; - } - - private void updateAll(final boolean forcedRelayout, final boolean now) { - mySelectedInfo = getSelectedInfo(); - updateContainer(forcedRelayout, now); - removeDeferred(); - updateListeners(); - updateTabActions(false); - updateEnabling(); - } - - private boolean isMyChildIsFocusedNow() { - final Component owner = getFocusOwner(); - if (owner == null) return false; - - - if (mySelectedInfo != null) { - if (!SwingUtilities.isDescendingFrom(owner, mySelectedInfo.getComponent())) return false; - } - - return SwingUtilities.isDescendingFrom(owner, this); - } - - @Nullable - private static JComponent getFocusOwner() { - final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - return (JComponent)(owner instanceof JComponent ? owner : null); - } - - public ActionCallback select(@NotNull TabInfo info, boolean requestFocus) { - return _setSelected(info, requestFocus); - } - - private ActionCallback _setSelected(final TabInfo info, final boolean requestFocus) { - if (mySelectedInfo != null && mySelectedInfo.equals(info)) { - if (!requestFocus) { - return new ActionCallback.Done(); - } - else { - return requestFocus(getToFocus()); - } - } - - if (myRequestFocusOnLastFocusedComponent && mySelectedInfo != null) { - if (isMyChildIsFocusedNow()) { - mySelectedInfo.setLastFocusOwner(getFocusOwner()); - } - } - - TabInfo oldInfo = mySelectedInfo; - mySelectedInfo = info; - final TabInfo newInfo = getSelectedInfo(); - - fireBeforeSelectionChanged(oldInfo, newInfo); - - updateContainer(false, true); - - fireSelectionChanged(oldInfo, newInfo); - - if (requestFocus) { - final JComponent toFocus = getToFocus(); - if (myProject != null && toFocus != null) { - final ActionCallback result = new ActionCallback(); - requestFocus(toFocus).doWhenProcessed(new Runnable() { - public void run() { - if (myDisposed) { - result.setRejected(); - } - else { - removeDeferred().notifyWhenDone(result); - } - } - }); - return result; - } - else { - requestFocus(); - return removeDeferred(); - } - } - else { - return removeDeferred(); - } - } - - private void fireBeforeSelectionChanged(TabInfo oldInfo, TabInfo newInfo) { - if (oldInfo != newInfo) { - for (TabsListener eachListener : myTabListeners) { - eachListener.beforeSelectionChanged(oldInfo, newInfo); - } - } - } - - private void fireSelectionChanged(TabInfo oldInfo, TabInfo newInfo) { - if (oldInfo != newInfo) { - for (TabsListener eachListener : myTabListeners) { - if (eachListener != null) { - eachListener.selectionChanged(oldInfo, newInfo); - } - } - } - } - - private ActionCallback requestFocus(final JComponent toFocus) { - if (toFocus == null) return new ActionCallback.Done(); - - if (myTestMode) { - toFocus.requestFocus(); - return new ActionCallback.Done(); - } - - return myFocusManager.requestFocus(new FocusCommand.ByComponent(toFocus), true); - } - - private ActionCallback removeDeferred() { - final ActionCallback callback = new ActionCallback(); - - final long executionRequest = ++myRemoveDefferredRequest; - - final Runnable onDone = new Runnable() { - public void run() { - if (myRemoveDefferredRequest == executionRequest) { - removeDeferredNow(); - } - - callback.setDone(); - } - }; - - myFocusManager.doWhenFocusSettlesDown(onDone); - - return callback; - } - - private void queueForRemove(Component c) { - if (c instanceof JComponent) { - addToDeferredRemove(c); - } - else { - remove(c); - } - } - - private void unqueueFromRemove(Component c) { - myDeferredToRemove.remove(c); - } - - private void removeDeferredNow() { - for (Component each : myDeferredToRemove.keySet()) { - if (each != null && each.getParent() == this) { - remove(each); - } - } - myDeferredToRemove.clear(); - } - - private void printRemoveInfo(final Component each) { - TabInfo removingInfo = null; - final List all = getTabs(); - for (TabInfo eachInfo : all) { - if (eachInfo.getComponent() == each) { - removingInfo = eachInfo; - break; - } - } - - //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each)); - } - - public void propertyChange(final PropertyChangeEvent evt) { - final TabInfo tabInfo = (TabInfo)evt.getSource(); - if (TabInfo.ACTION_GROUP.equals(evt.getPropertyName())) { - updateSideComponent(tabInfo); - relayout(false, false); - } - else if (TabInfo.COMPONENT.equals(evt.getPropertyName())) { - relayout(true, false); - } - else if (TabInfo.TEXT.equals(evt.getPropertyName())) { - updateText(tabInfo); - } - else if (TabInfo.ICON.equals(evt.getPropertyName())) { - updateIcon(tabInfo); - } - else if (TabInfo.TAB_COLOR.equals(evt.getPropertyName())) { - updateColor(tabInfo); - } - else if (TabInfo.ALERT_STATUS.equals(evt.getPropertyName())) { - boolean start = ((Boolean)evt.getNewValue()).booleanValue(); - updateAttraction(tabInfo, start); - } - else if (TabInfo.TAB_ACTION_GROUP.equals(evt.getPropertyName())) { - updateTabActions(tabInfo); - relayout(false, false); - } - else if (TabInfo.HIDDEN.equals(evt.getPropertyName())) { - updateHiding(); - relayout(false, false); - } - else if (TabInfo.ENABLED.equals(evt.getPropertyName())) { - updateEnabling(); - } - } - - private void updateEnabling() { - final List all = getTabs(); - for (TabInfo each : all) { - final TabLabel eachLabel = myInfo2Label.get(each); - eachLabel.setTabEnabled(each.isEnabled()); - } - - final TabInfo selected = getSelectedInfo(); - if (selected != null && !selected.isEnabled()) { - final TabInfo toSelect = getToSelectOnRemoveOf(selected); - if (toSelect != null) { - select(toSelect, myFocusManager.getFocusedDescendantFor(this) != null); - } - } - } - - private void updateHiding() { - boolean update = false; - - Iterator visible = myVisibleInfos.iterator(); - while (visible.hasNext()) { - TabInfo each = visible.next(); - if (each.isHidden() && !myHiddenInfos.containsKey(each)) { - myHiddenInfos.put(each, myVisibleInfos.indexOf(each)); - visible.remove(); - update = true; - } - } - - - Iterator hidden = myHiddenInfos.keySet().iterator(); - while (hidden.hasNext()) { - TabInfo each = hidden.next(); - if (!each.isHidden() && myHiddenInfos.containsKey(each)) { - myVisibleInfos.add(getIndexInVisibleArray(each), each); - hidden.remove(); - update = true; - } - } - - - if (update) { - resetTabsCache(); - if (mySelectedInfo != null && myHiddenInfos.containsKey(mySelectedInfo)) { - mySelectedInfo = getToSelectOnRemoveOf(mySelectedInfo); - } - updateAll(true, false); - } - } - - private int getIndexInVisibleArray(TabInfo each) { - Integer index = myHiddenInfos.get(each); - if (index == null) { - index = Integer.valueOf(myVisibleInfos.size()); - } - - if (index > myVisibleInfos.size()) { - index = myVisibleInfos.size(); - } - - if (index.intValue() < 0) { - index = 0; - } - - return index.intValue(); - } - - private void updateIcon(final TabInfo tabInfo) { - updateTab(new Computable() { - public Boolean compute() { - myInfo2Label.get(tabInfo).setIcon(tabInfo.getIcon()); - return true; - } - }, tabInfo); - } - - private void updateColor(final TabInfo tabInfo) { - myInfo2Label.get(tabInfo).setInactiveStateImage(null); - - updateTab(new Computable() { - public Boolean compute() { - repaint(); - return true; - } - }, tabInfo); - } - - private void updateTab(Computable update, TabInfo info) { - final TabLabel label = myInfo2Label.get(info); - Boolean changes = update.compute(); - if (label.getRootPane() != null) { - if (label.isValid()) { - if (changes) { - label.repaint(); - } - } - else { - revalidateAndRepaint(false); - } - } - } - - void revalidateAndRepaint(final boolean layoutNow) { - - if (myVisibleInfos.isEmpty()) { - setOpaque(false); - final Component nonOpaque = UIUtil.findUltimateParent(this); - if (nonOpaque != null && getParent() != null) { - final Rectangle toRepaint = SwingUtilities.convertRectangle(getParent(), getBounds(), nonOpaque); - nonOpaque.repaint(toRepaint.x, toRepaint.y, toRepaint.width, toRepaint.height); - } - } - else { - setOpaque(true); - } - - if (layoutNow) { - validate(); - } - else { - revalidate(); - } - - repaint(); - } - - - private void updateAttraction(final TabInfo tabInfo, boolean start) { - if (start) { - myAttractions.add(tabInfo); - } - else { - myAttractions.remove(tabInfo); - tabInfo.setBlinkCount(0); - } - - if (start && !myAnimator.isRunning()) { - myAnimator.resume(); - } - else if (!start && myAttractions.isEmpty()) { - myAnimator.suspend(); - repaintAttractions(); - } - } - - private void updateText(final TabInfo tabInfo) { - updateTab(new Computable() { - public Boolean compute() { - final TabLabel label = myInfo2Label.get(tabInfo); - label.setText(tabInfo.getColoredText()); - label.setToolTipText(tabInfo.getTooltipText()); - return true; - } - }, tabInfo); - } - - private void updateSideComponent(final TabInfo tabInfo) { - final Toolbar old = myInfo2Toolbar.get(tabInfo); - if (old != null) { - remove(old); - } - - final Toolbar toolbar = createToolbarComponent(tabInfo); - myInfo2Toolbar.put(tabInfo, toolbar); - add(toolbar); - } - - private void updateTabActions(final TabInfo info) { - myInfo2Label.get(info).setTabActions(info.getTabLabelActions()); - } - - @Nullable - public TabInfo getSelectedInfo() { - if (!myVisibleInfos.contains(mySelectedInfo)) { - mySelectedInfo = null; - } - return mySelectedInfo != null ? mySelectedInfo : !myVisibleInfos.isEmpty() ? myVisibleInfos.get(0) : null; - } - - @Nullable - private TabInfo getToSelectOnRemoveOf(TabInfo info) { - if (!myVisibleInfos.contains(info)) return null; - if (mySelectedInfo != info) return null; - - if (myVisibleInfos.size() == 1) return null; - - int index = myVisibleInfos.indexOf(info); - - TabInfo result = null; - if (index > 0) { - result = findEnabledBackward(index - 1); - } - - if (result == null) { - result = findEnabledForward(index + 1); - } - - return result; - } - - private TabInfo findEnabledForward(int from) { - int index = from; - while (index < myVisibleInfos.size() && index >= 0) { - final TabInfo each = myVisibleInfos.get(index); - if (each.isEnabled()) return each; - index++; - } - - return null; - } - - private TabInfo findEnabledBackward(int from) { - int index = from; - while (index >= 0 && from < myVisibleInfos.size()) { - final TabInfo each = myVisibleInfos.get(index); - if (each.isEnabled()) return each; - index--; - } - - return null; - } - - protected Toolbar createToolbarComponent(final TabInfo tabInfo) { - return new Toolbar(this, tabInfo); - } - - @NotNull - public TabInfo getTabAt(final int tabIndex) { - return getTabs().get(tabIndex); - } - - @NotNull - public List getTabs() { - if (myAllTabs != null) return myAllTabs; - - ArrayList result = new ArrayList(); - result.addAll(myVisibleInfos); - - for (TabInfo each : myHiddenInfos.keySet()) { - result.add(getIndexInVisibleArray(each), each); - } - - myAllTabs = result; - - return result; - } - - public TabInfo getTargetInfo() { - return myPopupInfo != null ? myPopupInfo : getSelectedInfo(); - } - - public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { - } - - public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { - resetPopup(); - } - - public void popupMenuCanceled(final PopupMenuEvent e) { - resetPopup(); - } - - private void resetPopup() { -//todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myPopupInfo = null; - } - }); - } - - public void setPaintBlocked(boolean blocked, final boolean takeSnapshot) { - if (blocked && !myPaintBlocked) { - if (takeSnapshot) { - myImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); - final Graphics2D g = myImage.createGraphics(); - super.paint(g); - g.dispose(); - } - } - - myPaintBlocked = blocked; - - if (!myPaintBlocked) { - if (myImage != null) { - myImage.flush(); - } - - myImage = null; - repaint(); - } - } - - - private void addToDeferredRemove(final Component c) { - if (!myDeferredToRemove.containsKey(c)) { - myDeferredToRemove.put(c, c); - } - } - - public boolean isToDrawBorderIfTabsHidden() { - return myToDrawBorderIfTabsHidden; - } - - @NotNull - public JBTabsPresentation setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden) { - myToDrawBorderIfTabsHidden = toDrawBorderIfTabsHidden; - return this; - } - - @NotNull - public JBTabs getJBTabs() { - return this; - } - - public static class Toolbar extends JPanel { - private final JBTabsImpl myTabs; - private final TabInfo myInfo; - - public Toolbar(JBTabsImpl tabs, TabInfo info) { - myTabs = tabs; - myInfo = info; - - setLayout(new BorderLayout()); - - final ActionGroup group = info.getGroup(); - final JComponent side = info.getSideComponent(); - - if (group != null && myTabs.myActionManager != null) { - final String place = info.getPlace(); - ActionToolbar toolbar = - myTabs.myActionManager.createActionToolbar(place != null ? place : ActionPlaces.UNKNOWN, group, myTabs.myHorizontalSide); - toolbar.setTargetComponent(info.getActionsContextComponent()); - final JComponent actionToolbar = toolbar.getComponent(); - add(actionToolbar, BorderLayout.CENTER); - } - - if (side != null) { - if (group != null) { - add(side, BorderLayout.EAST); - } - else { - add(side, BorderLayout.CENTER); - } - } - } - - public boolean isEmpty() { - return getComponentCount() == 0; - } - } - - - public void doLayout() { - try { - myHeaderFitSize = computeHeaderFitSize(); - - final Collection labels = myInfo2Label.values(); - for (TabLabel each : labels) { - each.setTabActionsAutoHide(myTabLabelActionsAutoHide); - } - - - List visible = new ArrayList(); - visible.addAll(myVisibleInfos); - - if (myDropInfo != null && !visible.contains(myDropInfo)) { - if (getDropInfoIndex() >= 0 && getDropInfoIndex() < visible.size()) { - visible.add(getDropInfoIndex(), myDropInfo); - } else { - visible.add(myDropInfo); - } - } - - if (isSingleRow()) { - myLastLayoutPass = mySingleRowLayout.layoutSingleRow(visible); - myTableLayout.myLastTableLayout = null; - } - else { - myLastLayoutPass = myTableLayout.layoutTable(visible); - mySingleRowLayout.myLastSingRowLayout = null; - } - - if (isStealthModeEffective() && !isHideTabs()) { - final TabLabel label = getSelectedLabel(); - final Rectangle bounds = label.getBounds(); - final Insets insets = getLayoutInsets(); - layout(label, insets.left, bounds.y, getWidth() - insets.right - insets.left, bounds.height); - } - - - moveDraggedTabLabel(); - - myTabActionsAutoHideListener.processMouseOver(); - } - finally { - myForcedRelayout = false; - } - - applyResetComponents(); - } - - void moveDraggedTabLabel() { - if (myDragHelper != null && myDragHelper.myDragRec != null) { - final TabLabel selectedLabel = myInfo2Label.get(getSelectedInfo()); - if (selectedLabel != null) { - final Rectangle bounds = selectedLabel.getBounds(); - if (isHorizontalTabs()) { - selectedLabel.setBounds(myDragHelper.myDragRec.x, bounds.y, bounds.width, bounds.height); - } - else { - selectedLabel.setBounds(bounds.x, myDragHelper.myDragRec.y, bounds.width, bounds.height); - } - } - } - } - - private Dimension computeHeaderFitSize() { - final Max max = computeMaxSize(); - - if (myPosition == JBTabsPosition.top || myPosition == JBTabsPosition.bottom) { - return new Dimension(getSize().width, myHorizontalSide ? Math.max(max.myLabel.height, max.myToolbar.height) : max.myLabel.height); - } - else { - return new Dimension(max.myLabel.width + (myHorizontalSide ? 0 : max.myToolbar.width), getSize().height); - } - } - - public Rectangle layoutComp(int componentX, int componentY, final JComponent comp, int deltaWidth, int deltaHeight) { - final Insets insets = getLayoutInsets(); - - final Insets border = isHideTabs() ? new Insets(0, 0, 0, 0) : myBorder.getEffectiveBorder(); - final boolean noTabsVisible = isStealthModeEffective() || isHideTabs(); - - if (noTabsVisible) { - border.top = getBorder(-1); - border.bottom = getBorder(-1); - border.left = getBorder(-1); - border.right = getBorder(-1); - } - - final Insets inner = getInnerInsets(); - border.top += inner.top; - border.bottom += inner.bottom; - border.left += inner.left; - border.right += inner.right; - - - int x = insets.left + componentX + border.left; - int y = insets.top + componentY + border.top; - int width = getWidth() - insets.left - insets.right - componentX - border.left - border.right; - int height = getHeight() - insets.top - insets.bottom - componentY - border.top - border.bottom; - - if (!noTabsVisible) { - width += deltaWidth; - height += deltaHeight; - } - - return layout(comp, x, y, width, height); - } - - - public JBTabsPresentation setInnerInsets(final Insets innerInsets) { - myInnerInsets = innerInsets; - return this; - } - - public Insets getInnerInsets() { - return myInnerInsets; - } - - public Insets getLayoutInsets() { - Insets insets = getInsets(); - if (insets == null) { - insets = new Insets(0, 0, 0, 0); - } - return insets; - } - - private int fixInset(int inset, int addin) { - return inset + addin; - } - - - public int getToolbarInset() { - return getArcSize() + 1; - } - - public void resetLayout(boolean resetLabels) { - if (resetLabels) { - mySingleRowLayout.myLeftGhost.reset(); - mySingleRowLayout.myRightGhost.reset(); - } - - for (TabInfo each : myVisibleInfos) { - reset(each, resetLabels); - } - - if (myDropInfo != null) { - reset(myDropInfo, resetLabels); - } - - for (TabInfo each : myHiddenInfos.keySet()) { - reset(each, resetLabels); - } - - for (Component eachDeferred : myDeferredToRemove.keySet()) { - resetLayout((JComponent)eachDeferred); - } - } - - private void reset(final TabInfo each, final boolean resetLabels) { - final JComponent c = each.getComponent(); - if (c != null) { - resetLayout(c); - } - - resetLayout(myInfo2Toolbar.get(each)); - - if (resetLabels) { - resetLayout(myInfo2Label.get(each)); - } - } - - - private int getArcSize() { - return 4; - } - - private int getEdgeArcSize() { - return 3; - } - - public int getGhostTabLength() { - return 15; - } - - - protected void paintComponent(final Graphics g) { - super.paintComponent(g); - - if (myVisibleInfos.isEmpty()) return; - - Graphics2D g2d = (Graphics2D)g; - - final GraphicsConfig config = new GraphicsConfig(g2d); - config.setAntialiasing(true); - - - g2d.setColor(getBackground()); - final Rectangle clip = g2d.getClipBounds(); - g2d.fillRect(clip.x, clip.y, clip.width, clip.height); - - final TabInfo selected = getSelectedInfo(); - - if (selected != null) { - Rectangle compBounds = selected.getComponent().getBounds(); - if (compBounds.contains(clip) && !compBounds.intersects(clip)) return; - } - - boolean leftGhostExists = isSingleRow(); - boolean rightGhostExists = isSingleRow(); - - if (!isStealthModeEffective() && !isHideTabs()) { - if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.lastGhostVisible) { - paintLastGhost(g2d); - } - - - paintNonSelectedTabs(g2d, leftGhostExists, rightGhostExists); - - if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.firstGhostVisible) { - paintFirstGhost(g2d); - } - - } - - config.setAntialiasing(false); - - if (isSideComponentVertical()) { - Toolbar toolbarComp = myInfo2Toolbar.get(mySelectedInfo); - if (toolbarComp != null && !toolbarComp.isEmpty()) { - Rectangle toolBounds = toolbarComp.getBounds(); - g2d.setColor(CaptionPanel.CNT_ACTIVE_COLOR); - g2d.drawLine((int)toolBounds.getMaxX(), toolBounds.y, (int)toolBounds.getMaxX(), (int)toolBounds.getMaxY() - 1); - } - } - - config.restore(); - } - - private Color getActiveTabColor(final Color c) { - final TabInfo info = getSelectedInfo(); - if (info == null) { - return c; - } - - final Color tabColor = info.getTabColor(); - return tabColor == null ? c : tabColor; - } - - private void paintSelectionAndBorder(Graphics2D g2d) { - if (mySelectedInfo == null) return; - - final ShapeInfo shapeInfo = computeSelectedLabelShape(); - if (!isHideTabs()) { - g2d.setColor(getBackground()); - g2d.fill(shapeInfo.fillPath.getShape()); - } - - final int alpha; - int paintTopY = shapeInfo.labelTopY; - int paintBottomY = shapeInfo.labelBottomY; - final boolean paintFocused = myPaintFocus && (myFocused || myActivePopup != null); - Color bgPreFill = null; - if (paintFocused) { - final Color bgColor = getActiveTabColor(getActiveTabFillIn()); - if (bgColor == null) { - shapeInfo.from = UIUtil.getFocusedFillColor(); - shapeInfo.to = UIUtil.getFocusedFillColor(); - } - else { - bgPreFill = bgColor; - alpha = 255; - paintBottomY = shapeInfo.labelTopY + shapeInfo.labelPath.deltaY(getArcSize() - 2); - shapeInfo.from = UIUtil.toAlpha(UIUtil.getFocusedFillColor(), alpha); - shapeInfo.to = UIUtil.toAlpha(getActiveTabFillIn(), alpha); - } - } - else { - final Color bgColor = getActiveTabColor(getActiveTabFillIn()); - if (isPaintFocus()) { - if (bgColor == null) { - alpha = 150; - shapeInfo.from = UIUtil.toAlpha(UIUtil.getPanelBackground().brighter(), alpha); - shapeInfo.to = UIUtil.toAlpha(UIUtil.getPanelBackground(), alpha); - } - else { - alpha = 255; - shapeInfo.from = UIUtil.toAlpha(bgColor, alpha); - shapeInfo.to = UIUtil.toAlpha(bgColor, alpha); - } - } - else { - alpha = 255; - final Color tabColor = getActiveTabColor(null); - shapeInfo.from = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha); - shapeInfo.to = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha); - } - } - - if (!isHideTabs()) { - if (bgPreFill != null) { - g2d.setColor(bgPreFill); - g2d.fill(shapeInfo.fillPath.getShape()); - } - - final Line2D.Float gradientLine = - shapeInfo.fillPath.transformLine(shapeInfo.fillPath.getX(), paintTopY, shapeInfo.fillPath.getX(), paintBottomY); - - - g2d.setPaint(new GradientPaint((float)gradientLine.getX1(), (float)gradientLine.getY1(), - shapeInfo.fillPath.transformY1(shapeInfo.from, shapeInfo.to), (float)gradientLine.getX2(), - (float)gradientLine.getY2(), shapeInfo.fillPath.transformY1(shapeInfo.to, shapeInfo.from))); - g2d.fill(shapeInfo.fillPath.getShape()); - } - - final Color tabColor = getActiveTabColor(null); - Color borderColor = tabColor == null ? UIUtil.getBoundsColor(paintFocused) : tabColor.darker(); - g2d.setColor(borderColor); - - if (!isHideTabs()) { - g2d.draw(shapeInfo.path.getShape()); - } - - paintBorder(g2d, shapeInfo, borderColor); - } - - private ShapeInfo computeSelectedLabelShape() { - final ShapeInfo shape = new ShapeInfo(); - - shape.path = getEffectiveLayout().createShapeTransform(getSize()); - shape.insets = shape.path.transformInsets(getLayoutInsets()); - shape.labelPath = shape.path.createTransform(getSelectedLabel().getBounds()); - - shape.labelBottomY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1); - shape.labelTopY = shape.labelPath.getY(); - shape.labelLeftX = shape.labelPath.getX(); - shape.labelRightX = shape.labelPath.getX() + shape.labelPath.deltaX(shape.labelPath.getWidth()); - - Insets border = myBorder.getEffectiveBorder(); - TabInfo selected = getSelectedInfo(); - boolean first = myLastLayoutPass.getPreviousFor(selected) == null; - boolean last = myLastLayoutPass.getNextFor(selected) == null; - - boolean leftEdge = !isSingleRow() && first && border.left == 0; - boolean rightEdge = !isSingleRow() && last && Boolean.TRUE.equals(myInfo2Label.get(selected).getClientProperty(STRETCHED_BY_WIDTH)) && border.right == 0; - - if (leftEdge) { - shape.path.moveTo(shape.insets.left, shape.labelTopY + shape.labelPath.deltaY(getEdgeArcSize())); - shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getEdgeArcSize()), shape.labelTopY); - shape.path.lineTo(shape.labelRightX - shape.labelPath.deltaX(getArcSize()), shape.labelTopY); - } else { - shape.path.moveTo(shape.insets.left, shape.labelBottomY); - shape.path.lineTo(shape.labelLeftX, shape.labelBottomY); - shape.path.lineTo(shape.labelLeftX, shape.labelTopY + shape.labelPath.deltaY(getArcSize())); - shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getArcSize()), shape.labelTopY); - } - - int lastX = shape.path.getWidth() - shape.path.deltaX(shape.insets.right + 1); - - if (isStealthModeEffective()) { - shape.path.lineTo(lastX - shape.path.deltaX(getArcSize()), shape.labelTopY); - shape.path.quadTo(lastX, shape.labelTopY, lastX, shape.labelTopY + shape.path.deltaY(getArcSize())); - shape.path.lineTo(lastX, shape.labelBottomY); - } - else { - if (rightEdge) { - shape.path.lineTo(shape.labelRightX + 1 - shape.path.deltaX(getArcSize()), shape.labelTopY); - shape.path.quadTo(shape.labelRightX + 1, shape.labelTopY, shape.labelRightX + 1, shape.labelTopY + shape.path.deltaY(getArcSize())); - } else { - shape.path.lineTo(shape.labelRightX - shape.path.deltaX(getArcSize()), shape.labelTopY); - shape.path.quadTo(shape.labelRightX, shape.labelTopY, shape.labelRightX, shape.labelTopY + shape.path.deltaY(getArcSize())); - } - if (myLastLayoutPass.hasCurveSpaceFor(selected)) { - shape.path.lineTo(shape.labelRightX, shape.labelBottomY - shape.path.deltaY(getArcSize())); - shape.path.quadTo(shape.labelRightX, shape.labelBottomY, shape.labelRightX + shape.path.deltaX(getArcSize()), shape.labelBottomY); - } - else { - if (rightEdge) { - shape.path.lineTo(shape.labelRightX + 1, shape.labelBottomY); - } else { - shape.path.lineTo(shape.labelRightX, shape.labelBottomY); - } - } - } - - if (!rightEdge) { - shape.path.lineTo(lastX, shape.labelBottomY); - } - - if (isStealthModeEffective()) { - shape.path.closePath(); - } - - shape.fillPath = shape.path.copy(); - if (!isHideTabs()) { - shape.fillPath.lineTo(lastX, shape.labelBottomY + shape.fillPath.deltaY(1)); - shape.fillPath.lineTo(shape.labelLeftX, shape.labelBottomY + shape.fillPath.deltaY(1)); - shape.fillPath.closePath(); - } - return shape; - } - - private TabLabel getSelectedLabel() { - return myInfo2Label.get(getSelectedInfo()); - } - - static class ShapeInfo { - ShapeTransform path; - ShapeTransform fillPath; - ShapeTransform labelPath; - int labelBottomY; - int labelTopY; - int labelLeftX; - int labelRightX; - Insets insets; - Color from; - Color to; - } - - - private void paintFirstGhost(Graphics2D g2d) { - final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.firstGhost); - - int topX = path.getX() + path.deltaX(getCurveArc()); - int topY = path.getY() + path.deltaY(getSelectionTabVShift()); - int bottomX = path.getMaxX() + path.deltaX(1); - int bottomY = path.getMaxY() + path.deltaY(1); - - path.moveTo(topX, topY); - - final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0; - - if (isLeftFromSelection) { - path.lineTo(bottomX, topY); - } - else { - path.lineTo(bottomX - getArcSize(), topY); - path.quadTo(bottomX, topY, bottomX, topY + path.deltaY(getArcSize())); - } - - path.lineTo(bottomX, bottomY); - path.lineTo(topX, bottomY); - - path.quadTo(topX - path.deltaX(getCurveArc() * 2 - 1), bottomY - path.deltaY(Math.abs(bottomY - topY) / 4), topX, - bottomY - path.deltaY(Math.abs(bottomY - topY) / 2)); - - path.quadTo(topX + path.deltaX(getCurveArc() - 1), topY + path.deltaY(Math.abs(bottomY - topY) / 4), topX, topY); - - path.closePath(); - - g2d.setColor(getBackground()); - g2d.fill(path.getShape()); - - g2d.setColor(getBoundsColor()); - g2d.draw(path.getShape()); - - g2d.setColor(getTopBlickColor()); - g2d.drawLine(topX + path.deltaX(1), topY + path.deltaY(1), bottomX - path.deltaX(getArcSize()), topY + path.deltaY(1)); - - g2d.setColor(getRightBlockColor()); - g2d.drawLine(bottomX - path.deltaX(1), topY + path.deltaY(getArcSize()), bottomX - path.deltaX(1), bottomY - path.deltaY(1)); - } - - private void paintLastGhost(Graphics2D g2d) { - final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.lastGhost); - - int topX = path.getX() - path.deltaX(getArcSize()); - int topY = path.getY() + path.deltaY(getSelectionTabVShift()); - int bottomX = path.getMaxX() - path.deltaX(getCurveArc()); - int bottomY = path.getMaxY() + path.deltaY(1); - - path.moveTo(topX, topY); - path.lineTo(bottomX, topY); - path.quadTo(bottomX - getCurveArc(), topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2); - path.quadTo(bottomX + getCurveArc(), bottomY - (bottomY - topY) / 4, bottomX, bottomY); - path.lineTo(topX, bottomY); - - path.closePath(); - - g2d.setColor(getBackground()); - g2d.fill(path.getShape()); - - g2d.setColor(getBoundsColor()); - g2d.draw(path.getShape()); - - g2d.setColor(getTopBlickColor()); - g2d.drawLine(topX, topY + path.deltaY(1), bottomX - path.deltaX(getCurveArc()), topY + path.deltaY(1)); - } - - private int getCurveArc() { - return 2; - } - - private Color getBoundsColor() { - return Color.gray; - } - - private Color getRightBlockColor() { - return Color.lightGray; - } - - private Color getTopBlickColor() { - return Color.white; - } - - private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists, final boolean rightGhostExists) { - TabInfo selected = getSelectedInfo(); - if (myLastPaintedSelection == null || !myLastPaintedSelection.equals(selected)) { - List tabs = getTabs(); - for (TabInfo each : tabs) { - myInfo2Label.get(each).setInactiveStateImage(null); - } - } - - for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) { - for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) { - final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn); - if (getSelectedInfo() == each) { - continue; - } - paintNonSelected(g2d, each, leftGhostExists, rightGhostExists); - } - } - - myLastPaintedSelection = selected; - } - - private void paintNonSelected(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists, final boolean rightGhostExists) { - if (myDropInfo == each) return; - - final TabLabel label = myInfo2Label.get(each); - if (label.getBounds().width == 0) return; - - int imageInsets = getArcSize() + 1; - - Rectangle bounds = label.getBounds(); - - int x = bounds.x - imageInsets; - int y = bounds.y; - int width = bounds.width + imageInsets * 2 + 1; - int height = bounds.height + getArcSize() + 1; - - if (isToBufferPainting()) { - BufferedImage img = label.getInactiveStateImage(bounds); - - if (img == null) { - img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D imgG2d = img.createGraphics(); - imgG2d.addRenderingHints(g2d.getRenderingHints()); - doPaintInactictive(imgG2d, leftGhostExists, label, new Rectangle(imageInsets, 0, label.getWidth(), label.getHeight()), rightGhostExists); - imgG2d.dispose(); - } - - g2d.drawImage(img, x, y, width, height, null); - - label.setInactiveStateImage(img); - } else { - doPaintInactictive(g2d, leftGhostExists, label, label.getBounds(), rightGhostExists); - label.setInactiveStateImage(null); - } - } - - private boolean isToBufferPainting() { - return Registry.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint; - } - - private void doPaintInactictive(Graphics2D g2d, boolean leftGhostExists, TabLabel label, Rectangle effectiveBounds, boolean rightGhostExists) { - int tabIndex = myVisibleInfos.indexOf(label.getInfo()); - - final int arc = getArcSize(); - Color topBlickColor = getTopBlickColor(); - Color rightBlockColor = getRightBlockColor(); - Color boundsColor = getBoundsColor(); - Color backgroundColor = getBackground(); - - final Color tabColor = label.getInfo().getTabColor(); - if (tabColor != null) { - backgroundColor = tabColor; - boundsColor = tabColor.darker(); - topBlickColor = tabColor.brighter().brighter(); - rightBlockColor = tabColor; - } - - final TabInfo selected = getSelectedInfo(); - final int selectionTabVShift = getSelectionTabVShift(); - - - final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex)); - final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex)); - - - boolean firstShowing = prev == null; - if (!firstShowing && !leftGhostExists) { - firstShowing = myInfo2Label.get(prev).getBounds().width == 0; - } - - boolean lastShowing = next == null; - if (!lastShowing) { - lastShowing = myInfo2Label.get(next).getBounds().width == 0; - } - - boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1; - - Rectangle originalBounds = effectiveBounds; - final ShapeTransform shape = getEffectiveLayout().createShapeTransform(originalBounds); - - int leftX = firstShowing ? shape.getX() : shape.getX() - shape.deltaX(arc + 1); - int topY = shape.getY() + shape.deltaY(selectionTabVShift); - int rigthX = !lastShowing && leftFromSelection ? shape.getMaxX() + shape.deltaX(arc + 1) : shape.getMaxX(); - int bottomY = shape.getMaxY() + shape.deltaY(1); - - Insets border = myBorder.getEffectiveBorder(); - - if (border.left > 0 || leftGhostExists || !firstShowing) { - shape.moveTo(leftX, bottomY); - shape.lineTo(leftX, topY + shape.deltaY(arc)); - shape.quadTo(leftX, topY, leftX + shape.deltaX(arc), topY); - } else { - if (firstShowing) { - shape.moveTo(leftX, topY + shape.deltaY(getEdgeArcSize())); - shape.quadTo(leftX, topY, leftX + shape.deltaX(getEdgeArcSize()), topY); - } - } - - boolean rightEdge = false; - if (border.right > 0 || rightGhostExists || !lastShowing || !Boolean.TRUE.equals(label.getClientProperty(STRETCHED_BY_WIDTH))) { - shape.lineTo(rigthX - shape.deltaX(arc), topY); - shape.quadTo(rigthX, topY, rigthX, topY + shape.deltaY(arc)); - shape.lineTo(rigthX, bottomY); - } else { - if (lastShowing) { - shape.lineTo(rigthX - shape.deltaX(arc), topY); - shape.quadTo(rigthX + 1, topY, rigthX + 1, topY + shape.deltaY(arc)); - - shape.lineTo(rigthX + 1, bottomY); - rightEdge = true; - } - } - - if (!isSingleRow()) { - final TablePassInfo info = myTableLayout.myLastTableLayout; - if (!info.isInSelectionRow(label.getInfo())) { - shape.lineTo(rigthX, bottomY + shape.deltaY(getArcSize())); - shape.lineTo(leftX, bottomY + shape.deltaY(getArcSize())); - shape.lineTo(leftX, bottomY); - } - } - - if (!rightEdge) { - shape.lineTo(leftX, bottomY); - } - - g2d.setColor(backgroundColor); - g2d.fill(shape.getShape()); - - // TODO - - final Line2D.Float gradientLine = - shape.transformLine(0, topY, 0, topY + shape.deltaY((int) (shape.getHeight() / 1.5 ))); - - final GradientPaint gp = - new GradientPaint(gradientLine.x1, gradientLine.y1, - shape.transformY1(backgroundColor.brighter().brighter(), backgroundColor), - gradientLine.x2, gradientLine.y2, - shape.transformY1(backgroundColor, backgroundColor.brighter().brighter())); - final Paint old = g2d.getPaint(); - g2d.setPaint(gp); - g2d.fill(shape.getShape()); - g2d.setPaint(old); - - g2d.setColor(topBlickColor); - g2d.draw( - shape.transformLine(leftX + shape.deltaX(arc + 1), topY + shape.deltaY(1), rigthX - shape.deltaX(arc - 1), topY + shape.deltaY(1))); - - if (!rightEdge) { - g2d.setColor(rightBlockColor); - g2d.draw(shape.transformLine(rigthX - shape.deltaX(1), topY + shape.deltaY(arc - 1), rigthX - shape.deltaX(1), bottomY)); - } - - g2d.setColor(boundsColor); - g2d.draw(shape.getShape()); - } - - public int getSelectionTabVShift() { - return 2; - } - - private void paintBorder(Graphics2D g2d, ShapeInfo shape, final Color borderColor) { - - final ShapeTransform shaper = shape.path.copy().reset(); - - final Insets paintBorder = shape.path.transformInsets(myBorder.getEffectiveBorder()); - - int topY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1); - - int bottomY = topY + paintBorder.top - 2; - int middleY = topY + (bottomY - topY) / 2; - - - final int boundsX = shape.path.getX() + shape.path.deltaX(shape.insets.left); - - final int boundsY = - isHideTabs() ? shape.path.getY() + shape.path.deltaY(shape.insets.top) : shape.labelPath.getMaxY() + shape.path.deltaY(1); - - final int boundsHeight = Math.abs(shape.path.getMaxY() - boundsY) - shape.insets.bottom - paintBorder.bottom; - final int boundsWidth = Math.abs(shape.path.getMaxX() - (shape.insets.left + shape.insets.right)); - - if (paintBorder.top > 0) { - if (isHideTabs()) { - if (isToDrawBorderIfTabsHidden()) { - g2d.setColor(borderColor); - g2d.fill(shaper.reset().doRect(boundsX, boundsY, boundsWidth, 1).getShape()); - } - } - else { - Color tabFillColor = getActiveTabColor(null); - if (tabFillColor == null) { - tabFillColor = shape.path.transformY1(shape.to, shape.from); - } - - g2d.setColor(tabFillColor); - g2d.fill(shaper.reset().doRect(boundsX, topY + shape.path.deltaY(1), boundsWidth, paintBorder.top - 1).getShape()); - - g2d.setColor(borderColor); - if (paintBorder.top == 2) { - final Line2D.Float line = shape.path.transformLine(boundsX, topY, boundsX + shape.path.deltaX(boundsWidth - 1), topY); - - g2d.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2); - } - else if (paintBorder.top > 2) { -//todo kirillk -//start hack - int deltaY = 0; - if (myPosition == JBTabsPosition.bottom || myPosition == JBTabsPosition.right) { - deltaY = 1; - } -//end hack - final int topLine = topY + shape.path.deltaY(paintBorder.top - 1); - g2d.fill(shaper.reset().doRect(boundsX, topLine + deltaY, boundsWidth - 1, 1).getShape()); - } - } - } - - g2d.setColor(borderColor); - - //bottom - g2d.fill(shaper.reset().doRect(boundsX, Math.abs(shape.path.getMaxY() - shape.insets.bottom - paintBorder.bottom), boundsWidth, - paintBorder.bottom).getShape()); - - //left - g2d.fill(shaper.reset().doRect(boundsX, boundsY, paintBorder.left, boundsHeight).getShape()); - - //right - g2d.fill(shaper.reset() - .doRect(shape.path.getMaxX() - shape.insets.right - paintBorder.right, boundsY, paintBorder.right, boundsHeight).getShape()); - - } - - public boolean isStealthModeEffective() { - return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition.top; - } - - - private boolean isNavigationVisible() { - if (myStealthTabMode && getTabCount() == 1) return false; - return !myVisibleInfos.isEmpty(); - } - - - public void paint(final Graphics g) { - Rectangle clip = g.getClipBounds(); - if (clip == null) { - return; - } - - if (myPaintBlocked) { - if (myImage != null) { - g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null); - } - return; - } - - super.paint(g); - } - - protected void paintChildren(final Graphics g) { - super.paintChildren(g); - - final GraphicsConfig config = new GraphicsConfig(g); - config.setAntialiasing(true); - paintSelectionAndBorder((Graphics2D)g); - config.restore(); - - final TabLabel selected = getSelectedLabel(); - if (selected != null) { - selected.paintImage(g); - } - - mySingleRowLayout.myMoreIcon.paintIcon(this, g); - } - - private Max computeMaxSize() { - Max max = new Max(); - for (TabInfo eachInfo : myVisibleInfos) { - final TabLabel label = myInfo2Label.get(eachInfo); - max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height); - max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width); - final Toolbar toolbar = myInfo2Toolbar.get(eachInfo); - if (myLayout.isSideComponentOnTabs() && toolbar != null && !toolbar.isEmpty()) { - max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height); - max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width); - } - } - - max.myToolbar.height++; - - return max; - } - - public Dimension getMinimumSize() { - return computeSize(new Transform() { - public Dimension transform(JComponent component) { - return component.getMinimumSize(); - } - }, 1); - } - - public Dimension getPreferredSize() { - return computeSize(new Transform() { - public Dimension transform(JComponent component) { - return component.getPreferredSize(); - } - }, 3); - } - - private Dimension computeSize(Transform transform, int tabCount) { - Dimension size = new Dimension(); - for (TabInfo each : myVisibleInfos) { - final JComponent c = each.getComponent(); - if (c != null) { - final Dimension eachSize = transform.transform(c); - size.width = Math.max(eachSize.width, size.width); - size.height = Math.max(eachSize.height, size.height); - } - } - - addHeaderSize(size, tabCount); - return size; - } - - private void addHeaderSize(Dimension size, final int tabsCount) { - Dimension header = computeHeaderPreferredSize(tabsCount); - - size.height += header.height; - size.width += header.width; - - final Insets insets = getLayoutInsets(); - size.width += insets.left + insets.right + 1; - size.height += insets.top + insets.bottom + 1; - } - - private Dimension computeHeaderPreferredSize(int tabsCount) { - final Iterator infos = myInfo2Label.keySet().iterator(); - Dimension size = new Dimension(); - int currentTab = 0; - - final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom; - - while (infos.hasNext()) { - final boolean canGrow = currentTab < tabsCount; - - TabInfo eachInfo = infos.next(); - final TabLabel eachLabel = myInfo2Label.get(eachInfo); - final Dimension eachPrefSize = eachLabel.getPreferredSize(); - if (horizontal) { - if (canGrow) { - size.width += eachPrefSize.width; - } - size.height = Math.max(size.height, eachPrefSize.height); - } - else { - size.width = Math.max(size.width, eachPrefSize.width); - if (canGrow) { - size.height += eachPrefSize.height; - } - } - - currentTab++; - } - - if (isSingleRow() && isGhostsAlwaysVisible()) { - if (horizontal) { - size.width += getGhostTabLength() * 2; - } - else { - size.height += getGhostTabLength() * 2; - } - } - - if (horizontal) { - size.height += myBorder.getTabBorderSize(); - } - else { - size.width += myBorder.getTabBorderSize(); - } - - return size; - } - - public int getTabCount() { - return getTabs().size(); - } - - @NotNull - public JBTabsPresentation getPresentation() { - return this; - } - - public ActionCallback removeTab(final JComponent component) { - return removeTab(findInfo(component)); - } - - public ActionCallback removeTab(final TabInfo info) { - return removeTab(info, null); - } - - public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer) { - return removeTab(info, forcedSelectionTranfer, true); - } - - public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer, boolean transferFocus) { - return removeTab(info, forcedSelectionTranfer, transferFocus, false); - } - - private ActionCallback removeTab(TabInfo info, TabInfo forcedSelectionTranfer, boolean transferFocus, boolean isDropTarget) { - if (!isDropTarget) { - if (info == null || !getTabs().contains(info)) return new ActionCallback.Done(); - } - - if (isDropTarget && myLastLayoutPass != null) { - myLastLayoutPass.myVisibleInfos.remove(info); - } - - final ActionCallback result = new ActionCallback(); - - TabInfo toSelect; - if (forcedSelectionTranfer == null) { - toSelect = getToSelectOnRemoveOf(info); - } - else { - assert myVisibleInfos.contains(forcedSelectionTranfer) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer; - toSelect = forcedSelectionTranfer; - } - - - if (toSelect != null) { - processRemove(info, false); - _setSelected(toSelect, transferFocus).doWhenProcessed(new Runnable() { - public void run() { - removeDeferred().notifyWhenDone(result); - } - }); - } - else { - processRemove(info, true); - removeDeferred().notifyWhenDone(result); - } - - if (myVisibleInfos.isEmpty()) { - removeDeferredNow(); - } - - revalidateAndRepaint(true); - - return result; - } - - private void processRemove(final TabInfo info, boolean forcedNow) { - remove(myInfo2Label.get(info)); - remove(myInfo2Toolbar.get(info)); - - JComponent tabComponent = info.getComponent(); - - if (!isToDeferRemoveForLater(tabComponent) || forcedNow) { - remove(tabComponent); - } - else { - queueForRemove(tabComponent); - } - - myVisibleInfos.remove(info); - myHiddenInfos.remove(info); - myInfo2Label.remove(info); - myInfo2Toolbar.remove(info); - resetTabsCache(); - - updateAll(false, false); - } - - public TabInfo findInfo(Component component) { - for (TabInfo each : getTabs()) { - if (each.getComponent() == component) return each; - } - - return null; - } - - public TabInfo findInfo(String text) { - if (text == null) return null; - - for (TabInfo each : getTabs()) { - if (text.equals(each.getText())) return each; - } - - return null; - } - - public TabInfo findInfo(MouseEvent event) { - return findInfo(event, false); - } - - private TabInfo findInfo(final MouseEvent event, final boolean labelsOnly) { - final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this); - return _findInfo(point, labelsOnly); - } - - public TabInfo findInfo(final Object object) { - for (int i = 0; i < getTabCount(); i++) { - final TabInfo each = getTabAt(i); - final Object eachObject = each.getObject(); - if (eachObject != null && eachObject.equals(object)) return each; - } - return null; - } - - public TabInfo findTabLabelBy(final Point point) { - return _findInfo(point, true); - } - - private TabInfo _findInfo(final Point point, boolean labelsOnly) { - Component component = findComponentAt(point); - if (component == null) return null; - while (component != this || component != null) { - if (component instanceof TabLabel) { - return ((TabLabel)component).getInfo(); - } - else if (!labelsOnly) { - final TabInfo info = findInfo(component); - if (info != null) return info; - } - if (component == null) break; - component = component.getParent(); - } - - return null; - } - - public void removeAllTabs() { - for (TabInfo each : getTabs()) { - removeTab(each); - } - } - - - private static class Max { - Dimension myLabel = new Dimension(); - Dimension myToolbar = new Dimension(); - } - - private void updateContainer(boolean forced, final boolean layoutNow) { - final TabLabel selectedLabel = getSelectedLabel(); - - for (TabInfo each : myVisibleInfos) { - final JComponent eachComponent = each.getComponent(); - if (getSelectedInfo() == each && getSelectedInfo() != null) { - unqueueFromRemove(eachComponent); - - final Container parent = eachComponent.getParent(); - if (parent != null && parent != this) { - parent.remove(eachComponent); - } - - if (eachComponent.getParent() == null) { - add(eachComponent); - } - } - else { - if (eachComponent.getParent() == null) continue; - if (isToDeferRemoveForLater(eachComponent)) { - queueForRemove(eachComponent); - } - else { - remove(eachComponent); - } - } - } - - relayout(forced, layoutNow); - } - - protected void addImpl(final Component comp, final Object constraints, final int index) { - unqueueFromRemove(comp); - - if (comp instanceof TabLabel) { - ((TabLabel)comp).apply(myUiDecorator.getDecoration()); - } - - super.addImpl(comp, constraints, index); - } - - - private boolean isToDeferRemoveForLater(JComponent c) { - return c.getRootPane() != null; - } - - void relayout(boolean forced, final boolean layoutNow) { - if (!myForcedRelayout) { - myForcedRelayout = forced; - } - revalidateAndRepaint(layoutNow); - } - - ActionManager getActionManager() { - return myActionManager; - } - - public TabsBorder getTabsBorder() { - return myBorder; - } - - @NotNull - public JBTabs addTabMouseMotionListener(@NotNull MouseMotionListener listener) { - removeListeners(); - myTabMouseListeners.add(listener); - addListeners(); - return this; - } - - @NotNull - public JBTabs addTabMouseListener(@NotNull MouseListener listener) { - removeListeners(); - myTabMouseListeners.add(listener); - addListeners(); - return this; - } - - @NotNull - public JComponent getComponent() { - return this; - } - - public boolean isCycleRoot() { - return false; - } - - @NotNull - public JBTabs removeTabMouseListener(@NotNull MouseListener listener) { - removeListeners(); - myTabMouseListeners.remove(listener); - addListeners(); - return this; - } - - private void addListeners() { - for (TabInfo eachInfo : myVisibleInfos) { - final TabLabel label = myInfo2Label.get(eachInfo); - for (EventListener eachListener : myTabMouseListeners) { - if (eachListener instanceof MouseListener) { - label.addMouseListener((MouseListener)eachListener); - } - else if (eachListener instanceof MouseMotionListener) { - label.addMouseMotionListener((MouseMotionListener)eachListener); - } - else { - assert false; - } - } - } - } - - private void removeListeners() { - for (TabInfo eachInfo : myVisibleInfos) { - final TabLabel label = myInfo2Label.get(eachInfo); - for (EventListener eachListener : myTabMouseListeners) { - if (eachListener instanceof MouseListener) { - label.removeMouseListener((MouseListener)eachListener); - } - else if (eachListener instanceof MouseMotionListener) { - label.removeMouseMotionListener((MouseMotionListener)eachListener); - } - else { - assert false; - } - } - } - } - - private void updateListeners() { - removeListeners(); - addListeners(); - } - - public JBTabs addListener(@NotNull TabsListener listener) { - myTabListeners.add(listener); - return this; - } - - public JBTabs removeListener(@NotNull final TabsListener listener) { - myTabListeners.remove(listener); - return this; - } - - protected void onPopup(final TabInfo popupInfo) { - } - - public void setFocused(final boolean focused) { - if (myFocused == focused) return; - - myFocused = focused; - - if (myPaintFocus) { - repaint(); - } - } - - public int getIndexOf(@Nullable final TabInfo tabInfo) { - return myVisibleInfos.indexOf(tabInfo); - } - - public boolean isHideTabs() { - return myHideTabs; - } - - public void setHideTabs(final boolean hideTabs) { - if (isHideTabs() == hideTabs) return; - - myHideTabs = hideTabs; - - relayout(true, false); - } - - public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) { - return myBorder.setPaintBorder(top, left, right, bottom); - } - - public JBTabsPresentation setTabSidePaintBorder(int size) { - return myBorder.setTabSidePaintBorder(size); - } - - static int getBorder(int size) { - return size == -1 ? 1 : size; - } - - public boolean isPaintFocus() { - return myPaintFocus; - } - - @NotNull - public JBTabsPresentation setAdjustBorders(final boolean adjust) { - myAdjustBorders = adjust; - return this; - } - - @NotNull - public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) { - if (!isChanged(myActiveTabFillIn, color)) return this; - - myActiveTabFillIn = color; - revalidateAndRepaint(false); - return this; - } - - private boolean isChanged(Object oldObject, Object newObject) { - if (oldObject == null && newObject == null) return false; - return oldObject != null && !oldObject.equals(newObject) || newObject != null && !newObject.equals(oldObject); - } - - @NotNull - public JBTabsPresentation setTabLabelActionsAutoHide(final boolean autoHide) { - if (myTabLabelActionsAutoHide != autoHide) { - myTabLabelActionsAutoHide = autoHide; - revalidateAndRepaint(false); - } - return this; - } - - @Nullable - public Color getActiveTabFillIn() { - return myActiveTabFillIn; - } - - public JBTabsPresentation setFocusCycle(final boolean root) { - setFocusCycleRoot(root); - return this; - } - - - public JBTabsPresentation setPaintFocus(final boolean paintFocus) { - myPaintFocus = paintFocus; - return this; - } - - private abstract static class BaseNavigationAction extends AnAction { - - private final ShadowAction myShadow; - private final ActionManager myActionManager; - private final JBTabsImpl myTabs; - - protected BaseNavigationAction(final String copyFromID, JBTabsImpl tabs, ActionManager mgr) { - myActionManager = mgr; - myTabs = tabs; - myShadow = new ShadowAction(this, myActionManager.getAction(copyFromID), tabs); - Disposer.register(tabs, myShadow); - setEnabledInModalContext(true); - } - - public final void update(final AnActionEvent e) { - JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY); - e.getPresentation().setVisible(tabs != null); - if (tabs == null) return; - - final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo()); - final boolean enabled = tabs == myTabs && myTabs.isNavigationVisible() && selectedIndex >= 0 && myTabs.myNavigationActionsEnabled; - e.getPresentation().setEnabled(enabled); - if (enabled) { - _update(e, tabs, selectedIndex); - } - } - - public void reconnect(String actionId) { - myShadow.reconnect(myActionManager.getAction(actionId)); - } - - protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex); - - public final void actionPerformed(final AnActionEvent e) { - JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY); - if (tabs == null) return; - - final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo()); - if (index == -1) return; - _actionPerformed(e, tabs, index); - } - - protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex); - } - - private static class SelectNextAction extends BaseNavigationAction { - - private SelectNextAction(JBTabsImpl tabs, ActionManager mgr) { - super(IdeActions.ACTION_NEXT_TAB, tabs, mgr); - } - - protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) { - e.getPresentation().setEnabled(tabs.findEnabledForward(selectedIndex + 1) != null); - } - - protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) { - tabs.select(tabs.findEnabledForward(selectedIndex + 1), true); - } - } - - private static class SelectPreviousAction extends BaseNavigationAction { - private SelectPreviousAction(JBTabsImpl tabs, ActionManager mgr) { - super(IdeActions.ACTION_PREVIOUS_TAB, tabs, mgr); - } - - protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) { - e.getPresentation().setEnabled(tabs.findEnabledBackward(selectedIndex - 1) != null); - } - - protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) { - tabs.select(tabs.findEnabledBackward(selectedIndex - 1), true); - } - } - - private void disposePopupListener() { - if (myActivePopup != null) { - myActivePopup.removePopupMenuListener(myPopupListener); - myActivePopup = null; - } - } - - public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) { - myStealthTabMode = stealthTabMode; - - relayout(true, false); - - return this; - } - - public boolean isStealthTabMode() { - return myStealthTabMode; - } - - public JBTabsPresentation setSideComponentVertical(final boolean vertical) { - myHorizontalSide = !vertical; - - for (TabInfo each : myVisibleInfos) { - each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2"); - } - - - relayout(true, false); - - return this; - } - - public JBTabsPresentation setSingleRow(boolean singleRow) { - myLayout = singleRow ? mySingleRowLayout : myTableLayout; - - relayout(true, false); - - return this; - } - - public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) { - myGhostsAlwaysVisible = visible; - - relayout(true, false); - - return this; - } - - public boolean isGhostsAlwaysVisible() { - return myGhostsAlwaysVisible; - } - - public boolean isSingleRow() { - return getEffectiveLayout() == mySingleRowLayout; - } - - public boolean isSideComponentVertical() { - return !myHorizontalSide; - } - - public TabLayout getEffectiveLayout() { - if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout; - return mySingleRowLayout; - } - - public JBTabsPresentation setUiDecorator(UiDecorator decorator) { - myUiDecorator = decorator == null ? ourDefaultDecorator : decorator; - applyDecoration(); - return this; - } - - protected void setUI(final ComponentUI newUI) { - super.setUI(newUI); - applyDecoration(); - } - - public void updateUI() { - super.updateUI(); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - applyDecoration(); - - revalidateAndRepaint(false); - } - }); - } - - private void applyDecoration() { - if (myUiDecorator != null) { - UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration(); - for (TabLabel each : myInfo2Label.values()) { - each.apply(uiDecoration); - } - } - - - for (TabInfo each : getTabs()) { - adjust(each); - } - - relayout(true, false); - } - - private void adjust(final TabInfo each) { - if (myAdjustBorders) { - UIUtil.removeScrollBorder(each.getComponent()); - } - } - - public void sortTabs(Comparator comparator) { - Collections.sort(myVisibleInfos, comparator); - - relayout(true, false); - } - - public boolean isRequestFocusOnLastFocusedComponent() { - return myRequestFocusOnLastFocusedComponent; - } - - public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) { - myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent; - return this; - } - - - @Nullable - public Object getData(@NonNls final String dataId) { - if (myDataProvider != null) { - final Object value = myDataProvider.getData(dataId); - if (value != null) return value; - } - - if (SwitchProvider.KEY.getName().equals(dataId) && myOwnSwitchProvider) { - return this; - } - - if (QuickActionProvider.KEY.getName().equals(dataId)) { - return this; - } - - return NAVIGATION_ACTIONS_KEY.is(dataId) ? this : null; - } - - public List getActions(boolean originalProvider) { - ArrayList result = new ArrayList(); - - TabInfo selection = getSelectedInfo(); - if (selection != null) { - ActionGroup group = selection.getGroup(); - if (group != null) { - AnAction[] children = group.getChildren(null); - for (int i = 0; i < children.length; i++) { - result.add(children[i]); - } - } - } - - return result; - } - - public DataProvider getDataProvider() { - return myDataProvider; - } - - public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) { - myDataProvider = dataProvider; - return this; - } - - - public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) { - if (e.getClickCount() == 1 || canBeQuick) { - if (!e.isPopupTrigger()) { - return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown(); - } - } - - return false; - } - - - private static class DefautDecorator implements UiDecorator { - @NotNull - public UiDecoration getDecoration() { - return new UiDecoration(null, new Insets(1, 4, 1, 5)); - } - } - - public static Rectangle layout(JComponent c, Rectangle bounds) { - final Rectangle now = c.getBounds(); - if (!bounds.equals(now)) { - c.setBounds(bounds); - } - c.putClientProperty(LAYOUT_DONE, Boolean.TRUE); - - return bounds; - } - - public static Rectangle layout(JComponent c, int x, int y, int width, int height) { - return layout(c, new Rectangle(x, y, width, height)); - } - - public static void resetLayout(JComponent c) { - if (c == null) return; - c.putClientProperty(LAYOUT_DONE, null); - c.putClientProperty(STRETCHED_BY_WIDTH, null); - } - - private void applyResetComponents() { - for (int i = 0; i < getComponentCount(); i++) { - final Component each = getComponent(i); - if (each instanceof JComponent) { - final JComponent jc = (JComponent)each; - final Object done = jc.getClientProperty(LAYOUT_DONE); - if (!Boolean.TRUE.equals(done)) { - layout(jc, new Rectangle(0, 0, 0, 0)); - } - } - } - } - - - @NotNull - public JBTabsPresentation setTabLabelActionsMouseDeadzone(final TimedDeadzone.Length length) { - myTabActionsMouseDeadzone = length; - final List all = getTabs(); - for (TabInfo each : all) { - final TabLabel eachLabel = myInfo2Label.get(each); - eachLabel.updateTabActions(); - } - return this; - } - - @NotNull - public JBTabsPresentation setTabsPosition(final JBTabsPosition position) { - myPosition = position; - relayout(true, false); - return this; - } - - public JBTabsPosition getTabsPosition() { - return myPosition; - } - - public TimedDeadzone.Length getTabActionsMouseDeadzone() { - return myTabActionsMouseDeadzone; - } - - public JBTabsPresentation setTabDraggingEnabled(boolean enabled) { - myTabDraggingEnabled = enabled; - return this; - } - - public boolean isTabDraggingEnabled() { - return myTabDraggingEnabled; - } - - public JBTabsPresentation setProvideSwitchTargets(boolean provide) { - myOwnSwitchProvider = provide; - return this; - } - - void reallocate(TabInfo source, TabInfo target, boolean before) { - if (source == target || source == null || target == null) return; - - final int targetIndex = myVisibleInfos.indexOf(target); - final int sourceIndex = myVisibleInfos.indexOf(source); - - boolean needsValidation = false; - - myVisibleInfos.remove(source); - myVisibleInfos.add(targetIndex, source); - needsValidation = true; - - //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) { - //} - - if (needsValidation) { - invalidate(); - relayout(true, true); - } - } - - boolean isHorizontalTabs() { - return getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom; - } - - public void putInfo(Map info) { - final TabInfo selected = getSelectedInfo(); - if (selected != null) { - selected.putInfo(info); - } - } - - public boolean isUseBufferedPaint() { - return myUseBufferedPaint; - } - - public void setUseBufferedPaint(boolean useBufferedPaint) { - myUseBufferedPaint = useBufferedPaint; - revalidate(); - repaint(); - } - - public List getTargets(boolean onlyVisible, boolean originalProvider) { - ArrayList result = new ArrayList(); - for (TabInfo each : myVisibleInfos) { - result.add(new TabTarget(each)); - } - - if (originalProvider && mySwitchDelegate != null) { - List additional = mySwitchDelegate.getTargets(onlyVisible, false); - if (additional != null) { - result.addAll(additional); - } - } - - return result; - } - - - public SwitchTarget getCurrentTarget() { - if (mySwitchDelegate != null) { - SwitchTarget selection = mySwitchDelegate.getCurrentTarget(); - if (selection != null) return selection; - } - - return new TabTarget(getSelectedInfo()); - } - - private class TabTarget extends ComparableObject.Impl implements SwitchTarget { - - private TabInfo myInfo; - - private TabTarget(TabInfo info) { - myInfo = info; - } - - public ActionCallback switchTo(boolean requestFocus) { - return select(myInfo, requestFocus); - } - - public boolean isVisible() { - return getRectangle() != null; - } - - public RelativeRectangle getRectangle() { - TabLabel label = myInfo2Label.get(myInfo); - if (label.getRootPane() == null) return null; - - Rectangle b = label.getBounds(); - b.x += 2; - b.width -= 4; - b.y += 2; - b.height -= 4; - return new RelativeRectangle(label.getParent(), b); - } - - public Component getComponent() { - return myInfo2Label.get(myInfo); - } - - @Override - public String toString() { - return myInfo.getText(); - } - - @Override - public Object[] getEqualityObjects() { - return new Object[] {myInfo}; - } - } - - @Override - public void resetDropOver(TabInfo tabInfo) { - if (myDropInfo != null) { - TabInfo dropInfo = myDropInfo; - myDropInfo = null; - setDropInfoIndex(-1); - removeTab(dropInfo, null, false, true); - } - } - - @Override - public Image startDropOver(TabInfo tabInfo, RelativePoint point) { - myDropInfo = tabInfo; - - int index = myLayout.getDropIndexFor(point.getPoint(this)); - setDropInfoIndex(index); - addTab(myDropInfo, index, true); - - TabLabel label = myInfo2Label.get(myDropInfo); - Dimension size = label.getPreferredSize(); - label.setBounds(0, 0, size.width, size.height); - - BufferedImage img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = img.createGraphics(); - label.paintOffscreen(g); - g.dispose(); - - relayout(true, false); - - return img; - } - - @Override - public void processDropOver(TabInfo over, RelativePoint point) { - int index = myLayout.getDropIndexFor(point.getPoint(this)); - if (index != getDropInfoIndex()) { - setDropInfoIndex(index); - relayout(true, false); - } - } - - public int getDropInfoIndex() { - return myDropInfoIndex; - } - - @Override - public boolean isEmptyVisible() { - return myVisibleInfos.isEmpty(); - } -} +/* + * 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.ui.tabs.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Queryable; +import com.intellij.openapi.ui.ShadowAction; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.FocusCommand; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.IdeGlassPane; +import com.intellij.openapi.wm.IdeGlassPaneUtil; +import com.intellij.openapi.wm.impl.content.GraphicsConfig; +import com.intellij.ui.CaptionPanel; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.awt.RelativeRectangle; +import com.intellij.ui.switcher.QuickActionProvider; +import com.intellij.ui.switcher.SwitchProvider; +import com.intellij.ui.switcher.SwitchTarget; +import com.intellij.ui.tabs.*; +import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout; +import com.intellij.ui.tabs.impl.singleRow.SingleRowPassInfo; +import com.intellij.ui.tabs.impl.table.TableLayout; +import com.intellij.ui.tabs.impl.table.TablePassInfo; +import com.intellij.util.ui.Animator; +import com.intellij.util.ui.TimedDeadzone; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.ComparableObject; +import com.intellij.util.ui.update.LazyUiDisposable; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.plaf.ComponentUI; +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.Line2D; +import java.awt.image.BufferedImage; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.*; +import java.util.List; + +public class JBTabsImpl extends JComponent + implements JBTabs, PropertyChangeListener, TimerListener, DataProvider, PopupMenuListener, Disposable, JBTabsPresentation, Queryable, QuickActionProvider { + + static DataKey NAVIGATION_ACTIONS_KEY = DataKey.create("JBTabs"); + + ActionManager myActionManager; + private final List myVisibleInfos = new ArrayList(); + private final Map myHiddenInfos = new HashMap(); + + private TabInfo mySelectedInfo; + public final Map myInfo2Label = new HashMap(); + public final Map myInfo2Toolbar = new HashMap(); + public Dimension myHeaderFitSize; + + private Insets myInnerInsets = new Insets(0, 0, 0, 0); + + private final List myTabMouseListeners = new ArrayList(); + private final List myTabListeners = new ArrayList(); + public boolean myFocused; + + private Getter myPopupGroup; + private String myPopupPlace; + + TabInfo myPopupInfo; + DefaultActionGroup myNavigationActions; + + PopupMenuListener myPopupListener; + JPopupMenu myActivePopup; + + public boolean myHorizontalSide = true; + + private boolean myStealthTabMode = false; + + private DataProvider myDataProvider; + + private final WeakHashMap myDeferredToRemove = new WeakHashMap(); + + private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this); + private final TableLayout myTableLayout = new TableLayout(this); + + + private TabLayout myLayout = mySingleRowLayout; + LayoutPassInfo myLastLayoutPass; + private TabInfo myLastPaintedSelection; + + public boolean myForcedRelayout; + + private UiDecorator myUiDecorator; + static final UiDecorator ourDefaultDecorator = new DefautDecorator(); + + private boolean myPaintFocus; + + private boolean myHideTabs = false; + @Nullable private Project myProject; + + private boolean myRequestFocusOnLastFocusedComponent = false; + private boolean myListenerAdded; + final Set myAttractions = new HashSet(); + private Animator myAnimator; + private List myAllTabs; + private boolean myPaintBlocked; + private BufferedImage myImage; + private IdeFocusManager myFocusManager; + private boolean myAdjustBorders = true; + + boolean myAddNavigationGroup = true; + + private boolean myGhostsAlwaysVisible = false; + private boolean myDisposed; + private boolean myToDrawBorderIfTabsHidden = true; + private Color myActiveTabFillIn; + + private boolean myTabLabelActionsAutoHide; + + private final TabActionsAutoHideListener myTabActionsAutoHideListener = new TabActionsAutoHideListener(); + private IdeGlassPane myGlassPane; + @NonNls private static final String LAYOUT_DONE = "Layout.done"; + @NonNls public static final String STRETCHED_BY_WIDTH = "Layout.stretchedByWidth"; + + private TimedDeadzone.Length myTabActionsMouseDeadzone = TimedDeadzone.DEFAULT; + + private long myRemoveDefferredRequest; + private boolean myTestMode; + + private JBTabsPosition myPosition = JBTabsPosition.top; + + private final TabsBorder myBorder = new TabsBorder(this); + private BaseNavigationAction myNextAction; + private BaseNavigationAction myPrevAction; + + private boolean myWasEverShown; + + private boolean myTabDraggingEnabled; + private DragHelper myDragHelper; + private boolean myNavigationActionsEnabled = true; + private boolean myUseBufferedPaint = true; + + private boolean myOwnSwitchProvider = true; + private SwitchProvider mySwitchDelegate; + private TabInfo myDropInfo; + private int myDropInfoIndex; + + public JBTabsImpl(@NotNull Project project) { + this(project, project); + } + + public JBTabsImpl(@NotNull Project project, @NotNull Disposable parent) { + this(project, ActionManager.getInstance(), IdeFocusManager.getInstance(project), parent); + } + + public JBTabsImpl(@Nullable Project project, IdeFocusManager focusManager, @NotNull Disposable parent) { + this(project, ActionManager.getInstance(), focusManager, parent); + } + + public JBTabsImpl(@Nullable Project project, ActionManager actionManager, IdeFocusManager focusManager, @NotNull Disposable parent) { + myProject = project; + myActionManager = actionManager; + myFocusManager = focusManager != null ? focusManager : IdeFocusManager.getGlobalInstance(); + + setOpaque(true); + setPaintBorder(-1, -1, -1, -1); + + Disposer.register(parent, this); + + myNavigationActions = new DefaultActionGroup(); + + if (myActionManager != null) { + myNextAction = new SelectNextAction(this, myActionManager); + myPrevAction = new SelectPreviousAction(this, myActionManager); + + myNavigationActions.add(myNextAction); + myNavigationActions.add(myPrevAction); + } + + setUiDecorator(null); + + myPopupListener = new PopupMenuListener() { + public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { + } + + public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { + disposePopupListener(); + } + + public void popupMenuCanceled(final PopupMenuEvent e) { + disposePopupListener(); + } + }; + + addMouseListener(new MouseAdapter() { + public void mousePressed(final MouseEvent e) { + if (mySingleRowLayout.myLastSingRowLayout != null && + mySingleRowLayout.myLastSingRowLayout.moreRect != null && + mySingleRowLayout.myLastSingRowLayout.moreRect.contains(e.getPoint())) { + showMorePopup(e); + } + } + }); + + myAnimator = new Animator("JBTabs Attractions", 2, 500, true, 0, -1) { + public void paintNow(final float frame, final float totalFrames, final float cycle) { + repaintAttractions(); + } + }; + myAnimator.setTakInitialDelay(false); + + setFocusCycleRoot(true); + setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { + public Component getDefaultComponent(final Container aContainer) { + return getToFocus(); + } + }); + + add(mySingleRowLayout.myLeftGhost); + add(mySingleRowLayout.myRightGhost); + + + new LazyUiDisposable(parent, this, this) { + protected void initialize(@NotNull Disposable parent, @NotNull JBTabsImpl child, @Nullable Project project) { + myProject = project; + + Disposer.register(child, myAnimator); + Disposer.register(child, new Disposable() { + public void dispose() { + removeTimerUpdate(); + } + }); + + if (!myTestMode) { + final IdeGlassPane gp = IdeGlassPaneUtil.find(child); + if (gp != null) { + gp.addMouseMotionPreprocessor(myTabActionsAutoHideListener, child); + myGlassPane = gp; + } + + UIUtil.addAwtListener(new AWTEventListener() { + public void eventDispatched(final AWTEvent event) { + if (mySingleRowLayout.myMorePopup != null) return; + processFocusChange(); + } + }, AWTEvent.FOCUS_EVENT_MASK, child); + + myDragHelper = new DragHelper(child); + myDragHelper.start(); + } + + if (myProject != null && myFocusManager == IdeFocusManager.getGlobalInstance()) { + myFocusManager = IdeFocusManager.getInstance(myProject); + } + } + }; + } + + + public JBTabs setNavigationActiondBinding(String prevActionId, String nextActionId) { + if (myNextAction != null) { + myNextAction.reconnect(nextActionId); + } + if (myPrevAction != null) { + myPrevAction.reconnect(prevActionId); + } + + return this; + } + + public JBTabs setNavigationActionsEnabled(boolean enabled) { + myNavigationActionsEnabled = enabled; + return this; + } + + public final boolean isDisposed() { + return myDisposed; + } + + public JBTabs setAdditinalSwitchProviderWhenOriginal(SwitchProvider delegate) { + mySwitchDelegate = delegate; + return this; + } + + @Override + public Image getComponentImage(TabInfo info) { + JComponent cmp = info.getComponent(); + + BufferedImage img; + if (cmp.isShowing()) { + img = new BufferedImage(cmp.getWidth(), cmp.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + cmp.paint(g); + } else { + img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); + } + return img; + } + + public void dispose() { + myDisposed = true; + mySelectedInfo = null; + resetTabsCache(); + myAttractions.clear(); + myVisibleInfos.clear(); + myUiDecorator = null; + myImage = null; + myActivePopup = null; + myInfo2Label.clear(); + myInfo2Toolbar.clear(); + myTabListeners.clear(); + } + + void resetTabsCache() { + myAllTabs = null; + } + + private void processFocusChange() { + Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (owner == null) { + setFocused(false); + return; + } + + if (owner == this || SwingUtilities.isDescendingFrom(owner, this)) { + setFocused(true); + } + else { + setFocused(false); + } + } + + private void repaintAttractions() { + boolean needsUpdate = false; + for (TabInfo each : myVisibleInfos) { + TabLabel eachLabel = myInfo2Label.get(each); + needsUpdate |= eachLabel.repaintAttraction(); + } + + if (needsUpdate) { + relayout(true, false); + } + } + + public void addNotify() { + super.addNotify(); + addTimerUpdate(); + } + + public void removeNotify() { + super.removeNotify(); + + setFocused(false); + + removeTimerUpdate(); + + if (myGlassPane != null) { + myGlassPane.removeMouseMotionPreprocessor(myTabActionsAutoHideListener); + myGlassPane = null; + } + } + + private void addTimerUpdate() { + if (myActionManager != null && !myListenerAdded) { + myActionManager.addTimerListener(500, this); + myListenerAdded = true; + } + } + + private void removeTimerUpdate() { + if (myActionManager != null && myListenerAdded) { + myActionManager.removeTimerListener(this); + myListenerAdded = false; + } + } + + void setTestMode(final boolean testMode) { + myTestMode = testMode; + } + + public void layoutComp(SingleRowPassInfo data, int deltaX, int deltaY, int deltaWidth, int deltaHeight) { + if (data.hToolbar != null) { + final int toolbarHeight = data.hToolbar.getPreferredSize().height; + final Rectangle compRect = layoutComp(deltaX, toolbarHeight + deltaY, data.comp, deltaWidth, deltaHeight); + layout(data.hToolbar, compRect.x, compRect.y - toolbarHeight, compRect.width, toolbarHeight); + } + else if (data.vToolbar != null) { + final int toolbarWidth = data.vToolbar.getPreferredSize().width; + final Rectangle compRect = layoutComp(toolbarWidth + deltaX, deltaY, data.comp, deltaWidth, deltaHeight); + layout(data.vToolbar, compRect.x - toolbarWidth, compRect.y, toolbarWidth, compRect.height); + } + else { + layoutComp(deltaX, deltaY, data.comp, deltaWidth, deltaHeight); + } + } + + public boolean isDropTarget(TabInfo info) { + return myDropInfo != null && myDropInfo == info; + } + + public void setDropInfoIndex(int dropInfoIndex) { + myDropInfoIndex = dropInfoIndex; + } + + class TabActionsAutoHideListener extends MouseMotionAdapter { + + private TabLabel myCurrentOverLabel; + private Point myLastOverPoint; + + @Override + public void mouseMoved(final MouseEvent e) { + if (!myTabLabelActionsAutoHide) return; + + final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getX(), e.getY(), JBTabsImpl.this); + myLastOverPoint = point; + processMouseOver(); + } + + void processMouseOver() { + if (!myTabLabelActionsAutoHide) return; + + if (myLastOverPoint == null) return; + + if (myLastOverPoint.x >= 0 && myLastOverPoint.x < getWidth() && myLastOverPoint.y > 0 && myLastOverPoint.y < getHeight()) { + final TabLabel label = myInfo2Label.get(_findInfo(myLastOverPoint, true)); + if (label != null) { + if (myCurrentOverLabel != null) { + myCurrentOverLabel.toggleShowActions(false); + } + label.toggleShowActions(true); + myCurrentOverLabel = label; + return; + } + } + + if (myCurrentOverLabel != null) { + myCurrentOverLabel.toggleShowActions(false); + myCurrentOverLabel = null; + } + } + } + + + public ModalityState getModalityState() { + return ModalityState.stateForComponent(this); + } + + public void run() { + updateTabActions(false); + } + + public void updateTabActions(final boolean validateNow) { + final Ref changed = new Ref(Boolean.FALSE); + for (final TabInfo eachInfo : myInfo2Label.keySet()) { + updateTab(new Computable() { + public Boolean compute() { + final boolean changes = myInfo2Label.get(eachInfo).updateTabActions(); + changed.set(changed.get().booleanValue() || changes); + return changes; + } + }, eachInfo); + } + + if (changed.get().booleanValue()) { + if (validateNow) { + validate(); + paintImmediately(0, 0, getWidth(), getHeight()); + } + } + } + + private void showMorePopup(final MouseEvent e) { + mySingleRowLayout.myMorePopup = new JPopupMenu(); + for (final TabInfo each : myVisibleInfos) { + final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText()); + mySingleRowLayout.myMorePopup.add(item); + if (getSelectedInfo() == each) { + item.setSelected(true); + } + item.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + select(each, true); + } + }); + } + + mySingleRowLayout.myMorePopup.addPopupMenuListener(new PopupMenuListener() { + public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { + } + + public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { + mySingleRowLayout.myMorePopup = null; + } + + public void popupMenuCanceled(final PopupMenuEvent e) { + mySingleRowLayout.myMorePopup = null; + } + }); + + mySingleRowLayout.myMorePopup.show(this, e.getX(), e.getY()); + } + + + private JComponent getToFocus() { + final TabInfo info = getSelectedInfo(); + + if (info == null) return null; + + JComponent toFocus = null; + + if (isRequestFocusOnLastFocusedComponent() && info.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) { + toFocus = info.getLastFocusOwner(); + } + + if (toFocus == null && info.getPreferredFocusableComponent() == null) { + return null; + } + + + if (toFocus == null) { + toFocus = info.getPreferredFocusableComponent(); + final JComponent policyToFocus = myFocusManager.getFocusTargetFor(toFocus); + if (policyToFocus != null) { + toFocus = policyToFocus; + } + } + + return toFocus; + } + + public void requestFocus() { + final JComponent toFocus = getToFocus(); + if (toFocus != null) { + toFocus.requestFocus(); + } + else { + super.requestFocus(); + } + } + + public boolean requestFocusInWindow() { + final JComponent toFocus = getToFocus(); + if (toFocus != null) { + return toFocus.requestFocusInWindow(); + } + else { + return super.requestFocusInWindow(); + } + } + + private JBTabsImpl findTabs(Component c) { + Component eachParent = c; + while (eachParent != null) { + if (eachParent instanceof JBTabsImpl) { + return (JBTabsImpl)eachParent; + } + eachParent = eachParent.getParent(); + } + + return null; + } + + + @NotNull + public TabInfo addTab(TabInfo info, int index) { + return addTab(info, index, false); + } + + private TabInfo addTab(TabInfo info, int index, boolean isDropTarget) { + if (!isDropTarget && getTabs().contains(info)) { + return getTabs().get(getTabs().indexOf(info)); + } + + info.getChangeSupport().addPropertyChangeListener(this); + final TabLabel label = new TabLabel(this, info); + myInfo2Label.put(info, label); + + if (!isDropTarget) { + if (index < 0) { + myVisibleInfos.add(info); + } + else if (index > myVisibleInfos.size() - 1) { + myVisibleInfos.add(info); + } + else { + myVisibleInfos.add(index, info); + } + } + + resetTabsCache(); + + + updateText(info); + updateIcon(info); + updateSideComponent(info); + updateTabActions(info); + + add(label); + + adjust(info); + + updateAll(false, false); + + if (info.isHidden()) { + updateHiding(); + } + + if (!isDropTarget) { + if (getTabCount() == 1) { + fireBeforeSelectionChanged(null, info); + fireSelectionChanged(null, info); + } + } + + revalidateAndRepaint(false); + + return info; + } + + + @NotNull + public TabInfo addTab(TabInfo info) { + return addTab(info, -1); + } + + public ActionGroup getPopupGroup() { + return myPopupGroup != null ? myPopupGroup.get() : null; + } + + public String getPopupPlace() { + return myPopupPlace; + } + + public JBTabs setPopupGroup(@NotNull final ActionGroup popupGroup, @NotNull String place, final boolean addNavigationGroup) { + return setPopupGroup(new Getter() { + public ActionGroup get() { + return popupGroup; + } + }, place, addNavigationGroup); + } + + public JBTabs setPopupGroup(@NotNull final Getter popupGroup, + @NotNull final String place, + final boolean addNavigationGroup) { + myPopupGroup = popupGroup; + myPopupPlace = place; + myAddNavigationGroup = addNavigationGroup; + return this; + } + + private void updateAll(final boolean forcedRelayout, final boolean now) { + mySelectedInfo = getSelectedInfo(); + updateContainer(forcedRelayout, now); + removeDeferred(); + updateListeners(); + updateTabActions(false); + updateEnabling(); + } + + private boolean isMyChildIsFocusedNow() { + final Component owner = getFocusOwner(); + if (owner == null) return false; + + + if (mySelectedInfo != null) { + if (!SwingUtilities.isDescendingFrom(owner, mySelectedInfo.getComponent())) return false; + } + + return SwingUtilities.isDescendingFrom(owner, this); + } + + @Nullable + private static JComponent getFocusOwner() { + final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + return (JComponent)(owner instanceof JComponent ? owner : null); + } + + public ActionCallback select(@NotNull TabInfo info, boolean requestFocus) { + return _setSelected(info, requestFocus); + } + + private ActionCallback _setSelected(final TabInfo info, final boolean requestFocus) { + if (mySelectedInfo != null && mySelectedInfo.equals(info)) { + if (!requestFocus) { + return new ActionCallback.Done(); + } + else { + return requestFocus(getToFocus()); + } + } + + if (myRequestFocusOnLastFocusedComponent && mySelectedInfo != null) { + if (isMyChildIsFocusedNow()) { + mySelectedInfo.setLastFocusOwner(getFocusOwner()); + } + } + + TabInfo oldInfo = mySelectedInfo; + mySelectedInfo = info; + final TabInfo newInfo = getSelectedInfo(); + + fireBeforeSelectionChanged(oldInfo, newInfo); + + updateContainer(false, true); + + fireSelectionChanged(oldInfo, newInfo); + + if (requestFocus) { + final JComponent toFocus = getToFocus(); + if (myProject != null && toFocus != null) { + final ActionCallback result = new ActionCallback(); + requestFocus(toFocus).doWhenProcessed(new Runnable() { + public void run() { + if (myDisposed) { + result.setRejected(); + } + else { + removeDeferred().notifyWhenDone(result); + } + } + }); + return result; + } + else { + requestFocus(); + return removeDeferred(); + } + } + else { + return removeDeferred(); + } + } + + private void fireBeforeSelectionChanged(TabInfo oldInfo, TabInfo newInfo) { + if (oldInfo != newInfo) { + for (TabsListener eachListener : myTabListeners) { + eachListener.beforeSelectionChanged(oldInfo, newInfo); + } + } + } + + private void fireSelectionChanged(TabInfo oldInfo, TabInfo newInfo) { + if (oldInfo != newInfo) { + for (TabsListener eachListener : myTabListeners) { + if (eachListener != null) { + eachListener.selectionChanged(oldInfo, newInfo); + } + } + } + } + + private ActionCallback requestFocus(final JComponent toFocus) { + if (toFocus == null) return new ActionCallback.Done(); + + if (myTestMode) { + toFocus.requestFocus(); + return new ActionCallback.Done(); + } + + return myFocusManager.requestFocus(new FocusCommand.ByComponent(toFocus), true); + } + + private ActionCallback removeDeferred() { + final ActionCallback callback = new ActionCallback(); + + final long executionRequest = ++myRemoveDefferredRequest; + + final Runnable onDone = new Runnable() { + public void run() { + if (myRemoveDefferredRequest == executionRequest) { + removeDeferredNow(); + } + + callback.setDone(); + } + }; + + myFocusManager.doWhenFocusSettlesDown(onDone); + + return callback; + } + + private void queueForRemove(Component c) { + if (c instanceof JComponent) { + addToDeferredRemove(c); + } + else { + remove(c); + } + } + + private void unqueueFromRemove(Component c) { + myDeferredToRemove.remove(c); + } + + private void removeDeferredNow() { + for (Component each : myDeferredToRemove.keySet()) { + if (each != null && each.getParent() == this) { + remove(each); + } + } + myDeferredToRemove.clear(); + } + + private void printRemoveInfo(final Component each) { + TabInfo removingInfo = null; + final List all = getTabs(); + for (TabInfo eachInfo : all) { + if (eachInfo.getComponent() == each) { + removingInfo = eachInfo; + break; + } + } + + //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each)); + } + + public void propertyChange(final PropertyChangeEvent evt) { + final TabInfo tabInfo = (TabInfo)evt.getSource(); + if (TabInfo.ACTION_GROUP.equals(evt.getPropertyName())) { + updateSideComponent(tabInfo); + relayout(false, false); + } + else if (TabInfo.COMPONENT.equals(evt.getPropertyName())) { + relayout(true, false); + } + else if (TabInfo.TEXT.equals(evt.getPropertyName())) { + updateText(tabInfo); + } + else if (TabInfo.ICON.equals(evt.getPropertyName())) { + updateIcon(tabInfo); + } + else if (TabInfo.TAB_COLOR.equals(evt.getPropertyName())) { + updateColor(tabInfo); + } + else if (TabInfo.ALERT_STATUS.equals(evt.getPropertyName())) { + boolean start = ((Boolean)evt.getNewValue()).booleanValue(); + updateAttraction(tabInfo, start); + } + else if (TabInfo.TAB_ACTION_GROUP.equals(evt.getPropertyName())) { + updateTabActions(tabInfo); + relayout(false, false); + } + else if (TabInfo.HIDDEN.equals(evt.getPropertyName())) { + updateHiding(); + relayout(false, false); + } + else if (TabInfo.ENABLED.equals(evt.getPropertyName())) { + updateEnabling(); + } + } + + private void updateEnabling() { + final List all = getTabs(); + for (TabInfo each : all) { + final TabLabel eachLabel = myInfo2Label.get(each); + eachLabel.setTabEnabled(each.isEnabled()); + } + + final TabInfo selected = getSelectedInfo(); + if (selected != null && !selected.isEnabled()) { + final TabInfo toSelect = getToSelectOnRemoveOf(selected); + if (toSelect != null) { + select(toSelect, myFocusManager.getFocusedDescendantFor(this) != null); + } + } + } + + private void updateHiding() { + boolean update = false; + + Iterator visible = myVisibleInfos.iterator(); + while (visible.hasNext()) { + TabInfo each = visible.next(); + if (each.isHidden() && !myHiddenInfos.containsKey(each)) { + myHiddenInfos.put(each, myVisibleInfos.indexOf(each)); + visible.remove(); + update = true; + } + } + + + Iterator hidden = myHiddenInfos.keySet().iterator(); + while (hidden.hasNext()) { + TabInfo each = hidden.next(); + if (!each.isHidden() && myHiddenInfos.containsKey(each)) { + myVisibleInfos.add(getIndexInVisibleArray(each), each); + hidden.remove(); + update = true; + } + } + + + if (update) { + resetTabsCache(); + if (mySelectedInfo != null && myHiddenInfos.containsKey(mySelectedInfo)) { + mySelectedInfo = getToSelectOnRemoveOf(mySelectedInfo); + } + updateAll(true, false); + } + } + + private int getIndexInVisibleArray(TabInfo each) { + Integer index = myHiddenInfos.get(each); + if (index == null) { + index = Integer.valueOf(myVisibleInfos.size()); + } + + if (index > myVisibleInfos.size()) { + index = myVisibleInfos.size(); + } + + if (index.intValue() < 0) { + index = 0; + } + + return index.intValue(); + } + + private void updateIcon(final TabInfo tabInfo) { + updateTab(new Computable() { + public Boolean compute() { + myInfo2Label.get(tabInfo).setIcon(tabInfo.getIcon()); + return true; + } + }, tabInfo); + } + + private void updateColor(final TabInfo tabInfo) { + myInfo2Label.get(tabInfo).setInactiveStateImage(null); + + updateTab(new Computable() { + public Boolean compute() { + repaint(); + return true; + } + }, tabInfo); + } + + private void updateTab(Computable update, TabInfo info) { + final TabLabel label = myInfo2Label.get(info); + Boolean changes = update.compute(); + if (label.getRootPane() != null) { + if (label.isValid()) { + if (changes) { + label.repaint(); + } + } + else { + revalidateAndRepaint(false); + } + } + } + + void revalidateAndRepaint(final boolean layoutNow) { + + if (myVisibleInfos.isEmpty()) { + setOpaque(false); + final Component nonOpaque = UIUtil.findUltimateParent(this); + if (nonOpaque != null && getParent() != null) { + final Rectangle toRepaint = SwingUtilities.convertRectangle(getParent(), getBounds(), nonOpaque); + nonOpaque.repaint(toRepaint.x, toRepaint.y, toRepaint.width, toRepaint.height); + } + } + else { + setOpaque(true); + } + + if (layoutNow) { + validate(); + } + else { + revalidate(); + } + + repaint(); + } + + + private void updateAttraction(final TabInfo tabInfo, boolean start) { + if (start) { + myAttractions.add(tabInfo); + } + else { + myAttractions.remove(tabInfo); + tabInfo.setBlinkCount(0); + } + + if (start && !myAnimator.isRunning()) { + myAnimator.resume(); + } + else if (!start && myAttractions.isEmpty()) { + myAnimator.suspend(); + repaintAttractions(); + } + } + + private void updateText(final TabInfo tabInfo) { + updateTab(new Computable() { + public Boolean compute() { + final TabLabel label = myInfo2Label.get(tabInfo); + label.setText(tabInfo.getColoredText()); + label.setToolTipText(tabInfo.getTooltipText()); + return true; + } + }, tabInfo); + } + + private void updateSideComponent(final TabInfo tabInfo) { + final Toolbar old = myInfo2Toolbar.get(tabInfo); + if (old != null) { + remove(old); + } + + final Toolbar toolbar = createToolbarComponent(tabInfo); + myInfo2Toolbar.put(tabInfo, toolbar); + add(toolbar); + } + + private void updateTabActions(final TabInfo info) { + myInfo2Label.get(info).setTabActions(info.getTabLabelActions()); + } + + @Nullable + public TabInfo getSelectedInfo() { + if (!myVisibleInfos.contains(mySelectedInfo)) { + mySelectedInfo = null; + } + return mySelectedInfo != null ? mySelectedInfo : !myVisibleInfos.isEmpty() ? myVisibleInfos.get(0) : null; + } + + @Nullable + private TabInfo getToSelectOnRemoveOf(TabInfo info) { + if (!myVisibleInfos.contains(info)) return null; + if (mySelectedInfo != info) return null; + + if (myVisibleInfos.size() == 1) return null; + + int index = myVisibleInfos.indexOf(info); + + TabInfo result = null; + if (index > 0) { + result = findEnabledBackward(index - 1); + } + + if (result == null) { + result = findEnabledForward(index + 1); + } + + return result; + } + + private TabInfo findEnabledForward(int from) { + int index = from; + while (index < myVisibleInfos.size() && index >= 0) { + final TabInfo each = myVisibleInfos.get(index); + if (each.isEnabled()) return each; + index++; + } + + return null; + } + + private TabInfo findEnabledBackward(int from) { + int index = from; + while (index >= 0 && from < myVisibleInfos.size()) { + final TabInfo each = myVisibleInfos.get(index); + if (each.isEnabled()) return each; + index--; + } + + return null; + } + + protected Toolbar createToolbarComponent(final TabInfo tabInfo) { + return new Toolbar(this, tabInfo); + } + + @NotNull + public TabInfo getTabAt(final int tabIndex) { + return getTabs().get(tabIndex); + } + + @NotNull + public List getTabs() { + if (myAllTabs != null) return myAllTabs; + + ArrayList result = new ArrayList(); + result.addAll(myVisibleInfos); + + for (TabInfo each : myHiddenInfos.keySet()) { + result.add(getIndexInVisibleArray(each), each); + } + + myAllTabs = result; + + return result; + } + + public TabInfo getTargetInfo() { + return myPopupInfo != null ? myPopupInfo : getSelectedInfo(); + } + + public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { + } + + public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { + resetPopup(); + } + + public void popupMenuCanceled(final PopupMenuEvent e) { + resetPopup(); + } + + private void resetPopup() { +//todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myPopupInfo = null; + } + }); + } + + public void setPaintBlocked(boolean blocked, final boolean takeSnapshot) { + if (blocked && !myPaintBlocked) { + if (takeSnapshot) { + myImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); + final Graphics2D g = myImage.createGraphics(); + super.paint(g); + g.dispose(); + } + } + + myPaintBlocked = blocked; + + if (!myPaintBlocked) { + if (myImage != null) { + myImage.flush(); + } + + myImage = null; + repaint(); + } + } + + + private void addToDeferredRemove(final Component c) { + if (!myDeferredToRemove.containsKey(c)) { + myDeferredToRemove.put(c, c); + } + } + + public boolean isToDrawBorderIfTabsHidden() { + return myToDrawBorderIfTabsHidden; + } + + @NotNull + public JBTabsPresentation setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden) { + myToDrawBorderIfTabsHidden = toDrawBorderIfTabsHidden; + return this; + } + + @NotNull + public JBTabs getJBTabs() { + return this; + } + + public static class Toolbar extends JPanel { + private final JBTabsImpl myTabs; + private final TabInfo myInfo; + + public Toolbar(JBTabsImpl tabs, TabInfo info) { + myTabs = tabs; + myInfo = info; + + setLayout(new BorderLayout()); + + final ActionGroup group = info.getGroup(); + final JComponent side = info.getSideComponent(); + + if (group != null && myTabs.myActionManager != null) { + final String place = info.getPlace(); + ActionToolbar toolbar = + myTabs.myActionManager.createActionToolbar(place != null ? place : ActionPlaces.UNKNOWN, group, myTabs.myHorizontalSide); + toolbar.setTargetComponent(info.getActionsContextComponent()); + final JComponent actionToolbar = toolbar.getComponent(); + add(actionToolbar, BorderLayout.CENTER); + } + + if (side != null) { + if (group != null) { + add(side, BorderLayout.EAST); + } + else { + add(side, BorderLayout.CENTER); + } + } + } + + public boolean isEmpty() { + return getComponentCount() == 0; + } + } + + + public void doLayout() { + try { + myHeaderFitSize = computeHeaderFitSize(); + + final Collection labels = myInfo2Label.values(); + for (TabLabel each : labels) { + each.setTabActionsAutoHide(myTabLabelActionsAutoHide); + } + + + List visible = new ArrayList(); + visible.addAll(myVisibleInfos); + + if (myDropInfo != null && !visible.contains(myDropInfo)) { + if (getDropInfoIndex() >= 0 && getDropInfoIndex() < visible.size()) { + visible.add(getDropInfoIndex(), myDropInfo); + } else { + visible.add(myDropInfo); + } + } + + if (isSingleRow()) { + myLastLayoutPass = mySingleRowLayout.layoutSingleRow(visible); + myTableLayout.myLastTableLayout = null; + } + else { + myLastLayoutPass = myTableLayout.layoutTable(visible); + mySingleRowLayout.myLastSingRowLayout = null; + } + + if (isStealthModeEffective() && !isHideTabs()) { + final TabLabel label = getSelectedLabel(); + final Rectangle bounds = label.getBounds(); + final Insets insets = getLayoutInsets(); + layout(label, insets.left, bounds.y, getWidth() - insets.right - insets.left, bounds.height); + } + + + moveDraggedTabLabel(); + + myTabActionsAutoHideListener.processMouseOver(); + } + finally { + myForcedRelayout = false; + } + + applyResetComponents(); + } + + void moveDraggedTabLabel() { + if (myDragHelper != null && myDragHelper.myDragRec != null) { + final TabLabel selectedLabel = myInfo2Label.get(getSelectedInfo()); + if (selectedLabel != null) { + final Rectangle bounds = selectedLabel.getBounds(); + if (isHorizontalTabs()) { + selectedLabel.setBounds(myDragHelper.myDragRec.x, bounds.y, bounds.width, bounds.height); + } + else { + selectedLabel.setBounds(bounds.x, myDragHelper.myDragRec.y, bounds.width, bounds.height); + } + } + } + } + + private Dimension computeHeaderFitSize() { + final Max max = computeMaxSize(); + + if (myPosition == JBTabsPosition.top || myPosition == JBTabsPosition.bottom) { + return new Dimension(getSize().width, myHorizontalSide ? Math.max(max.myLabel.height, max.myToolbar.height) : max.myLabel.height); + } + else { + return new Dimension(max.myLabel.width + (myHorizontalSide ? 0 : max.myToolbar.width), getSize().height); + } + } + + public Rectangle layoutComp(int componentX, int componentY, final JComponent comp, int deltaWidth, int deltaHeight) { + final Insets insets = getLayoutInsets(); + + final Insets border = isHideTabs() ? new Insets(0, 0, 0, 0) : myBorder.getEffectiveBorder(); + final boolean noTabsVisible = isStealthModeEffective() || isHideTabs(); + + if (noTabsVisible) { + border.top = getBorder(-1); + border.bottom = getBorder(-1); + border.left = getBorder(-1); + border.right = getBorder(-1); + } + + final Insets inner = getInnerInsets(); + border.top += inner.top; + border.bottom += inner.bottom; + border.left += inner.left; + border.right += inner.right; + + + int x = insets.left + componentX + border.left; + int y = insets.top + componentY + border.top; + int width = getWidth() - insets.left - insets.right - componentX - border.left - border.right; + int height = getHeight() - insets.top - insets.bottom - componentY - border.top - border.bottom; + + if (!noTabsVisible) { + width += deltaWidth; + height += deltaHeight; + } + + return layout(comp, x, y, width, height); + } + + + public JBTabsPresentation setInnerInsets(final Insets innerInsets) { + myInnerInsets = innerInsets; + return this; + } + + public Insets getInnerInsets() { + return myInnerInsets; + } + + public Insets getLayoutInsets() { + Insets insets = getInsets(); + if (insets == null) { + insets = new Insets(0, 0, 0, 0); + } + return insets; + } + + private int fixInset(int inset, int addin) { + return inset + addin; + } + + + public int getToolbarInset() { + return getArcSize() + 1; + } + + public void resetLayout(boolean resetLabels) { + if (resetLabels) { + mySingleRowLayout.myLeftGhost.reset(); + mySingleRowLayout.myRightGhost.reset(); + } + + for (TabInfo each : myVisibleInfos) { + reset(each, resetLabels); + } + + if (myDropInfo != null) { + reset(myDropInfo, resetLabels); + } + + for (TabInfo each : myHiddenInfos.keySet()) { + reset(each, resetLabels); + } + + for (Component eachDeferred : myDeferredToRemove.keySet()) { + resetLayout((JComponent)eachDeferred); + } + } + + private void reset(final TabInfo each, final boolean resetLabels) { + final JComponent c = each.getComponent(); + if (c != null) { + resetLayout(c); + } + + resetLayout(myInfo2Toolbar.get(each)); + + if (resetLabels) { + resetLayout(myInfo2Label.get(each)); + } + } + + + private int getArcSize() { + return 4; + } + + private int getEdgeArcSize() { + return 3; + } + + public int getGhostTabLength() { + return 15; + } + + + protected void paintComponent(final Graphics g) { + super.paintComponent(g); + + if (myVisibleInfos.isEmpty()) return; + + Graphics2D g2d = (Graphics2D)g; + + final GraphicsConfig config = new GraphicsConfig(g2d); + config.setAntialiasing(true); + + + g2d.setColor(getBackground()); + final Rectangle clip = g2d.getClipBounds(); + g2d.fillRect(clip.x, clip.y, clip.width, clip.height); + + final TabInfo selected = getSelectedInfo(); + + if (selected != null) { + Rectangle compBounds = selected.getComponent().getBounds(); + if (compBounds.contains(clip) && !compBounds.intersects(clip)) return; + } + + boolean leftGhostExists = isSingleRow(); + boolean rightGhostExists = isSingleRow(); + + if (!isStealthModeEffective() && !isHideTabs()) { + if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.lastGhostVisible) { + paintLastGhost(g2d); + } + + + paintNonSelectedTabs(g2d, leftGhostExists, rightGhostExists); + + if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.firstGhostVisible) { + paintFirstGhost(g2d); + } + + } + + config.setAntialiasing(false); + + if (isSideComponentVertical()) { + Toolbar toolbarComp = myInfo2Toolbar.get(mySelectedInfo); + if (toolbarComp != null && !toolbarComp.isEmpty()) { + Rectangle toolBounds = toolbarComp.getBounds(); + g2d.setColor(CaptionPanel.CNT_ACTIVE_COLOR); + g2d.drawLine((int)toolBounds.getMaxX(), toolBounds.y, (int)toolBounds.getMaxX(), (int)toolBounds.getMaxY() - 1); + } + } + + config.restore(); + } + + private Color getActiveTabColor(final Color c) { + final TabInfo info = getSelectedInfo(); + if (info == null) { + return c; + } + + final Color tabColor = info.getTabColor(); + return tabColor == null ? c : tabColor; + } + + private void paintSelectionAndBorder(Graphics2D g2d) { + if (mySelectedInfo == null) return; + + final ShapeInfo shapeInfo = computeSelectedLabelShape(); + if (!isHideTabs()) { + g2d.setColor(getBackground()); + g2d.fill(shapeInfo.fillPath.getShape()); + } + + final int alpha; + int paintTopY = shapeInfo.labelTopY; + int paintBottomY = shapeInfo.labelBottomY; + final boolean paintFocused = myPaintFocus && (myFocused || myActivePopup != null); + Color bgPreFill = null; + if (paintFocused) { + final Color bgColor = getActiveTabColor(getActiveTabFillIn()); + if (bgColor == null) { + shapeInfo.from = UIUtil.getFocusedFillColor(); + shapeInfo.to = UIUtil.getFocusedFillColor(); + } + else { + bgPreFill = bgColor; + alpha = 255; + paintBottomY = shapeInfo.labelTopY + shapeInfo.labelPath.deltaY(getArcSize() - 2); + shapeInfo.from = UIUtil.toAlpha(UIUtil.getFocusedFillColor(), alpha); + shapeInfo.to = UIUtil.toAlpha(getActiveTabFillIn(), alpha); + } + } + else { + final Color bgColor = getActiveTabColor(getActiveTabFillIn()); + if (isPaintFocus()) { + if (bgColor == null) { + alpha = 150; + shapeInfo.from = UIUtil.toAlpha(UIUtil.getPanelBackground().brighter(), alpha); + shapeInfo.to = UIUtil.toAlpha(UIUtil.getPanelBackground(), alpha); + } + else { + alpha = 255; + shapeInfo.from = UIUtil.toAlpha(bgColor, alpha); + shapeInfo.to = UIUtil.toAlpha(bgColor, alpha); + } + } + else { + alpha = 255; + final Color tabColor = getActiveTabColor(null); + shapeInfo.from = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha); + shapeInfo.to = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha); + } + } + + if (!isHideTabs()) { + if (bgPreFill != null) { + g2d.setColor(bgPreFill); + g2d.fill(shapeInfo.fillPath.getShape()); + } + + final Line2D.Float gradientLine = + shapeInfo.fillPath.transformLine(shapeInfo.fillPath.getX(), paintTopY, shapeInfo.fillPath.getX(), paintBottomY); + + + g2d.setPaint(new GradientPaint((float)gradientLine.getX1(), (float)gradientLine.getY1(), + shapeInfo.fillPath.transformY1(shapeInfo.from, shapeInfo.to), (float)gradientLine.getX2(), + (float)gradientLine.getY2(), shapeInfo.fillPath.transformY1(shapeInfo.to, shapeInfo.from))); + g2d.fill(shapeInfo.fillPath.getShape()); + } + + final Color tabColor = getActiveTabColor(null); + Color borderColor = tabColor == null ? UIUtil.getBoundsColor(paintFocused) : tabColor.darker(); + g2d.setColor(borderColor); + + if (!isHideTabs()) { + g2d.draw(shapeInfo.path.getShape()); + } + + paintBorder(g2d, shapeInfo, borderColor); + } + + private ShapeInfo computeSelectedLabelShape() { + final ShapeInfo shape = new ShapeInfo(); + + shape.path = getEffectiveLayout().createShapeTransform(getSize()); + shape.insets = shape.path.transformInsets(getLayoutInsets()); + shape.labelPath = shape.path.createTransform(getSelectedLabel().getBounds()); + + shape.labelBottomY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1); + shape.labelTopY = shape.labelPath.getY(); + shape.labelLeftX = shape.labelPath.getX(); + shape.labelRightX = shape.labelPath.getX() + shape.labelPath.deltaX(shape.labelPath.getWidth()); + + Insets border = myBorder.getEffectiveBorder(); + TabInfo selected = getSelectedInfo(); + boolean first = myLastLayoutPass.getPreviousFor(selected) == null; + boolean last = myLastLayoutPass.getNextFor(selected) == null; + + boolean leftEdge = !isSingleRow() && first && border.left == 0; + boolean rightEdge = !isSingleRow() && last && Boolean.TRUE.equals(myInfo2Label.get(selected).getClientProperty(STRETCHED_BY_WIDTH)) && border.right == 0; + + if (leftEdge) { + shape.path.moveTo(shape.insets.left, shape.labelTopY + shape.labelPath.deltaY(getEdgeArcSize())); + shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getEdgeArcSize()), shape.labelTopY); + shape.path.lineTo(shape.labelRightX - shape.labelPath.deltaX(getArcSize()), shape.labelTopY); + } else { + shape.path.moveTo(shape.insets.left, shape.labelBottomY); + shape.path.lineTo(shape.labelLeftX, shape.labelBottomY); + shape.path.lineTo(shape.labelLeftX, shape.labelTopY + shape.labelPath.deltaY(getArcSize())); + shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getArcSize()), shape.labelTopY); + } + + int lastX = shape.path.getWidth() - shape.path.deltaX(shape.insets.right + 1); + + if (isStealthModeEffective()) { + shape.path.lineTo(lastX - shape.path.deltaX(getArcSize()), shape.labelTopY); + shape.path.quadTo(lastX, shape.labelTopY, lastX, shape.labelTopY + shape.path.deltaY(getArcSize())); + shape.path.lineTo(lastX, shape.labelBottomY); + } + else { + if (rightEdge) { + shape.path.lineTo(shape.labelRightX + 1 - shape.path.deltaX(getArcSize()), shape.labelTopY); + shape.path.quadTo(shape.labelRightX + 1, shape.labelTopY, shape.labelRightX + 1, shape.labelTopY + shape.path.deltaY(getArcSize())); + } else { + shape.path.lineTo(shape.labelRightX - shape.path.deltaX(getArcSize()), shape.labelTopY); + shape.path.quadTo(shape.labelRightX, shape.labelTopY, shape.labelRightX, shape.labelTopY + shape.path.deltaY(getArcSize())); + } + if (myLastLayoutPass.hasCurveSpaceFor(selected)) { + shape.path.lineTo(shape.labelRightX, shape.labelBottomY - shape.path.deltaY(getArcSize())); + shape.path.quadTo(shape.labelRightX, shape.labelBottomY, shape.labelRightX + shape.path.deltaX(getArcSize()), shape.labelBottomY); + } + else { + if (rightEdge) { + shape.path.lineTo(shape.labelRightX + 1, shape.labelBottomY); + } else { + shape.path.lineTo(shape.labelRightX, shape.labelBottomY); + } + } + } + + if (!rightEdge) { + shape.path.lineTo(lastX, shape.labelBottomY); + } + + if (isStealthModeEffective()) { + shape.path.closePath(); + } + + shape.fillPath = shape.path.copy(); + if (!isHideTabs()) { + shape.fillPath.lineTo(lastX, shape.labelBottomY + shape.fillPath.deltaY(1)); + shape.fillPath.lineTo(shape.labelLeftX, shape.labelBottomY + shape.fillPath.deltaY(1)); + shape.fillPath.closePath(); + } + return shape; + } + + private TabLabel getSelectedLabel() { + return myInfo2Label.get(getSelectedInfo()); + } + + static class ShapeInfo { + ShapeTransform path; + ShapeTransform fillPath; + ShapeTransform labelPath; + int labelBottomY; + int labelTopY; + int labelLeftX; + int labelRightX; + Insets insets; + Color from; + Color to; + } + + + private void paintFirstGhost(Graphics2D g2d) { + final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.firstGhost); + + int topX = path.getX() + path.deltaX(getCurveArc()); + int topY = path.getY() + path.deltaY(getSelectionTabVShift()); + int bottomX = path.getMaxX() + path.deltaX(1); + int bottomY = path.getMaxY() + path.deltaY(1); + + path.moveTo(topX, topY); + + final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0; + + if (isLeftFromSelection) { + path.lineTo(bottomX, topY); + } + else { + path.lineTo(bottomX - getArcSize(), topY); + path.quadTo(bottomX, topY, bottomX, topY + path.deltaY(getArcSize())); + } + + path.lineTo(bottomX, bottomY); + path.lineTo(topX, bottomY); + + path.quadTo(topX - path.deltaX(getCurveArc() * 2 - 1), bottomY - path.deltaY(Math.abs(bottomY - topY) / 4), topX, + bottomY - path.deltaY(Math.abs(bottomY - topY) / 2)); + + path.quadTo(topX + path.deltaX(getCurveArc() - 1), topY + path.deltaY(Math.abs(bottomY - topY) / 4), topX, topY); + + path.closePath(); + + g2d.setColor(getBackground()); + g2d.fill(path.getShape()); + + g2d.setColor(getBoundsColor()); + g2d.draw(path.getShape()); + + g2d.setColor(getTopBlickColor()); + g2d.drawLine(topX + path.deltaX(1), topY + path.deltaY(1), bottomX - path.deltaX(getArcSize()), topY + path.deltaY(1)); + + g2d.setColor(getRightBlockColor()); + g2d.drawLine(bottomX - path.deltaX(1), topY + path.deltaY(getArcSize()), bottomX - path.deltaX(1), bottomY - path.deltaY(1)); + } + + private void paintLastGhost(Graphics2D g2d) { + final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.lastGhost); + + int topX = path.getX() - path.deltaX(getArcSize()); + int topY = path.getY() + path.deltaY(getSelectionTabVShift()); + int bottomX = path.getMaxX() - path.deltaX(getCurveArc()); + int bottomY = path.getMaxY() + path.deltaY(1); + + path.moveTo(topX, topY); + path.lineTo(bottomX, topY); + path.quadTo(bottomX - getCurveArc(), topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2); + path.quadTo(bottomX + getCurveArc(), bottomY - (bottomY - topY) / 4, bottomX, bottomY); + path.lineTo(topX, bottomY); + + path.closePath(); + + g2d.setColor(getBackground()); + g2d.fill(path.getShape()); + + g2d.setColor(getBoundsColor()); + g2d.draw(path.getShape()); + + g2d.setColor(getTopBlickColor()); + g2d.drawLine(topX, topY + path.deltaY(1), bottomX - path.deltaX(getCurveArc()), topY + path.deltaY(1)); + } + + private int getCurveArc() { + return 2; + } + + private Color getBoundsColor() { + return Color.gray; + } + + private Color getRightBlockColor() { + return Color.lightGray; + } + + private Color getTopBlickColor() { + return Color.white; + } + + private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists, final boolean rightGhostExists) { + TabInfo selected = getSelectedInfo(); + if (myLastPaintedSelection == null || !myLastPaintedSelection.equals(selected)) { + List tabs = getTabs(); + for (TabInfo each : tabs) { + myInfo2Label.get(each).setInactiveStateImage(null); + } + } + + for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) { + for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) { + final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn); + if (getSelectedInfo() == each) { + continue; + } + paintNonSelected(g2d, each, leftGhostExists, rightGhostExists); + } + } + + myLastPaintedSelection = selected; + } + + private void paintNonSelected(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists, final boolean rightGhostExists) { + if (myDropInfo == each) return; + + final TabLabel label = myInfo2Label.get(each); + if (label.getBounds().width == 0) return; + + int imageInsets = getArcSize() + 1; + + Rectangle bounds = label.getBounds(); + + int x = bounds.x - imageInsets; + int y = bounds.y; + int width = bounds.width + imageInsets * 2 + 1; + int height = bounds.height + getArcSize() + 1; + + if (isToBufferPainting()) { + BufferedImage img = label.getInactiveStateImage(bounds); + + if (img == null) { + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D imgG2d = img.createGraphics(); + imgG2d.addRenderingHints(g2d.getRenderingHints()); + doPaintInactictive(imgG2d, leftGhostExists, label, new Rectangle(imageInsets, 0, label.getWidth(), label.getHeight()), rightGhostExists); + imgG2d.dispose(); + } + + g2d.drawImage(img, x, y, width, height, null); + + label.setInactiveStateImage(img); + } else { + doPaintInactictive(g2d, leftGhostExists, label, label.getBounds(), rightGhostExists); + label.setInactiveStateImage(null); + } + } + + private boolean isToBufferPainting() { + return Registry.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint; + } + + private void doPaintInactictive(Graphics2D g2d, boolean leftGhostExists, TabLabel label, Rectangle effectiveBounds, boolean rightGhostExists) { + int tabIndex = myVisibleInfos.indexOf(label.getInfo()); + + final int arc = getArcSize(); + Color topBlickColor = getTopBlickColor(); + Color rightBlockColor = getRightBlockColor(); + Color boundsColor = getBoundsColor(); + Color backgroundColor = getBackground(); + + final Color tabColor = label.getInfo().getTabColor(); + if (tabColor != null) { + backgroundColor = tabColor; + boundsColor = tabColor.darker(); + topBlickColor = tabColor.brighter().brighter(); + rightBlockColor = tabColor; + } + + final TabInfo selected = getSelectedInfo(); + final int selectionTabVShift = getSelectionTabVShift(); + + + final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex)); + final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex)); + + + boolean firstShowing = prev == null; + if (!firstShowing && !leftGhostExists) { + firstShowing = myInfo2Label.get(prev).getBounds().width == 0; + } + + boolean lastShowing = next == null; + if (!lastShowing) { + lastShowing = myInfo2Label.get(next).getBounds().width == 0; + } + + boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1; + + Rectangle originalBounds = effectiveBounds; + final ShapeTransform shape = getEffectiveLayout().createShapeTransform(originalBounds); + + int leftX = firstShowing ? shape.getX() : shape.getX() - shape.deltaX(arc + 1); + int topY = shape.getY() + shape.deltaY(selectionTabVShift); + int rigthX = !lastShowing && leftFromSelection ? shape.getMaxX() + shape.deltaX(arc + 1) : shape.getMaxX(); + int bottomY = shape.getMaxY() + shape.deltaY(1); + + Insets border = myBorder.getEffectiveBorder(); + + if (border.left > 0 || leftGhostExists || !firstShowing) { + shape.moveTo(leftX, bottomY); + shape.lineTo(leftX, topY + shape.deltaY(arc)); + shape.quadTo(leftX, topY, leftX + shape.deltaX(arc), topY); + } else { + if (firstShowing) { + shape.moveTo(leftX, topY + shape.deltaY(getEdgeArcSize())); + shape.quadTo(leftX, topY, leftX + shape.deltaX(getEdgeArcSize()), topY); + } + } + + boolean rightEdge = false; + if (border.right > 0 || rightGhostExists || !lastShowing || !Boolean.TRUE.equals(label.getClientProperty(STRETCHED_BY_WIDTH))) { + shape.lineTo(rigthX - shape.deltaX(arc), topY); + shape.quadTo(rigthX, topY, rigthX, topY + shape.deltaY(arc)); + shape.lineTo(rigthX, bottomY); + } else { + if (lastShowing) { + shape.lineTo(rigthX - shape.deltaX(arc), topY); + shape.quadTo(rigthX + 1, topY, rigthX + 1, topY + shape.deltaY(arc)); + + shape.lineTo(rigthX + 1, bottomY); + rightEdge = true; + } + } + + if (!isSingleRow()) { + final TablePassInfo info = myTableLayout.myLastTableLayout; + if (!info.isInSelectionRow(label.getInfo())) { + shape.lineTo(rigthX, bottomY + shape.deltaY(getArcSize())); + shape.lineTo(leftX, bottomY + shape.deltaY(getArcSize())); + shape.lineTo(leftX, bottomY); + } + } + + if (!rightEdge) { + shape.lineTo(leftX, bottomY); + } + + g2d.setColor(backgroundColor); + g2d.fill(shape.getShape()); + + // TODO + + final Line2D.Float gradientLine = + shape.transformLine(0, topY, 0, topY + shape.deltaY((int) (shape.getHeight() / 1.5 ))); + + final GradientPaint gp = + new GradientPaint(gradientLine.x1, gradientLine.y1, + shape.transformY1(backgroundColor.brighter().brighter(), backgroundColor), + gradientLine.x2, gradientLine.y2, + shape.transformY1(backgroundColor, backgroundColor.brighter().brighter())); + final Paint old = g2d.getPaint(); + g2d.setPaint(gp); + g2d.fill(shape.getShape()); + g2d.setPaint(old); + + g2d.setColor(topBlickColor); + g2d.draw( + shape.transformLine(leftX + shape.deltaX(arc + 1), topY + shape.deltaY(1), rigthX - shape.deltaX(arc - 1), topY + shape.deltaY(1))); + + if (!rightEdge) { + g2d.setColor(rightBlockColor); + g2d.draw(shape.transformLine(rigthX - shape.deltaX(1), topY + shape.deltaY(arc - 1), rigthX - shape.deltaX(1), bottomY)); + } + + g2d.setColor(boundsColor); + g2d.draw(shape.getShape()); + } + + public int getSelectionTabVShift() { + return 2; + } + + private void paintBorder(Graphics2D g2d, ShapeInfo shape, final Color borderColor) { + + final ShapeTransform shaper = shape.path.copy().reset(); + + final Insets paintBorder = shape.path.transformInsets(myBorder.getEffectiveBorder()); + + int topY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1); + + int bottomY = topY + paintBorder.top - 2; + int middleY = topY + (bottomY - topY) / 2; + + + final int boundsX = shape.path.getX() + shape.path.deltaX(shape.insets.left); + + final int boundsY = + isHideTabs() ? shape.path.getY() + shape.path.deltaY(shape.insets.top) : shape.labelPath.getMaxY() + shape.path.deltaY(1); + + final int boundsHeight = Math.abs(shape.path.getMaxY() - boundsY) - shape.insets.bottom - paintBorder.bottom; + final int boundsWidth = Math.abs(shape.path.getMaxX() - (shape.insets.left + shape.insets.right)); + + if (paintBorder.top > 0) { + if (isHideTabs()) { + if (isToDrawBorderIfTabsHidden()) { + g2d.setColor(borderColor); + g2d.fill(shaper.reset().doRect(boundsX, boundsY, boundsWidth, 1).getShape()); + } + } + else { + Color tabFillColor = getActiveTabColor(null); + if (tabFillColor == null) { + tabFillColor = shape.path.transformY1(shape.to, shape.from); + } + + g2d.setColor(tabFillColor); + g2d.fill(shaper.reset().doRect(boundsX, topY + shape.path.deltaY(1), boundsWidth, paintBorder.top - 1).getShape()); + + g2d.setColor(borderColor); + if (paintBorder.top == 2) { + final Line2D.Float line = shape.path.transformLine(boundsX, topY, boundsX + shape.path.deltaX(boundsWidth - 1), topY); + + g2d.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2); + } + else if (paintBorder.top > 2) { +//todo kirillk +//start hack + int deltaY = 0; + if (myPosition == JBTabsPosition.bottom || myPosition == JBTabsPosition.right) { + deltaY = 1; + } +//end hack + final int topLine = topY + shape.path.deltaY(paintBorder.top - 1); + g2d.fill(shaper.reset().doRect(boundsX, topLine + deltaY, boundsWidth - 1, 1).getShape()); + } + } + } + + g2d.setColor(borderColor); + + //bottom + g2d.fill(shaper.reset().doRect(boundsX, Math.abs(shape.path.getMaxY() - shape.insets.bottom - paintBorder.bottom), boundsWidth, + paintBorder.bottom).getShape()); + + //left + g2d.fill(shaper.reset().doRect(boundsX, boundsY, paintBorder.left, boundsHeight).getShape()); + + //right + g2d.fill(shaper.reset() + .doRect(shape.path.getMaxX() - shape.insets.right - paintBorder.right, boundsY, paintBorder.right, boundsHeight).getShape()); + + } + + public boolean isStealthModeEffective() { + return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition.top; + } + + + private boolean isNavigationVisible() { + if (myStealthTabMode && getTabCount() == 1) return false; + return !myVisibleInfos.isEmpty(); + } + + + public void paint(final Graphics g) { + Rectangle clip = g.getClipBounds(); + if (clip == null) { + return; + } + + if (myPaintBlocked) { + if (myImage != null) { + g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null); + } + return; + } + + super.paint(g); + } + + protected void paintChildren(final Graphics g) { + super.paintChildren(g); + + final GraphicsConfig config = new GraphicsConfig(g); + config.setAntialiasing(true); + paintSelectionAndBorder((Graphics2D)g); + config.restore(); + + final TabLabel selected = getSelectedLabel(); + if (selected != null) { + selected.paintImage(g); + } + + mySingleRowLayout.myMoreIcon.paintIcon(this, g); + } + + private Max computeMaxSize() { + Max max = new Max(); + for (TabInfo eachInfo : myVisibleInfos) { + final TabLabel label = myInfo2Label.get(eachInfo); + max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height); + max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width); + final Toolbar toolbar = myInfo2Toolbar.get(eachInfo); + if (myLayout.isSideComponentOnTabs() && toolbar != null && !toolbar.isEmpty()) { + max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height); + max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width); + } + } + + max.myToolbar.height++; + + return max; + } + + public Dimension getMinimumSize() { + return computeSize(new Transform() { + public Dimension transform(JComponent component) { + return component.getMinimumSize(); + } + }, 1); + } + + public Dimension getPreferredSize() { + return computeSize(new Transform() { + public Dimension transform(JComponent component) { + return component.getPreferredSize(); + } + }, 3); + } + + private Dimension computeSize(Transform transform, int tabCount) { + Dimension size = new Dimension(); + for (TabInfo each : myVisibleInfos) { + final JComponent c = each.getComponent(); + if (c != null) { + final Dimension eachSize = transform.transform(c); + size.width = Math.max(eachSize.width, size.width); + size.height = Math.max(eachSize.height, size.height); + } + } + + addHeaderSize(size, tabCount); + return size; + } + + private void addHeaderSize(Dimension size, final int tabsCount) { + Dimension header = computeHeaderPreferredSize(tabsCount); + + size.height += header.height; + size.width += header.width; + + final Insets insets = getLayoutInsets(); + size.width += insets.left + insets.right + 1; + size.height += insets.top + insets.bottom + 1; + } + + private Dimension computeHeaderPreferredSize(int tabsCount) { + final Iterator infos = myInfo2Label.keySet().iterator(); + Dimension size = new Dimension(); + int currentTab = 0; + + final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom; + + while (infos.hasNext()) { + final boolean canGrow = currentTab < tabsCount; + + TabInfo eachInfo = infos.next(); + final TabLabel eachLabel = myInfo2Label.get(eachInfo); + final Dimension eachPrefSize = eachLabel.getPreferredSize(); + if (horizontal) { + if (canGrow) { + size.width += eachPrefSize.width; + } + size.height = Math.max(size.height, eachPrefSize.height); + } + else { + size.width = Math.max(size.width, eachPrefSize.width); + if (canGrow) { + size.height += eachPrefSize.height; + } + } + + currentTab++; + } + + if (isSingleRow() && isGhostsAlwaysVisible()) { + if (horizontal) { + size.width += getGhostTabLength() * 2; + } + else { + size.height += getGhostTabLength() * 2; + } + } + + if (horizontal) { + size.height += myBorder.getTabBorderSize(); + } + else { + size.width += myBorder.getTabBorderSize(); + } + + return size; + } + + public int getTabCount() { + return getTabs().size(); + } + + @NotNull + public JBTabsPresentation getPresentation() { + return this; + } + + public ActionCallback removeTab(final JComponent component) { + return removeTab(findInfo(component)); + } + + public ActionCallback removeTab(final TabInfo info) { + return removeTab(info, null); + } + + public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer) { + return removeTab(info, forcedSelectionTranfer, true); + } + + public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer, boolean transferFocus) { + return removeTab(info, forcedSelectionTranfer, transferFocus, false); + } + + private ActionCallback removeTab(TabInfo info, TabInfo forcedSelectionTranfer, boolean transferFocus, boolean isDropTarget) { + if (!isDropTarget) { + if (info == null || !getTabs().contains(info)) return new ActionCallback.Done(); + } + + if (isDropTarget && myLastLayoutPass != null) { + myLastLayoutPass.myVisibleInfos.remove(info); + } + + final ActionCallback result = new ActionCallback(); + + TabInfo toSelect; + if (forcedSelectionTranfer == null) { + toSelect = getToSelectOnRemoveOf(info); + } + else { + assert myVisibleInfos.contains(forcedSelectionTranfer) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer; + toSelect = forcedSelectionTranfer; + } + + + if (toSelect != null) { + processRemove(info, false); + _setSelected(toSelect, transferFocus).doWhenProcessed(new Runnable() { + public void run() { + removeDeferred().notifyWhenDone(result); + } + }); + } + else { + processRemove(info, true); + removeDeferred().notifyWhenDone(result); + } + + if (myVisibleInfos.isEmpty()) { + removeDeferredNow(); + } + + revalidateAndRepaint(true); + + return result; + } + + private void processRemove(final TabInfo info, boolean forcedNow) { + remove(myInfo2Label.get(info)); + remove(myInfo2Toolbar.get(info)); + + JComponent tabComponent = info.getComponent(); + + if (!isToDeferRemoveForLater(tabComponent) || forcedNow) { + remove(tabComponent); + } + else { + queueForRemove(tabComponent); + } + + myVisibleInfos.remove(info); + myHiddenInfos.remove(info); + myInfo2Label.remove(info); + myInfo2Toolbar.remove(info); + resetTabsCache(); + + updateAll(false, false); + } + + public TabInfo findInfo(Component component) { + for (TabInfo each : getTabs()) { + if (each.getComponent() == component) return each; + } + + return null; + } + + public TabInfo findInfo(String text) { + if (text == null) return null; + + for (TabInfo each : getTabs()) { + if (text.equals(each.getText())) return each; + } + + return null; + } + + public TabInfo findInfo(MouseEvent event) { + return findInfo(event, false); + } + + private TabInfo findInfo(final MouseEvent event, final boolean labelsOnly) { + final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this); + return _findInfo(point, labelsOnly); + } + + public TabInfo findInfo(final Object object) { + for (int i = 0; i < getTabCount(); i++) { + final TabInfo each = getTabAt(i); + final Object eachObject = each.getObject(); + if (eachObject != null && eachObject.equals(object)) return each; + } + return null; + } + + public TabInfo findTabLabelBy(final Point point) { + return _findInfo(point, true); + } + + private TabInfo _findInfo(final Point point, boolean labelsOnly) { + Component component = findComponentAt(point); + if (component == null) return null; + while (component != this || component != null) { + if (component instanceof TabLabel) { + return ((TabLabel)component).getInfo(); + } + else if (!labelsOnly) { + final TabInfo info = findInfo(component); + if (info != null) return info; + } + if (component == null) break; + component = component.getParent(); + } + + return null; + } + + public void removeAllTabs() { + for (TabInfo each : getTabs()) { + removeTab(each); + } + } + + + private static class Max { + Dimension myLabel = new Dimension(); + Dimension myToolbar = new Dimension(); + } + + private void updateContainer(boolean forced, final boolean layoutNow) { + final TabLabel selectedLabel = getSelectedLabel(); + + for (TabInfo each : myVisibleInfos) { + final JComponent eachComponent = each.getComponent(); + if (getSelectedInfo() == each && getSelectedInfo() != null) { + unqueueFromRemove(eachComponent); + + final Container parent = eachComponent.getParent(); + if (parent != null && parent != this) { + parent.remove(eachComponent); + } + + if (eachComponent.getParent() == null) { + add(eachComponent); + } + } + else { + if (eachComponent.getParent() == null) continue; + if (isToDeferRemoveForLater(eachComponent)) { + queueForRemove(eachComponent); + } + else { + remove(eachComponent); + } + } + } + + relayout(forced, layoutNow); + } + + protected void addImpl(final Component comp, final Object constraints, final int index) { + unqueueFromRemove(comp); + + if (comp instanceof TabLabel) { + ((TabLabel)comp).apply(myUiDecorator.getDecoration()); + } + + super.addImpl(comp, constraints, index); + } + + + private boolean isToDeferRemoveForLater(JComponent c) { + return c.getRootPane() != null; + } + + void relayout(boolean forced, final boolean layoutNow) { + if (!myForcedRelayout) { + myForcedRelayout = forced; + } + revalidateAndRepaint(layoutNow); + } + + ActionManager getActionManager() { + return myActionManager; + } + + public TabsBorder getTabsBorder() { + return myBorder; + } + + @NotNull + public JBTabs addTabMouseMotionListener(@NotNull MouseMotionListener listener) { + removeListeners(); + myTabMouseListeners.add(listener); + addListeners(); + return this; + } + + @NotNull + public JBTabs addTabMouseListener(@NotNull MouseListener listener) { + removeListeners(); + myTabMouseListeners.add(listener); + addListeners(); + return this; + } + + @NotNull + public JComponent getComponent() { + return this; + } + + public boolean isCycleRoot() { + return false; + } + + @NotNull + public JBTabs removeTabMouseListener(@NotNull MouseListener listener) { + removeListeners(); + myTabMouseListeners.remove(listener); + addListeners(); + return this; + } + + private void addListeners() { + for (TabInfo eachInfo : myVisibleInfos) { + final TabLabel label = myInfo2Label.get(eachInfo); + for (EventListener eachListener : myTabMouseListeners) { + if (eachListener instanceof MouseListener) { + label.addMouseListener((MouseListener)eachListener); + } + else if (eachListener instanceof MouseMotionListener) { + label.addMouseMotionListener((MouseMotionListener)eachListener); + } + else { + assert false; + } + } + } + } + + private void removeListeners() { + for (TabInfo eachInfo : myVisibleInfos) { + final TabLabel label = myInfo2Label.get(eachInfo); + for (EventListener eachListener : myTabMouseListeners) { + if (eachListener instanceof MouseListener) { + label.removeMouseListener((MouseListener)eachListener); + } + else if (eachListener instanceof MouseMotionListener) { + label.removeMouseMotionListener((MouseMotionListener)eachListener); + } + else { + assert false; + } + } + } + } + + private void updateListeners() { + removeListeners(); + addListeners(); + } + + public JBTabs addListener(@NotNull TabsListener listener) { + myTabListeners.add(listener); + return this; + } + + public JBTabs removeListener(@NotNull final TabsListener listener) { + myTabListeners.remove(listener); + return this; + } + + protected void onPopup(final TabInfo popupInfo) { + } + + public void setFocused(final boolean focused) { + if (myFocused == focused) return; + + myFocused = focused; + + if (myPaintFocus) { + repaint(); + } + } + + public int getIndexOf(@Nullable final TabInfo tabInfo) { + return myVisibleInfos.indexOf(tabInfo); + } + + public boolean isHideTabs() { + return myHideTabs; + } + + public void setHideTabs(final boolean hideTabs) { + if (isHideTabs() == hideTabs) return; + + myHideTabs = hideTabs; + + relayout(true, false); + } + + public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) { + return myBorder.setPaintBorder(top, left, right, bottom); + } + + public JBTabsPresentation setTabSidePaintBorder(int size) { + return myBorder.setTabSidePaintBorder(size); + } + + static int getBorder(int size) { + return size == -1 ? 1 : size; + } + + public boolean isPaintFocus() { + return myPaintFocus; + } + + @NotNull + public JBTabsPresentation setAdjustBorders(final boolean adjust) { + myAdjustBorders = adjust; + return this; + } + + @NotNull + public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) { + if (!isChanged(myActiveTabFillIn, color)) return this; + + myActiveTabFillIn = color; + revalidateAndRepaint(false); + return this; + } + + private boolean isChanged(Object oldObject, Object newObject) { + if (oldObject == null && newObject == null) return false; + return oldObject != null && !oldObject.equals(newObject) || newObject != null && !newObject.equals(oldObject); + } + + @NotNull + public JBTabsPresentation setTabLabelActionsAutoHide(final boolean autoHide) { + if (myTabLabelActionsAutoHide != autoHide) { + myTabLabelActionsAutoHide = autoHide; + revalidateAndRepaint(false); + } + return this; + } + + @Nullable + public Color getActiveTabFillIn() { + return myActiveTabFillIn; + } + + public JBTabsPresentation setFocusCycle(final boolean root) { + setFocusCycleRoot(root); + return this; + } + + + public JBTabsPresentation setPaintFocus(final boolean paintFocus) { + myPaintFocus = paintFocus; + return this; + } + + private abstract static class BaseNavigationAction extends AnAction { + + private final ShadowAction myShadow; + private final ActionManager myActionManager; + private final JBTabsImpl myTabs; + + protected BaseNavigationAction(final String copyFromID, JBTabsImpl tabs, ActionManager mgr) { + myActionManager = mgr; + myTabs = tabs; + myShadow = new ShadowAction(this, myActionManager.getAction(copyFromID), tabs); + Disposer.register(tabs, myShadow); + setEnabledInModalContext(true); + } + + public final void update(final AnActionEvent e) { + JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY); + e.getPresentation().setVisible(tabs != null); + if (tabs == null) return; + + final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo()); + final boolean enabled = tabs == myTabs && myTabs.isNavigationVisible() && selectedIndex >= 0 && myTabs.myNavigationActionsEnabled; + e.getPresentation().setEnabled(enabled); + if (enabled) { + _update(e, tabs, selectedIndex); + } + } + + public void reconnect(String actionId) { + myShadow.reconnect(myActionManager.getAction(actionId)); + } + + protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex); + + public final void actionPerformed(final AnActionEvent e) { + JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY); + if (tabs == null) return; + + final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo()); + if (index == -1) return; + _actionPerformed(e, tabs, index); + } + + protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex); + } + + private static class SelectNextAction extends BaseNavigationAction { + + private SelectNextAction(JBTabsImpl tabs, ActionManager mgr) { + super(IdeActions.ACTION_NEXT_TAB, tabs, mgr); + } + + protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) { + e.getPresentation().setEnabled(tabs.findEnabledForward(selectedIndex + 1) != null); + } + + protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) { + tabs.select(tabs.findEnabledForward(selectedIndex + 1), true); + } + } + + private static class SelectPreviousAction extends BaseNavigationAction { + private SelectPreviousAction(JBTabsImpl tabs, ActionManager mgr) { + super(IdeActions.ACTION_PREVIOUS_TAB, tabs, mgr); + } + + protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) { + e.getPresentation().setEnabled(tabs.findEnabledBackward(selectedIndex - 1) != null); + } + + protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) { + tabs.select(tabs.findEnabledBackward(selectedIndex - 1), true); + } + } + + private void disposePopupListener() { + if (myActivePopup != null) { + myActivePopup.removePopupMenuListener(myPopupListener); + myActivePopup = null; + } + } + + public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) { + myStealthTabMode = stealthTabMode; + + relayout(true, false); + + return this; + } + + public boolean isStealthTabMode() { + return myStealthTabMode; + } + + public JBTabsPresentation setSideComponentVertical(final boolean vertical) { + myHorizontalSide = !vertical; + + for (TabInfo each : myVisibleInfos) { + each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2"); + } + + + relayout(true, false); + + return this; + } + + public JBTabsPresentation setSingleRow(boolean singleRow) { + myLayout = singleRow ? mySingleRowLayout : myTableLayout; + + relayout(true, false); + + return this; + } + + public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) { + myGhostsAlwaysVisible = visible; + + relayout(true, false); + + return this; + } + + public boolean isGhostsAlwaysVisible() { + return myGhostsAlwaysVisible; + } + + public boolean isSingleRow() { + return getEffectiveLayout() == mySingleRowLayout; + } + + public boolean isSideComponentVertical() { + return !myHorizontalSide; + } + + public TabLayout getEffectiveLayout() { + if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout; + return mySingleRowLayout; + } + + public JBTabsPresentation setUiDecorator(UiDecorator decorator) { + myUiDecorator = decorator == null ? ourDefaultDecorator : decorator; + applyDecoration(); + return this; + } + + protected void setUI(final ComponentUI newUI) { + super.setUI(newUI); + applyDecoration(); + } + + public void updateUI() { + super.updateUI(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + applyDecoration(); + + revalidateAndRepaint(false); + } + }); + } + + private void applyDecoration() { + if (myUiDecorator != null) { + UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration(); + for (TabLabel each : myInfo2Label.values()) { + each.apply(uiDecoration); + } + } + + + for (TabInfo each : getTabs()) { + adjust(each); + } + + relayout(true, false); + } + + private void adjust(final TabInfo each) { + if (myAdjustBorders) { + UIUtil.removeScrollBorder(each.getComponent()); + } + } + + public void sortTabs(Comparator comparator) { + Collections.sort(myVisibleInfos, comparator); + + relayout(true, false); + } + + public boolean isRequestFocusOnLastFocusedComponent() { + return myRequestFocusOnLastFocusedComponent; + } + + public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) { + myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent; + return this; + } + + + @Nullable + public Object getData(@NonNls final String dataId) { + if (myDataProvider != null) { + final Object value = myDataProvider.getData(dataId); + if (value != null) return value; + } + + if (SwitchProvider.KEY.getName().equals(dataId) && myOwnSwitchProvider) { + return this; + } + + if (QuickActionProvider.KEY.getName().equals(dataId)) { + return this; + } + + return NAVIGATION_ACTIONS_KEY.is(dataId) ? this : null; + } + + public List getActions(boolean originalProvider) { + ArrayList result = new ArrayList(); + + TabInfo selection = getSelectedInfo(); + if (selection != null) { + ActionGroup group = selection.getGroup(); + if (group != null) { + AnAction[] children = group.getChildren(null); + for (int i = 0; i < children.length; i++) { + result.add(children[i]); + } + } + } + + return result; + } + + public DataProvider getDataProvider() { + return myDataProvider; + } + + public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) { + myDataProvider = dataProvider; + return this; + } + + + public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) { + if (e.getClickCount() == 1 || canBeQuick) { + if (!e.isPopupTrigger()) { + return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown(); + } + } + + return false; + } + + + private static class DefautDecorator implements UiDecorator { + @NotNull + public UiDecoration getDecoration() { + return new UiDecoration(null, new Insets(1, 4, 1, 5)); + } + } + + public static Rectangle layout(JComponent c, Rectangle bounds) { + final Rectangle now = c.getBounds(); + if (!bounds.equals(now)) { + c.setBounds(bounds); + } + c.putClientProperty(LAYOUT_DONE, Boolean.TRUE); + + return bounds; + } + + public static Rectangle layout(JComponent c, int x, int y, int width, int height) { + return layout(c, new Rectangle(x, y, width, height)); + } + + public static void resetLayout(JComponent c) { + if (c == null) return; + c.putClientProperty(LAYOUT_DONE, null); + c.putClientProperty(STRETCHED_BY_WIDTH, null); + } + + private void applyResetComponents() { + for (int i = 0; i < getComponentCount(); i++) { + final Component each = getComponent(i); + if (each instanceof JComponent) { + final JComponent jc = (JComponent)each; + final Object done = jc.getClientProperty(LAYOUT_DONE); + if (!Boolean.TRUE.equals(done)) { + layout(jc, new Rectangle(0, 0, 0, 0)); + } + } + } + } + + + @NotNull + public JBTabsPresentation setTabLabelActionsMouseDeadzone(final TimedDeadzone.Length length) { + myTabActionsMouseDeadzone = length; + final List all = getTabs(); + for (TabInfo each : all) { + final TabLabel eachLabel = myInfo2Label.get(each); + eachLabel.updateTabActions(); + } + return this; + } + + @NotNull + public JBTabsPresentation setTabsPosition(final JBTabsPosition position) { + myPosition = position; + relayout(true, false); + return this; + } + + public JBTabsPosition getTabsPosition() { + return myPosition; + } + + public TimedDeadzone.Length getTabActionsMouseDeadzone() { + return myTabActionsMouseDeadzone; + } + + public JBTabsPresentation setTabDraggingEnabled(boolean enabled) { + myTabDraggingEnabled = enabled; + return this; + } + + public boolean isTabDraggingEnabled() { + return myTabDraggingEnabled; + } + + public JBTabsPresentation setProvideSwitchTargets(boolean provide) { + myOwnSwitchProvider = provide; + return this; + } + + void reallocate(TabInfo source, TabInfo target, boolean before) { + if (source == target || source == null || target == null) return; + + final int targetIndex = myVisibleInfos.indexOf(target); + final int sourceIndex = myVisibleInfos.indexOf(source); + + boolean needsValidation = false; + + myVisibleInfos.remove(source); + myVisibleInfos.add(targetIndex, source); + needsValidation = true; + + //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) { + //} + + if (needsValidation) { + invalidate(); + relayout(true, true); + } + } + + boolean isHorizontalTabs() { + return getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom; + } + + public void putInfo(Map info) { + final TabInfo selected = getSelectedInfo(); + if (selected != null) { + selected.putInfo(info); + } + } + + public boolean isUseBufferedPaint() { + return myUseBufferedPaint; + } + + public void setUseBufferedPaint(boolean useBufferedPaint) { + myUseBufferedPaint = useBufferedPaint; + revalidate(); + repaint(); + } + + public List getTargets(boolean onlyVisible, boolean originalProvider) { + ArrayList result = new ArrayList(); + for (TabInfo each : myVisibleInfos) { + result.add(new TabTarget(each)); + } + + if (originalProvider && mySwitchDelegate != null) { + List additional = mySwitchDelegate.getTargets(onlyVisible, false); + if (additional != null) { + result.addAll(additional); + } + } + + return result; + } + + + public SwitchTarget getCurrentTarget() { + if (mySwitchDelegate != null) { + SwitchTarget selection = mySwitchDelegate.getCurrentTarget(); + if (selection != null) return selection; + } + + return new TabTarget(getSelectedInfo()); + } + + private class TabTarget extends ComparableObject.Impl implements SwitchTarget { + + private TabInfo myInfo; + + private TabTarget(TabInfo info) { + myInfo = info; + } + + public ActionCallback switchTo(boolean requestFocus) { + return select(myInfo, requestFocus); + } + + public boolean isVisible() { + return getRectangle() != null; + } + + public RelativeRectangle getRectangle() { + TabLabel label = myInfo2Label.get(myInfo); + if (label.getRootPane() == null) return null; + + Rectangle b = label.getBounds(); + b.x += 2; + b.width -= 4; + b.y += 2; + b.height -= 4; + return new RelativeRectangle(label.getParent(), b); + } + + public Component getComponent() { + return myInfo2Label.get(myInfo); + } + + @Override + public String toString() { + return myInfo.getText(); + } + + @Override + public Object[] getEqualityObjects() { + return new Object[] {myInfo}; + } + } + + @Override + public void resetDropOver(TabInfo tabInfo) { + if (myDropInfo != null) { + TabInfo dropInfo = myDropInfo; + myDropInfo = null; + setDropInfoIndex(-1); + removeTab(dropInfo, null, false, true); + } + } + + @Override + public Image startDropOver(TabInfo tabInfo, RelativePoint point) { + myDropInfo = tabInfo; + + int index = myLayout.getDropIndexFor(point.getPoint(this)); + setDropInfoIndex(index); + addTab(myDropInfo, index, true); + + TabLabel label = myInfo2Label.get(myDropInfo); + Dimension size = label.getPreferredSize(); + label.setBounds(0, 0, size.width, size.height); + + BufferedImage img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + label.paintOffscreen(g); + g.dispose(); + + relayout(true, false); + + return img; + } + + @Override + public void processDropOver(TabInfo over, RelativePoint point) { + int index = myLayout.getDropIndexFor(point.getPoint(this)); + if (index != getDropInfoIndex()) { + setDropInfoIndex(index); + relayout(true, false); + } + } + + public int getDropInfoIndex() { + return myDropInfoIndex; + } + + @Override + public boolean isEmptyVisible() { + return myVisibleInfos.isEmpty(); + } +} diff --git a/platform/platform-api/src/com/intellij/ui/treeStructure/SimpleTree.java b/platform/platform-api/src/com/intellij/ui/treeStructure/SimpleTree.java index 0b8b3f2d3d5c..e468d675dc50 100644 --- a/platform/platform-api/src/com/intellij/ui/treeStructure/SimpleTree.java +++ b/platform/platform-api/src/com/intellij/ui/treeStructure/SimpleTree.java @@ -1,611 +1,611 @@ -/* - * 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.ui.treeStructure; - -import com.intellij.ide.util.treeView.AbstractTreeBuilder; -import com.intellij.ide.util.treeView.TreeVisitor; -import com.intellij.openapi.actionSystem.ActionGroup; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.ActionPopupMenu; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.ui.TreeUIHelper; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.CellEditorListener; -import javax.swing.event.ChangeEvent; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.plaf.basic.BasicTreeUI; -import javax.swing.text.Position; -import javax.swing.tree.*; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; -import java.util.List; - -public class SimpleTree extends Tree implements CellEditorListener { - - private final MouseListener myMouseListener = new MyMouseListener(); - - private ActionGroup myPopupGroup; - private String myPlace; - - private static final SimpleNode NULL_NODE = new NullNode(); - - // From FTree: - private static final int INVALID = -1; - private JComponent myEditorComponent; - private boolean myEscapePressed; - private int myEditingRow; - private boolean myIgnoreSelectionChange; - - private int myMinHeightInRows = 5; - - private Icon myExpandedHandle; - private Icon myCollapsedHandle; - private Icon myEmptyHandle; - - public SimpleTree() { - setModel(new DefaultTreeModel(new PatchedDefaultMutableTreeNode())); - TreeUtil.installActions(this); - - configureUiHelper(TreeUIHelper.getInstance()); - - addMouseListener(myMouseListener); - setCellRenderer(new SimpleNodeRenderer()); - - setEditable(false); - getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); - ToolTipManager.sharedInstance().registerComponent(this); - - getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { - public void valueChanged(TreeSelectionEvent e) { - if (!myIgnoreSelectionChange && hasSingleSelection()) { - getNodeFor(getSelectionPath()).handleSelection(SimpleTree.this); - } - } - }); - - addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER && hasSingleSelection()) { - handleDoubleClickOrEnter(getSelectionPath(), e); - } - if (e.getKeyCode() == KeyEvent.VK_F2 && e.getModifiers() == 0) { - e.consume(); // ignore start editing by F2 - } - } - }); - - UIUtil.setLineStyleAngled(this); - if (SystemInfo.isWindowsXP) { - setUI(new BasicTreeUI()); // In WindowsXP UI handles are not shown :( - } - - setOpaque(false); - } - - public SimpleTree(TreeModel aModel) { - this(); - setModel(aModel); - } - - protected void configureUiHelper(final TreeUIHelper helper) { - helper.installTreeSpeedSearch(this); - } - - @Override - public TreePath getNextMatch(final String prefix, final int startingRow, final Position.Bias bias) { - // turn JTree Speed Search off - return null; - } - - public boolean accept(AbstractTreeBuilder builder, final SimpleNodeVisitor visitor) { - return builder.accept(SimpleNode.class, new TreeVisitor() { - public boolean visit(SimpleNode node) { - return visitor.accept(node); - } - }) != null; - } - - public void setPopupGroup(ActionGroup aPopupGroup, String aPlace) { - myPopupGroup = aPopupGroup; - myPlace = aPlace; - } - - public SimpleNode getNodeFor(int row) { - return getNodeFor(getPathForRow(row)); - } - - public SimpleNode getNodeFor(TreePath aPath) { - if (aPath == null) { - return NULL_NODE; - } - - DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)aPath.getLastPathComponent(); - if (treeNode == null) { - return NULL_NODE; - } - - final Object userObject = treeNode.getUserObject(); - if (userObject instanceof SimpleNode) { - return (SimpleNode)userObject; - } - else { - return NULL_NODE; - } - } - - @Nullable - public TreePath getPathFor(SimpleNode node) { - final TreeNode nodeWithObject = TreeUtil.findNodeWithObject((DefaultMutableTreeNode)getModel().getRoot(), node); - if (nodeWithObject != null) { - return TreeUtil.getPathFromRoot(nodeWithObject); - } - return null; - } - - @Nullable - public SimpleNode getSelectedNode() { - if (isSelectionEmpty()) { - return null; - } - - return getNodeFor(getSelectionPath()); - } - - public boolean isSelectionEmpty() { - final TreePath selection = super.getSelectionPath(); - return selection == null || getNodeFor(selection) == NULL_NODE; - - } - - public SimpleNode[] getSelectedNodesIfUniform() { - List result = new ArrayList(); - TreePath[] selectionPaths = getSelectionPaths(); - if (selectionPaths != null) { - SimpleNode lastNode = null; - for (TreePath selectionPath : selectionPaths) { - SimpleNode nodeFor = getNodeFor(selectionPath); - - if (lastNode != null && lastNode.getClass() != nodeFor.getClass()) { - return new SimpleNode[0]; - } - - result.add(nodeFor); - lastNode = nodeFor; - } - } - return result.toArray(new SimpleNode[result.size()]); - } - - public void setSelectedNode(AbstractTreeBuilder builder, SimpleNode node, boolean expand) { - builder.select(node.getElement(), null, false); - } - - protected void paintChildren(Graphics g) { - super.paintChildren(g); - g.setColor(UIManager.getColor("Tree.line")); - - for (int row = 0; row < getRowCount(); row++) { - final TreePath path = getPathForRow(row); - if (!getNodeFor(path).shouldHaveSeparator()) { - continue; - } - - final Rectangle bounds = getRowBounds(row); - int x = (int)bounds.getMaxX(); - int y = (int)(bounds.getY() + bounds.height / 2); - g.drawLine(x, y, getWidth() - 5, y); - } - } - - public void doClick(int row) { - setSelectionRow(row); - } - - // From FTree: - - public void cancelEditing() { - if (isEditing()) { - cellEditor.cancelCellEditing(); - doStopEditing(); - } - } - - public void editingStopped(ChangeEvent e) { - doStopEditing(); - } - - public void editingCanceled(ChangeEvent e) { - doStopEditing(); - } - - public JComponent getEditorComponent() { - return myEditorComponent; - } - - public boolean isEditing() { - return myEditorComponent != null; - } - - public TreePath getEditingPath() { - if (isEditing()) { - return getPathForRow(myEditingRow); - } - return super.getEditingPath(); - } - - public boolean isPathEditable(TreePath path) { - return true; - } - - @Override - protected boolean paintNodes() { - return true; - } - - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - if (isEditing()) { - Rectangle editedNodeRect = getRowBounds(myEditingRow); - if (editedNodeRect == null) return; - g.setColor(getBackground()); - g.fillRect(editedNodeRect.x, editedNodeRect.y, editedNodeRect.width, editedNodeRect.height); - } - } - - public void setCellEditor(TreeCellEditor aCellEditor) { - if (cellEditor != null) { - cellEditor.removeCellEditorListener(this); - } - super.setCellEditor(aCellEditor); - - if (cellEditor != null) { - cellEditor.addCellEditorListener(this); - } - } - - public boolean stopEditing() { - boolean result = isEditing(); - if (result) { - if (!cellEditor.stopCellEditing()) { - cellEditor.cancelCellEditing(); - } - doStopEditing(); - } - return result; - } - - public void startEditingAtPath(final TreePath path) { - if (path != null && isVisible(path)) { - - if (isEditing() && !stopEditing()) { - return; - } - - startEditing(path); - } - } - - private void startEditing(final TreePath path) { - - CellEditor editor = getCellEditor(); - if (editor != null && editor.isCellEditable(null) && isPathEditable(path)) { - - getSelectionModel().clearSelection(); - getSelectionModel().setSelectionPath(path); - - myEditingRow = getRowForPath(path); - myEditorComponent = (JComponent)getCellEditor() - .getTreeCellEditorComponent(this, path.getLastPathComponent(), isPathSelected(path), isExpanded(path), - treeModel.isLeaf(path.getLastPathComponent()), myEditingRow); - - putEditor(path); - - if (myEditorComponent.isFocusable()) { - myEditorComponent.requestFocusInWindow(); - } - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - scrollPathToVisible(path); - } - }); - } - } - - private void putEditor(TreePath path) { - - add(myEditorComponent); - Rectangle nodeBounds = getPathBounds(path); - Dimension editorPrefSize = myEditorComponent.getPreferredSize(); - if (editorPrefSize.height > nodeBounds.height) { - nodeBounds.y -= (editorPrefSize.height - nodeBounds.height) / 2; - nodeBounds.height = editorPrefSize.height; - } - - myEditorComponent.setBounds(nodeBounds); - - myEscapePressed = false; - } - - private void doStopEditing() { - if (isEditing()) { - remove(myEditorComponent); - myEditorComponent = null; - setSelectionRow(myEditingRow); - myEditingRow = INVALID; - repaint(); - } - } - - public boolean isEscapePressed() { - return myEscapePressed; - } - - public void setEscapePressed() { - myEscapePressed = true; - } - - public void addSelectionPath(TreePath path) { - myIgnoreSelectionChange = true; - super.addSelectionPath(path); - myIgnoreSelectionChange = false; - } - - public void addSelectionPaths(TreePath[] path) { - myIgnoreSelectionChange = true; - super.addSelectionPaths(path); - myIgnoreSelectionChange = false; - } - - private boolean isSelected(TreePath path) { - TreePath[] selectionPaths = getSelectionPaths(); - if (selectionPaths != null) { - for (TreePath selectionPath : selectionPaths) { - if (path.equals(selectionPath)) { - return true; - } - } - } - return false; - } - - public boolean isMultipleSelection() { - return getSelectionRows() != null && getSelectionRows().length > 1; - } - - private void handleDoubleClickOrEnter(final TreePath treePath, final InputEvent e) { - Runnable runnable = new Runnable() { - public void run() { - getNodeFor(treePath).handleDoubleClickOrEnter(SimpleTree.this, e); - } - }; - ApplicationManager.getApplication().invokeLater(runnable, ModalityState.stateForComponent(this)); - } - - // TODO: move to some util? - public static boolean isDoubleClick(MouseEvent e) { - return e != null && e.getClickCount() > 0 && e.getClickCount() % 2 == 0; - } - - protected ActionGroup getPopupGroup() { - return myPopupGroup; - } - - protected void invokeContextMenu(final MouseEvent e) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - final ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(myPlace, myPopupGroup); - menu.getComponent().show(e.getComponent(), e.getPoint().x, e.getPoint().y); - } - }); - } - - private class MyMouseListener extends MouseAdapter { - public void mousePressed(MouseEvent e) { - if (e.isPopupTrigger()) { - invokePopup(e); - } - else if (isDoubleClick(e)) { - handleDoubleClickOrEnter(getClosestPathForLocation(e.getX(), e.getY()), e); - /* - if (!TreeWizardPopupImpl.isLocationInExpandControl(SimpleTree.this, getSelectionPath(), e.getX(), e.getY())) { - TreePath treePath = getClosestPathForLocation(e.getX(), e.getY()); - handleDoubleClickOrEnter(treePath, e); - } - */ - } - } - - public void mouseReleased(MouseEvent e) { - invokePopup(e); - } - - public void mouseClicked(MouseEvent e) { - invokePopup(e); - } - - private void invokePopup(final MouseEvent e) { - if (e.isPopupTrigger() && insideTreeItemsArea(e)) { - - selectPathUnderCursorIfNeeded(e); - - if (myPopupGroup != null) { - invokeContextMenu(e); - } - } - } - - private void selectPathUnderCursorIfNeeded(final MouseEvent e) { - TreePath pathForLocation = getClosestPathForLocation(e.getX(), e.getY()); - if (!isSelected(pathForLocation)) { - setSelectionPath(pathForLocation); - } - } - - private boolean insideTreeItemsArea(MouseEvent e) { - Rectangle rowBounds = getRowBounds(getRowCount() - 1); - if (rowBounds == null) { - return false; - } - double lastItemBottomLine = rowBounds.getMaxY(); - return e.getY() <= lastItemBottomLine; - } - } - - public boolean select(AbstractTreeBuilder aBuilder, final SimpleNodeVisitor aVisitor, boolean shouldExpand) { - return aBuilder.select(SimpleNode.class, new TreeVisitor() { - public boolean visit(SimpleNode node) { - return aVisitor.accept(node); - } - }, null, false); - } - - private void debugTree(AbstractTreeBuilder aBuilder) { - TreeUtil.traverseDepth((TreeNode)aBuilder.getTree().getModel().getRoot(), new TreeUtil.Traverse() { - public boolean accept(Object node) { - System.out.println("Node: " + node); - return true; - } - }); - } - - private boolean hasSingleSelection() { - return !isSelectionEmpty() && getSelectionPaths().length == 1; - } - - public DefaultTreeModel getBuilderModel() { - return (DefaultTreeModel)getModel(); - } - - public Dimension getPreferredScrollableViewportSize() { - return super.getPreferredSize(); - } - - public SimpleNodeRenderer getRenderer() { - return (SimpleNodeRenderer)getCellRenderer(); - } - - public String toString() { - return getClass().getName() + '#' + System.identityHashCode(this); - } - - public final void setMinSizeInRows(int rows) { - myMinHeightInRows = rows; - } - - public Dimension getMinimumSize() { - Dimension superSize = super.getMinimumSize(); - - if (myMinHeightInRows == -1) return superSize; - int rowCount = getRowCount(); - if (rowCount == 0) return superSize; - - double rowHeight = getRowBounds(0).getHeight(); - return new Dimension(superSize.width, (int)(rowHeight * myMinHeightInRows)); - } - - public final int getToggleClickCount() { - SimpleNode node = getSelectedNode(); - if (node != null) { - if (!node.expandOnDoubleClick()) return -1; - } - return super.getToggleClickCount(); - } - - @Override - public void processKeyEvent(final KeyEvent e) { - super.processKeyEvent(e); - } - - private int getBoxWidth(TreePath path) { - final Object root = getModel().getRoot(); - if (!isRootVisible()) { - if (path.getPathCount() == 2) { - final TreePath parent = path.getParentPath(); - if (parent.getLastPathComponent() == root && !getShowsRootHandles()) { - return 0; - } - } - } - - return getBoxWidth(this); - } - - private static int getBoxWidth(JTree tree) { - BasicTreeUI basicTreeUI = (BasicTreeUI)tree.getUI(); - int boxWidth; - if (basicTreeUI.getExpandedIcon() != null) { - boxWidth = basicTreeUI.getExpandedIcon().getIconWidth(); - } - else { - boxWidth = 8; - } - return boxWidth; - } - - @Override - public void updateUI() { - super.updateUI(); - - myExpandedHandle = null; - myCollapsedHandle = null; - myExpandedHandle = null; - } - - public Icon getHandleIcon(DefaultMutableTreeNode node, TreePath path) { - if (node.getChildCount() == 0) return getEmptyHandle(); - - - return isExpanded(path) ? getExpandedHandle() : getCollapsedHandle(); - - } - - public Icon getExpandedHandle() { - if (myExpandedHandle == null) { - myExpandedHandle = UIUtil.getTreeExpandedIcon(); - } - - return myExpandedHandle; - } - - public Icon getCollapsedHandle() { - if (myCollapsedHandle == null) { - myCollapsedHandle = UIUtil.getTreeCollapsedIcon(); - } - - return myCollapsedHandle; - } - - public Icon getEmptyHandle() { - if (myEmptyHandle == null) { - final Icon expand = getExpandedHandle(); - myEmptyHandle = expand != null ? new EmptyIcon(expand) : new EmptyIcon(0, 0); - } - - return myEmptyHandle; - } - -} +/* + * 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.ui.treeStructure; + +import com.intellij.ide.util.treeView.AbstractTreeBuilder; +import com.intellij.ide.util.treeView.TreeVisitor; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPopupMenu; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.ui.TreeUIHelper; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.plaf.basic.BasicTreeUI; +import javax.swing.text.Position; +import javax.swing.tree.*; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.List; + +public class SimpleTree extends Tree implements CellEditorListener { + + private final MouseListener myMouseListener = new MyMouseListener(); + + private ActionGroup myPopupGroup; + private String myPlace; + + private static final SimpleNode NULL_NODE = new NullNode(); + + // From FTree: + private static final int INVALID = -1; + private JComponent myEditorComponent; + private boolean myEscapePressed; + private int myEditingRow; + private boolean myIgnoreSelectionChange; + + private int myMinHeightInRows = 5; + + private Icon myExpandedHandle; + private Icon myCollapsedHandle; + private Icon myEmptyHandle; + + public SimpleTree() { + setModel(new DefaultTreeModel(new PatchedDefaultMutableTreeNode())); + TreeUtil.installActions(this); + + configureUiHelper(TreeUIHelper.getInstance()); + + addMouseListener(myMouseListener); + setCellRenderer(new SimpleNodeRenderer()); + + setEditable(false); + getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + ToolTipManager.sharedInstance().registerComponent(this); + + getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + if (!myIgnoreSelectionChange && hasSingleSelection()) { + getNodeFor(getSelectionPath()).handleSelection(SimpleTree.this); + } + } + }); + + addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER && hasSingleSelection()) { + handleDoubleClickOrEnter(getSelectionPath(), e); + } + if (e.getKeyCode() == KeyEvent.VK_F2 && e.getModifiers() == 0) { + e.consume(); // ignore start editing by F2 + } + } + }); + + UIUtil.setLineStyleAngled(this); + if (SystemInfo.isWindowsXP) { + setUI(new BasicTreeUI()); // In WindowsXP UI handles are not shown :( + } + + setOpaque(false); + } + + public SimpleTree(TreeModel aModel) { + this(); + setModel(aModel); + } + + protected void configureUiHelper(final TreeUIHelper helper) { + helper.installTreeSpeedSearch(this); + } + + @Override + public TreePath getNextMatch(final String prefix, final int startingRow, final Position.Bias bias) { + // turn JTree Speed Search off + return null; + } + + public boolean accept(AbstractTreeBuilder builder, final SimpleNodeVisitor visitor) { + return builder.accept(SimpleNode.class, new TreeVisitor() { + public boolean visit(SimpleNode node) { + return visitor.accept(node); + } + }) != null; + } + + public void setPopupGroup(ActionGroup aPopupGroup, String aPlace) { + myPopupGroup = aPopupGroup; + myPlace = aPlace; + } + + public SimpleNode getNodeFor(int row) { + return getNodeFor(getPathForRow(row)); + } + + public SimpleNode getNodeFor(TreePath aPath) { + if (aPath == null) { + return NULL_NODE; + } + + DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)aPath.getLastPathComponent(); + if (treeNode == null) { + return NULL_NODE; + } + + final Object userObject = treeNode.getUserObject(); + if (userObject instanceof SimpleNode) { + return (SimpleNode)userObject; + } + else { + return NULL_NODE; + } + } + + @Nullable + public TreePath getPathFor(SimpleNode node) { + final TreeNode nodeWithObject = TreeUtil.findNodeWithObject((DefaultMutableTreeNode)getModel().getRoot(), node); + if (nodeWithObject != null) { + return TreeUtil.getPathFromRoot(nodeWithObject); + } + return null; + } + + @Nullable + public SimpleNode getSelectedNode() { + if (isSelectionEmpty()) { + return null; + } + + return getNodeFor(getSelectionPath()); + } + + public boolean isSelectionEmpty() { + final TreePath selection = super.getSelectionPath(); + return selection == null || getNodeFor(selection) == NULL_NODE; + + } + + public SimpleNode[] getSelectedNodesIfUniform() { + List result = new ArrayList(); + TreePath[] selectionPaths = getSelectionPaths(); + if (selectionPaths != null) { + SimpleNode lastNode = null; + for (TreePath selectionPath : selectionPaths) { + SimpleNode nodeFor = getNodeFor(selectionPath); + + if (lastNode != null && lastNode.getClass() != nodeFor.getClass()) { + return new SimpleNode[0]; + } + + result.add(nodeFor); + lastNode = nodeFor; + } + } + return result.toArray(new SimpleNode[result.size()]); + } + + public void setSelectedNode(AbstractTreeBuilder builder, SimpleNode node, boolean expand) { + builder.select(node.getElement(), null, false); + } + + protected void paintChildren(Graphics g) { + super.paintChildren(g); + g.setColor(UIManager.getColor("Tree.line")); + + for (int row = 0; row < getRowCount(); row++) { + final TreePath path = getPathForRow(row); + if (!getNodeFor(path).shouldHaveSeparator()) { + continue; + } + + final Rectangle bounds = getRowBounds(row); + int x = (int)bounds.getMaxX(); + int y = (int)(bounds.getY() + bounds.height / 2); + g.drawLine(x, y, getWidth() - 5, y); + } + } + + public void doClick(int row) { + setSelectionRow(row); + } + + // From FTree: + + public void cancelEditing() { + if (isEditing()) { + cellEditor.cancelCellEditing(); + doStopEditing(); + } + } + + public void editingStopped(ChangeEvent e) { + doStopEditing(); + } + + public void editingCanceled(ChangeEvent e) { + doStopEditing(); + } + + public JComponent getEditorComponent() { + return myEditorComponent; + } + + public boolean isEditing() { + return myEditorComponent != null; + } + + public TreePath getEditingPath() { + if (isEditing()) { + return getPathForRow(myEditingRow); + } + return super.getEditingPath(); + } + + public boolean isPathEditable(TreePath path) { + return true; + } + + @Override + protected boolean paintNodes() { + return true; + } + + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + if (isEditing()) { + Rectangle editedNodeRect = getRowBounds(myEditingRow); + if (editedNodeRect == null) return; + g.setColor(getBackground()); + g.fillRect(editedNodeRect.x, editedNodeRect.y, editedNodeRect.width, editedNodeRect.height); + } + } + + public void setCellEditor(TreeCellEditor aCellEditor) { + if (cellEditor != null) { + cellEditor.removeCellEditorListener(this); + } + super.setCellEditor(aCellEditor); + + if (cellEditor != null) { + cellEditor.addCellEditorListener(this); + } + } + + public boolean stopEditing() { + boolean result = isEditing(); + if (result) { + if (!cellEditor.stopCellEditing()) { + cellEditor.cancelCellEditing(); + } + doStopEditing(); + } + return result; + } + + public void startEditingAtPath(final TreePath path) { + if (path != null && isVisible(path)) { + + if (isEditing() && !stopEditing()) { + return; + } + + startEditing(path); + } + } + + private void startEditing(final TreePath path) { + + CellEditor editor = getCellEditor(); + if (editor != null && editor.isCellEditable(null) && isPathEditable(path)) { + + getSelectionModel().clearSelection(); + getSelectionModel().setSelectionPath(path); + + myEditingRow = getRowForPath(path); + myEditorComponent = (JComponent)getCellEditor() + .getTreeCellEditorComponent(this, path.getLastPathComponent(), isPathSelected(path), isExpanded(path), + treeModel.isLeaf(path.getLastPathComponent()), myEditingRow); + + putEditor(path); + + if (myEditorComponent.isFocusable()) { + myEditorComponent.requestFocusInWindow(); + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + scrollPathToVisible(path); + } + }); + } + } + + private void putEditor(TreePath path) { + + add(myEditorComponent); + Rectangle nodeBounds = getPathBounds(path); + Dimension editorPrefSize = myEditorComponent.getPreferredSize(); + if (editorPrefSize.height > nodeBounds.height) { + nodeBounds.y -= (editorPrefSize.height - nodeBounds.height) / 2; + nodeBounds.height = editorPrefSize.height; + } + + myEditorComponent.setBounds(nodeBounds); + + myEscapePressed = false; + } + + private void doStopEditing() { + if (isEditing()) { + remove(myEditorComponent); + myEditorComponent = null; + setSelectionRow(myEditingRow); + myEditingRow = INVALID; + repaint(); + } + } + + public boolean isEscapePressed() { + return myEscapePressed; + } + + public void setEscapePressed() { + myEscapePressed = true; + } + + public void addSelectionPath(TreePath path) { + myIgnoreSelectionChange = true; + super.addSelectionPath(path); + myIgnoreSelectionChange = false; + } + + public void addSelectionPaths(TreePath[] path) { + myIgnoreSelectionChange = true; + super.addSelectionPaths(path); + myIgnoreSelectionChange = false; + } + + private boolean isSelected(TreePath path) { + TreePath[] selectionPaths = getSelectionPaths(); + if (selectionPaths != null) { + for (TreePath selectionPath : selectionPaths) { + if (path.equals(selectionPath)) { + return true; + } + } + } + return false; + } + + public boolean isMultipleSelection() { + return getSelectionRows() != null && getSelectionRows().length > 1; + } + + private void handleDoubleClickOrEnter(final TreePath treePath, final InputEvent e) { + Runnable runnable = new Runnable() { + public void run() { + getNodeFor(treePath).handleDoubleClickOrEnter(SimpleTree.this, e); + } + }; + ApplicationManager.getApplication().invokeLater(runnable, ModalityState.stateForComponent(this)); + } + + // TODO: move to some util? + public static boolean isDoubleClick(MouseEvent e) { + return e != null && e.getClickCount() > 0 && e.getClickCount() % 2 == 0; + } + + protected ActionGroup getPopupGroup() { + return myPopupGroup; + } + + protected void invokeContextMenu(final MouseEvent e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(myPlace, myPopupGroup); + menu.getComponent().show(e.getComponent(), e.getPoint().x, e.getPoint().y); + } + }); + } + + private class MyMouseListener extends MouseAdapter { + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + invokePopup(e); + } + else if (isDoubleClick(e)) { + handleDoubleClickOrEnter(getClosestPathForLocation(e.getX(), e.getY()), e); + /* + if (!TreeWizardPopupImpl.isLocationInExpandControl(SimpleTree.this, getSelectionPath(), e.getX(), e.getY())) { + TreePath treePath = getClosestPathForLocation(e.getX(), e.getY()); + handleDoubleClickOrEnter(treePath, e); + } + */ + } + } + + public void mouseReleased(MouseEvent e) { + invokePopup(e); + } + + public void mouseClicked(MouseEvent e) { + invokePopup(e); + } + + private void invokePopup(final MouseEvent e) { + if (e.isPopupTrigger() && insideTreeItemsArea(e)) { + + selectPathUnderCursorIfNeeded(e); + + if (myPopupGroup != null) { + invokeContextMenu(e); + } + } + } + + private void selectPathUnderCursorIfNeeded(final MouseEvent e) { + TreePath pathForLocation = getClosestPathForLocation(e.getX(), e.getY()); + if (!isSelected(pathForLocation)) { + setSelectionPath(pathForLocation); + } + } + + private boolean insideTreeItemsArea(MouseEvent e) { + Rectangle rowBounds = getRowBounds(getRowCount() - 1); + if (rowBounds == null) { + return false; + } + double lastItemBottomLine = rowBounds.getMaxY(); + return e.getY() <= lastItemBottomLine; + } + } + + public boolean select(AbstractTreeBuilder aBuilder, final SimpleNodeVisitor aVisitor, boolean shouldExpand) { + return aBuilder.select(SimpleNode.class, new TreeVisitor() { + public boolean visit(SimpleNode node) { + return aVisitor.accept(node); + } + }, null, false); + } + + private void debugTree(AbstractTreeBuilder aBuilder) { + TreeUtil.traverseDepth((TreeNode)aBuilder.getTree().getModel().getRoot(), new TreeUtil.Traverse() { + public boolean accept(Object node) { + System.out.println("Node: " + node); + return true; + } + }); + } + + private boolean hasSingleSelection() { + return !isSelectionEmpty() && getSelectionPaths().length == 1; + } + + public DefaultTreeModel getBuilderModel() { + return (DefaultTreeModel)getModel(); + } + + public Dimension getPreferredScrollableViewportSize() { + return super.getPreferredSize(); + } + + public SimpleNodeRenderer getRenderer() { + return (SimpleNodeRenderer)getCellRenderer(); + } + + public String toString() { + return getClass().getName() + '#' + System.identityHashCode(this); + } + + public final void setMinSizeInRows(int rows) { + myMinHeightInRows = rows; + } + + public Dimension getMinimumSize() { + Dimension superSize = super.getMinimumSize(); + + if (myMinHeightInRows == -1) return superSize; + int rowCount = getRowCount(); + if (rowCount == 0) return superSize; + + double rowHeight = getRowBounds(0).getHeight(); + return new Dimension(superSize.width, (int)(rowHeight * myMinHeightInRows)); + } + + public final int getToggleClickCount() { + SimpleNode node = getSelectedNode(); + if (node != null) { + if (!node.expandOnDoubleClick()) return -1; + } + return super.getToggleClickCount(); + } + + @Override + public void processKeyEvent(final KeyEvent e) { + super.processKeyEvent(e); + } + + private int getBoxWidth(TreePath path) { + final Object root = getModel().getRoot(); + if (!isRootVisible()) { + if (path.getPathCount() == 2) { + final TreePath parent = path.getParentPath(); + if (parent.getLastPathComponent() == root && !getShowsRootHandles()) { + return 0; + } + } + } + + return getBoxWidth(this); + } + + private static int getBoxWidth(JTree tree) { + BasicTreeUI basicTreeUI = (BasicTreeUI)tree.getUI(); + int boxWidth; + if (basicTreeUI.getExpandedIcon() != null) { + boxWidth = basicTreeUI.getExpandedIcon().getIconWidth(); + } + else { + boxWidth = 8; + } + return boxWidth; + } + + @Override + public void updateUI() { + super.updateUI(); + + myExpandedHandle = null; + myCollapsedHandle = null; + myExpandedHandle = null; + } + + public Icon getHandleIcon(DefaultMutableTreeNode node, TreePath path) { + if (node.getChildCount() == 0) return getEmptyHandle(); + + + return isExpanded(path) ? getExpandedHandle() : getCollapsedHandle(); + + } + + public Icon getExpandedHandle() { + if (myExpandedHandle == null) { + myExpandedHandle = UIUtil.getTreeExpandedIcon(); + } + + return myExpandedHandle; + } + + public Icon getCollapsedHandle() { + if (myCollapsedHandle == null) { + myCollapsedHandle = UIUtil.getTreeCollapsedIcon(); + } + + return myCollapsedHandle; + } + + public Icon getEmptyHandle() { + if (myEmptyHandle == null) { + final Icon expand = getExpandedHandle(); + myEmptyHandle = expand != null ? new EmptyIcon(expand) : new EmptyIcon(0, 0); + } + + return myEmptyHandle; + } + +} diff --git a/platform/platform-api/src/com/intellij/util/ui/AnimatedIcon.java b/platform/platform-api/src/com/intellij/util/ui/AnimatedIcon.java index bfe73fff3b86..d23a8677248f 100644 --- a/platform/platform-api/src/com/intellij/util/ui/AnimatedIcon.java +++ b/platform/platform-api/src/com/intellij/util/ui/AnimatedIcon.java @@ -1,201 +1,201 @@ -/* - * 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.util.ui; - -import com.intellij.openapi.Disposable; - -import javax.swing.*; -import java.awt.*; - -public abstract class AnimatedIcon extends JComponent implements Disposable { - - private Icon[] myIcons; - private Dimension myPrefSize = new Dimension(); - - private int myCurrentIconIndex; - - private Icon myPassiveIcon; - - private Icon myEmptyPassiveIcon; - private boolean myPaintPassive = true; - - private boolean myRunning = true; - - protected Animator myAnimator; - - private final String myName; - - private boolean myLastPaintWasRunning; - private boolean myPaintingBgNow; - - protected AnimatedIcon(final String name) { - myName = name; - } - - protected final void init(Icon[] icons, Icon passiveIcon, int cycleLength, final int interCycleGap, final int maxRepeatCount) { - myIcons = icons; - myPassiveIcon = passiveIcon; - - myPrefSize = new Dimension(); - for (Icon each : icons) { - myPrefSize.width = Math.max(each.getIconWidth(), myPrefSize.width); - myPrefSize.height = Math.max(each.getIconHeight(), myPrefSize.height); - } - - myPrefSize.width = Math.max(passiveIcon.getIconWidth(), myPrefSize.width); - myPrefSize.height = Math.max(passiveIcon.getIconHeight(), myPrefSize.height); - - UIUtil.removeQuaquaVisualMarginsIn(this); - - myAnimator = new Animator(myName, icons.length, cycleLength, true, interCycleGap, maxRepeatCount) { - public void paintNow(final float frame, final float totalFrames, final float cycle) { - myCurrentIconIndex = (int)frame; - paintImmediately(0, 0, getWidth(), getHeight()); - } - - protected void onAnimationMaxCycleReached() throws InterruptedException { - AnimatedIcon.this.onAnimationMaxCycleReached(); - } - - public boolean isAnimated() { - return AnimatedIcon.this.isAnimated(); - } - }; - - - if (icons.length > 0) { - myEmptyPassiveIcon = new EmptyIcon(icons[0]); - } else { - myEmptyPassiveIcon = new EmptyIcon(0); - } - - setOpaque(false); - } - - public void setPaintPassiveIcon(boolean paintPassive) { - myPaintPassive = paintPassive; - } - - protected void onAnimationMaxCycleReached() throws InterruptedException { - - } - - private boolean ensureAnimation(boolean running) { - boolean changes = myAnimator.isRunning() != running; - - if (running) { - myAnimator.resume(); - } else { - myAnimator.suspend(); - } - - return changes; - } - - public void addNotify() { - super.addNotify(); - if (myRunning) { - ensureAnimation(true); - } - } - - public void removeNotify() { - super.removeNotify(); - ensureAnimation(false); - } - - public void resume() { - myRunning = true; - ensureAnimation(true); - } - - public void suspend() { - myRunning = false; - if (ensureAnimation(false)) { - repaint(); - } - } - - public void dispose() { - myAnimator.dispose(); - } - - public Dimension getPreferredSize() { - final Insets insets = getInsets(); - return new Dimension(myPrefSize.width + insets.left + insets.right, myPrefSize.height + insets.top + insets.bottom); - } - - public Dimension getMinimumSize() { - return getPreferredSize(); - } - - public Dimension getMaximumSize() { - return getPreferredSize(); - } - - protected void paintComponent(Graphics g) { - if (myPaintingBgNow) return; - - if (isOpaque()) { - final Container parent = getParent(); - JComponent opaque = null; - if (parent instanceof JComponent) { - opaque = (JComponent)UIUtil.findNearestOpaque((JComponent)parent); - } - Color bg = opaque != null ? opaque.getBackground() : UIUtil.getPanelBackground(); - g.setColor(bg); - g.fillRect(0, 0, getWidth(), getHeight()); - } - - Icon icon; - - if (myAnimator.isRunning()) { - icon = myIcons[myCurrentIconIndex]; - } else { - icon = getPassiveIcon(); - } - - final Dimension size = getSize(); - int x = (size.width - icon.getIconWidth()) / 2; - int y = (size.height - icon.getIconHeight()) / 2; - - paintIcon(g, icon, x, y); - - myLastPaintWasRunning = myAnimator.isRunning(); - } - - protected void paintIcon(Graphics g, Icon icon, int x, int y) { - icon.paintIcon(this, g, x, y); - } - - protected Icon getPassiveIcon() { - return myPaintPassive ? myPassiveIcon : myEmptyPassiveIcon; - } - - public boolean isRunning() { - return myAnimator.isRunning(); - } - - public boolean isAnimated() { - return true; - } - - @Override - public String toString() { - return myName + " isRunning=" + myRunning + " isOpaque=" + isOpaque() + " paintPassive=" + myPaintPassive; - } -} +/* + * 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.util.ui; + +import com.intellij.openapi.Disposable; + +import javax.swing.*; +import java.awt.*; + +public abstract class AnimatedIcon extends JComponent implements Disposable { + + private Icon[] myIcons; + private Dimension myPrefSize = new Dimension(); + + private int myCurrentIconIndex; + + private Icon myPassiveIcon; + + private Icon myEmptyPassiveIcon; + private boolean myPaintPassive = true; + + private boolean myRunning = true; + + protected Animator myAnimator; + + private final String myName; + + private boolean myLastPaintWasRunning; + private boolean myPaintingBgNow; + + protected AnimatedIcon(final String name) { + myName = name; + } + + protected final void init(Icon[] icons, Icon passiveIcon, int cycleLength, final int interCycleGap, final int maxRepeatCount) { + myIcons = icons; + myPassiveIcon = passiveIcon; + + myPrefSize = new Dimension(); + for (Icon each : icons) { + myPrefSize.width = Math.max(each.getIconWidth(), myPrefSize.width); + myPrefSize.height = Math.max(each.getIconHeight(), myPrefSize.height); + } + + myPrefSize.width = Math.max(passiveIcon.getIconWidth(), myPrefSize.width); + myPrefSize.height = Math.max(passiveIcon.getIconHeight(), myPrefSize.height); + + UIUtil.removeQuaquaVisualMarginsIn(this); + + myAnimator = new Animator(myName, icons.length, cycleLength, true, interCycleGap, maxRepeatCount) { + public void paintNow(final float frame, final float totalFrames, final float cycle) { + myCurrentIconIndex = (int)frame; + paintImmediately(0, 0, getWidth(), getHeight()); + } + + protected void onAnimationMaxCycleReached() throws InterruptedException { + AnimatedIcon.this.onAnimationMaxCycleReached(); + } + + public boolean isAnimated() { + return AnimatedIcon.this.isAnimated(); + } + }; + + + if (icons.length > 0) { + myEmptyPassiveIcon = new EmptyIcon(icons[0]); + } else { + myEmptyPassiveIcon = new EmptyIcon(0); + } + + setOpaque(false); + } + + public void setPaintPassiveIcon(boolean paintPassive) { + myPaintPassive = paintPassive; + } + + protected void onAnimationMaxCycleReached() throws InterruptedException { + + } + + private boolean ensureAnimation(boolean running) { + boolean changes = myAnimator.isRunning() != running; + + if (running) { + myAnimator.resume(); + } else { + myAnimator.suspend(); + } + + return changes; + } + + public void addNotify() { + super.addNotify(); + if (myRunning) { + ensureAnimation(true); + } + } + + public void removeNotify() { + super.removeNotify(); + ensureAnimation(false); + } + + public void resume() { + myRunning = true; + ensureAnimation(true); + } + + public void suspend() { + myRunning = false; + if (ensureAnimation(false)) { + repaint(); + } + } + + public void dispose() { + myAnimator.dispose(); + } + + public Dimension getPreferredSize() { + final Insets insets = getInsets(); + return new Dimension(myPrefSize.width + insets.left + insets.right, myPrefSize.height + insets.top + insets.bottom); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + protected void paintComponent(Graphics g) { + if (myPaintingBgNow) return; + + if (isOpaque()) { + final Container parent = getParent(); + JComponent opaque = null; + if (parent instanceof JComponent) { + opaque = (JComponent)UIUtil.findNearestOpaque((JComponent)parent); + } + Color bg = opaque != null ? opaque.getBackground() : UIUtil.getPanelBackground(); + g.setColor(bg); + g.fillRect(0, 0, getWidth(), getHeight()); + } + + Icon icon; + + if (myAnimator.isRunning()) { + icon = myIcons[myCurrentIconIndex]; + } else { + icon = getPassiveIcon(); + } + + final Dimension size = getSize(); + int x = (size.width - icon.getIconWidth()) / 2; + int y = (size.height - icon.getIconHeight()) / 2; + + paintIcon(g, icon, x, y); + + myLastPaintWasRunning = myAnimator.isRunning(); + } + + protected void paintIcon(Graphics g, Icon icon, int x, int y) { + icon.paintIcon(this, g, x, y); + } + + protected Icon getPassiveIcon() { + return myPaintPassive ? myPassiveIcon : myEmptyPassiveIcon; + } + + public boolean isRunning() { + return myAnimator.isRunning(); + } + + public boolean isAnimated() { + return true; + } + + @Override + public String toString() { + return myName + " isRunning=" + myRunning + " isOpaque=" + isOpaque() + " paintPassive=" + myPaintPassive; + } +} diff --git a/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java b/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java index 51e94f060224..f2d42203256f 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java +++ b/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java @@ -1,223 +1,223 @@ -/* - * 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.codeInsight.hint; - -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.editor.LogicalPosition; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.ex.FoldingModelEx; -import com.intellij.openapi.util.TextRange; -import com.intellij.ui.HintHint; -import com.intellij.ui.LightweightHint; -import com.intellij.ui.ScreenUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import java.awt.*; -import java.awt.image.BufferedImage; - -public class EditorFragmentComponent extends JPanel { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.hint.EditorFragmentComponent"); - - private EditorFragmentComponent(EditorEx editor, int startLine, int endLine, boolean showFolding, boolean showGutter) { - Document doc = editor.getDocument(); - final int endOffset = endLine < doc.getLineCount() ? doc.getLineEndOffset(endLine) : doc.getTextLength(); - int textWidth = Math.min(editor.getMaxWidthInRange(doc.getLineStartOffset(startLine), endOffset), ScreenUtil.getScreenRectangle(1, 1).width); - LOG.assertTrue(textWidth > 0, "TextWidth: "+textWidth+"; startLine:" + startLine + "; endLine:" + endLine + ";"); - - FoldingModelEx foldingModel = editor.getFoldingModel(); - boolean isFoldingEnabled = foldingModel.isFoldingEnabled(); - if (!showFolding) { - foldingModel.setFoldingEnabled(false); - } - - Point p1 = editor.logicalPositionToXY(new LogicalPosition(startLine, 0)); - Point p2 = editor.logicalPositionToXY(new LogicalPosition(Math.max(endLine, startLine + 1), 0)); - int y1 = p1.y; - int y2 = p2.y; - int height = y2 - y1; - LOG.assertTrue(height > 0, "Height: " + height + "; startLine:" + startLine + "; endLine:" + endLine + "; p1:" + p1 + "; p2:" + p2); - - int savedScrollOffset = editor.getScrollingModel().getHorizontalScrollOffset(); - if (savedScrollOffset > 0) { - editor.stopOptimizedScrolling(); - editor.getScrollingModel().scrollHorizontally(0); - } - - final Image textImage = new BufferedImage(textWidth, height, BufferedImage.TYPE_INT_RGB); - Graphics textGraphics = textImage.getGraphics(); - - final JComponent rowHeader; - final Image markersImage; - if (showGutter) { - rowHeader = editor.getGutterComponentEx(); - markersImage = new BufferedImage(Math.max(1, rowHeader.getWidth()), height, BufferedImage.TYPE_INT_RGB); - Graphics markerGraphics = markersImage.getGraphics(); - - markerGraphics.translate(0, -y1); - markerGraphics.setClip(0, y1, rowHeader.getWidth(), height); - markerGraphics.setColor(getBackgroundColor(editor)); - markerGraphics.fillRect(0, y1, rowHeader.getWidth(), height); - rowHeader.paint(markerGraphics); - } - else { - rowHeader = null; - markersImage = null; - } - - textGraphics.translate(0, -y1); - textGraphics.setClip(0, y1, textWidth, height); - final boolean wasVisible = editor.setCaretVisible(false); - editor.getContentComponent().paint(textGraphics); - if (wasVisible) { - editor.setCaretVisible(true); - } - - if (!showFolding) { - foldingModel.setFoldingEnabled(isFoldingEnabled); - } - - if (savedScrollOffset > 0) { - editor.stopOptimizedScrolling(); - editor.getScrollingModel().scrollHorizontally(savedScrollOffset); - } - - JComponent component = new JComponent() { - public Dimension getPreferredSize() { - return new Dimension(textImage.getWidth(null) +(markersImage == null ? 0 : markersImage.getWidth(null)), textImage.getHeight(null)); - } - - protected void paintComponent(Graphics graphics) { - if (markersImage != null) { - graphics.drawImage(markersImage, 0, 0, null); - graphics.drawImage(textImage, rowHeader.getWidth(), 0, null); - } - else { - graphics.drawImage(textImage, 0, 0, null); - } - } - }; - - setLayout(new BorderLayout()); - add(component); - - final Color borderColor = editor.getColorsScheme().getColor(EditorColors.SELECTED_TEARLINE_COLOR); - - Border outsideBorder = BorderFactory.createLineBorder(borderColor, 1); - Border insideBorder = BorderFactory.createEmptyBorder(2, 2, 2, 2); - setBorder(BorderFactory.createCompoundBorder(outsideBorder, insideBorder)); - } - - /** - * @param hideByAnyKey - * @param x x coordinate in layered pane coordinate system. - * @param y y coordinate in layered pane coordinate system. - */ - @Nullable - public static LightweightHint showEditorFragmentHintAt(Editor editor, - TextRange range, - int x, - int y, - boolean showUpward, - boolean showFolding, - boolean hideByAnyKey) { - if (ApplicationManager.getApplication().isUnitTestMode()) return null; - - int startLine = editor.offsetToLogicalPosition(range.getStartOffset()).line; - int endLine = Math.min(editor.offsetToLogicalPosition(range.getEndOffset()).line + 1, editor.getDocument().getLineCount() - 1); - - //if (editor.logicalPositionToXY(new LogicalPosition(startLine, 0)).y >= editor.logicalPositionToXY(new LogicalPosition(endLine, 0)).y) return null; - if (startLine >= endLine) return null; - - EditorFragmentComponent fragmentComponent = createEditorFragmentComponent(editor, startLine, endLine, showFolding, true); - - - if (showUpward) { - y -= fragmentComponent.getPreferredSize().height + 10; - y = Math.max(0,y); - } - - Point p = new Point(x, y); - LightweightHint hint = new MyComponentHint(fragmentComponent); - HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, p, (hideByAnyKey ? HintManager.HIDE_BY_ANY_KEY : 0) | - HintManager.HIDE_BY_TEXT_CHANGE, 0, false, new HintHint(editor, p)); - return hint; - } - - public static EditorFragmentComponent createEditorFragmentComponent(Editor editor, - int startLine, - int endLine, - boolean showFolding, boolean showGutter) { - final EditorEx editorEx = (EditorEx)editor; - final Color old = editorEx.getBackgroundColor(); - Color backColor = getBackgroundColor(editor); - editorEx.setBackgroundColor(backColor); - EditorFragmentComponent fragmentComponent = new EditorFragmentComponent(editorEx, startLine, endLine, - showFolding, showGutter); - fragmentComponent.setBackground(backColor); - - editorEx.setBackgroundColor(old); - return fragmentComponent; - } - - @Nullable - public static LightweightHint showEditorFragmentHint(Editor editor, TextRange range, boolean showFolding, boolean hideByAnyKey){ - - JComponent editorComponent = editor.getComponent(); - final JRootPane rootPane = editorComponent.getRootPane(); - if (rootPane == null) return null; - JLayeredPane layeredPane = rootPane.getLayeredPane(); - int x = -2; - int y = 0; - Point point = SwingUtilities.convertPoint(editorComponent, x, y, layeredPane); - - return showEditorFragmentHintAt(editor, range, point.x, point.y, true, showFolding, hideByAnyKey); - } - - public static Color getBackgroundColor(Editor editor){ - EditorColorsScheme colorsScheme = editor.getColorsScheme(); - Color color = colorsScheme.getColor(EditorColors.CARET_ROW_COLOR); - if (color == null){ - color = colorsScheme.getDefaultBackground(); - } - return color; - } - - private static class MyComponentHint extends LightweightHint { - public MyComponentHint(JComponent component) { - super(component); - setForceLightweightPopup(true); - } - - public void hide() { - // needed for Alt-Q multiple times - // Q: not good? - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - MyComponentHint.super.hide(); - } - } - ); - } - } -} +/* + * 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.codeInsight.hint; + +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.editor.LogicalPosition; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.ex.FoldingModelEx; +import com.intellij.openapi.util.TextRange; +import com.intellij.ui.HintHint; +import com.intellij.ui.LightweightHint; +import com.intellij.ui.ScreenUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.image.BufferedImage; + +public class EditorFragmentComponent extends JPanel { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.hint.EditorFragmentComponent"); + + private EditorFragmentComponent(EditorEx editor, int startLine, int endLine, boolean showFolding, boolean showGutter) { + Document doc = editor.getDocument(); + final int endOffset = endLine < doc.getLineCount() ? doc.getLineEndOffset(endLine) : doc.getTextLength(); + int textWidth = Math.min(editor.getMaxWidthInRange(doc.getLineStartOffset(startLine), endOffset), ScreenUtil.getScreenRectangle(1, 1).width); + LOG.assertTrue(textWidth > 0, "TextWidth: "+textWidth+"; startLine:" + startLine + "; endLine:" + endLine + ";"); + + FoldingModelEx foldingModel = editor.getFoldingModel(); + boolean isFoldingEnabled = foldingModel.isFoldingEnabled(); + if (!showFolding) { + foldingModel.setFoldingEnabled(false); + } + + Point p1 = editor.logicalPositionToXY(new LogicalPosition(startLine, 0)); + Point p2 = editor.logicalPositionToXY(new LogicalPosition(Math.max(endLine, startLine + 1), 0)); + int y1 = p1.y; + int y2 = p2.y; + int height = y2 - y1; + LOG.assertTrue(height > 0, "Height: " + height + "; startLine:" + startLine + "; endLine:" + endLine + "; p1:" + p1 + "; p2:" + p2); + + int savedScrollOffset = editor.getScrollingModel().getHorizontalScrollOffset(); + if (savedScrollOffset > 0) { + editor.stopOptimizedScrolling(); + editor.getScrollingModel().scrollHorizontally(0); + } + + final Image textImage = new BufferedImage(textWidth, height, BufferedImage.TYPE_INT_RGB); + Graphics textGraphics = textImage.getGraphics(); + + final JComponent rowHeader; + final Image markersImage; + if (showGutter) { + rowHeader = editor.getGutterComponentEx(); + markersImage = new BufferedImage(Math.max(1, rowHeader.getWidth()), height, BufferedImage.TYPE_INT_RGB); + Graphics markerGraphics = markersImage.getGraphics(); + + markerGraphics.translate(0, -y1); + markerGraphics.setClip(0, y1, rowHeader.getWidth(), height); + markerGraphics.setColor(getBackgroundColor(editor)); + markerGraphics.fillRect(0, y1, rowHeader.getWidth(), height); + rowHeader.paint(markerGraphics); + } + else { + rowHeader = null; + markersImage = null; + } + + textGraphics.translate(0, -y1); + textGraphics.setClip(0, y1, textWidth, height); + final boolean wasVisible = editor.setCaretVisible(false); + editor.getContentComponent().paint(textGraphics); + if (wasVisible) { + editor.setCaretVisible(true); + } + + if (!showFolding) { + foldingModel.setFoldingEnabled(isFoldingEnabled); + } + + if (savedScrollOffset > 0) { + editor.stopOptimizedScrolling(); + editor.getScrollingModel().scrollHorizontally(savedScrollOffset); + } + + JComponent component = new JComponent() { + public Dimension getPreferredSize() { + return new Dimension(textImage.getWidth(null) +(markersImage == null ? 0 : markersImage.getWidth(null)), textImage.getHeight(null)); + } + + protected void paintComponent(Graphics graphics) { + if (markersImage != null) { + graphics.drawImage(markersImage, 0, 0, null); + graphics.drawImage(textImage, rowHeader.getWidth(), 0, null); + } + else { + graphics.drawImage(textImage, 0, 0, null); + } + } + }; + + setLayout(new BorderLayout()); + add(component); + + final Color borderColor = editor.getColorsScheme().getColor(EditorColors.SELECTED_TEARLINE_COLOR); + + Border outsideBorder = BorderFactory.createLineBorder(borderColor, 1); + Border insideBorder = BorderFactory.createEmptyBorder(2, 2, 2, 2); + setBorder(BorderFactory.createCompoundBorder(outsideBorder, insideBorder)); + } + + /** + * @param hideByAnyKey + * @param x x coordinate in layered pane coordinate system. + * @param y y coordinate in layered pane coordinate system. + */ + @Nullable + public static LightweightHint showEditorFragmentHintAt(Editor editor, + TextRange range, + int x, + int y, + boolean showUpward, + boolean showFolding, + boolean hideByAnyKey) { + if (ApplicationManager.getApplication().isUnitTestMode()) return null; + + int startLine = editor.offsetToLogicalPosition(range.getStartOffset()).line; + int endLine = Math.min(editor.offsetToLogicalPosition(range.getEndOffset()).line + 1, editor.getDocument().getLineCount() - 1); + + //if (editor.logicalPositionToXY(new LogicalPosition(startLine, 0)).y >= editor.logicalPositionToXY(new LogicalPosition(endLine, 0)).y) return null; + if (startLine >= endLine) return null; + + EditorFragmentComponent fragmentComponent = createEditorFragmentComponent(editor, startLine, endLine, showFolding, true); + + + if (showUpward) { + y -= fragmentComponent.getPreferredSize().height + 10; + y = Math.max(0,y); + } + + Point p = new Point(x, y); + LightweightHint hint = new MyComponentHint(fragmentComponent); + HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, p, (hideByAnyKey ? HintManager.HIDE_BY_ANY_KEY : 0) | + HintManager.HIDE_BY_TEXT_CHANGE, 0, false, new HintHint(editor, p)); + return hint; + } + + public static EditorFragmentComponent createEditorFragmentComponent(Editor editor, + int startLine, + int endLine, + boolean showFolding, boolean showGutter) { + final EditorEx editorEx = (EditorEx)editor; + final Color old = editorEx.getBackgroundColor(); + Color backColor = getBackgroundColor(editor); + editorEx.setBackgroundColor(backColor); + EditorFragmentComponent fragmentComponent = new EditorFragmentComponent(editorEx, startLine, endLine, + showFolding, showGutter); + fragmentComponent.setBackground(backColor); + + editorEx.setBackgroundColor(old); + return fragmentComponent; + } + + @Nullable + public static LightweightHint showEditorFragmentHint(Editor editor, TextRange range, boolean showFolding, boolean hideByAnyKey){ + + JComponent editorComponent = editor.getComponent(); + final JRootPane rootPane = editorComponent.getRootPane(); + if (rootPane == null) return null; + JLayeredPane layeredPane = rootPane.getLayeredPane(); + int x = -2; + int y = 0; + Point point = SwingUtilities.convertPoint(editorComponent, x, y, layeredPane); + + return showEditorFragmentHintAt(editor, range, point.x, point.y, true, showFolding, hideByAnyKey); + } + + public static Color getBackgroundColor(Editor editor){ + EditorColorsScheme colorsScheme = editor.getColorsScheme(); + Color color = colorsScheme.getColor(EditorColors.CARET_ROW_COLOR); + if (color == null){ + color = colorsScheme.getDefaultBackground(); + } + return color; + } + + private static class MyComponentHint extends LightweightHint { + public MyComponentHint(JComponent component) { + super(component); + setForceLightweightPopup(true); + } + + public void hide() { + // needed for Alt-Q multiple times + // Q: not good? + SwingUtilities.invokeLater( + new Runnable() { + public void run() { + MyComponentHint.super.hide(); + } + } + ); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java index a72ef2e83eb6..713593416790 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java @@ -1,789 +1,789 @@ -/* - * 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.diagnostic; - -/** - * @author kir, max - */ - -import com.intellij.ExtensionPoints; -import com.intellij.ide.DataManager; -import com.intellij.ide.impl.DataManagerImpl; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ex.ApplicationEx; -import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.application.impl.LaterInvocator; -import com.intellij.openapi.diagnostic.ErrorReportSubmitter; -import com.intellij.openapi.diagnostic.IdeaLoggingEvent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.diagnostic.SubmittedReportInfo; -import com.intellij.openapi.extensions.ExtensionException; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.extensions.PluginDescriptor; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.LabeledComponent; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.util.Consumer; -import com.intellij.util.text.DateFormatUtil; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.List; - -public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListener, TypeSafeDataProvider { - public static DataKey CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key"); - - private static final Logger LOG = Logger.getInstance("#com.intellij.diagnostic.IdeErrorsDialog"); - private JTextPane myDetailsPane; - private JTextPane myMoreInfoPane; - private List myFatalErrors; - private final List> myModel = new ArrayList>(); - private final MessagePool myMessagePool; - private JLabel myCountLabel; - private JLabel myInfoLabel; - private JCheckBox myImmediatePopupCheckbox; - private JLabel myCredentialsLabel; - private final BlameAction myBlameAction = new BlameAction(); - private final DisablePluginAction myDisablePluginAction = new DisablePluginAction(); - - private int myIndex = 0; - @NonNls public static final String IMMEDIATE_POPUP_OPTION = "IMMEDIATE_FATAL_ERROR_POPUP"; - private Box myCredentialsPane; - - public IdeErrorsDialog(MessagePool messagePool) { - super(JOptionPane.getRootFrame(), false); - - myMessagePool = messagePool; - - init(); - } - - public void newEntryAdded() { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - rebuildHeaders(); - } - }); - } - - public void poolCleared() { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - doOKAction(); - } - }); - } - - protected Action[] createActions() { - return new Action[]{new ShutdownAction(), new CloseAction()}; - } - - @Override - protected Action[] createLeftSideActions() { - List actions = new ArrayList(); - actions.add(myBlameAction); - actions.add(myDisablePluginAction); - if (((ApplicationEx)ApplicationManager.getApplication()).isInternal()) { - final AnAction analyze = ActionManager.getInstance().getAction("AnalyzeStacktraceOnError"); - if (analyze != null) { - actions.add(new AnalyzeAction(analyze)); - } - } - - return actions.toArray(new Action[actions.size()]); - } - - public void calcData(DataKey key, DataSink sink) { - if (CURRENT_TRACE_KEY == key) { - final AbstractMessage msg = getSelectedMessage(); - if (msg != null) { - sink.put(CURRENT_TRACE_KEY, msg.getMessage() + msg.getThrowableText()); - } - } - } - - private AbstractMessage getSelectedMessage() { - return getMessageAt(myIndex); - } - - private ActionToolbar createNavigationToolbar() { - DefaultActionGroup group = new DefaultActionGroup(); - - BackAction back = new BackAction(); - back.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)), getRootPane()); - group.add(back); - - ForwardAction forward = new ForwardAction(); - forward.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0)), getRootPane()); - group.add(forward); - - group.add(new ClearFatalsAction()); - - - - return ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true); - } - - private void goBack() { - myIndex--; - updateControls(); - } - - private void goForward() { - myIndex++; - updateControls(); - } - - private void updateControls() { - updateCountLabel(); - updateInfoLabel(); - updateDetailsPane(); - updateCredentialsPane(); - myBlameAction.update(); - myDisablePluginAction.update(); - final AbstractMessage selectedMessage = getSelectedMessage(); - if (selectedMessage == null) { - myMoreInfoPane.setEditable(false); - myMoreInfoPane.setText(""); - } - else { - myMoreInfoPane.setText(selectedMessage.getAdditionalInfo()); - myMoreInfoPane.setEditable(true); - } - } - - private void updateCredentialsPane() { - final AbstractMessage message = getSelectedMessage(); - final ErrorReportSubmitter submitter = message == null ? null : getSubmitter(message.getThrowable()); - if (submitter instanceof ITNReporter) { - myCredentialsPane.setVisible(true); - String userName = ErrorReportConfigurable.getInstance().ITN_LOGIN; - if (StringUtil.isEmpty(userName)) { - myCredentialsLabel.setText("Submit report anonymously"); - } - else { - myCredentialsLabel.setText("Submit report as " + userName); - } - } - else { - myCredentialsPane.setVisible(false); - } - } - - private void updateInfoLabel() { - final AbstractMessage message = getSelectedMessage(); - if (message != null) { - StringBuffer txt = new StringBuffer(); - txt.append("").append(getBlameLabel()).append(" "); - txt.append(DiagnosticBundle.message("error.list.message.info", - DateFormatUtil.formatPrettyDateTime(message.getDate()), myModel.get(myIndex).size())); - - if (message.isSubmitted()) { - final SubmittedReportInfo info = message.getSubmissionInfo(); - if (info.getStatus() == SubmittedReportInfo.SubmissionStatus.FAILED) { - txt.append(" ").append(DiagnosticBundle.message("error.list.message.submission.failed")); - } - else { - if (info.getLinkText() != null) { - txt.append(" ").append(DiagnosticBundle.message("error.list.message.submitted.as.link", info.getLinkText())); - if (info.getStatus() == SubmittedReportInfo.SubmissionStatus.DUPLICATE) { - txt.append(DiagnosticBundle.message("error.list.message.duplicate")); - } - } - else { - txt.append(DiagnosticBundle.message("error.list.message.submitted")); - } - } - txt.append(". "); - } - else if (message.isSubmitting()) { - txt.append(" Submitting..."); - } - else if (!message.isRead()) { - txt.append(" ").append(DiagnosticBundle.message("error.list.message.unread")); - } - myInfoLabel.setText(txt.toString()); - } - else { - myInfoLabel.setText(""); - } - } - - @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"}) - private String getBlameLabel() { - final AbstractMessage message = getSelectedMessage(); - if (message != null && !(message.getThrowable() instanceof MessagePool.TooManyErrorsException)) { - final PluginId pluginId = findPluginId(message.getThrowable()); - if (pluginId == null) { - if (message.getThrowable() instanceof AbstractMethodError) { - return DiagnosticBundle.message("error.list.message.blame.unknown.plugin"); - } - else { - return DiagnosticBundle.message("error.list.message.blame.core", - ApplicationNamesInfo.getInstance().getProductName()); - } - } - else { - final Application app = ApplicationManager.getApplication(); - return DiagnosticBundle.message("error.list.message.blame.plugin", app.getPlugin(pluginId).getName()); - } - } - return ""; - } - - private void updateDetailsPane() { - final AbstractMessage message = getSelectedMessage(); - if (message != null) { - showMessageDetails(message); - } - else { - hideMessageDetails(); - } - } - - private void updateCountLabel() { - myCountLabel.setText(DiagnosticBundle.message("error.list.message.index.count", Integer.toString(myIndex + 1), myModel.size())); - } - - private class BackAction extends AnAction implements DumbAware { - public BackAction() { - super(DiagnosticBundle.message("error.list.back.action"), null, IconLoader.getIcon("/actions/back.png")); - } - - public void actionPerformed(AnActionEvent e) { - goBack(); - } - - - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabled(myIndex > 0); - } - } - - private class ForwardAction extends AnAction implements DumbAware { - public ForwardAction() { - super(DiagnosticBundle.message("error.list.forward.action"), null, IconLoader.getIcon("/actions/forward.png")); - } - - public void actionPerformed(AnActionEvent e) { - goForward(); - } - - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabled(myIndex < myModel.size() - 1); - } - } - - protected JComponent createCenterPanel() { - setTitle(DiagnosticBundle.message("error.list.title")); - - JPanel root = new JPanel(new BorderLayout()); - JPanel top = new JPanel(new BorderLayout()); - JPanel toolbar = new JPanel(new FlowLayout()); - - myImmediatePopupCheckbox = new JCheckBox(DiagnosticBundle.message("error.list.popup.immediately.checkbox")); - myImmediatePopupCheckbox.setSelected(PropertiesComponent.getInstance().isTrueValue(IMMEDIATE_POPUP_OPTION)); - myImmediatePopupCheckbox.setVisible(((ApplicationImpl) ApplicationManager.getApplication()).isInternal()); - - myCountLabel = new JLabel(); - myInfoLabel = new JLabel(); - ActionToolbar navToolbar = createNavigationToolbar(); - toolbar.add(navToolbar.getComponent()); - toolbar.add(myCountLabel); - top.add(toolbar, BorderLayout.WEST); - - root.add(top, BorderLayout.NORTH); - - myDetailsPane = new JTextPane(); - myDetailsPane.setBackground(UIUtil.getTextFieldBackground()); - myDetailsPane.setEditable(false); - JPanel infoPanel = new JPanel(new BorderLayout()); - JPanel gapPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 7, 0)); - gapPanel.add(myInfoLabel); - infoPanel.add(gapPanel, BorderLayout.NORTH); - infoPanel.add(ScrollPaneFactory.createScrollPane(myDetailsPane), BorderLayout.CENTER); - - myMoreInfoPane = new JTextPane(); - myMoreInfoPane.setBackground(UIUtil.getTextFieldBackground()); - final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myMoreInfoPane); - scrollPane.setPreferredSize(new Dimension(100, 50)); - final LabeledComponent labeledComponent = - LabeledComponent.create(scrollPane, "Additional information (steps to reproduce, what were you doing when the exception occurred):"); - labeledComponent.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0)); - infoPanel.add(labeledComponent, BorderLayout.SOUTH); - - myMoreInfoPane.getDocument().addDocumentListener(new DocumentAdapter() { - @Override - protected void textChanged(DocumentEvent e) { - if (myMoreInfoPane.isEditable()) { - final AbstractMessage message = getSelectedMessage(); - if (message != null) { - message.setAdditionalInfo(myMoreInfoPane.getText()); - } - } - } - }); - - root.add(infoPanel, BorderLayout.CENTER); - - JPanel controlsPane = new JPanel(new BorderLayout()); - - myCredentialsPane = Box.createHorizontalBox(); - myCredentialsLabel = new JLabel("Submit report anonymously"); - myCredentialsPane.add(myCredentialsLabel); - myCredentialsPane.add(Box.createHorizontalGlue()); - JButton specifyCredentialsButton = new JButton("Use JetBrains Account"); - specifyCredentialsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - new JetBrainsAccountDialog(getRootPane()).show(); - } - }); - myCredentialsPane.add(specifyCredentialsButton); - controlsPane.add(myCredentialsPane, BorderLayout.CENTER); - myCredentialsPane.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0)); - - controlsPane.add(myImmediatePopupCheckbox, BorderLayout.SOUTH); - root.add(controlsPane, BorderLayout.SOUTH); - - root.setPreferredSize(new Dimension(600, 550)); - - rebuildHeaders(); - moveSelectionToEarliestMessage(); - updateControls(); - return root; - } - - private AbstractMessage getMessageAt(int idx) { - if (idx < 0 || idx >= myModel.size()) return null; - return myModel.get(idx).get(0); - } - - private void moveSelectionToEarliestMessage() { - myIndex = 0; - for (int i = 0; i < myModel.size(); i++) { - final AbstractMessage each = getMessageAt(i); - if (!each.isRead()) { - myIndex = i; - break; - } - } - - updateControls(); - } - - private void rebuildHeaders() { - myModel.clear(); - myFatalErrors = myMessagePool.getFatalErrors(true, true); - - Map> hash2Messages = buildHashcode2MessageListMap(myFatalErrors); - - for (final ArrayList abstractMessages : hash2Messages.values()) { - myModel.add(abstractMessages); - } - - updateControls(); - } - - private static Map> buildHashcode2MessageListMap(List aErrors) { - Map> hash2Messages = new LinkedHashMap>(); - for (final AbstractMessage each : aErrors) { - final String hashcode = getThrowableHashCode(each.getThrowable()); - ArrayList list; - if (hash2Messages.containsKey(hashcode)) { - list = hash2Messages.get(hashcode); - } - else { - list = new ArrayList(); - hash2Messages.put(hashcode, list); - } - list.add(0, each); - } - return hash2Messages; - } - - private void showMessageDetails(AbstractMessage aMessage) { - if (aMessage.getThrowable() instanceof MessagePool.TooManyErrorsException) { - myDetailsPane.setText(aMessage.getThrowable().getMessage()); - } - else { - myDetailsPane.setText(new StringBuffer().append(aMessage.getMessage()).append("\n").append(aMessage.getThrowableText()).toString()); - } - if (myDetailsPane.getCaret() != null) { // Upon some strange circumstances caret may be missing from the text component making the following line fail with NPE. - myDetailsPane.setCaretPosition(0); - } - } - - private void hideMessageDetails() { - myDetailsPane.setText(""); - } - - @Nullable - public static PluginId findPluginId(Throwable t) { - if (t instanceof PluginException) { - return ((PluginException)t).getPluginId(); - } - - StackTraceElement[] elements = t.getStackTrace(); - for (StackTraceElement element : elements) { - String className = element.getClassName(); - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); - } - } - - if (t instanceof NoSuchMethodException) { - // check is method called from plugin classes - if (t.getMessage() != null) { - String className = ""; - StringTokenizer tok = new StringTokenizer(t.getMessage(), "."); - while (tok.hasMoreTokens()) { - String token = tok.nextToken(); - if (token.length() > 0 && Character.isJavaIdentifierStart(token.charAt(0))) { - className += token; - } - } - - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); - } - } - } - else if (t instanceof ClassNotFoundException) { - // check is class from plugin classes - if (t.getMessage() != null) { - String className = t.getMessage(); - - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); - } - } - } - else if (t instanceof AbstractMethodError && t.getMessage() != null) { - String s = t.getMessage(); - // org.antlr.works.plugin.intellij.PIFileType.getHighlighter(Lcom/intellij/openapi/project/Project;Lcom/intellij/openapi/vfs/VirtualFile;)Lcom/intellij/openapi/fileTypes/SyntaxHighlighter; - int pos = s.indexOf('('); - if (pos >= 0) { - s = s.substring(0, pos); - pos = s.lastIndexOf('.'); - if (pos >= 0) { - s = s.substring(0, pos); - if (PluginManager.isPluginClass(s)) { - return PluginManager.getPluginByClassName(s); - } - } - } - } - - else if (t instanceof ExtensionException) { - String className = ((ExtensionException)t).getExtensionClass().getName(); - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); - } - } - - return null; - } - - private class ShutdownAction extends AbstractAction { - public ShutdownAction() { - super(ApplicationManager.getApplication().isRestartCapable() ? "Restart" : DiagnosticBundle.message("error.list.shutdown.action")); - } - - public void actionPerformed(ActionEvent e) { - final boolean canRestart = ApplicationManager.getApplication().isRestartCapable(); - int rc = Messages.showYesNoDialog(getRootPane(), "Are you sure you would like to " + (canRestart ? "restart" : "shutdown") + - " " + ApplicationNamesInfo.getInstance().getFullProductName() + "?", - "Confirm", Messages.getQuestionIcon()); - if (rc != 0) return; - myMessagePool.setJvmIsShuttingDown(); - LaterInvocator.invokeLater(new Runnable() { - public void run() { - final Application app = ApplicationManager.getApplication(); - if (app.isRestartCapable()) { - app.restart(); - } - else { - app.exit(); - } - } - }, ModalityState.NON_MODAL); - doOKAction(); - } - } - - private class ClearFatalsAction extends AnAction { - public ClearFatalsAction() { - super("Clear all fatal errors", "Clear all fatal errors", - IconLoader.getIcon("/general/reset.png")); - } - - public void actionPerformed(AnActionEvent e) { - myMessagePool.clearFatals(); - } - } - - private class BlameAction extends AbstractAction { - public BlameAction() { - super(DiagnosticBundle.message("error.list.submit.action")); - } - - public void update() { - final AbstractMessage logMessage = getSelectedMessage(); - if (logMessage == null) { - setEnabled(false); - return; - } - - final ErrorReportSubmitter submitter = getSubmitter(logMessage.getThrowable()); - if (logMessage.isSubmitted() || submitter == null) { - setEnabled(false); - return; - } - setEnabled(true); - putValue(NAME, submitter.getReportActionText()); - } - - public void actionPerformed(ActionEvent e) { - final AbstractMessage logMessage = getSelectedMessage(); - reportMessage(logMessage); - rebuildHeaders(); - updateControls(); - } - - private void reportMessage(final AbstractMessage logMessage) { - ErrorReportSubmitter submitter = getSubmitter(logMessage.getThrowable()); - - if (submitter != null) { - logMessage.setSubmitting(true); - updateControls(); - submitter.submitAsync(getEvents(logMessage), logMessage.getAdditionalInfo(), getContentPane(), new Consumer() { - @Override - public void consume(SubmittedReportInfo submittedReportInfo) { - logMessage.setSubmitting(false); - logMessage.setSubmitted(submittedReportInfo); - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - updateControls(); - } - }); - } - }); - } - } - - private IdeaLoggingEvent[] getEvents(final AbstractMessage logMessage) { - if (logMessage instanceof GroupedLogMessage) { - final List messages = ((GroupedLogMessage)logMessage).getMessages(); - IdeaLoggingEvent[] res = new IdeaLoggingEvent[messages.size()]; - for (int i = 0; i < res.length; i++) { - res[i] = getEvent(messages.get(i)); - } - return res; - } - return new IdeaLoggingEvent[]{getEvent(logMessage)}; - } - - private IdeaLoggingEvent getEvent(final AbstractMessage logMessage) { - return new IdeaLoggingEvent(logMessage.getMessage(), logMessage.getThrowable()); - } - } - - @Nullable - public static ErrorReportSubmitter getSubmitter(final Throwable throwable) { - if (throwable instanceof MessagePool.TooManyErrorsException || throwable instanceof AbstractMethodError) { - return null; - } - final PluginId pluginId = findPluginId(throwable); - final ErrorReportSubmitter[] reporters; - try { - reporters = Extensions.getExtensions(ExtensionPoints.ERROR_HANDLER_EP); - } - catch (Throwable t) { - return null; - } - ErrorReportSubmitter submitter = null; - for (ErrorReportSubmitter reporter : reporters) { - final PluginDescriptor descriptor = reporter.getPluginDescriptor(); - if (pluginId == null && (descriptor == null || PluginId.getId("com.intellij") == descriptor.getPluginId()) - || descriptor != null && Comparing.equal(pluginId, descriptor.getPluginId())) { - submitter = reporter; - } - } - return submitter; - } - - protected void doOKAction() { - PropertiesComponent.getInstance().setValue(IMMEDIATE_POPUP_OPTION, String.valueOf(myImmediatePopupCheckbox.isSelected())); - markAllAsRead(); - super.doOKAction(); - } - - private void markAllAsRead() { - for (AbstractMessage each : myFatalErrors) { - each.setRead(true); - } - } - - public void doCancelAction() { - PropertiesComponent.getInstance().setValue(IMMEDIATE_POPUP_OPTION, String.valueOf(myImmediatePopupCheckbox.isSelected())); - markAllAsRead(); - super.doCancelAction(); - } - - @Override - public JComponent getPreferredFocusedComponent() { - return myMoreInfoPane; - } - - protected class CloseAction extends AbstractAction { - public CloseAction() { - putValue(NAME, DiagnosticBundle.message("error.list.close.action")); - } - - public void actionPerformed(ActionEvent e) { - doOKAction(); - } - } - - protected String getDimensionServiceKey() { - return "IdeErrosDialog"; - } - - private static String getThrowableHashCode(Throwable exception) { - try { - return md5(StringUtil.getThrowableText(exception), "stack-trace"); - } - catch (NoSuchAlgorithmException e) { - LOG.error(e); - return ""; - } - } - - private static String md5 (String buffer, @NonNls String key) throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.update(buffer.getBytes()); - byte [] code = md5.digest(key.getBytes()); - BigInteger bi = new BigInteger(code).abs(); - return bi.abs().toString(16); - } - - private class AnalyzeAction extends AbstractAction { - private final AnAction myAnalyze; - - public AnalyzeAction(AnAction analyze) { - super(analyze.getTemplatePresentation().getText()); - myAnalyze = analyze; - } - - public void actionPerformed(ActionEvent e) { - final DataContext dataContext = ((DataManagerImpl)DataManager.getInstance()).getDataContextTest((Component)e.getSource()); - - AnActionEvent event = new AnActionEvent( - null, dataContext, - ActionPlaces.UNKNOWN, - myAnalyze.getTemplatePresentation(), - ActionManager.getInstance(), - e.getModifiers() - ); - - final Project project = PlatformDataKeys.PROJECT.getData(dataContext); - setEnabled(project != null); - if (project != null) { - myAnalyze.actionPerformed(event); - doOKAction(); - } - } - } - - private class DisablePluginAction extends AbstractAction { - @Override - public void actionPerformed(ActionEvent e) { - PluginId pluginId = getSelectedPluginId(); - if (pluginId != null) { - PluginManager.disablePlugin(pluginId.toString()); - final Application app = ApplicationManager.getApplication(); - final String pluginName = app.getPlugin(pluginId).getName(); - int rc = Messages.showYesNoDialog(getRootPane(), "Are you sure you would like to disable the plugin " + pluginName + - "? The features provided by the plugin will no longer be available.", - "Disable Plugin", Messages.getQuestionIcon()); - if (rc != 0) return; - final String productName = ApplicationNamesInfo.getInstance().getFullProductName(); - if (app.isRestartCapable()) { - rc = Messages.showYesNoDialog(getRootPane(), pluginName + " has been disabled. Would you like to restart " + productName + " so that the changes would take effect?", - "Disable Plugin", Messages.getInformationIcon()); - if (rc == 0) { - app.restart(); - } - } - else { - Messages.showMessageDialog(getRootPane(), pluginName + " has been disabled. You'll need to restart " + productName + " for the changes to take effect.", - "Disable Plugin", Messages.getInformationIcon()); - } - } - } - - public void update() { - PluginId pluginId = getSelectedPluginId(); - if (pluginId != null) { - setEnabled(true); - putValue(NAME, "Disable " + ApplicationManager.getApplication().getPlugin(pluginId).getName()); - } - else { - setEnabled(false); - putValue(NAME, "Disable plugin"); - } - } - - @Nullable - private PluginId getSelectedPluginId() { - final AbstractMessage message = getSelectedMessage(); - if (message != null) { - //noinspection ThrowableResultOfMethodCallIgnored - return findPluginId(message.getThrowable()); - } - return null; - } - } -} +/* + * 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.diagnostic; + +/** + * @author kir, max + */ + +import com.intellij.ExtensionPoints; +import com.intellij.ide.DataManager; +import com.intellij.ide.impl.DataManagerImpl; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ex.ApplicationEx; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.application.impl.LaterInvocator; +import com.intellij.openapi.diagnostic.ErrorReportSubmitter; +import com.intellij.openapi.diagnostic.IdeaLoggingEvent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.diagnostic.SubmittedReportInfo; +import com.intellij.openapi.extensions.ExtensionException; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.extensions.PluginDescriptor; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.LabeledComponent; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.Consumer; +import com.intellij.util.text.DateFormatUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.List; + +public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListener, TypeSafeDataProvider { + public static DataKey CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key"); + + private static final Logger LOG = Logger.getInstance("#com.intellij.diagnostic.IdeErrorsDialog"); + private JTextPane myDetailsPane; + private JTextPane myMoreInfoPane; + private List myFatalErrors; + private final List> myModel = new ArrayList>(); + private final MessagePool myMessagePool; + private JLabel myCountLabel; + private JLabel myInfoLabel; + private JCheckBox myImmediatePopupCheckbox; + private JLabel myCredentialsLabel; + private final BlameAction myBlameAction = new BlameAction(); + private final DisablePluginAction myDisablePluginAction = new DisablePluginAction(); + + private int myIndex = 0; + @NonNls public static final String IMMEDIATE_POPUP_OPTION = "IMMEDIATE_FATAL_ERROR_POPUP"; + private Box myCredentialsPane; + + public IdeErrorsDialog(MessagePool messagePool) { + super(JOptionPane.getRootFrame(), false); + + myMessagePool = messagePool; + + init(); + } + + public void newEntryAdded() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + rebuildHeaders(); + } + }); + } + + public void poolCleared() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + doOKAction(); + } + }); + } + + protected Action[] createActions() { + return new Action[]{new ShutdownAction(), new CloseAction()}; + } + + @Override + protected Action[] createLeftSideActions() { + List actions = new ArrayList(); + actions.add(myBlameAction); + actions.add(myDisablePluginAction); + if (((ApplicationEx)ApplicationManager.getApplication()).isInternal()) { + final AnAction analyze = ActionManager.getInstance().getAction("AnalyzeStacktraceOnError"); + if (analyze != null) { + actions.add(new AnalyzeAction(analyze)); + } + } + + return actions.toArray(new Action[actions.size()]); + } + + public void calcData(DataKey key, DataSink sink) { + if (CURRENT_TRACE_KEY == key) { + final AbstractMessage msg = getSelectedMessage(); + if (msg != null) { + sink.put(CURRENT_TRACE_KEY, msg.getMessage() + msg.getThrowableText()); + } + } + } + + private AbstractMessage getSelectedMessage() { + return getMessageAt(myIndex); + } + + private ActionToolbar createNavigationToolbar() { + DefaultActionGroup group = new DefaultActionGroup(); + + BackAction back = new BackAction(); + back.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)), getRootPane()); + group.add(back); + + ForwardAction forward = new ForwardAction(); + forward.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0)), getRootPane()); + group.add(forward); + + group.add(new ClearFatalsAction()); + + + + return ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true); + } + + private void goBack() { + myIndex--; + updateControls(); + } + + private void goForward() { + myIndex++; + updateControls(); + } + + private void updateControls() { + updateCountLabel(); + updateInfoLabel(); + updateDetailsPane(); + updateCredentialsPane(); + myBlameAction.update(); + myDisablePluginAction.update(); + final AbstractMessage selectedMessage = getSelectedMessage(); + if (selectedMessage == null) { + myMoreInfoPane.setEditable(false); + myMoreInfoPane.setText(""); + } + else { + myMoreInfoPane.setText(selectedMessage.getAdditionalInfo()); + myMoreInfoPane.setEditable(true); + } + } + + private void updateCredentialsPane() { + final AbstractMessage message = getSelectedMessage(); + final ErrorReportSubmitter submitter = message == null ? null : getSubmitter(message.getThrowable()); + if (submitter instanceof ITNReporter) { + myCredentialsPane.setVisible(true); + String userName = ErrorReportConfigurable.getInstance().ITN_LOGIN; + if (StringUtil.isEmpty(userName)) { + myCredentialsLabel.setText("Submit report anonymously"); + } + else { + myCredentialsLabel.setText("Submit report as " + userName); + } + } + else { + myCredentialsPane.setVisible(false); + } + } + + private void updateInfoLabel() { + final AbstractMessage message = getSelectedMessage(); + if (message != null) { + StringBuffer txt = new StringBuffer(); + txt.append("").append(getBlameLabel()).append(" "); + txt.append(DiagnosticBundle.message("error.list.message.info", + DateFormatUtil.formatPrettyDateTime(message.getDate()), myModel.get(myIndex).size())); + + if (message.isSubmitted()) { + final SubmittedReportInfo info = message.getSubmissionInfo(); + if (info.getStatus() == SubmittedReportInfo.SubmissionStatus.FAILED) { + txt.append(" ").append(DiagnosticBundle.message("error.list.message.submission.failed")); + } + else { + if (info.getLinkText() != null) { + txt.append(" ").append(DiagnosticBundle.message("error.list.message.submitted.as.link", info.getLinkText())); + if (info.getStatus() == SubmittedReportInfo.SubmissionStatus.DUPLICATE) { + txt.append(DiagnosticBundle.message("error.list.message.duplicate")); + } + } + else { + txt.append(DiagnosticBundle.message("error.list.message.submitted")); + } + } + txt.append(". "); + } + else if (message.isSubmitting()) { + txt.append(" Submitting..."); + } + else if (!message.isRead()) { + txt.append(" ").append(DiagnosticBundle.message("error.list.message.unread")); + } + myInfoLabel.setText(txt.toString()); + } + else { + myInfoLabel.setText(""); + } + } + + @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"}) + private String getBlameLabel() { + final AbstractMessage message = getSelectedMessage(); + if (message != null && !(message.getThrowable() instanceof MessagePool.TooManyErrorsException)) { + final PluginId pluginId = findPluginId(message.getThrowable()); + if (pluginId == null) { + if (message.getThrowable() instanceof AbstractMethodError) { + return DiagnosticBundle.message("error.list.message.blame.unknown.plugin"); + } + else { + return DiagnosticBundle.message("error.list.message.blame.core", + ApplicationNamesInfo.getInstance().getProductName()); + } + } + else { + final Application app = ApplicationManager.getApplication(); + return DiagnosticBundle.message("error.list.message.blame.plugin", app.getPlugin(pluginId).getName()); + } + } + return ""; + } + + private void updateDetailsPane() { + final AbstractMessage message = getSelectedMessage(); + if (message != null) { + showMessageDetails(message); + } + else { + hideMessageDetails(); + } + } + + private void updateCountLabel() { + myCountLabel.setText(DiagnosticBundle.message("error.list.message.index.count", Integer.toString(myIndex + 1), myModel.size())); + } + + private class BackAction extends AnAction implements DumbAware { + public BackAction() { + super(DiagnosticBundle.message("error.list.back.action"), null, IconLoader.getIcon("/actions/back.png")); + } + + public void actionPerformed(AnActionEvent e) { + goBack(); + } + + + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(myIndex > 0); + } + } + + private class ForwardAction extends AnAction implements DumbAware { + public ForwardAction() { + super(DiagnosticBundle.message("error.list.forward.action"), null, IconLoader.getIcon("/actions/forward.png")); + } + + public void actionPerformed(AnActionEvent e) { + goForward(); + } + + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(myIndex < myModel.size() - 1); + } + } + + protected JComponent createCenterPanel() { + setTitle(DiagnosticBundle.message("error.list.title")); + + JPanel root = new JPanel(new BorderLayout()); + JPanel top = new JPanel(new BorderLayout()); + JPanel toolbar = new JPanel(new FlowLayout()); + + myImmediatePopupCheckbox = new JCheckBox(DiagnosticBundle.message("error.list.popup.immediately.checkbox")); + myImmediatePopupCheckbox.setSelected(PropertiesComponent.getInstance().isTrueValue(IMMEDIATE_POPUP_OPTION)); + myImmediatePopupCheckbox.setVisible(((ApplicationImpl) ApplicationManager.getApplication()).isInternal()); + + myCountLabel = new JLabel(); + myInfoLabel = new JLabel(); + ActionToolbar navToolbar = createNavigationToolbar(); + toolbar.add(navToolbar.getComponent()); + toolbar.add(myCountLabel); + top.add(toolbar, BorderLayout.WEST); + + root.add(top, BorderLayout.NORTH); + + myDetailsPane = new JTextPane(); + myDetailsPane.setBackground(UIUtil.getTextFieldBackground()); + myDetailsPane.setEditable(false); + JPanel infoPanel = new JPanel(new BorderLayout()); + JPanel gapPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 7, 0)); + gapPanel.add(myInfoLabel); + infoPanel.add(gapPanel, BorderLayout.NORTH); + infoPanel.add(ScrollPaneFactory.createScrollPane(myDetailsPane), BorderLayout.CENTER); + + myMoreInfoPane = new JTextPane(); + myMoreInfoPane.setBackground(UIUtil.getTextFieldBackground()); + final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myMoreInfoPane); + scrollPane.setPreferredSize(new Dimension(100, 50)); + final LabeledComponent labeledComponent = + LabeledComponent.create(scrollPane, "Additional information (steps to reproduce, what were you doing when the exception occurred):"); + labeledComponent.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0)); + infoPanel.add(labeledComponent, BorderLayout.SOUTH); + + myMoreInfoPane.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + if (myMoreInfoPane.isEditable()) { + final AbstractMessage message = getSelectedMessage(); + if (message != null) { + message.setAdditionalInfo(myMoreInfoPane.getText()); + } + } + } + }); + + root.add(infoPanel, BorderLayout.CENTER); + + JPanel controlsPane = new JPanel(new BorderLayout()); + + myCredentialsPane = Box.createHorizontalBox(); + myCredentialsLabel = new JLabel("Submit report anonymously"); + myCredentialsPane.add(myCredentialsLabel); + myCredentialsPane.add(Box.createHorizontalGlue()); + JButton specifyCredentialsButton = new JButton("Use JetBrains Account"); + specifyCredentialsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new JetBrainsAccountDialog(getRootPane()).show(); + } + }); + myCredentialsPane.add(specifyCredentialsButton); + controlsPane.add(myCredentialsPane, BorderLayout.CENTER); + myCredentialsPane.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0)); + + controlsPane.add(myImmediatePopupCheckbox, BorderLayout.SOUTH); + root.add(controlsPane, BorderLayout.SOUTH); + + root.setPreferredSize(new Dimension(600, 550)); + + rebuildHeaders(); + moveSelectionToEarliestMessage(); + updateControls(); + return root; + } + + private AbstractMessage getMessageAt(int idx) { + if (idx < 0 || idx >= myModel.size()) return null; + return myModel.get(idx).get(0); + } + + private void moveSelectionToEarliestMessage() { + myIndex = 0; + for (int i = 0; i < myModel.size(); i++) { + final AbstractMessage each = getMessageAt(i); + if (!each.isRead()) { + myIndex = i; + break; + } + } + + updateControls(); + } + + private void rebuildHeaders() { + myModel.clear(); + myFatalErrors = myMessagePool.getFatalErrors(true, true); + + Map> hash2Messages = buildHashcode2MessageListMap(myFatalErrors); + + for (final ArrayList abstractMessages : hash2Messages.values()) { + myModel.add(abstractMessages); + } + + updateControls(); + } + + private static Map> buildHashcode2MessageListMap(List aErrors) { + Map> hash2Messages = new LinkedHashMap>(); + for (final AbstractMessage each : aErrors) { + final String hashcode = getThrowableHashCode(each.getThrowable()); + ArrayList list; + if (hash2Messages.containsKey(hashcode)) { + list = hash2Messages.get(hashcode); + } + else { + list = new ArrayList(); + hash2Messages.put(hashcode, list); + } + list.add(0, each); + } + return hash2Messages; + } + + private void showMessageDetails(AbstractMessage aMessage) { + if (aMessage.getThrowable() instanceof MessagePool.TooManyErrorsException) { + myDetailsPane.setText(aMessage.getThrowable().getMessage()); + } + else { + myDetailsPane.setText(new StringBuffer().append(aMessage.getMessage()).append("\n").append(aMessage.getThrowableText()).toString()); + } + if (myDetailsPane.getCaret() != null) { // Upon some strange circumstances caret may be missing from the text component making the following line fail with NPE. + myDetailsPane.setCaretPosition(0); + } + } + + private void hideMessageDetails() { + myDetailsPane.setText(""); + } + + @Nullable + public static PluginId findPluginId(Throwable t) { + if (t instanceof PluginException) { + return ((PluginException)t).getPluginId(); + } + + StackTraceElement[] elements = t.getStackTrace(); + for (StackTraceElement element : elements) { + String className = element.getClassName(); + if (PluginManager.isPluginClass(className)) { + return PluginManager.getPluginByClassName(className); + } + } + + if (t instanceof NoSuchMethodException) { + // check is method called from plugin classes + if (t.getMessage() != null) { + String className = ""; + StringTokenizer tok = new StringTokenizer(t.getMessage(), "."); + while (tok.hasMoreTokens()) { + String token = tok.nextToken(); + if (token.length() > 0 && Character.isJavaIdentifierStart(token.charAt(0))) { + className += token; + } + } + + if (PluginManager.isPluginClass(className)) { + return PluginManager.getPluginByClassName(className); + } + } + } + else if (t instanceof ClassNotFoundException) { + // check is class from plugin classes + if (t.getMessage() != null) { + String className = t.getMessage(); + + if (PluginManager.isPluginClass(className)) { + return PluginManager.getPluginByClassName(className); + } + } + } + else if (t instanceof AbstractMethodError && t.getMessage() != null) { + String s = t.getMessage(); + // org.antlr.works.plugin.intellij.PIFileType.getHighlighter(Lcom/intellij/openapi/project/Project;Lcom/intellij/openapi/vfs/VirtualFile;)Lcom/intellij/openapi/fileTypes/SyntaxHighlighter; + int pos = s.indexOf('('); + if (pos >= 0) { + s = s.substring(0, pos); + pos = s.lastIndexOf('.'); + if (pos >= 0) { + s = s.substring(0, pos); + if (PluginManager.isPluginClass(s)) { + return PluginManager.getPluginByClassName(s); + } + } + } + } + + else if (t instanceof ExtensionException) { + String className = ((ExtensionException)t).getExtensionClass().getName(); + if (PluginManager.isPluginClass(className)) { + return PluginManager.getPluginByClassName(className); + } + } + + return null; + } + + private class ShutdownAction extends AbstractAction { + public ShutdownAction() { + super(ApplicationManager.getApplication().isRestartCapable() ? "Restart" : DiagnosticBundle.message("error.list.shutdown.action")); + } + + public void actionPerformed(ActionEvent e) { + final boolean canRestart = ApplicationManager.getApplication().isRestartCapable(); + int rc = Messages.showYesNoDialog(getRootPane(), "Are you sure you would like to " + (canRestart ? "restart" : "shutdown") + + " " + ApplicationNamesInfo.getInstance().getFullProductName() + "?", + "Confirm", Messages.getQuestionIcon()); + if (rc != 0) return; + myMessagePool.setJvmIsShuttingDown(); + LaterInvocator.invokeLater(new Runnable() { + public void run() { + final Application app = ApplicationManager.getApplication(); + if (app.isRestartCapable()) { + app.restart(); + } + else { + app.exit(); + } + } + }, ModalityState.NON_MODAL); + doOKAction(); + } + } + + private class ClearFatalsAction extends AnAction { + public ClearFatalsAction() { + super("Clear all fatal errors", "Clear all fatal errors", + IconLoader.getIcon("/general/reset.png")); + } + + public void actionPerformed(AnActionEvent e) { + myMessagePool.clearFatals(); + } + } + + private class BlameAction extends AbstractAction { + public BlameAction() { + super(DiagnosticBundle.message("error.list.submit.action")); + } + + public void update() { + final AbstractMessage logMessage = getSelectedMessage(); + if (logMessage == null) { + setEnabled(false); + return; + } + + final ErrorReportSubmitter submitter = getSubmitter(logMessage.getThrowable()); + if (logMessage.isSubmitted() || submitter == null) { + setEnabled(false); + return; + } + setEnabled(true); + putValue(NAME, submitter.getReportActionText()); + } + + public void actionPerformed(ActionEvent e) { + final AbstractMessage logMessage = getSelectedMessage(); + reportMessage(logMessage); + rebuildHeaders(); + updateControls(); + } + + private void reportMessage(final AbstractMessage logMessage) { + ErrorReportSubmitter submitter = getSubmitter(logMessage.getThrowable()); + + if (submitter != null) { + logMessage.setSubmitting(true); + updateControls(); + submitter.submitAsync(getEvents(logMessage), logMessage.getAdditionalInfo(), getContentPane(), new Consumer() { + @Override + public void consume(SubmittedReportInfo submittedReportInfo) { + logMessage.setSubmitting(false); + logMessage.setSubmitted(submittedReportInfo); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + updateControls(); + } + }); + } + }); + } + } + + private IdeaLoggingEvent[] getEvents(final AbstractMessage logMessage) { + if (logMessage instanceof GroupedLogMessage) { + final List messages = ((GroupedLogMessage)logMessage).getMessages(); + IdeaLoggingEvent[] res = new IdeaLoggingEvent[messages.size()]; + for (int i = 0; i < res.length; i++) { + res[i] = getEvent(messages.get(i)); + } + return res; + } + return new IdeaLoggingEvent[]{getEvent(logMessage)}; + } + + private IdeaLoggingEvent getEvent(final AbstractMessage logMessage) { + return new IdeaLoggingEvent(logMessage.getMessage(), logMessage.getThrowable()); + } + } + + @Nullable + public static ErrorReportSubmitter getSubmitter(final Throwable throwable) { + if (throwable instanceof MessagePool.TooManyErrorsException || throwable instanceof AbstractMethodError) { + return null; + } + final PluginId pluginId = findPluginId(throwable); + final ErrorReportSubmitter[] reporters; + try { + reporters = Extensions.getExtensions(ExtensionPoints.ERROR_HANDLER_EP); + } + catch (Throwable t) { + return null; + } + ErrorReportSubmitter submitter = null; + for (ErrorReportSubmitter reporter : reporters) { + final PluginDescriptor descriptor = reporter.getPluginDescriptor(); + if (pluginId == null && (descriptor == null || PluginId.getId("com.intellij") == descriptor.getPluginId()) + || descriptor != null && Comparing.equal(pluginId, descriptor.getPluginId())) { + submitter = reporter; + } + } + return submitter; + } + + protected void doOKAction() { + PropertiesComponent.getInstance().setValue(IMMEDIATE_POPUP_OPTION, String.valueOf(myImmediatePopupCheckbox.isSelected())); + markAllAsRead(); + super.doOKAction(); + } + + private void markAllAsRead() { + for (AbstractMessage each : myFatalErrors) { + each.setRead(true); + } + } + + public void doCancelAction() { + PropertiesComponent.getInstance().setValue(IMMEDIATE_POPUP_OPTION, String.valueOf(myImmediatePopupCheckbox.isSelected())); + markAllAsRead(); + super.doCancelAction(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myMoreInfoPane; + } + + protected class CloseAction extends AbstractAction { + public CloseAction() { + putValue(NAME, DiagnosticBundle.message("error.list.close.action")); + } + + public void actionPerformed(ActionEvent e) { + doOKAction(); + } + } + + protected String getDimensionServiceKey() { + return "IdeErrosDialog"; + } + + private static String getThrowableHashCode(Throwable exception) { + try { + return md5(StringUtil.getThrowableText(exception), "stack-trace"); + } + catch (NoSuchAlgorithmException e) { + LOG.error(e); + return ""; + } + } + + private static String md5 (String buffer, @NonNls String key) throws NoSuchAlgorithmException { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(buffer.getBytes()); + byte [] code = md5.digest(key.getBytes()); + BigInteger bi = new BigInteger(code).abs(); + return bi.abs().toString(16); + } + + private class AnalyzeAction extends AbstractAction { + private final AnAction myAnalyze; + + public AnalyzeAction(AnAction analyze) { + super(analyze.getTemplatePresentation().getText()); + myAnalyze = analyze; + } + + public void actionPerformed(ActionEvent e) { + final DataContext dataContext = ((DataManagerImpl)DataManager.getInstance()).getDataContextTest((Component)e.getSource()); + + AnActionEvent event = new AnActionEvent( + null, dataContext, + ActionPlaces.UNKNOWN, + myAnalyze.getTemplatePresentation(), + ActionManager.getInstance(), + e.getModifiers() + ); + + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + setEnabled(project != null); + if (project != null) { + myAnalyze.actionPerformed(event); + doOKAction(); + } + } + } + + private class DisablePluginAction extends AbstractAction { + @Override + public void actionPerformed(ActionEvent e) { + PluginId pluginId = getSelectedPluginId(); + if (pluginId != null) { + PluginManager.disablePlugin(pluginId.toString()); + final Application app = ApplicationManager.getApplication(); + final String pluginName = app.getPlugin(pluginId).getName(); + int rc = Messages.showYesNoDialog(getRootPane(), "Are you sure you would like to disable the plugin " + pluginName + + "? The features provided by the plugin will no longer be available.", + "Disable Plugin", Messages.getQuestionIcon()); + if (rc != 0) return; + final String productName = ApplicationNamesInfo.getInstance().getFullProductName(); + if (app.isRestartCapable()) { + rc = Messages.showYesNoDialog(getRootPane(), pluginName + " has been disabled. Would you like to restart " + productName + " so that the changes would take effect?", + "Disable Plugin", Messages.getInformationIcon()); + if (rc == 0) { + app.restart(); + } + } + else { + Messages.showMessageDialog(getRootPane(), pluginName + " has been disabled. You'll need to restart " + productName + " for the changes to take effect.", + "Disable Plugin", Messages.getInformationIcon()); + } + } + } + + public void update() { + PluginId pluginId = getSelectedPluginId(); + if (pluginId != null) { + setEnabled(true); + putValue(NAME, "Disable " + ApplicationManager.getApplication().getPlugin(pluginId).getName()); + } + else { + setEnabled(false); + putValue(NAME, "Disable plugin"); + } + } + + @Nullable + private PluginId getSelectedPluginId() { + final AbstractMessage message = getSelectedMessage(); + if (message != null) { + //noinspection ThrowableResultOfMethodCallIgnored + return findPluginId(message.getThrowable()); + } + return null; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/GeneralSettingsConfigurable.java b/platform/platform-impl/src/com/intellij/ide/GeneralSettingsConfigurable.java index a254f9f6b82e..e11111b063b8 100644 --- a/platform/platform-impl/src/com/intellij/ide/GeneralSettingsConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/GeneralSettingsConfigurable.java @@ -1,183 +1,183 @@ -/* - * 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.ide; - -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.options.*; -import com.intellij.openapi.util.IconLoader; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.*; -import java.util.List; - -/** - * To provide additional options in General section register implementation of {@link com.intellij.openapi.options.SearchableConfigurable} in the plugin.xml: - *

- * <extensions defaultExtensionNs="com.intellij">
- *   <generalOptionsProvider instance="class-name"/>
- * </extensions> - *

- * A new instance of the specified class will be created each time then the Settings dialog is opened - */ -public class GeneralSettingsConfigurable extends CompositeConfigurable implements SearchableConfigurable { - private static ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.generalOptionsProvider"); - - private MyComponent myComponent; - - - public void apply() throws ConfigurationException { - super.apply(); - GeneralSettings settings = GeneralSettings.getInstance(); - - settings.setReopenLastProject(myComponent.myChkReopenLastProject.isSelected()); - settings.setSyncOnFrameActivation(myComponent.myChkSyncOnFrameActivation.isSelected()); - settings.setSaveOnFrameDeactivation(myComponent.myChkSaveOnFrameDeactivation.isSelected()); - settings.setConfirmExit(myComponent.myConfirmExit.isSelected()); - if (myComponent.myConfirmFrameToOpenCheckBox.isSelected()) { - settings.setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_ASK); - } - else if (settings.getConfirmOpenNewProject() == GeneralSettings.OPEN_PROJECT_ASK) { - settings.setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_NEW_WINDOW); - } - // AutoSave in inactive - settings.setAutoSaveIfInactive(myComponent.myChkAutoSaveIfInactive.isSelected()); - try { - int newInactiveTimeout = Integer.parseInt(myComponent.myTfInactiveTimeout.getText()); - if (newInactiveTimeout > 0) { - settings.setInactiveTimeout(newInactiveTimeout); - } - } - catch (NumberFormatException e) { - } - - - } - - public boolean isModified() { - if (super.isModified()) return true; - boolean isModified = false; - GeneralSettings settings = GeneralSettings.getInstance(); - isModified |= settings.isReopenLastProject() != myComponent.myChkReopenLastProject.isSelected(); - isModified |= settings.isSyncOnFrameActivation() != myComponent.myChkSyncOnFrameActivation.isSelected(); - isModified |= settings.isSaveOnFrameDeactivation() != myComponent.myChkSaveOnFrameDeactivation.isSelected(); - isModified |= settings.isAutoSaveIfInactive() != myComponent.myChkAutoSaveIfInactive.isSelected(); - isModified |= settings.isConfirmExit() != myComponent.myConfirmExit.isSelected(); - - int openProjectOption = settings.getConfirmOpenNewProject(); - - isModified |= (myComponent.myConfirmFrameToOpenCheckBox.isSelected() && openProjectOption >= 0) || (!myComponent.myConfirmFrameToOpenCheckBox.isSelected() == openProjectOption < 0); - - int inactiveTimeout = -1; - try { - inactiveTimeout = Integer.parseInt(myComponent.myTfInactiveTimeout.getText()); - } - catch (NumberFormatException e) { - } - - isModified |= inactiveTimeout > 0 && settings.getInactiveTimeout() != inactiveTimeout; - - return isModified; - } - - public JComponent createComponent() { -// optionGroup.add(getDiffOptions().getPanel()); - myComponent = new MyComponent(); - - myComponent.myChkAutoSaveIfInactive.addChangeListener(new ChangeListener() { - public void stateChanged(ChangeEvent e) { - myComponent.myTfInactiveTimeout.setEditable(myComponent.myChkAutoSaveIfInactive.isSelected()); - } - }); - - List list = getConfigurables(); - if (!list.isEmpty()) { - myComponent.myPluginOptionsPanel.setLayout(new GridLayout(list.size(), 1)); - for (Configurable c : list) { - myComponent.myPluginOptionsPanel.add(c.createComponent()); - } - } - - return myComponent.myPanel; - } - - public String getDisplayName() { - return IdeBundle.message("title.general"); - } - - public Icon getIcon() { - return IconLoader.getIcon("/general/configurableGeneral.png"); - } - - public void reset() { - super.reset(); - GeneralSettings settings = GeneralSettings.getInstance(); - myComponent.myChkReopenLastProject.setSelected(settings.isReopenLastProject()); - myComponent.myChkSyncOnFrameActivation.setSelected(settings.isSyncOnFrameActivation()); - myComponent.myChkSaveOnFrameDeactivation.setSelected(settings.isSaveOnFrameDeactivation()); - - myComponent.myChkAutoSaveIfInactive.setSelected(settings.isAutoSaveIfInactive()); - myComponent.myTfInactiveTimeout.setText(Integer.toString(settings.getInactiveTimeout())); - myComponent.myTfInactiveTimeout.setEditable(settings.isAutoSaveIfInactive()); - myComponent.myConfirmExit.setSelected(settings.isConfirmExit()); - myComponent.myConfirmFrameToOpenCheckBox.setSelected(settings.getConfirmOpenNewProject() < 0); - } - - public void disposeUIResources() { - super.disposeUIResources(); - myComponent = null; - } - - @NotNull - public String getHelpTopic() { - return "preferences.general"; - } - - - - private static class MyComponent { - JPanel myPanel; - - private JCheckBox myChkReopenLastProject; - private JCheckBox myChkSyncOnFrameActivation; - private JCheckBox myChkSaveOnFrameDeactivation; - private JCheckBox myChkAutoSaveIfInactive; - private JTextField myTfInactiveTimeout; - public JCheckBox myConfirmExit; - private JPanel myPluginOptionsPanel; - private JCheckBox myConfirmFrameToOpenCheckBox; - - - public MyComponent() { - } - } - - public String getId() { - return getHelpTopic(); - } - - @Nullable - public Runnable enableSearch(String option) { - return null; - } - - protected List createConfigurables() { - return AbstractConfigurableEP.createConfigurables(EP_NAME); - } +/* + * 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.ide; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.options.*; +import com.intellij.openapi.util.IconLoader; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.util.List; + +/** + * To provide additional options in General section register implementation of {@link com.intellij.openapi.options.SearchableConfigurable} in the plugin.xml: + *

+ * <extensions defaultExtensionNs="com.intellij">
+ *   <generalOptionsProvider instance="class-name"/>
+ * </extensions> + *

+ * A new instance of the specified class will be created each time then the Settings dialog is opened + */ +public class GeneralSettingsConfigurable extends CompositeConfigurable implements SearchableConfigurable { + private static ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.generalOptionsProvider"); + + private MyComponent myComponent; + + + public void apply() throws ConfigurationException { + super.apply(); + GeneralSettings settings = GeneralSettings.getInstance(); + + settings.setReopenLastProject(myComponent.myChkReopenLastProject.isSelected()); + settings.setSyncOnFrameActivation(myComponent.myChkSyncOnFrameActivation.isSelected()); + settings.setSaveOnFrameDeactivation(myComponent.myChkSaveOnFrameDeactivation.isSelected()); + settings.setConfirmExit(myComponent.myConfirmExit.isSelected()); + if (myComponent.myConfirmFrameToOpenCheckBox.isSelected()) { + settings.setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_ASK); + } + else if (settings.getConfirmOpenNewProject() == GeneralSettings.OPEN_PROJECT_ASK) { + settings.setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_NEW_WINDOW); + } + // AutoSave in inactive + settings.setAutoSaveIfInactive(myComponent.myChkAutoSaveIfInactive.isSelected()); + try { + int newInactiveTimeout = Integer.parseInt(myComponent.myTfInactiveTimeout.getText()); + if (newInactiveTimeout > 0) { + settings.setInactiveTimeout(newInactiveTimeout); + } + } + catch (NumberFormatException e) { + } + + + } + + public boolean isModified() { + if (super.isModified()) return true; + boolean isModified = false; + GeneralSettings settings = GeneralSettings.getInstance(); + isModified |= settings.isReopenLastProject() != myComponent.myChkReopenLastProject.isSelected(); + isModified |= settings.isSyncOnFrameActivation() != myComponent.myChkSyncOnFrameActivation.isSelected(); + isModified |= settings.isSaveOnFrameDeactivation() != myComponent.myChkSaveOnFrameDeactivation.isSelected(); + isModified |= settings.isAutoSaveIfInactive() != myComponent.myChkAutoSaveIfInactive.isSelected(); + isModified |= settings.isConfirmExit() != myComponent.myConfirmExit.isSelected(); + + int openProjectOption = settings.getConfirmOpenNewProject(); + + isModified |= (myComponent.myConfirmFrameToOpenCheckBox.isSelected() && openProjectOption >= 0) || (!myComponent.myConfirmFrameToOpenCheckBox.isSelected() == openProjectOption < 0); + + int inactiveTimeout = -1; + try { + inactiveTimeout = Integer.parseInt(myComponent.myTfInactiveTimeout.getText()); + } + catch (NumberFormatException e) { + } + + isModified |= inactiveTimeout > 0 && settings.getInactiveTimeout() != inactiveTimeout; + + return isModified; + } + + public JComponent createComponent() { +// optionGroup.add(getDiffOptions().getPanel()); + myComponent = new MyComponent(); + + myComponent.myChkAutoSaveIfInactive.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + myComponent.myTfInactiveTimeout.setEditable(myComponent.myChkAutoSaveIfInactive.isSelected()); + } + }); + + List list = getConfigurables(); + if (!list.isEmpty()) { + myComponent.myPluginOptionsPanel.setLayout(new GridLayout(list.size(), 1)); + for (Configurable c : list) { + myComponent.myPluginOptionsPanel.add(c.createComponent()); + } + } + + return myComponent.myPanel; + } + + public String getDisplayName() { + return IdeBundle.message("title.general"); + } + + public Icon getIcon() { + return IconLoader.getIcon("/general/configurableGeneral.png"); + } + + public void reset() { + super.reset(); + GeneralSettings settings = GeneralSettings.getInstance(); + myComponent.myChkReopenLastProject.setSelected(settings.isReopenLastProject()); + myComponent.myChkSyncOnFrameActivation.setSelected(settings.isSyncOnFrameActivation()); + myComponent.myChkSaveOnFrameDeactivation.setSelected(settings.isSaveOnFrameDeactivation()); + + myComponent.myChkAutoSaveIfInactive.setSelected(settings.isAutoSaveIfInactive()); + myComponent.myTfInactiveTimeout.setText(Integer.toString(settings.getInactiveTimeout())); + myComponent.myTfInactiveTimeout.setEditable(settings.isAutoSaveIfInactive()); + myComponent.myConfirmExit.setSelected(settings.isConfirmExit()); + myComponent.myConfirmFrameToOpenCheckBox.setSelected(settings.getConfirmOpenNewProject() < 0); + } + + public void disposeUIResources() { + super.disposeUIResources(); + myComponent = null; + } + + @NotNull + public String getHelpTopic() { + return "preferences.general"; + } + + + + private static class MyComponent { + JPanel myPanel; + + private JCheckBox myChkReopenLastProject; + private JCheckBox myChkSyncOnFrameActivation; + private JCheckBox myChkSaveOnFrameDeactivation; + private JCheckBox myChkAutoSaveIfInactive; + private JTextField myTfInactiveTimeout; + public JCheckBox myConfirmExit; + private JPanel myPluginOptionsPanel; + private JCheckBox myConfirmFrameToOpenCheckBox; + + + public MyComponent() { + } + } + + public String getId() { + return getHelpTopic(); + } + + @Nullable + public Runnable enableSearch(String option) { + return null; + } + + protected List createConfigurables() { + return AbstractConfigurableEP.createConfigurables(EP_NAME); + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index beae2597bba4..7e6b443de6d3 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -1,894 +1,894 @@ -/* - * 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.ide; - - -import com.intellij.Patches; -import com.intellij.ide.dnd.DnDManager; -import com.intellij.ide.dnd.DnDManagerImpl; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher; -import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.util.Alarm; -import com.intellij.util.ReflectionUtil; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; - - -/** - * @author Vladimir Kondratyev - * @author Anton Katilin - */ - -public class IdeEventQueue extends EventQueue { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue"); - - /** - * Adding/Removing of "idle" listeners should be thread safe. - */ - private final Object myLock = new Object(); - - private final ArrayList myIdleListeners = new ArrayList(2); - - private final ArrayList myActivityListeners = new ArrayList(2); - - private final Alarm myIdleRequestsAlarm = new Alarm(); - - private final Alarm myIdleTimeCounterAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - - private long myIdleTime; - - private final Map myListener2Request = new HashMap(); - // IdleListener -> MyFireIdleRequest - - private final IdeKeyEventDispatcher myKeyEventDispatcher = new IdeKeyEventDispatcher(this); - - private final IdeMouseEventDispatcher myMouseEventDispatcher = new IdeMouseEventDispatcher(); - - private final IdePopupManager myPopupManager = new IdePopupManager(); - - - private final ToolkitBugsProcessor myToolkitBugsProcessor = new ToolkitBugsProcessor(); - - private boolean mySuspendMode; - - /** - * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events - *

- * in the queue. If WINDOW_OPENED event does exists in the queus then we restart the alarm. - */ - - private Component myFocusOwner; - - private final Runnable myExitSuspendModeRunnable = new ExitSuspendModeRunnable(); - - /** - * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED - *

- * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm. - */ - private final Alarm mySuspendModeAlarm = new Alarm(); - - /** - * Counter of processed events. It is used to assert that data context lives only inside single - *

- * Swing event. - */ - - private int myEventCount; - - - private boolean myIsInInputEvent = false; - - private AWTEvent myCurrentEvent = null; - - private long myLastActiveTime; - - private WindowManagerEx myWindowManager; - - - private final Set myDispatchers = new LinkedHashSet(); - private final Set myPostprocessors = new LinkedHashSet(); - - private final Set myReady = new HashSet(); - private boolean myKeyboardBusy; - - private static class IdeEventQueueHolder { - private static final IdeEventQueue INSTANCE = new IdeEventQueue(); - } - - public static IdeEventQueue getInstance() { - return IdeEventQueueHolder.INSTANCE; - } - - private IdeEventQueue() { - addIdleTimeCounterRequest(); - final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - - //noinspection HardCodedStringLiteral - keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() { - - public void propertyChange(final PropertyChangeEvent e) { - final Application application = ApplicationManager.getApplication(); - if (application == null) { - - // We can get focus event before application is initialized - return; - } - application.assertIsDispatchThread(); - final Window focusedWindow = keyboardFocusManager.getFocusedWindow(); - final Component focusOwner = keyboardFocusManager.getFocusOwner(); - if (mySuspendMode && focusedWindow != null && focusOwner != null && focusOwner != myFocusOwner && !(focusOwner instanceof Window)) { - exitSuspendMode(); - } - } - }); - - addDispatcher(new WindowsAltSupressor(), null); - } - - - public void setWindowManager(final WindowManagerEx windowManager) { - myWindowManager = windowManager; - } - - - private void addIdleTimeCounterRequest() { - Application application = ApplicationManager.getApplication(); - if (application != null && application.isUnitTestMode()) return; - - myIdleTimeCounterAlarm.cancelAllRequests(); - myLastActiveTime = System.currentTimeMillis(); - myIdleTimeCounterAlarm.addRequest(new Runnable() { - public void run() { - myIdleTime += System.currentTimeMillis() - myLastActiveTime; - addIdleTimeCounterRequest(); - } - }, 20000, ModalityState.NON_MODAL); - } - - - public boolean shouldNotTypeInEditor() { - return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode; - } - - - private void enterSuspendMode() { - mySuspendMode = true; - myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - mySuspendModeAlarm.cancelAllRequests(); - mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750); - } - - - /** - * Exits supend mode and pumps all suspended events. - */ - - private void exitSuspendMode() { - if (shallEnterSuspendMode()) { - - // We have to exit from suspend mode (focus owner changes or alarm is triggered) but - - // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until - - // all WINDOW_OPENED event will be processed. - mySuspendModeAlarm.cancelAllRequests(); - mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 250); - } - else { - - // Now we can pump all suspended events. - mySuspendMode = false; - myFocusOwner = null; // to prevent memory leaks - } - } - - - public void addIdleListener(@NotNull final Runnable runnable, final int timeout) { - LOG.assertTrue(timeout > 0); - synchronized (myLock) { - myIdleListeners.add(runnable); - final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeout); - myListener2Request.put(runnable, request); - myIdleRequestsAlarm.addRequest(request, timeout); - } - } - - - public void removeIdleListener(@NotNull final Runnable runnable) { - synchronized (myLock) { - final boolean wasRemoved = myIdleListeners.remove(runnable); - if (!wasRemoved) { - LOG.error("unknown runnable: " + runnable); - } - final MyFireIdleRequest request = myListener2Request.remove(runnable); - LOG.assertTrue(request != null); - myIdleRequestsAlarm.cancelRequest(request); - } - } - - - public void addActivityListener(@NotNull final Runnable runnable) { - synchronized (myLock) { - myActivityListeners.add(runnable); - } - } - - public void addActivityListener(@NotNull final Runnable runnable, Disposable parentDisposable) { - synchronized (myLock) { - ContainerUtil.add(runnable, myActivityListeners, parentDisposable); - } - } - - - public void removeActivityListener(@NotNull final Runnable runnable) { - synchronized (myLock) { - final boolean wasRemoved = myActivityListeners.remove(runnable); - if (!wasRemoved) { - LOG.error("unknown runnable: " + runnable); - } - } - } - - - public void addDispatcher(final EventDispatcher dispatcher, Disposable parent) { - _addProcessor(dispatcher, parent, myDispatchers); - } - - public void removeDispatcher(EventDispatcher dispatcher) { - myDispatchers.remove(dispatcher); - } - - public boolean containsDispatcher(EventDispatcher dispatcher) { - return myDispatchers.contains(dispatcher); - } - - public void addPostprocessor(EventDispatcher dispatcher, Disposable parent) { - _addProcessor(dispatcher, parent, myPostprocessors); - } - - public void removePostprocessor(EventDispatcher dispatcher) { - myPostprocessors.remove(dispatcher); - } - - private void _addProcessor(final EventDispatcher dispatcher, Disposable parent, Set set) { - set.add(dispatcher); - if (parent != null) { - Disposer.register(parent, new Disposable() { - public void dispose() { - removeDispatcher(dispatcher); - } - }); - } - } - - public int getEventCount() { - return myEventCount; - } - - - public void setEventCount(int evCount) { - myEventCount = evCount; - } - - - public AWTEvent getTrueCurrentEvent() { - return myCurrentEvent; - } - - //[jeka] commented for performance reasons - - /* - - public void postEvent(final AWTEvent e) { - - // [vova] sometime people call SwingUtilities.invokeLater(null). To - - // find such situations we will specially check InvokationEvents - - try { - - if (e instanceof InvocationEvent) { - - //noinspection HardCodedStringLiteral - - final Field field = InvocationEvent.class.getDeclaredField("runnable"); - - field.setAccessible(true); - - final Object runnable = field.get(e); - - if (runnable == null) { - - //noinspection HardCodedStringLiteral - - throw new IllegalStateException("InvocationEvent contains null runnable: " + e); - - } - - } - - } - - catch (final Exception exc) { - - throw new Error(exc); - - } - - super.postEvent(e); - - } - - */ - - public void dispatchEvent(final AWTEvent e) { - boolean wasInputEvent = myIsInInputEvent; - myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent; - AWTEvent oldEvent = myCurrentEvent; - myCurrentEvent = e; - - try { - _dispatchEvent(e); - } - finally { - myIsInInputEvent = wasInputEvent; - myCurrentEvent = oldEvent; - - for (EventDispatcher each : myPostprocessors) { - each.dispatch(e); - } - - if (e instanceof KeyEvent) { - maybeReady(); - } - } - } - - @SuppressWarnings({"ALL"}) - private static String toDebugString(final AWTEvent e) { - if (e instanceof InvocationEvent) { - try { - final Field f = InvocationEvent.class.getDeclaredField("runnable"); - f.setAccessible(true); - Object runnable = f.get(e); - - return "Invoke Later[" + runnable.toString() + "]"; - } - catch (NoSuchFieldException e1) { - } - catch (IllegalAccessException e1) { - } - } - return e.toString(); - } - - - private void _dispatchEvent(final AWTEvent e) { - _dispatchEvent(e, false); - } - - public void _dispatchEvent(AWTEvent e, boolean typeaheadFlushing) { - if (e.getID() == MouseEvent.MOUSE_DRAGGED) { - DnDManagerImpl dndManager = (DnDManagerImpl)DnDManager.getInstance(); - if (dndManager != null) { - dndManager.setLastDropHandler(null); - } - } - - - myEventCount++; - - - if (processAppActivationEvents(e)) return; - - if (!typeaheadFlushing) { - fixStickyFocusedComponents(e); - } - - if (!myPopupManager.isPopupActive()) { - enterSuspendModeIfNeeded(e); - } - - if (e instanceof KeyEvent) { - myKeyboardBusy = e.getID() != KeyEvent.KEY_RELEASED || ((KeyEvent)e).getModifiers() != 0; - } - - if (!typeaheadFlushing && typeAheadDispatchToFocusManager(e)) return; - - if (e instanceof WindowEvent) { - ActivityTracker.getInstance().inc(); - } - - - // Process "idle" and "activity" listeners - if (e instanceof KeyEvent || e instanceof MouseEvent) { - ActivityTracker.getInstance().inc(); - - synchronized (myLock) { - myIdleRequestsAlarm.cancelAllRequests(); - for (Runnable idleListener : myIdleListeners) { - final MyFireIdleRequest request = myListener2Request.get(idleListener); - if (request == null) { - LOG.error("There is no request for " + idleListener); - } - int timeout = request.getTimeout(); - myIdleRequestsAlarm.addRequest(request, timeout, ModalityState.NON_MODAL); - } - if (KeyEvent.KEY_PRESSED == e.getID() || - KeyEvent.KEY_TYPED == e.getID() || - MouseEvent.MOUSE_PRESSED == e.getID() || - MouseEvent.MOUSE_RELEASED == e.getID() || - MouseEvent.MOUSE_CLICKED == e.getID()) { - addIdleTimeCounterRequest(); - for (Runnable activityListener : myActivityListeners) { - activityListener.run(); - } - } - } - } - if (myPopupManager.isPopupActive() && myPopupManager.dispatch(e)) { - return; - } - - for (EventDispatcher eachDispatcher : myDispatchers) { - if (eachDispatcher.dispatch(e)) { - return; - } - } - - if (e instanceof InputMethodEvent) { - if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) { - return; - } - } - if (e instanceof InputEvent && Patches.SPECIAL_WINPUT_METHOD_PROCESSING) { - final InputEvent inputEvent = (InputEvent)e; - if (!inputEvent.getComponent().isShowing()) { - return; - } - } - if (e instanceof ComponentEvent && myWindowManager != null) { - myWindowManager.dispatchComponentEvent((ComponentEvent)e); - } - if (e instanceof KeyEvent) { - if (mySuspendMode || !myKeyEventDispatcher.dispatchKeyEvent((KeyEvent)e)) { - defaultDispatchEvent(e); - } - else { - ((KeyEvent)e).consume(); - defaultDispatchEvent(e); - } - } - else if (e instanceof MouseEvent) { - if (!myMouseEventDispatcher.dispatchMouseEvent((MouseEvent)e)) { - defaultDispatchEvent(e); - } - } - else { - defaultDispatchEvent(e); - } - } - - private static void fixStickyWindow(KeyboardFocusManager mgr, Window wnd, String resetMethod) { - Window showingWindow = wnd; - - if (wnd != null && !wnd.isShowing()) { - while (showingWindow != null) { - if (showingWindow.isShowing()) break; - showingWindow = (Window)showingWindow.getParent(); - } - - if (showingWindow == null) { - final Frame[] allFrames = Frame.getFrames(); - for (Frame each : allFrames) { - if (each.isShowing()) { - showingWindow = each; - break; - } - } - } - - - if (showingWindow != null && showingWindow != wnd) { - final Method setActive = ReflectionUtil.findMethod(KeyboardFocusManager.class.getDeclaredMethods(), resetMethod, Window.class); - if (setActive != null) { - try { - setActive.setAccessible(true); - setActive.invoke(mgr, (Window)showingWindow); - } - catch (Exception exc) { - LOG.info(exc); - } - } - } - } - } - - public void fixStickyFocusedComponents(@Nullable AWTEvent e) { - if (e != null && !(e instanceof InputEvent)) return; - - final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - - if (Registry.is("actionSystem.fixStickyFocusedWindows")) { - fixStickyWindow(mgr, mgr.getActiveWindow(), "setGlobalActiveWindow"); - fixStickyWindow(mgr, mgr.getFocusedWindow(), "setGlobalFocusedWindow"); - } - - if (Registry.is("actionSystem.fixNullFocusedComponent")) { - final Component focusOwner = mgr.getFocusOwner(); - if (focusOwner == null) { - - IdeEventQueue queue = IdeEventQueue.getInstance(); - boolean mouseEventsAhead = e instanceof MouseEvent || - queue.peekEvent(MouseEvent.MOUSE_PRESSED) != null || - queue.peekEvent(MouseEvent.MOUSE_RELEASED) != null || - queue.peekEvent(MouseEvent.MOUSE_CLICKED) != null; - - if (!mouseEventsAhead) { - Window showingWindow = mgr.getActiveWindow(); - if (showingWindow == null) { - Method getNativeFocusOwner = ReflectionUtil.getDeclaredMethod(KeyboardFocusManager.class, "getNativeFocusOwner"); - if (getNativeFocusOwner != null) { - getNativeFocusOwner.setAccessible(true); - try { - Object owner = getNativeFocusOwner.invoke(mgr); - if (owner instanceof Component) { - Component nativeFocusOwner = (Component)owner; - if (nativeFocusOwner instanceof Window) { - showingWindow = (Window)nativeFocusOwner; - } else { - showingWindow = SwingUtilities.getWindowAncestor(nativeFocusOwner); - } - } - } - catch (Exception e1) { - LOG.debug(e1); - } - } - } - if (showingWindow != null) { - final IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(showingWindow); - Runnable requestDefaultFocus = new Runnable() { - public void run() { - if (mgr.getFocusOwner() == null) { - if (getPopupManager().requestDefaultFocus(false)) return; - - final Application app = ApplicationManager.getApplication(); - if (app != null && app.isActive()) { - fm.requestDefaultFocus(false); - } - } - } - }; - if (e != null) { - fm.doWhenFocusSettlesDown(requestDefaultFocus); - } - else { - requestDefaultFocus.run(); - } - } - } - } - } - } - - private void enterSuspendModeIfNeeded(AWTEvent e) { - if (e instanceof KeyEvent) { - if (!mySuspendMode && shallEnterSuspendMode()) { - enterSuspendMode(); - } - } - } - - private boolean shallEnterSuspendMode() { - return peekEvent(WindowEvent.WINDOW_OPENED) != null; - } - - private static boolean processAppActivationEvents(AWTEvent e) { - final Application app = ApplicationManager.getApplication(); - if (!(app instanceof ApplicationImpl)) return false; - - ApplicationImpl appImpl = (ApplicationImpl)app; - - boolean consumed = false; - if (e instanceof WindowEvent) { - WindowEvent we = (WindowEvent)e; - if (we.getID() == WindowEvent.WINDOW_GAINED_FOCUS && we.getWindow() != null) { - if (we.getOppositeWindow() == null && !appImpl.isActive()) { - consumed = appImpl.tryToApplyActivationState(true, we.getWindow()); - } - } - else if (we.getID() == WindowEvent.WINDOW_LOST_FOCUS && we.getWindow() != null) { - if (we.getOppositeWindow() == null && appImpl.isActive()) { - consumed = appImpl.tryToApplyActivationState(false, we.getWindow()); - } - } - } - - return false; - } - - - private void defaultDispatchEvent(final AWTEvent e) { - try { - super.dispatchEvent(e); - } - catch (ProcessCanceledException pce) { - throw pce; - } - catch (Throwable exc) { - if (!myToolkitBugsProcessor.process(exc)) { - LOG.error("Error during dispatching of " + e, exc); - } - } - } - - private static boolean typeAheadDispatchToFocusManager(AWTEvent e) { - if (e instanceof KeyEvent) { - final KeyEvent event = (KeyEvent)e; - if (!event.isConsumed()) { - final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(event.getComponent()); - return focusManager.dispatch(event); - } - } - - return false; - } - - - public void flushQueue() { - while (true) { - AWTEvent event = peekEvent(); - if (event == null) return; - try { - AWTEvent event1 = getNextEvent(); - dispatchEvent(event1); - } - catch (Exception e) { - LOG.error(e); //? - } - } - } - - public void pumpEventsForHierarchy(Component modalComponent, Condition exitCondition) { - AWTEvent event; - do { - try { - event = getNextEvent(); - boolean eventOk = true; - if (event instanceof InputEvent) { - final Object s = event.getSource(); - if (s instanceof Component) { - Component c = (Component)s; - Window modalWindow = SwingUtilities.windowForComponent(modalComponent); - while (c != null && c != modalWindow) c = c.getParent(); - if (c == null) { - eventOk = false; - ((InputEvent)event).consume(); - } - } - } - - if (eventOk) { - dispatchEvent(event); - } - } - catch (Throwable e) { - LOG.error(e); - event = null; - } - } - while (!exitCondition.value(event)); - } - - - public interface EventDispatcher { - boolean dispatch(AWTEvent e); - } - - - private final class MyFireIdleRequest implements Runnable { - private final Runnable myRunnable; - private final int myTimeout; - - - public MyFireIdleRequest(@NotNull Runnable runnable, final int timeout) { - myTimeout = timeout; - myRunnable = runnable; - } - - - public void run() { - myRunnable.run(); - synchronized (myLock) { - if (myIdleListeners.contains(myRunnable)) // do not reschedule if not interested anymore - { - myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL); - } - } - } - - public int getTimeout() { - return myTimeout; - } - } - - - private final class ExitSuspendModeRunnable implements Runnable { - - public void run() { - if (mySuspendMode) { - exitSuspendMode(); - } - } - } - - - public long getIdleTime() { - return myIdleTime; - } - - - public IdePopupManager getPopupManager() { - return myPopupManager; - } - - public IdeKeyEventDispatcher getKeyEventDispatcher() { - return myKeyEventDispatcher; - } - - public void blockNextEvents(final MouseEvent e) { - myMouseEventDispatcher.blockNextEvents(e); - } - - public boolean isSuspendMode() { - return mySuspendMode; - } - - public boolean hasFocusEventsPending() { - return peekEvent(FocusEvent.FOCUS_GAINED) != null || peekEvent(FocusEvent.FOCUS_LOST) != null; - } - - private boolean isReady() { - return !myKeyboardBusy && myKeyEventDispatcher.isReady(); - } - - public void maybeReady() { - flushReady(); - } - - private void flushReady() { - if (myReady.isEmpty() || !isReady()) return; - - Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]); - myReady.clear(); - - for (Runnable each : ready) { - each.run(); - } - } - - public void doWhenReady(final Runnable runnable) { - if (EventQueue.isDispatchThread()) { - myReady.add(runnable); - maybeReady(); - } - else { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myReady.add(runnable); - maybeReady(); - } - }); - } - } - - public boolean isPopupActive() { - return myPopupManager.isPopupActive(); - } - - private static class WindowsAltSupressor implements EventDispatcher { - - private boolean myPureAltWasPressed; - private boolean myWaitingForAltRelease; - private boolean myWaiterScheduled; - - private Robot myRobot; - - public boolean dispatch(AWTEvent e) { - boolean dispatch = true; - if (e instanceof KeyEvent) { - KeyEvent ke = ((KeyEvent)e); - boolean pureAlt = ke.getKeyCode() == KeyEvent.VK_ALT && (ke.getModifiers() | KeyEvent.ALT_MASK) == KeyEvent.ALT_MASK; - if (!pureAlt) { - myPureAltWasPressed = false; - myWaitingForAltRelease = false; - myWaiterScheduled = false; - } - else { - Application app = ApplicationManager.getApplication(); - if (app == null || - !SystemInfo.isWindows || - !Registry.is("actionSystem.win.supressAlt") || - !UISettings.getInstance().HIDE_TOOL_STRIPES) { - return !dispatch; - } - - if (ke.getID() == KeyEvent.KEY_PRESSED) { - myPureAltWasPressed = true; - dispatch = !myWaitingForAltRelease; - } - else if (ke.getID() == KeyEvent.KEY_RELEASED) { - if (myWaitingForAltRelease) { - myPureAltWasPressed = false; - myWaitingForAltRelease = false; - myWaiterScheduled = false; - dispatch = false; - } - else { - myWaiterScheduled = true; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - myWaitingForAltRelease = true; - if (myRobot == null) { - myRobot = new Robot(); - } - myRobot.keyPress(KeyEvent.VK_ALT); - myRobot.keyRelease(KeyEvent.VK_ALT); - } - catch (AWTException e1) { - LOG.debug(e1); - } - } - }); - } - } - } - } - - return !dispatch; - } - } -} +/* + * 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.ide; + + +import com.intellij.Patches; +import com.intellij.ide.dnd.DnDManager; +import com.intellij.ide.dnd.DnDManagerImpl; +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher; +import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.util.Alarm; +import com.intellij.util.ReflectionUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + + +/** + * @author Vladimir Kondratyev + * @author Anton Katilin + */ + +public class IdeEventQueue extends EventQueue { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue"); + + /** + * Adding/Removing of "idle" listeners should be thread safe. + */ + private final Object myLock = new Object(); + + private final ArrayList myIdleListeners = new ArrayList(2); + + private final ArrayList myActivityListeners = new ArrayList(2); + + private final Alarm myIdleRequestsAlarm = new Alarm(); + + private final Alarm myIdleTimeCounterAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + + private long myIdleTime; + + private final Map myListener2Request = new HashMap(); + // IdleListener -> MyFireIdleRequest + + private final IdeKeyEventDispatcher myKeyEventDispatcher = new IdeKeyEventDispatcher(this); + + private final IdeMouseEventDispatcher myMouseEventDispatcher = new IdeMouseEventDispatcher(); + + private final IdePopupManager myPopupManager = new IdePopupManager(); + + + private final ToolkitBugsProcessor myToolkitBugsProcessor = new ToolkitBugsProcessor(); + + private boolean mySuspendMode; + + /** + * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events + *

+ * in the queue. If WINDOW_OPENED event does exists in the queus then we restart the alarm. + */ + + private Component myFocusOwner; + + private final Runnable myExitSuspendModeRunnable = new ExitSuspendModeRunnable(); + + /** + * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED + *

+ * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm. + */ + private final Alarm mySuspendModeAlarm = new Alarm(); + + /** + * Counter of processed events. It is used to assert that data context lives only inside single + *

+ * Swing event. + */ + + private int myEventCount; + + + private boolean myIsInInputEvent = false; + + private AWTEvent myCurrentEvent = null; + + private long myLastActiveTime; + + private WindowManagerEx myWindowManager; + + + private final Set myDispatchers = new LinkedHashSet(); + private final Set myPostprocessors = new LinkedHashSet(); + + private final Set myReady = new HashSet(); + private boolean myKeyboardBusy; + + private static class IdeEventQueueHolder { + private static final IdeEventQueue INSTANCE = new IdeEventQueue(); + } + + public static IdeEventQueue getInstance() { + return IdeEventQueueHolder.INSTANCE; + } + + private IdeEventQueue() { + addIdleTimeCounterRequest(); + final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + + //noinspection HardCodedStringLiteral + keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() { + + public void propertyChange(final PropertyChangeEvent e) { + final Application application = ApplicationManager.getApplication(); + if (application == null) { + + // We can get focus event before application is initialized + return; + } + application.assertIsDispatchThread(); + final Window focusedWindow = keyboardFocusManager.getFocusedWindow(); + final Component focusOwner = keyboardFocusManager.getFocusOwner(); + if (mySuspendMode && focusedWindow != null && focusOwner != null && focusOwner != myFocusOwner && !(focusOwner instanceof Window)) { + exitSuspendMode(); + } + } + }); + + addDispatcher(new WindowsAltSupressor(), null); + } + + + public void setWindowManager(final WindowManagerEx windowManager) { + myWindowManager = windowManager; + } + + + private void addIdleTimeCounterRequest() { + Application application = ApplicationManager.getApplication(); + if (application != null && application.isUnitTestMode()) return; + + myIdleTimeCounterAlarm.cancelAllRequests(); + myLastActiveTime = System.currentTimeMillis(); + myIdleTimeCounterAlarm.addRequest(new Runnable() { + public void run() { + myIdleTime += System.currentTimeMillis() - myLastActiveTime; + addIdleTimeCounterRequest(); + } + }, 20000, ModalityState.NON_MODAL); + } + + + public boolean shouldNotTypeInEditor() { + return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode; + } + + + private void enterSuspendMode() { + mySuspendMode = true; + myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + mySuspendModeAlarm.cancelAllRequests(); + mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750); + } + + + /** + * Exits supend mode and pumps all suspended events. + */ + + private void exitSuspendMode() { + if (shallEnterSuspendMode()) { + + // We have to exit from suspend mode (focus owner changes or alarm is triggered) but + + // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until + + // all WINDOW_OPENED event will be processed. + mySuspendModeAlarm.cancelAllRequests(); + mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 250); + } + else { + + // Now we can pump all suspended events. + mySuspendMode = false; + myFocusOwner = null; // to prevent memory leaks + } + } + + + public void addIdleListener(@NotNull final Runnable runnable, final int timeout) { + LOG.assertTrue(timeout > 0); + synchronized (myLock) { + myIdleListeners.add(runnable); + final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeout); + myListener2Request.put(runnable, request); + myIdleRequestsAlarm.addRequest(request, timeout); + } + } + + + public void removeIdleListener(@NotNull final Runnable runnable) { + synchronized (myLock) { + final boolean wasRemoved = myIdleListeners.remove(runnable); + if (!wasRemoved) { + LOG.error("unknown runnable: " + runnable); + } + final MyFireIdleRequest request = myListener2Request.remove(runnable); + LOG.assertTrue(request != null); + myIdleRequestsAlarm.cancelRequest(request); + } + } + + + public void addActivityListener(@NotNull final Runnable runnable) { + synchronized (myLock) { + myActivityListeners.add(runnable); + } + } + + public void addActivityListener(@NotNull final Runnable runnable, Disposable parentDisposable) { + synchronized (myLock) { + ContainerUtil.add(runnable, myActivityListeners, parentDisposable); + } + } + + + public void removeActivityListener(@NotNull final Runnable runnable) { + synchronized (myLock) { + final boolean wasRemoved = myActivityListeners.remove(runnable); + if (!wasRemoved) { + LOG.error("unknown runnable: " + runnable); + } + } + } + + + public void addDispatcher(final EventDispatcher dispatcher, Disposable parent) { + _addProcessor(dispatcher, parent, myDispatchers); + } + + public void removeDispatcher(EventDispatcher dispatcher) { + myDispatchers.remove(dispatcher); + } + + public boolean containsDispatcher(EventDispatcher dispatcher) { + return myDispatchers.contains(dispatcher); + } + + public void addPostprocessor(EventDispatcher dispatcher, Disposable parent) { + _addProcessor(dispatcher, parent, myPostprocessors); + } + + public void removePostprocessor(EventDispatcher dispatcher) { + myPostprocessors.remove(dispatcher); + } + + private void _addProcessor(final EventDispatcher dispatcher, Disposable parent, Set set) { + set.add(dispatcher); + if (parent != null) { + Disposer.register(parent, new Disposable() { + public void dispose() { + removeDispatcher(dispatcher); + } + }); + } + } + + public int getEventCount() { + return myEventCount; + } + + + public void setEventCount(int evCount) { + myEventCount = evCount; + } + + + public AWTEvent getTrueCurrentEvent() { + return myCurrentEvent; + } + + //[jeka] commented for performance reasons + + /* + + public void postEvent(final AWTEvent e) { + + // [vova] sometime people call SwingUtilities.invokeLater(null). To + + // find such situations we will specially check InvokationEvents + + try { + + if (e instanceof InvocationEvent) { + + //noinspection HardCodedStringLiteral + + final Field field = InvocationEvent.class.getDeclaredField("runnable"); + + field.setAccessible(true); + + final Object runnable = field.get(e); + + if (runnable == null) { + + //noinspection HardCodedStringLiteral + + throw new IllegalStateException("InvocationEvent contains null runnable: " + e); + + } + + } + + } + + catch (final Exception exc) { + + throw new Error(exc); + + } + + super.postEvent(e); + + } + + */ + + public void dispatchEvent(final AWTEvent e) { + boolean wasInputEvent = myIsInInputEvent; + myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent; + AWTEvent oldEvent = myCurrentEvent; + myCurrentEvent = e; + + try { + _dispatchEvent(e); + } + finally { + myIsInInputEvent = wasInputEvent; + myCurrentEvent = oldEvent; + + for (EventDispatcher each : myPostprocessors) { + each.dispatch(e); + } + + if (e instanceof KeyEvent) { + maybeReady(); + } + } + } + + @SuppressWarnings({"ALL"}) + private static String toDebugString(final AWTEvent e) { + if (e instanceof InvocationEvent) { + try { + final Field f = InvocationEvent.class.getDeclaredField("runnable"); + f.setAccessible(true); + Object runnable = f.get(e); + + return "Invoke Later[" + runnable.toString() + "]"; + } + catch (NoSuchFieldException e1) { + } + catch (IllegalAccessException e1) { + } + } + return e.toString(); + } + + + private void _dispatchEvent(final AWTEvent e) { + _dispatchEvent(e, false); + } + + public void _dispatchEvent(AWTEvent e, boolean typeaheadFlushing) { + if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + DnDManagerImpl dndManager = (DnDManagerImpl)DnDManager.getInstance(); + if (dndManager != null) { + dndManager.setLastDropHandler(null); + } + } + + + myEventCount++; + + + if (processAppActivationEvents(e)) return; + + if (!typeaheadFlushing) { + fixStickyFocusedComponents(e); + } + + if (!myPopupManager.isPopupActive()) { + enterSuspendModeIfNeeded(e); + } + + if (e instanceof KeyEvent) { + myKeyboardBusy = e.getID() != KeyEvent.KEY_RELEASED || ((KeyEvent)e).getModifiers() != 0; + } + + if (!typeaheadFlushing && typeAheadDispatchToFocusManager(e)) return; + + if (e instanceof WindowEvent) { + ActivityTracker.getInstance().inc(); + } + + + // Process "idle" and "activity" listeners + if (e instanceof KeyEvent || e instanceof MouseEvent) { + ActivityTracker.getInstance().inc(); + + synchronized (myLock) { + myIdleRequestsAlarm.cancelAllRequests(); + for (Runnable idleListener : myIdleListeners) { + final MyFireIdleRequest request = myListener2Request.get(idleListener); + if (request == null) { + LOG.error("There is no request for " + idleListener); + } + int timeout = request.getTimeout(); + myIdleRequestsAlarm.addRequest(request, timeout, ModalityState.NON_MODAL); + } + if (KeyEvent.KEY_PRESSED == e.getID() || + KeyEvent.KEY_TYPED == e.getID() || + MouseEvent.MOUSE_PRESSED == e.getID() || + MouseEvent.MOUSE_RELEASED == e.getID() || + MouseEvent.MOUSE_CLICKED == e.getID()) { + addIdleTimeCounterRequest(); + for (Runnable activityListener : myActivityListeners) { + activityListener.run(); + } + } + } + } + if (myPopupManager.isPopupActive() && myPopupManager.dispatch(e)) { + return; + } + + for (EventDispatcher eachDispatcher : myDispatchers) { + if (eachDispatcher.dispatch(e)) { + return; + } + } + + if (e instanceof InputMethodEvent) { + if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) { + return; + } + } + if (e instanceof InputEvent && Patches.SPECIAL_WINPUT_METHOD_PROCESSING) { + final InputEvent inputEvent = (InputEvent)e; + if (!inputEvent.getComponent().isShowing()) { + return; + } + } + if (e instanceof ComponentEvent && myWindowManager != null) { + myWindowManager.dispatchComponentEvent((ComponentEvent)e); + } + if (e instanceof KeyEvent) { + if (mySuspendMode || !myKeyEventDispatcher.dispatchKeyEvent((KeyEvent)e)) { + defaultDispatchEvent(e); + } + else { + ((KeyEvent)e).consume(); + defaultDispatchEvent(e); + } + } + else if (e instanceof MouseEvent) { + if (!myMouseEventDispatcher.dispatchMouseEvent((MouseEvent)e)) { + defaultDispatchEvent(e); + } + } + else { + defaultDispatchEvent(e); + } + } + + private static void fixStickyWindow(KeyboardFocusManager mgr, Window wnd, String resetMethod) { + Window showingWindow = wnd; + + if (wnd != null && !wnd.isShowing()) { + while (showingWindow != null) { + if (showingWindow.isShowing()) break; + showingWindow = (Window)showingWindow.getParent(); + } + + if (showingWindow == null) { + final Frame[] allFrames = Frame.getFrames(); + for (Frame each : allFrames) { + if (each.isShowing()) { + showingWindow = each; + break; + } + } + } + + + if (showingWindow != null && showingWindow != wnd) { + final Method setActive = ReflectionUtil.findMethod(KeyboardFocusManager.class.getDeclaredMethods(), resetMethod, Window.class); + if (setActive != null) { + try { + setActive.setAccessible(true); + setActive.invoke(mgr, (Window)showingWindow); + } + catch (Exception exc) { + LOG.info(exc); + } + } + } + } + } + + public void fixStickyFocusedComponents(@Nullable AWTEvent e) { + if (e != null && !(e instanceof InputEvent)) return; + + final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + + if (Registry.is("actionSystem.fixStickyFocusedWindows")) { + fixStickyWindow(mgr, mgr.getActiveWindow(), "setGlobalActiveWindow"); + fixStickyWindow(mgr, mgr.getFocusedWindow(), "setGlobalFocusedWindow"); + } + + if (Registry.is("actionSystem.fixNullFocusedComponent")) { + final Component focusOwner = mgr.getFocusOwner(); + if (focusOwner == null) { + + IdeEventQueue queue = IdeEventQueue.getInstance(); + boolean mouseEventsAhead = e instanceof MouseEvent || + queue.peekEvent(MouseEvent.MOUSE_PRESSED) != null || + queue.peekEvent(MouseEvent.MOUSE_RELEASED) != null || + queue.peekEvent(MouseEvent.MOUSE_CLICKED) != null; + + if (!mouseEventsAhead) { + Window showingWindow = mgr.getActiveWindow(); + if (showingWindow == null) { + Method getNativeFocusOwner = ReflectionUtil.getDeclaredMethod(KeyboardFocusManager.class, "getNativeFocusOwner"); + if (getNativeFocusOwner != null) { + getNativeFocusOwner.setAccessible(true); + try { + Object owner = getNativeFocusOwner.invoke(mgr); + if (owner instanceof Component) { + Component nativeFocusOwner = (Component)owner; + if (nativeFocusOwner instanceof Window) { + showingWindow = (Window)nativeFocusOwner; + } else { + showingWindow = SwingUtilities.getWindowAncestor(nativeFocusOwner); + } + } + } + catch (Exception e1) { + LOG.debug(e1); + } + } + } + if (showingWindow != null) { + final IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(showingWindow); + Runnable requestDefaultFocus = new Runnable() { + public void run() { + if (mgr.getFocusOwner() == null) { + if (getPopupManager().requestDefaultFocus(false)) return; + + final Application app = ApplicationManager.getApplication(); + if (app != null && app.isActive()) { + fm.requestDefaultFocus(false); + } + } + } + }; + if (e != null) { + fm.doWhenFocusSettlesDown(requestDefaultFocus); + } + else { + requestDefaultFocus.run(); + } + } + } + } + } + } + + private void enterSuspendModeIfNeeded(AWTEvent e) { + if (e instanceof KeyEvent) { + if (!mySuspendMode && shallEnterSuspendMode()) { + enterSuspendMode(); + } + } + } + + private boolean shallEnterSuspendMode() { + return peekEvent(WindowEvent.WINDOW_OPENED) != null; + } + + private static boolean processAppActivationEvents(AWTEvent e) { + final Application app = ApplicationManager.getApplication(); + if (!(app instanceof ApplicationImpl)) return false; + + ApplicationImpl appImpl = (ApplicationImpl)app; + + boolean consumed = false; + if (e instanceof WindowEvent) { + WindowEvent we = (WindowEvent)e; + if (we.getID() == WindowEvent.WINDOW_GAINED_FOCUS && we.getWindow() != null) { + if (we.getOppositeWindow() == null && !appImpl.isActive()) { + consumed = appImpl.tryToApplyActivationState(true, we.getWindow()); + } + } + else if (we.getID() == WindowEvent.WINDOW_LOST_FOCUS && we.getWindow() != null) { + if (we.getOppositeWindow() == null && appImpl.isActive()) { + consumed = appImpl.tryToApplyActivationState(false, we.getWindow()); + } + } + } + + return false; + } + + + private void defaultDispatchEvent(final AWTEvent e) { + try { + super.dispatchEvent(e); + } + catch (ProcessCanceledException pce) { + throw pce; + } + catch (Throwable exc) { + if (!myToolkitBugsProcessor.process(exc)) { + LOG.error("Error during dispatching of " + e, exc); + } + } + } + + private static boolean typeAheadDispatchToFocusManager(AWTEvent e) { + if (e instanceof KeyEvent) { + final KeyEvent event = (KeyEvent)e; + if (!event.isConsumed()) { + final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(event.getComponent()); + return focusManager.dispatch(event); + } + } + + return false; + } + + + public void flushQueue() { + while (true) { + AWTEvent event = peekEvent(); + if (event == null) return; + try { + AWTEvent event1 = getNextEvent(); + dispatchEvent(event1); + } + catch (Exception e) { + LOG.error(e); //? + } + } + } + + public void pumpEventsForHierarchy(Component modalComponent, Condition exitCondition) { + AWTEvent event; + do { + try { + event = getNextEvent(); + boolean eventOk = true; + if (event instanceof InputEvent) { + final Object s = event.getSource(); + if (s instanceof Component) { + Component c = (Component)s; + Window modalWindow = SwingUtilities.windowForComponent(modalComponent); + while (c != null && c != modalWindow) c = c.getParent(); + if (c == null) { + eventOk = false; + ((InputEvent)event).consume(); + } + } + } + + if (eventOk) { + dispatchEvent(event); + } + } + catch (Throwable e) { + LOG.error(e); + event = null; + } + } + while (!exitCondition.value(event)); + } + + + public interface EventDispatcher { + boolean dispatch(AWTEvent e); + } + + + private final class MyFireIdleRequest implements Runnable { + private final Runnable myRunnable; + private final int myTimeout; + + + public MyFireIdleRequest(@NotNull Runnable runnable, final int timeout) { + myTimeout = timeout; + myRunnable = runnable; + } + + + public void run() { + myRunnable.run(); + synchronized (myLock) { + if (myIdleListeners.contains(myRunnable)) // do not reschedule if not interested anymore + { + myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL); + } + } + } + + public int getTimeout() { + return myTimeout; + } + } + + + private final class ExitSuspendModeRunnable implements Runnable { + + public void run() { + if (mySuspendMode) { + exitSuspendMode(); + } + } + } + + + public long getIdleTime() { + return myIdleTime; + } + + + public IdePopupManager getPopupManager() { + return myPopupManager; + } + + public IdeKeyEventDispatcher getKeyEventDispatcher() { + return myKeyEventDispatcher; + } + + public void blockNextEvents(final MouseEvent e) { + myMouseEventDispatcher.blockNextEvents(e); + } + + public boolean isSuspendMode() { + return mySuspendMode; + } + + public boolean hasFocusEventsPending() { + return peekEvent(FocusEvent.FOCUS_GAINED) != null || peekEvent(FocusEvent.FOCUS_LOST) != null; + } + + private boolean isReady() { + return !myKeyboardBusy && myKeyEventDispatcher.isReady(); + } + + public void maybeReady() { + flushReady(); + } + + private void flushReady() { + if (myReady.isEmpty() || !isReady()) return; + + Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]); + myReady.clear(); + + for (Runnable each : ready) { + each.run(); + } + } + + public void doWhenReady(final Runnable runnable) { + if (EventQueue.isDispatchThread()) { + myReady.add(runnable); + maybeReady(); + } + else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myReady.add(runnable); + maybeReady(); + } + }); + } + } + + public boolean isPopupActive() { + return myPopupManager.isPopupActive(); + } + + private static class WindowsAltSupressor implements EventDispatcher { + + private boolean myPureAltWasPressed; + private boolean myWaitingForAltRelease; + private boolean myWaiterScheduled; + + private Robot myRobot; + + public boolean dispatch(AWTEvent e) { + boolean dispatch = true; + if (e instanceof KeyEvent) { + KeyEvent ke = ((KeyEvent)e); + boolean pureAlt = ke.getKeyCode() == KeyEvent.VK_ALT && (ke.getModifiers() | KeyEvent.ALT_MASK) == KeyEvent.ALT_MASK; + if (!pureAlt) { + myPureAltWasPressed = false; + myWaitingForAltRelease = false; + myWaiterScheduled = false; + } + else { + Application app = ApplicationManager.getApplication(); + if (app == null || + !SystemInfo.isWindows || + !Registry.is("actionSystem.win.supressAlt") || + !UISettings.getInstance().HIDE_TOOL_STRIPES) { + return !dispatch; + } + + if (ke.getID() == KeyEvent.KEY_PRESSED) { + myPureAltWasPressed = true; + dispatch = !myWaitingForAltRelease; + } + else if (ke.getID() == KeyEvent.KEY_RELEASED) { + if (myWaitingForAltRelease) { + myPureAltWasPressed = false; + myWaitingForAltRelease = false; + myWaiterScheduled = false; + dispatch = false; + } + else { + myWaiterScheduled = true; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + myWaitingForAltRelease = true; + if (myRobot == null) { + myRobot = new Robot(); + } + myRobot.keyPress(KeyEvent.VK_ALT); + myRobot.keyRelease(KeyEvent.VK_ALT); + } + catch (AWTException e1) { + LOG.debug(e1); + } + } + }); + } + } + } + } + + return !dispatch; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java index d832678b6f31..815c3a125f78 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java @@ -1,550 +1,550 @@ -/* - * 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.ide; - -import com.intellij.codeInsight.hint.HintUtil; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.ui.popup.BalloonBuilder; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.util.registry.RegistryValue; -import com.intellij.openapi.util.registry.RegistryValueListener; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.BalloonImpl; -import com.intellij.ui.HintHint; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.util.Alarm; -import com.intellij.util.IJSwingUtilities; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.EmptyBorder; -import javax.swing.text.*; -import javax.swing.text.html.HTML; -import javax.swing.text.html.HTMLEditorKit; -import java.awt.*; -import java.awt.event.AWTEventListener; -import java.awt.event.MouseEvent; - -public class IdeTooltipManager implements ApplicationComponent, AWTEventListener { - - private RegistryValue myIsEnabled; - - private Component myCurrentComponent; - private Component myQueuedComponent; - - private BalloonImpl myCurrentTipUi; - private MouseEvent myCurrentEvent; - private boolean myCurrentTipIsCentered; - - private Runnable myHideRunnable; - - private JBPopupFactory myPopupFactory; - private JEditorPane myTipLabel; - - private boolean myShowDelay = true; - - private Alarm myAlarm = new Alarm(); - - private int myX; - private int myY; - private RegistryValue myMode; - - private IdeTooltip myCurrentTooltip; - private Runnable myShowRequest; - private IdeTooltip myQueuedTooltip; - - - public IdeTooltipManager(JBPopupFactory popupFactory) { - myPopupFactory = popupFactory; - } - - @Override - public void initComponent() { - myMode = Registry.get("ide.tooltip.mode"); - - myIsEnabled = Registry.get("ide.tooltip.callout"); - myIsEnabled.addListener(new RegistryValueListener.Adapter() { - @Override - public void afterValueChanged(RegistryValue value) { - processEnabled(); - } - }, ApplicationManager.getApplication()); - - Toolkit.getDefaultToolkit().addAWTEventListener(this, MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK); - - processEnabled(); - } - - @Override - public void eventDispatched(AWTEvent event) { - if (!myIsEnabled.asBoolean()) return; - - MouseEvent me = (MouseEvent)event; - Component c = me.getComponent(); - if (me.getID() == MouseEvent.MOUSE_ENTERED) { - boolean canShow = true; - if (me.getComponent() != myCurrentComponent) { - canShow = hideCurrent(me); - } - if (canShow) { - maybeShowFor(c, me); - } - } else if (me.getID() == MouseEvent.MOUSE_EXITED) { - if (me.getComponent() == myCurrentComponent || me.getComponent() == myQueuedComponent) { - hideCurrent(me); - } - } else if (me.getID() == MouseEvent.MOUSE_MOVED) { - if (me.getComponent() == myCurrentComponent || me.getComponent() == myQueuedComponent) { - if (myCurrentTipUi != null && myCurrentTipUi.wasFadedIn()) { - if (hideCurrent(me)) { - maybeShowFor(c, me); - } - } else { - if (!myCurrentTipIsCentered) { - myX = me.getX(); - myY = me.getY(); - maybeShowFor(c, me); - } - } - } else if (myCurrentComponent == null && myQueuedComponent == null) { - maybeShowFor(c, me); - } - } else if (me.getID() == MouseEvent.MOUSE_PRESSED) { - if (me.getComponent() == myCurrentComponent) { - hideCurrent(me); - } - } else if (me.getID() == MouseEvent.MOUSE_DRAGGED) { - hideCurrent(me); - } - } - - private void maybeShowFor(Component c, MouseEvent me) { - if (!(c instanceof JComponent)) return; - - JComponent comp = (JComponent)c; - - String tooltipText = comp.getToolTipText(me); - if (tooltipText == null || tooltipText.trim().length() == 0) return; - - queueShow(comp, me, Boolean.TRUE.equals(comp.getClientProperty(UIUtil.CENTER_TOOLTIP))); - } - - private void queueShow(final JComponent c, final MouseEvent me, final boolean toCenter) { - final IdeTooltip tooltip = new IdeTooltip(c, me.getPoint(), null, new Object()) { - @Override - protected boolean beforeShow() { - myCurrentEvent = me; - - if (!c.isShowing()) return false; - - String text = c.getToolTipText(myCurrentEvent); - if (text == null || text.trim().length() == 0) return false; - - JLayeredPane layeredPane = IJSwingUtilities.findParentOfType(c, JLayeredPane.class); - - myTipLabel = initPane(text, new HintHint(me).setAwtTooltip(true), layeredPane); - - setTipComponent(myTipLabel); - return true; - } - }.setToCenter(toCenter); - - show(tooltip, false); - } - - public IdeTooltip show(final IdeTooltip tooltip, boolean now) { - myAlarm.cancelAllRequests(); - - hideCurrent(null); - - myQueuedComponent = tooltip.getComponent(); - myQueuedTooltip = tooltip; - - myShowRequest = new Runnable() { - @Override - public void run() { - if (myShowRequest == null) { - return; - } - - if (myQueuedComponent != tooltip.getComponent() || !tooltip.getComponent().isShowing()) { - hideCurrent(null); - return; - } - - if (tooltip.beforeShow()) { - show(tooltip, null); - } - else { - hideCurrent(null); - } - } - }; - - if (now) { - myShowRequest.run(); - } else { - myAlarm.addRequest(myShowRequest, myShowDelay ? tooltip.getShowDelay() : tooltip.getInitialReshowDelay()); - } - - return tooltip; - } - - private void show(final IdeTooltip tooltip, Runnable beforeShow) { - boolean toCenterX; - boolean toCenterY; - - boolean toCenter = tooltip.isToCenter(); - if (!toCenter && tooltip.isToCenterIfSmall()) { - Dimension size = tooltip.getComponent().getSize(); - toCenterX = size.width < 64; - toCenterY = size.height < 64; - toCenter = toCenterX || toCenterY; - } else { - toCenterX = true; - toCenterY = true; - } - - Point effectivePoint = tooltip.getPoint(); - if (toCenter) { - Rectangle bounds = tooltip.getComponent().getBounds(); - effectivePoint.x = toCenterX ? bounds.width / 2 : effectivePoint.x; - effectivePoint.y = toCenterY ? (bounds.height / 2) : effectivePoint.y; - } - - - if (myCurrentComponent == tooltip.getComponent() && effectivePoint.equals(new Point(myX, myY))) { - return; - } - - Color bg = tooltip.getTextBackground() != null ? tooltip.getTextBackground() : getTextBackground(true); - Color fg = tooltip.getTextForeground() != null ? tooltip.getTextForeground() : getTextForeground(true); - Color border = tooltip.getBorderColor() != null ? tooltip.getBorderColor() : getBorderColor(true); - - BalloonBuilder builder = myPopupFactory.createBalloonBuilder(tooltip.getTipComponent()) - .setPreferredPosition(tooltip.getPreferredPosition()) - .setFillColor(bg) - .setBorderColor(border) - .setAnimationCycle(150) - .setShowCallout(true) - .setCalloutShift(tooltip.getCalloutShift()); - tooltip.getTipComponent().setForeground(fg); - tooltip.getTipComponent().setBorder(new EmptyBorder(1, 3, 2, 3)); - tooltip.getTipComponent().setFont(tooltip.getFont() != null ? tooltip.getFont() : getTextFont(true)); - - - if (beforeShow != null) { - beforeShow.run(); - } - - myCurrentTipUi = (BalloonImpl)builder.createBalloon(); - myCurrentComponent = tooltip.getComponent(); - myX = effectivePoint.x; - myY = effectivePoint.y; - myCurrentTipIsCentered = toCenter; - myCurrentTooltip = tooltip; - myShowRequest = null; - myQueuedComponent = null; - myQueuedTooltip = null; - - myCurrentTipUi.show(new RelativePoint(tooltip.getComponent(), effectivePoint), tooltip.getPreferredPosition()); - myAlarm.addRequest(new Runnable() { - @Override - public void run() { - if (myCurrentTooltip == tooltip && tooltip.canBeDismissedOnTimeout()) { - hideCurrent(null); - } - } - }, tooltip.getDismissDelay()); - } - - - public Color getTextForeground(boolean awtTooltip) { - return useGraphite(awtTooltip) ? Color.white : UIUtil.getToolTipForeground(); - } - - public Color getLinkForeground(boolean awtTooltip) { - return useGraphite(awtTooltip) ? new Color(209, 209, 255) : Color.blue; - } - - public Color getTextBackground(boolean awtTooltip) { - return useGraphite(awtTooltip) ? new Color(100, 100, 100, 230) : UIUtil.getToolTipBackground(); - } - - public String getUlImg(boolean awtTooltip) { - return useGraphite(awtTooltip) ? "/general/mdot-white.png" : "/general/mdot.png"; - } - - public Color getBorderColor(boolean awtTooltip) { - return useGraphite(awtTooltip) ? getTextBackground(awtTooltip).darker() : Color.darkGray; - } - - public boolean isOwnBorderAllowed(boolean awtTooltip) { - return !awtTooltip; - } - - public boolean isOpaqueAllowed(boolean awtTooltip) { - return !awtTooltip; - } - - public Font getTextFont(boolean awtTooltip) { - return UIManager.getFont("ToolTip.font"); - } - - private boolean isUseSystemLook() { - boolean useSystem; - - if ("default".equalsIgnoreCase(myMode.asString())) { - useSystem = !SystemInfo.isMac; - } else if ("system".equalsIgnoreCase(myMode.asString())) { - useSystem = true; - } else if ("graphite".equalsIgnoreCase(myMode.asString())) { - useSystem = false; - } else { - useSystem = false; - } - return useSystem; - } - - private boolean hideCurrent(@Nullable MouseEvent me) { - myShowRequest = null; - myQueuedComponent = null; - myQueuedTooltip = null; - - if (myCurrentTooltip == null) return true; - - if (me != null && myCurrentTipUi != null) { - if (!myCurrentTooltip.canAutohideOn(new TooltipEvent(me, myCurrentTipUi.isInsideBalloon(me)))) { - if (myHideRunnable != null) { - myHideRunnable = null; - } - return false; - } - } - - myHideRunnable = new Runnable() { - @Override - public void run() { - if (myHideRunnable != null) { - hideCurrentNow(); - myHideRunnable = null; - } - } - }; - - if (me != null) { - myAlarm.addRequest(myHideRunnable, Registry.intValue("ide.tooltip.autoDismissDeadZone")); - } else { - myHideRunnable.run(); - myHideRunnable = null; - } - - return true; - } - - private void hideCurrentNow() { - if (myCurrentTipUi != null) { - myCurrentTipUi.hide(); - myCurrentTooltip.onHidden(); - myShowDelay = false; - myAlarm.addRequest(new Runnable() { - @Override - public void run() { - myShowDelay = true; - } - }, Registry.intValue("ide.tooltip.reshowDelay")); - } - - myShowRequest = null; - myCurrentTooltip = null; - myCurrentTipUi = null; - myCurrentComponent = null; - myQueuedComponent = null; - myQueuedTooltip = null; - myCurrentEvent = null; - myCurrentTipIsCentered = false; - myX = -1; - myY = -1; - } - - private void processEnabled() { - if (myIsEnabled.asBoolean()) { - ToolTipManager.sharedInstance().setEnabled(false); - } else { - ToolTipManager.sharedInstance().setEnabled(true); - } - } - - @Override - public void disposeComponent() { - } - - public static IdeTooltipManager getInstance() { - return ApplicationManager.getApplication().getComponent(IdeTooltipManager.class); - } - - private boolean useGraphite(boolean awtHint) { - return !isUseSystemLook() && awtHint; - } - - public void hide(IdeTooltip tooltip) { - if (myCurrentTooltip == tooltip || tooltip == null) { - hideCurrent(null); - } - } - - public void cancelAutoHide() { - myHideRunnable = null; - } - - - public static JEditorPane initPane(@NonNls String text, final HintHint hintHint, @Nullable JLayeredPane layeredPane) { - final Ref prefSize = new Ref(null); - String htmlBody = getHtmlBody(text); - htmlBody = UIUtil.convertSpace2Nbsp(htmlBody); - text = "" + - UIUtil.getCssFontDeclaration(hintHint.getTextFont(), hintHint.getTextForeground(), hintHint.getLinkForeground(), hintHint.getUlImg()) + - "" + - htmlBody + - ""; - - final JEditorPane pane = new JEditorPane() { - @Override - public Dimension getPreferredSize() { - Dimension s = prefSize.get() != null ? new Dimension(prefSize.get()) : super.getPreferredSize(); - Border b = getBorder(); - if (b != null) { - Insets insets = b.getBorderInsets(this); - if (insets != null) { - s.width += insets.left + insets.right; - s.height += insets.top + insets.bottom; - } - } - return s; - } - }; - - final HTMLEditorKit.HTMLFactory factory = new HTMLEditorKit.HTMLFactory() { - @Override - public View create(Element elem) { - AttributeSet attrs = elem.getAttributes(); - Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute); - Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute); - if (o instanceof HTML.Tag) { - HTML.Tag kind = (HTML.Tag)o; - if (kind == HTML.Tag.HR) { - return new CustomHrView(elem, hintHint.getTextForeground()); - } - } - return super.create(elem); - } - }; - - HTMLEditorKit kit = new HTMLEditorKit() { - @Override - public ViewFactory getViewFactory() { - return factory; - } - }; - pane.setEditorKit(kit); - pane.setText(text); - - pane.setCaretPosition(0); - pane.setEditable(false); - - if (hintHint.isOwnBorderAllowed()) { - setBorder(pane); - setColors(pane); - } - else { - pane.setBorder(null); - } - - if (hintHint.isAwtTooltip()) { - Dimension size = layeredPane.getSize(); - int fitWidth = (int)(size.width * 0.8); - Dimension prefSizeOriginal = pane.getPreferredSize(); - if (prefSizeOriginal.width > fitWidth) { - pane.setSize(new Dimension(fitWidth, Integer.MAX_VALUE)); - Dimension fixedWidthSize = pane.getPreferredSize(); - prefSize.set(new Dimension(fitWidth, fixedWidthSize.height)); - } - else { - prefSize.set(new Dimension(prefSizeOriginal)); - } - } - - pane.setOpaque(hintHint.isOpaqueAllowed()); - pane.setBackground(hintHint.getTextBackground()); - - return pane; - } - - public static void setColors(JComponent pane) { - pane.setForeground(Color.black); - pane.setBackground(HintUtil.INFORMATION_COLOR); - pane.setOpaque(true); - } - - public static void setBorder(JComponent pane) { - pane.setBorder( - BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.black), BorderFactory.createEmptyBorder(0, 5, 0, 5))); - } - - public static String getHtmlBody(@NonNls String text) { - String result = text; - if (!text.startsWith("")) { - result = text.replaceAll("\n", "
"); - } - else { - final int bodyIdx = text.indexOf(""); - final int closedBodyIdx = text.indexOf(""); - if (bodyIdx != -1 && closedBodyIdx != -1) { - result = text.substring(bodyIdx + "".length(), closedBodyIdx); - } - else { - text = StringUtil.trimStart(text, "").trim(); - text = StringUtil.trimEnd(text, "").trim(); - text = StringUtil.trimStart(text, "").trim(); - text = StringUtil.trimEnd(text, "").trim(); - result = text; - } - } - - - - return result.replaceAll("", "").replaceAll("", ""); - } - - @NotNull - @Override - public String getComponentName() { - return "IDE Tooltip Manager"; - } - - public boolean isQueuedToShow(IdeTooltip tooltip) { - return Comparing.equal(myQueuedTooltip, tooltip); - } -} +/* + * 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.ide; + +import com.intellij.codeInsight.hint.HintUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.ui.popup.BalloonBuilder; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.registry.RegistryValue; +import com.intellij.openapi.util.registry.RegistryValueListener; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.BalloonImpl; +import com.intellij.ui.HintHint; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.Alarm; +import com.intellij.util.IJSwingUtilities; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.text.*; +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLEditorKit; +import java.awt.*; +import java.awt.event.AWTEventListener; +import java.awt.event.MouseEvent; + +public class IdeTooltipManager implements ApplicationComponent, AWTEventListener { + + private RegistryValue myIsEnabled; + + private Component myCurrentComponent; + private Component myQueuedComponent; + + private BalloonImpl myCurrentTipUi; + private MouseEvent myCurrentEvent; + private boolean myCurrentTipIsCentered; + + private Runnable myHideRunnable; + + private JBPopupFactory myPopupFactory; + private JEditorPane myTipLabel; + + private boolean myShowDelay = true; + + private Alarm myAlarm = new Alarm(); + + private int myX; + private int myY; + private RegistryValue myMode; + + private IdeTooltip myCurrentTooltip; + private Runnable myShowRequest; + private IdeTooltip myQueuedTooltip; + + + public IdeTooltipManager(JBPopupFactory popupFactory) { + myPopupFactory = popupFactory; + } + + @Override + public void initComponent() { + myMode = Registry.get("ide.tooltip.mode"); + + myIsEnabled = Registry.get("ide.tooltip.callout"); + myIsEnabled.addListener(new RegistryValueListener.Adapter() { + @Override + public void afterValueChanged(RegistryValue value) { + processEnabled(); + } + }, ApplicationManager.getApplication()); + + Toolkit.getDefaultToolkit().addAWTEventListener(this, MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK); + + processEnabled(); + } + + @Override + public void eventDispatched(AWTEvent event) { + if (!myIsEnabled.asBoolean()) return; + + MouseEvent me = (MouseEvent)event; + Component c = me.getComponent(); + if (me.getID() == MouseEvent.MOUSE_ENTERED) { + boolean canShow = true; + if (me.getComponent() != myCurrentComponent) { + canShow = hideCurrent(me); + } + if (canShow) { + maybeShowFor(c, me); + } + } else if (me.getID() == MouseEvent.MOUSE_EXITED) { + if (me.getComponent() == myCurrentComponent || me.getComponent() == myQueuedComponent) { + hideCurrent(me); + } + } else if (me.getID() == MouseEvent.MOUSE_MOVED) { + if (me.getComponent() == myCurrentComponent || me.getComponent() == myQueuedComponent) { + if (myCurrentTipUi != null && myCurrentTipUi.wasFadedIn()) { + if (hideCurrent(me)) { + maybeShowFor(c, me); + } + } else { + if (!myCurrentTipIsCentered) { + myX = me.getX(); + myY = me.getY(); + maybeShowFor(c, me); + } + } + } else if (myCurrentComponent == null && myQueuedComponent == null) { + maybeShowFor(c, me); + } + } else if (me.getID() == MouseEvent.MOUSE_PRESSED) { + if (me.getComponent() == myCurrentComponent) { + hideCurrent(me); + } + } else if (me.getID() == MouseEvent.MOUSE_DRAGGED) { + hideCurrent(me); + } + } + + private void maybeShowFor(Component c, MouseEvent me) { + if (!(c instanceof JComponent)) return; + + JComponent comp = (JComponent)c; + + String tooltipText = comp.getToolTipText(me); + if (tooltipText == null || tooltipText.trim().length() == 0) return; + + queueShow(comp, me, Boolean.TRUE.equals(comp.getClientProperty(UIUtil.CENTER_TOOLTIP))); + } + + private void queueShow(final JComponent c, final MouseEvent me, final boolean toCenter) { + final IdeTooltip tooltip = new IdeTooltip(c, me.getPoint(), null, new Object()) { + @Override + protected boolean beforeShow() { + myCurrentEvent = me; + + if (!c.isShowing()) return false; + + String text = c.getToolTipText(myCurrentEvent); + if (text == null || text.trim().length() == 0) return false; + + JLayeredPane layeredPane = IJSwingUtilities.findParentOfType(c, JLayeredPane.class); + + myTipLabel = initPane(text, new HintHint(me).setAwtTooltip(true), layeredPane); + + setTipComponent(myTipLabel); + return true; + } + }.setToCenter(toCenter); + + show(tooltip, false); + } + + public IdeTooltip show(final IdeTooltip tooltip, boolean now) { + myAlarm.cancelAllRequests(); + + hideCurrent(null); + + myQueuedComponent = tooltip.getComponent(); + myQueuedTooltip = tooltip; + + myShowRequest = new Runnable() { + @Override + public void run() { + if (myShowRequest == null) { + return; + } + + if (myQueuedComponent != tooltip.getComponent() || !tooltip.getComponent().isShowing()) { + hideCurrent(null); + return; + } + + if (tooltip.beforeShow()) { + show(tooltip, null); + } + else { + hideCurrent(null); + } + } + }; + + if (now) { + myShowRequest.run(); + } else { + myAlarm.addRequest(myShowRequest, myShowDelay ? tooltip.getShowDelay() : tooltip.getInitialReshowDelay()); + } + + return tooltip; + } + + private void show(final IdeTooltip tooltip, Runnable beforeShow) { + boolean toCenterX; + boolean toCenterY; + + boolean toCenter = tooltip.isToCenter(); + if (!toCenter && tooltip.isToCenterIfSmall()) { + Dimension size = tooltip.getComponent().getSize(); + toCenterX = size.width < 64; + toCenterY = size.height < 64; + toCenter = toCenterX || toCenterY; + } else { + toCenterX = true; + toCenterY = true; + } + + Point effectivePoint = tooltip.getPoint(); + if (toCenter) { + Rectangle bounds = tooltip.getComponent().getBounds(); + effectivePoint.x = toCenterX ? bounds.width / 2 : effectivePoint.x; + effectivePoint.y = toCenterY ? (bounds.height / 2) : effectivePoint.y; + } + + + if (myCurrentComponent == tooltip.getComponent() && effectivePoint.equals(new Point(myX, myY))) { + return; + } + + Color bg = tooltip.getTextBackground() != null ? tooltip.getTextBackground() : getTextBackground(true); + Color fg = tooltip.getTextForeground() != null ? tooltip.getTextForeground() : getTextForeground(true); + Color border = tooltip.getBorderColor() != null ? tooltip.getBorderColor() : getBorderColor(true); + + BalloonBuilder builder = myPopupFactory.createBalloonBuilder(tooltip.getTipComponent()) + .setPreferredPosition(tooltip.getPreferredPosition()) + .setFillColor(bg) + .setBorderColor(border) + .setAnimationCycle(150) + .setShowCallout(true) + .setCalloutShift(tooltip.getCalloutShift()); + tooltip.getTipComponent().setForeground(fg); + tooltip.getTipComponent().setBorder(new EmptyBorder(1, 3, 2, 3)); + tooltip.getTipComponent().setFont(tooltip.getFont() != null ? tooltip.getFont() : getTextFont(true)); + + + if (beforeShow != null) { + beforeShow.run(); + } + + myCurrentTipUi = (BalloonImpl)builder.createBalloon(); + myCurrentComponent = tooltip.getComponent(); + myX = effectivePoint.x; + myY = effectivePoint.y; + myCurrentTipIsCentered = toCenter; + myCurrentTooltip = tooltip; + myShowRequest = null; + myQueuedComponent = null; + myQueuedTooltip = null; + + myCurrentTipUi.show(new RelativePoint(tooltip.getComponent(), effectivePoint), tooltip.getPreferredPosition()); + myAlarm.addRequest(new Runnable() { + @Override + public void run() { + if (myCurrentTooltip == tooltip && tooltip.canBeDismissedOnTimeout()) { + hideCurrent(null); + } + } + }, tooltip.getDismissDelay()); + } + + + public Color getTextForeground(boolean awtTooltip) { + return useGraphite(awtTooltip) ? Color.white : UIUtil.getToolTipForeground(); + } + + public Color getLinkForeground(boolean awtTooltip) { + return useGraphite(awtTooltip) ? new Color(209, 209, 255) : Color.blue; + } + + public Color getTextBackground(boolean awtTooltip) { + return useGraphite(awtTooltip) ? new Color(100, 100, 100, 230) : UIUtil.getToolTipBackground(); + } + + public String getUlImg(boolean awtTooltip) { + return useGraphite(awtTooltip) ? "/general/mdot-white.png" : "/general/mdot.png"; + } + + public Color getBorderColor(boolean awtTooltip) { + return useGraphite(awtTooltip) ? getTextBackground(awtTooltip).darker() : Color.darkGray; + } + + public boolean isOwnBorderAllowed(boolean awtTooltip) { + return !awtTooltip; + } + + public boolean isOpaqueAllowed(boolean awtTooltip) { + return !awtTooltip; + } + + public Font getTextFont(boolean awtTooltip) { + return UIManager.getFont("ToolTip.font"); + } + + private boolean isUseSystemLook() { + boolean useSystem; + + if ("default".equalsIgnoreCase(myMode.asString())) { + useSystem = !SystemInfo.isMac; + } else if ("system".equalsIgnoreCase(myMode.asString())) { + useSystem = true; + } else if ("graphite".equalsIgnoreCase(myMode.asString())) { + useSystem = false; + } else { + useSystem = false; + } + return useSystem; + } + + private boolean hideCurrent(@Nullable MouseEvent me) { + myShowRequest = null; + myQueuedComponent = null; + myQueuedTooltip = null; + + if (myCurrentTooltip == null) return true; + + if (me != null && myCurrentTipUi != null) { + if (!myCurrentTooltip.canAutohideOn(new TooltipEvent(me, myCurrentTipUi.isInsideBalloon(me)))) { + if (myHideRunnable != null) { + myHideRunnable = null; + } + return false; + } + } + + myHideRunnable = new Runnable() { + @Override + public void run() { + if (myHideRunnable != null) { + hideCurrentNow(); + myHideRunnable = null; + } + } + }; + + if (me != null) { + myAlarm.addRequest(myHideRunnable, Registry.intValue("ide.tooltip.autoDismissDeadZone")); + } else { + myHideRunnable.run(); + myHideRunnable = null; + } + + return true; + } + + private void hideCurrentNow() { + if (myCurrentTipUi != null) { + myCurrentTipUi.hide(); + myCurrentTooltip.onHidden(); + myShowDelay = false; + myAlarm.addRequest(new Runnable() { + @Override + public void run() { + myShowDelay = true; + } + }, Registry.intValue("ide.tooltip.reshowDelay")); + } + + myShowRequest = null; + myCurrentTooltip = null; + myCurrentTipUi = null; + myCurrentComponent = null; + myQueuedComponent = null; + myQueuedTooltip = null; + myCurrentEvent = null; + myCurrentTipIsCentered = false; + myX = -1; + myY = -1; + } + + private void processEnabled() { + if (myIsEnabled.asBoolean()) { + ToolTipManager.sharedInstance().setEnabled(false); + } else { + ToolTipManager.sharedInstance().setEnabled(true); + } + } + + @Override + public void disposeComponent() { + } + + public static IdeTooltipManager getInstance() { + return ApplicationManager.getApplication().getComponent(IdeTooltipManager.class); + } + + private boolean useGraphite(boolean awtHint) { + return !isUseSystemLook() && awtHint; + } + + public void hide(IdeTooltip tooltip) { + if (myCurrentTooltip == tooltip || tooltip == null) { + hideCurrent(null); + } + } + + public void cancelAutoHide() { + myHideRunnable = null; + } + + + public static JEditorPane initPane(@NonNls String text, final HintHint hintHint, @Nullable JLayeredPane layeredPane) { + final Ref prefSize = new Ref(null); + String htmlBody = getHtmlBody(text); + htmlBody = UIUtil.convertSpace2Nbsp(htmlBody); + text = "" + + UIUtil.getCssFontDeclaration(hintHint.getTextFont(), hintHint.getTextForeground(), hintHint.getLinkForeground(), hintHint.getUlImg()) + + "" + + htmlBody + + ""; + + final JEditorPane pane = new JEditorPane() { + @Override + public Dimension getPreferredSize() { + Dimension s = prefSize.get() != null ? new Dimension(prefSize.get()) : super.getPreferredSize(); + Border b = getBorder(); + if (b != null) { + Insets insets = b.getBorderInsets(this); + if (insets != null) { + s.width += insets.left + insets.right; + s.height += insets.top + insets.bottom; + } + } + return s; + } + }; + + final HTMLEditorKit.HTMLFactory factory = new HTMLEditorKit.HTMLFactory() { + @Override + public View create(Element elem) { + AttributeSet attrs = elem.getAttributes(); + Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute); + Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute); + if (o instanceof HTML.Tag) { + HTML.Tag kind = (HTML.Tag)o; + if (kind == HTML.Tag.HR) { + return new CustomHrView(elem, hintHint.getTextForeground()); + } + } + return super.create(elem); + } + }; + + HTMLEditorKit kit = new HTMLEditorKit() { + @Override + public ViewFactory getViewFactory() { + return factory; + } + }; + pane.setEditorKit(kit); + pane.setText(text); + + pane.setCaretPosition(0); + pane.setEditable(false); + + if (hintHint.isOwnBorderAllowed()) { + setBorder(pane); + setColors(pane); + } + else { + pane.setBorder(null); + } + + if (hintHint.isAwtTooltip()) { + Dimension size = layeredPane.getSize(); + int fitWidth = (int)(size.width * 0.8); + Dimension prefSizeOriginal = pane.getPreferredSize(); + if (prefSizeOriginal.width > fitWidth) { + pane.setSize(new Dimension(fitWidth, Integer.MAX_VALUE)); + Dimension fixedWidthSize = pane.getPreferredSize(); + prefSize.set(new Dimension(fitWidth, fixedWidthSize.height)); + } + else { + prefSize.set(new Dimension(prefSizeOriginal)); + } + } + + pane.setOpaque(hintHint.isOpaqueAllowed()); + pane.setBackground(hintHint.getTextBackground()); + + return pane; + } + + public static void setColors(JComponent pane) { + pane.setForeground(Color.black); + pane.setBackground(HintUtil.INFORMATION_COLOR); + pane.setOpaque(true); + } + + public static void setBorder(JComponent pane) { + pane.setBorder( + BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.black), BorderFactory.createEmptyBorder(0, 5, 0, 5))); + } + + public static String getHtmlBody(@NonNls String text) { + String result = text; + if (!text.startsWith("")) { + result = text.replaceAll("\n", "
"); + } + else { + final int bodyIdx = text.indexOf(""); + final int closedBodyIdx = text.indexOf(""); + if (bodyIdx != -1 && closedBodyIdx != -1) { + result = text.substring(bodyIdx + "".length(), closedBodyIdx); + } + else { + text = StringUtil.trimStart(text, "").trim(); + text = StringUtil.trimEnd(text, "").trim(); + text = StringUtil.trimStart(text, "").trim(); + text = StringUtil.trimEnd(text, "").trim(); + result = text; + } + } + + + + return result.replaceAll("", "").replaceAll("", ""); + } + + @NotNull + @Override + public String getComponentName() { + return "IDE Tooltip Manager"; + } + + public boolean isQueuedToShow(IdeTooltip tooltip) { + return Comparing.equal(myQueuedTooltip, tooltip); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/SaveAndSyncHandler.java b/platform/platform-impl/src/com/intellij/ide/SaveAndSyncHandler.java index 2d58182f6d77..480ce2036384 100644 --- a/platform/platform-impl/src/com/intellij/ide/SaveAndSyncHandler.java +++ b/platform/platform-impl/src/com/intellij/ide/SaveAndSyncHandler.java @@ -1,175 +1,175 @@ -/* - * 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.ide; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.application.impl.LaterInvocator; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.RefreshQueue; -import com.intellij.openapi.vfs.newvfs.RefreshSession; -import org.jetbrains.annotations.NotNull; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -/** - * @author Anton Katilin - * @author Vladimir Kondratyev - */ -public class SaveAndSyncHandler implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.SaveAndSyncHandler"); - private final Runnable myIdleListener; - private final PropertyChangeListener myGeneralSettingsListener; - private final ProgressManager myProgressManager; - - public SaveAndSyncHandler(final FrameStateManager frameStateManager, - final FileDocumentManager fileDocumentManager, - final GeneralSettings generalSettings, - final ProgressManager progressManager) { - myProgressManager = progressManager; - - myIdleListener = new Runnable() { - public void run() { - if (generalSettings.isAutoSaveIfInactive() && canSyncOrSave()) { - fileDocumentManager.saveAllDocuments(); - } - } - }; - - - IdeEventQueue.getInstance().addIdleListener( - myIdleListener, - generalSettings.getInactiveTimeout() * 1000 - ); - - myGeneralSettingsListener = new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent e) { - if (GeneralSettings.PROP_INACTIVE_TIMEOUT.equals(e.getPropertyName())) { - IdeEventQueue eventQueue = IdeEventQueue.getInstance(); - eventQueue.removeIdleListener(myIdleListener); - Integer timeout = (Integer)e.getNewValue(); - eventQueue.addIdleListener(myIdleListener, timeout.intValue() * 1000); - } - } - }; - generalSettings.addPropertyChangeListener(myGeneralSettingsListener); - - frameStateManager.addListener(new FrameStateListener() { - public void onFrameDeactivated() { - if (canSyncOrSave()) { - saveProjectsAndDocuments(); - } - } - - public void onFrameActivated() { - refreshFiles(); - } - }); - - } - - @NotNull - public String getComponentName() { - return "SaveAndSyncHandler"; - } - - public void initComponent() { - } - - public void disposeComponent() { - GeneralSettings.getInstance().removePropertyChangeListener(myGeneralSettingsListener); - IdeEventQueue.getInstance().removeIdleListener(myIdleListener); - } - - private boolean canSyncOrSave() { - return !LaterInvocator.isInModalContext() && !myProgressManager.hasModalProgressIndicator(); - } - - // made public for tests - public static void saveProjectsAndDocuments() { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: save()"); - } - if (ApplicationManager.getApplication().isDisposed()) return; - - if (GeneralSettings.getInstance().isSaveOnFrameDeactivation()) { - FileDocumentManager.getInstance().saveAllDocuments(); - - Project[] openProjects = ProjectManagerEx.getInstanceEx().getOpenProjects(); - for (Project project : openProjects) { - if (LOG.isDebugEnabled()) { - LOG.debug("save project: " + project); - } - project.save(); - } - if (LOG.isDebugEnabled()) { - LOG.debug("save application settings"); - } - ApplicationManagerEx.getApplicationEx().saveSettings(); - if (LOG.isDebugEnabled()) { - LOG.debug("exit: save()"); - } - } - } - - private void refreshFiles() { - if (ApplicationManager.getApplication().isDisposed()) return; - if (LOG.isDebugEnabled()) { - LOG.debug("enter: synchronize()"); - } - - if (canSyncOrSave()) { - refreshOpenFiles(); - } - - if (GeneralSettings.getInstance().isSyncOnFrameActivation()) { - if (LOG.isDebugEnabled()) { - LOG.debug("refresh VFS"); - } - VirtualFileManager.getInstance().refresh(true); - } - - if (LOG.isDebugEnabled()) { - LOG.debug("exit: synchronize()"); - } - } - - public static void refreshOpenFiles() { - // Refresh open files synchronously so it doesn't wait for potentially longish refresh request in the queue to finish - final RefreshSession session = RefreshQueue.getInstance().createSession(false, false, null); - - for (Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) { - VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles(); - for (VirtualFile file : files) { - if (file instanceof NewVirtualFile) { - session.addFile(file); - } - } - } - - session.launch(); - } +/* + * 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.ide; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.application.impl.LaterInvocator; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.RefreshQueue; +import com.intellij.openapi.vfs.newvfs.RefreshSession; +import org.jetbrains.annotations.NotNull; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * @author Anton Katilin + * @author Vladimir Kondratyev + */ +public class SaveAndSyncHandler implements ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.SaveAndSyncHandler"); + private final Runnable myIdleListener; + private final PropertyChangeListener myGeneralSettingsListener; + private final ProgressManager myProgressManager; + + public SaveAndSyncHandler(final FrameStateManager frameStateManager, + final FileDocumentManager fileDocumentManager, + final GeneralSettings generalSettings, + final ProgressManager progressManager) { + myProgressManager = progressManager; + + myIdleListener = new Runnable() { + public void run() { + if (generalSettings.isAutoSaveIfInactive() && canSyncOrSave()) { + fileDocumentManager.saveAllDocuments(); + } + } + }; + + + IdeEventQueue.getInstance().addIdleListener( + myIdleListener, + generalSettings.getInactiveTimeout() * 1000 + ); + + myGeneralSettingsListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + if (GeneralSettings.PROP_INACTIVE_TIMEOUT.equals(e.getPropertyName())) { + IdeEventQueue eventQueue = IdeEventQueue.getInstance(); + eventQueue.removeIdleListener(myIdleListener); + Integer timeout = (Integer)e.getNewValue(); + eventQueue.addIdleListener(myIdleListener, timeout.intValue() * 1000); + } + } + }; + generalSettings.addPropertyChangeListener(myGeneralSettingsListener); + + frameStateManager.addListener(new FrameStateListener() { + public void onFrameDeactivated() { + if (canSyncOrSave()) { + saveProjectsAndDocuments(); + } + } + + public void onFrameActivated() { + refreshFiles(); + } + }); + + } + + @NotNull + public String getComponentName() { + return "SaveAndSyncHandler"; + } + + public void initComponent() { + } + + public void disposeComponent() { + GeneralSettings.getInstance().removePropertyChangeListener(myGeneralSettingsListener); + IdeEventQueue.getInstance().removeIdleListener(myIdleListener); + } + + private boolean canSyncOrSave() { + return !LaterInvocator.isInModalContext() && !myProgressManager.hasModalProgressIndicator(); + } + + // made public for tests + public static void saveProjectsAndDocuments() { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: save()"); + } + if (ApplicationManager.getApplication().isDisposed()) return; + + if (GeneralSettings.getInstance().isSaveOnFrameDeactivation()) { + FileDocumentManager.getInstance().saveAllDocuments(); + + Project[] openProjects = ProjectManagerEx.getInstanceEx().getOpenProjects(); + for (Project project : openProjects) { + if (LOG.isDebugEnabled()) { + LOG.debug("save project: " + project); + } + project.save(); + } + if (LOG.isDebugEnabled()) { + LOG.debug("save application settings"); + } + ApplicationManagerEx.getApplicationEx().saveSettings(); + if (LOG.isDebugEnabled()) { + LOG.debug("exit: save()"); + } + } + } + + private void refreshFiles() { + if (ApplicationManager.getApplication().isDisposed()) return; + if (LOG.isDebugEnabled()) { + LOG.debug("enter: synchronize()"); + } + + if (canSyncOrSave()) { + refreshOpenFiles(); + } + + if (GeneralSettings.getInstance().isSyncOnFrameActivation()) { + if (LOG.isDebugEnabled()) { + LOG.debug("refresh VFS"); + } + VirtualFileManager.getInstance().refresh(true); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("exit: synchronize()"); + } + } + + public static void refreshOpenFiles() { + // Refresh open files synchronously so it doesn't wait for potentially longish refresh request in the queue to finish + final RefreshSession session = RefreshQueue.getInstance().createSession(false, false, null); + + for (Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) { + VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles(); + for (VirtualFile file : files) { + if (file instanceof NewVirtualFile) { + session.addFile(file); + } + } + } + + session.launch(); + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/actions/BaseNavigateToSourceAction.java b/platform/platform-impl/src/com/intellij/ide/actions/BaseNavigateToSourceAction.java index aeec39e2c21b..14ba0980674e 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/BaseNavigateToSourceAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/BaseNavigateToSourceAction.java @@ -1,77 +1,77 @@ -/* - * 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.ide.actions; - -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.project.DumbAware; -import com.intellij.pom.Navigatable; -import com.intellij.pom.NavigatableWithText; -import com.intellij.util.OpenSourceUtil; -import org.jetbrains.annotations.Nullable; - -public abstract class BaseNavigateToSourceAction extends AnAction implements DumbAware { - private final boolean myFocusEditor; - - protected BaseNavigateToSourceAction(boolean focusEditor) { - myFocusEditor = focusEditor; - } - - public void actionPerformed(AnActionEvent e) { - DataContext dataContext = e.getDataContext(); - OpenSourceUtil.navigate(myFocusEditor, getNavigatables(dataContext)); - } - - - public void update(AnActionEvent event) { - DataContext dataContext = event.getDataContext(); - final Navigatable target = getTarget(dataContext); - boolean enabled = target != null; - if (ActionPlaces.isPopupPlace(event.getPlace())) { - event.getPresentation().setVisible(enabled); - } - else { - event.getPresentation().setEnabled(enabled); - } - if (target != null && target instanceof NavigatableWithText) { - final String navigateActionText = ((NavigatableWithText)target).getNavigateActionText(myFocusEditor); - if (navigateActionText != null) { - event.getPresentation().setText(navigateActionText); - } - else { - event.getPresentation().setText(getTemplatePresentation().getText()); - } - } - else { - event.getPresentation().setText(getTemplatePresentation().getText()); - } - } - - @Nullable - private Navigatable getTarget(final DataContext dataContext) { - Navigatable[] navigatables = getNavigatables(dataContext); - if (navigatables != null) { - for (Navigatable navigatable : navigatables) { - if (navigatable.canNavigate()) return navigatable; - } - } - return null; - } - - @Nullable - protected Navigatable[] getNavigatables(final DataContext dataContext) { - return PlatformDataKeys.NAVIGATABLE_ARRAY.getData(dataContext); - } -} +/* + * 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.ide.actions; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; +import com.intellij.pom.Navigatable; +import com.intellij.pom.NavigatableWithText; +import com.intellij.util.OpenSourceUtil; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseNavigateToSourceAction extends AnAction implements DumbAware { + private final boolean myFocusEditor; + + protected BaseNavigateToSourceAction(boolean focusEditor) { + myFocusEditor = focusEditor; + } + + public void actionPerformed(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + OpenSourceUtil.navigate(myFocusEditor, getNavigatables(dataContext)); + } + + + public void update(AnActionEvent event) { + DataContext dataContext = event.getDataContext(); + final Navigatable target = getTarget(dataContext); + boolean enabled = target != null; + if (ActionPlaces.isPopupPlace(event.getPlace())) { + event.getPresentation().setVisible(enabled); + } + else { + event.getPresentation().setEnabled(enabled); + } + if (target != null && target instanceof NavigatableWithText) { + final String navigateActionText = ((NavigatableWithText)target).getNavigateActionText(myFocusEditor); + if (navigateActionText != null) { + event.getPresentation().setText(navigateActionText); + } + else { + event.getPresentation().setText(getTemplatePresentation().getText()); + } + } + else { + event.getPresentation().setText(getTemplatePresentation().getText()); + } + } + + @Nullable + private Navigatable getTarget(final DataContext dataContext) { + Navigatable[] navigatables = getNavigatables(dataContext); + if (navigatables != null) { + for (Navigatable navigatable : navigatables) { + if (navigatable.canNavigate()) return navigatable; + } + } + return null; + } + + @Nullable + protected Navigatable[] getNavigatables(final DataContext dataContext) { + return PlatformDataKeys.NAVIGATABLE_ARRAY.getData(dataContext); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ChooseComponentsToExportDialog.java b/platform/platform-impl/src/com/intellij/ide/actions/ChooseComponentsToExportDialog.java index 48bd4264c15e..7874189358ca 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ChooseComponentsToExportDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/ChooseComponentsToExportDialog.java @@ -1,223 +1,223 @@ -/* - * 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.ide.actions; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.util.ElementsChooser; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.components.ExportableComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.VerticalFlowLayout; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.FieldPanel; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.util.*; -import java.util.List; - -public class ChooseComponentsToExportDialog extends DialogWrapper { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.ChooseComponentsToExportDialog"); - - private final ElementsChooser myChooser; - private final FieldPanel myPathPanel; - @NonNls - public static final String DEFAULT_PATH = FileUtil.toSystemDependentName(PathManager.getConfigPath()+"/"+"settings.jar"); - private final boolean myShowFilePath; - private final String myDescription; - - public ChooseComponentsToExportDialog(List components, - Map> fileToComponents, - boolean showFilePath, final String title, String description) { - super(false); - myDescription = description; - myShowFilePath = showFilePath; - Map componentToContainingListElement = new LinkedHashMap(); - - for (ExportableComponent component : components) { - if (!addToExistingListElement(component, componentToContainingListElement, fileToComponents)) { - ComponentElementProperties componentElementProperties = new ComponentElementProperties(); - componentElementProperties.addComponent(component); - - componentToContainingListElement.put(component, componentElementProperties); - } - } - final Set componentElementProperties = new LinkedHashSet(componentToContainingListElement.values()); - myChooser = new ElementsChooser(true); - myChooser.setColorUnmarkedElements(false); - for (final ComponentElementProperties componentElementProperty : componentElementProperties) { - myChooser.addElement(componentElementProperty, true, componentElementProperty); - } - - final ActionListener browseAction = new ActionListener() { - public void actionPerformed(ActionEvent e) { - String oldPath = myPathPanel.getText(); - String path = chooseSettingsFile(oldPath, getWindow(), IdeBundle.message("title.export.file.location"), - IdeBundle.message("prompt.choose.export.settings.file.path")); - if (path == null) return; - myPathPanel.setText(FileUtil.toSystemDependentName(path)); - } - }; - - myPathPanel = new FieldPanel(IdeBundle.message("editbox.export.settings.to"), null, browseAction, null); - - String exportPath = PropertiesComponent.getInstance().getOrInit("export.settings.path", DEFAULT_PATH); - myPathPanel.setText(exportPath); - - setTitle(title); - init(); - } - - @Override - protected void doOKAction() { - PropertiesComponent.getInstance().setValue("export.settings.path", myPathPanel.getText()); - super.doOKAction(); - } - - private static boolean addToExistingListElement(ExportableComponent component, - Map componentToContainingListElement, - Map> fileToComponents) { - final File[] exportFiles = component.getExportFiles(); - File file = null; - for (File exportFile : exportFiles) { - final Set tiedComponents = fileToComponents.get(exportFile); - - for (final ExportableComponent tiedComponent : tiedComponents) { - if (tiedComponent == component) continue; - final ComponentElementProperties elementProperties = componentToContainingListElement.get(tiedComponent); - if (elementProperties != null && !exportFile.equals(file)) { - LOG.assertTrue(file == null, "Component " + component + " serialize itself into " + file + " and " + exportFile); - // found - elementProperties.addComponent(component); - componentToContainingListElement.put(component, elementProperties); - file = exportFile; - } - } - } - return file != null; - } - - @Nullable - public static String chooseSettingsFile(String oldPath, Component parent, final String title, final String description) { - FileChooserDescriptor chooserDescriptor; - chooserDescriptor = new FileChooserDescriptor(true, true, true, true, false, false); - chooserDescriptor.setDescription(description); - chooserDescriptor.setHideIgnored(false); - chooserDescriptor.setTitle(title); - - VirtualFile initialDir; - if (oldPath != null) { - final File oldFile = new File(oldPath); - initialDir = LocalFileSystem.getInstance().findFileByIoFile(oldFile); - if (initialDir == null && oldFile.getParentFile() != null) { - initialDir = LocalFileSystem.getInstance().findFileByIoFile(oldFile.getParentFile()); - } - } - else { - initialDir = null; - } - final VirtualFile[] files = FileChooser.chooseFiles(parent, chooserDescriptor, initialDir); - if (files.length == 0) { - return null; - } - VirtualFile file = files[0]; - String path; - if (file.isDirectory()) { - String defaultName = new File(DEFAULT_PATH).getName(); - path = file.getPath() + "/" + defaultName; - } - else { - path = file.getPath(); - } - return path; - } - - public JComponent getPreferredFocusedComponent() { - return myPathPanel.getTextField(); - } - - protected JComponent createNorthPanel() { - return new JLabel(myDescription); - } - - protected JComponent createCenterPanel() { - return myChooser; - } - - protected JComponent createSouthPanel() { - final JComponent buttons = super.createSouthPanel(); - if (!myShowFilePath) return buttons; - final JPanel panel = new JPanel(new VerticalFlowLayout()); - panel.add(myPathPanel); - panel.add(buttons); - return panel; - } - - Set getExportableComponents() { - final List markedElements = myChooser.getMarkedElements(); - final Set components = new HashSet(); - for (ComponentElementProperties elementProperties : markedElements) { - components.addAll(elementProperties.myComponents); - } - return components; - } - - private static class ComponentElementProperties implements ElementsChooser.ElementProperties { - private final Set myComponents = new HashSet(); - - private boolean addComponent(ExportableComponent component) { - return myComponents.add(component); - } - - @Nullable - public Icon getIcon() { - return null; - } - - @Nullable - public Color getColor() { - return null; - } - - public String toString() { - String result = ""; - - for (final ExportableComponent component : myComponents) { - result += (result.length() == 0 ? "" : ", ") + component.getPresentableName(); - } - return result; - } - } - - File getExportFile() { - return new File(myPathPanel.getText()); - } - - protected String getDimensionServiceKey() { - return "#com.intellij.ide.actions.ChooseComponentsToExportDialog"; - } -} +/* + * 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.ide.actions; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.util.ElementsChooser; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.components.ExportableComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.VerticalFlowLayout; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.FieldPanel; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.*; +import java.util.List; + +public class ChooseComponentsToExportDialog extends DialogWrapper { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.ChooseComponentsToExportDialog"); + + private final ElementsChooser myChooser; + private final FieldPanel myPathPanel; + @NonNls + public static final String DEFAULT_PATH = FileUtil.toSystemDependentName(PathManager.getConfigPath()+"/"+"settings.jar"); + private final boolean myShowFilePath; + private final String myDescription; + + public ChooseComponentsToExportDialog(List components, + Map> fileToComponents, + boolean showFilePath, final String title, String description) { + super(false); + myDescription = description; + myShowFilePath = showFilePath; + Map componentToContainingListElement = new LinkedHashMap(); + + for (ExportableComponent component : components) { + if (!addToExistingListElement(component, componentToContainingListElement, fileToComponents)) { + ComponentElementProperties componentElementProperties = new ComponentElementProperties(); + componentElementProperties.addComponent(component); + + componentToContainingListElement.put(component, componentElementProperties); + } + } + final Set componentElementProperties = new LinkedHashSet(componentToContainingListElement.values()); + myChooser = new ElementsChooser(true); + myChooser.setColorUnmarkedElements(false); + for (final ComponentElementProperties componentElementProperty : componentElementProperties) { + myChooser.addElement(componentElementProperty, true, componentElementProperty); + } + + final ActionListener browseAction = new ActionListener() { + public void actionPerformed(ActionEvent e) { + String oldPath = myPathPanel.getText(); + String path = chooseSettingsFile(oldPath, getWindow(), IdeBundle.message("title.export.file.location"), + IdeBundle.message("prompt.choose.export.settings.file.path")); + if (path == null) return; + myPathPanel.setText(FileUtil.toSystemDependentName(path)); + } + }; + + myPathPanel = new FieldPanel(IdeBundle.message("editbox.export.settings.to"), null, browseAction, null); + + String exportPath = PropertiesComponent.getInstance().getOrInit("export.settings.path", DEFAULT_PATH); + myPathPanel.setText(exportPath); + + setTitle(title); + init(); + } + + @Override + protected void doOKAction() { + PropertiesComponent.getInstance().setValue("export.settings.path", myPathPanel.getText()); + super.doOKAction(); + } + + private static boolean addToExistingListElement(ExportableComponent component, + Map componentToContainingListElement, + Map> fileToComponents) { + final File[] exportFiles = component.getExportFiles(); + File file = null; + for (File exportFile : exportFiles) { + final Set tiedComponents = fileToComponents.get(exportFile); + + for (final ExportableComponent tiedComponent : tiedComponents) { + if (tiedComponent == component) continue; + final ComponentElementProperties elementProperties = componentToContainingListElement.get(tiedComponent); + if (elementProperties != null && !exportFile.equals(file)) { + LOG.assertTrue(file == null, "Component " + component + " serialize itself into " + file + " and " + exportFile); + // found + elementProperties.addComponent(component); + componentToContainingListElement.put(component, elementProperties); + file = exportFile; + } + } + } + return file != null; + } + + @Nullable + public static String chooseSettingsFile(String oldPath, Component parent, final String title, final String description) { + FileChooserDescriptor chooserDescriptor; + chooserDescriptor = new FileChooserDescriptor(true, true, true, true, false, false); + chooserDescriptor.setDescription(description); + chooserDescriptor.setHideIgnored(false); + chooserDescriptor.setTitle(title); + + VirtualFile initialDir; + if (oldPath != null) { + final File oldFile = new File(oldPath); + initialDir = LocalFileSystem.getInstance().findFileByIoFile(oldFile); + if (initialDir == null && oldFile.getParentFile() != null) { + initialDir = LocalFileSystem.getInstance().findFileByIoFile(oldFile.getParentFile()); + } + } + else { + initialDir = null; + } + final VirtualFile[] files = FileChooser.chooseFiles(parent, chooserDescriptor, initialDir); + if (files.length == 0) { + return null; + } + VirtualFile file = files[0]; + String path; + if (file.isDirectory()) { + String defaultName = new File(DEFAULT_PATH).getName(); + path = file.getPath() + "/" + defaultName; + } + else { + path = file.getPath(); + } + return path; + } + + public JComponent getPreferredFocusedComponent() { + return myPathPanel.getTextField(); + } + + protected JComponent createNorthPanel() { + return new JLabel(myDescription); + } + + protected JComponent createCenterPanel() { + return myChooser; + } + + protected JComponent createSouthPanel() { + final JComponent buttons = super.createSouthPanel(); + if (!myShowFilePath) return buttons; + final JPanel panel = new JPanel(new VerticalFlowLayout()); + panel.add(myPathPanel); + panel.add(buttons); + return panel; + } + + Set getExportableComponents() { + final List markedElements = myChooser.getMarkedElements(); + final Set components = new HashSet(); + for (ComponentElementProperties elementProperties : markedElements) { + components.addAll(elementProperties.myComponents); + } + return components; + } + + private static class ComponentElementProperties implements ElementsChooser.ElementProperties { + private final Set myComponents = new HashSet(); + + private boolean addComponent(ExportableComponent component) { + return myComponents.add(component); + } + + @Nullable + public Icon getIcon() { + return null; + } + + @Nullable + public Color getColor() { + return null; + } + + public String toString() { + String result = ""; + + for (final ExportableComponent component : myComponents) { + result += (result.length() == 0 ? "" : ", ") + component.getPresentableName(); + } + return result; + } + } + + File getExportFile() { + return new File(myPathPanel.getText()); + } + + protected String getDimensionServiceKey() { + return "#com.intellij.ide.actions.ChooseComponentsToExportDialog"; + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java b/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java index 874cf0c4bb9d..7800e7a6a1c2 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java @@ -1,44 +1,44 @@ -/* - * 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.ide.actions; - -import com.intellij.ide.RecentProjectsManagerBase; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.DumbAware; - -/** - * - */ -public class CloseProjectAction extends AnAction implements DumbAware { - public void actionPerformed(AnActionEvent e) { - Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); - assert project != null; - - ProjectUtil.closeAndDispose(project); - RecentProjectsManagerBase.getInstance().updateLastProjectPath(); - } - - public void update(AnActionEvent event){ - Presentation presentation = event.getPresentation(); - Project project = PlatformDataKeys.PROJECT.getData(event.getDataContext()); - presentation.setEnabled(project != null); - } -} +/* + * 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.ide.actions; + +import com.intellij.ide.RecentProjectsManagerBase; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.DumbAware; + +/** + * + */ +public class CloseProjectAction extends AnAction implements DumbAware { + public void actionPerformed(AnActionEvent e) { + Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + assert project != null; + + ProjectUtil.closeAndDispose(project); + RecentProjectsManagerBase.getInstance().updateLastProjectPath(); + } + + public void update(AnActionEvent event){ + Presentation presentation = event.getPresentation(); + Project project = PlatformDataKeys.PROJECT.getData(event.getDataContext()); + presentation.setEnabled(project != null); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/actions/SaveAsDirectoryBasedFormatAction.java b/platform/platform-impl/src/com/intellij/ide/actions/SaveAsDirectoryBasedFormatAction.java index ae211c27dd44..51b0bd5edd4a 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/SaveAsDirectoryBasedFormatAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/SaveAsDirectoryBasedFormatAction.java @@ -1,89 +1,89 @@ -/* - * 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.ide.actions; - -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.components.StorageScheme; -import com.intellij.openapi.components.impl.stores.IProjectStore; -import com.intellij.openapi.components.impl.stores.StateStorageManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ex.ProjectEx; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; - -import java.io.File; - -/** - * @author spleaner - */ -public class SaveAsDirectoryBasedFormatAction extends AnAction implements DumbAware { - - public void actionPerformed(AnActionEvent e) { - final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); - if (project instanceof ProjectEx) { - final IProjectStore projectStore = ((ProjectEx)project).getStateStore(); - if (StorageScheme.DIRECTORY_BASED != projectStore.getStorageScheme()) { - final int result = Messages.showOkCancelDialog(project, - "Project will be saved and reopened in new Directory-Based format.\nAre you sure you want to continue?", - "Save project to Directory-Based format", Messages.getWarningIcon()); - if (result == 0) { - final VirtualFile baseDir = project.getBaseDir(); - assert baseDir != null; - - File ideaDir = new File(baseDir.getPath(), ProjectEx.DIRECTORY_STORE_FOLDER + File.separatorChar); - final boolean ok = (ideaDir.exists() && ideaDir.isDirectory()) || ideaDir.mkdirs(); - if (ok) { - LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ideaDir); - - - final StateStorageManager storageManager = projectStore.getStateStorageManager(); - for (String file : storageManager.getStorageFileNames()) { - storageManager.clearStateStorage(file); - } - - projectStore.setProjectFilePath(baseDir.getPath()); - project.save(); - ProjectUtil.closeAndDispose(project); - ProjectUtil.openProject(baseDir.getPath(), null, false); - } - else { - Messages.showErrorDialog(project, String.format("Unable to create '.idea' directory (%s)", ideaDir), "Error saving project!"); - } - } - } - } - } - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - - final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); - boolean visible = project != null; - - if (project instanceof ProjectEx) { - visible = ((ProjectEx)project).getStateStore().getStorageScheme() != StorageScheme.DIRECTORY_BASED; - } - - presentation.setVisible(visible); - } -} +/* + * 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.ide.actions; + +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.components.StorageScheme; +import com.intellij.openapi.components.impl.stores.IProjectStore; +import com.intellij.openapi.components.impl.stores.StateStorageManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ex.ProjectEx; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.File; + +/** + * @author spleaner + */ +public class SaveAsDirectoryBasedFormatAction extends AnAction implements DumbAware { + + public void actionPerformed(AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + if (project instanceof ProjectEx) { + final IProjectStore projectStore = ((ProjectEx)project).getStateStore(); + if (StorageScheme.DIRECTORY_BASED != projectStore.getStorageScheme()) { + final int result = Messages.showOkCancelDialog(project, + "Project will be saved and reopened in new Directory-Based format.\nAre you sure you want to continue?", + "Save project to Directory-Based format", Messages.getWarningIcon()); + if (result == 0) { + final VirtualFile baseDir = project.getBaseDir(); + assert baseDir != null; + + File ideaDir = new File(baseDir.getPath(), ProjectEx.DIRECTORY_STORE_FOLDER + File.separatorChar); + final boolean ok = (ideaDir.exists() && ideaDir.isDirectory()) || ideaDir.mkdirs(); + if (ok) { + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ideaDir); + + + final StateStorageManager storageManager = projectStore.getStateStorageManager(); + for (String file : storageManager.getStorageFileNames()) { + storageManager.clearStateStorage(file); + } + + projectStore.setProjectFilePath(baseDir.getPath()); + project.save(); + ProjectUtil.closeAndDispose(project); + ProjectUtil.openProject(baseDir.getPath(), null, false); + } + else { + Messages.showErrorDialog(project, String.format("Unable to create '.idea' directory (%s)", ideaDir), "Error saving project!"); + } + } + } + } + } + + @Override + public void update(AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + boolean visible = project != null; + + if (project instanceof ProjectEx) { + visible = ((ProjectEx)project).getStateStore().getStorageScheme() != StorageScheme.DIRECTORY_BASED; + } + + presentation.setVisible(visible); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/dnd/Highlighters.java b/platform/platform-impl/src/com/intellij/ide/dnd/Highlighters.java index b9da08642744..e53bac7a5f2b 100644 --- a/platform/platform-impl/src/com/intellij/ide/dnd/Highlighters.java +++ b/platform/platform-impl/src/com/intellij/ide/dnd/Highlighters.java @@ -1,284 +1,284 @@ -/* - * 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.ide.dnd; - -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.ui.UIUtil; - -import javax.accessibility.Accessible; -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; - -public class Highlighters implements DnDEvent.DropTargetHighlightingType { - private static final List ourHightlighters = new ArrayList(); - - private static final List ourCurrentHighlighters = new ArrayList(); - - static { - ourHightlighters.add(new RectangleHighlighter()); - ourHightlighters.add(new FilledRectangleHighlighter()); - ourHightlighters.add(new HorizontalLinesHighlighter()); - ourHightlighters.add(new TextHighlighter()); - ourHightlighters.add(new ErrorTextHighlighter()); - ourHightlighters.add(new VerticalLinesHighlighter()); - } - - static void show(int aType, JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - List toShow = new ArrayList(); - for (Accessible ourHightlighter : ourHightlighters) { - DropTargetHighlighter each = (DropTargetHighlighter)ourHightlighter; - if ((each.getMask() & aType) != 0) { - toShow.add(each); - } - } - - for (int i = 0; i < toShow.size(); i++) { - DropTargetHighlighter each = toShow.get(i); - each.show(aPane, aRectangle, aEvent); - } - ourCurrentHighlighters.addAll(toShow); - } - - static void hideAllBut(int aType) { - for (int i = 0; i < ourCurrentHighlighters.size(); i++) { - final DropTargetHighlighter each = ourCurrentHighlighters.get(i); - if ((each.getMask() & aType) == 0) { - each.vanish(); - ourCurrentHighlighters.remove(each); - } - } - } - - static void hide() { - for (int i = 0; i < ourCurrentHighlighters.size(); i++) { - (ourCurrentHighlighters.get(i)).vanish(); - } - ourCurrentHighlighters.clear(); - } - - static void hide(int aType) { - for (int i = 0; i < ourCurrentHighlighters.size(); i++) { - final DropTargetHighlighter each = ourCurrentHighlighters.get(i); - if ((each.getMask() & aType) != 0) { - each.vanish(); - ourCurrentHighlighters.remove(each); - } - } - } - - static boolean isVisibleExcept(int type) { - int resultType = type; - for (int i = 0; i < ourCurrentHighlighters.size(); i++) { - final DropTargetHighlighter each = ourCurrentHighlighters.get(i); - resultType = resultType | each.getMask(); - } - - return type != resultType; - } - - static boolean isVisible() { - return ourCurrentHighlighters.size() > 0; - } - - private static abstract class AbstractComponentHighlighter extends JPanel implements DropTargetHighlighter { - - protected AbstractComponentHighlighter() { - setOpaque(false); - setLayout(new BorderLayout()); - } - - public final void show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - if (getParent() != aPane) { - vanish(); - aPane.add(this, getLayer()); - } - _show(aPane, aRectangle, aEvent); - } - - protected Integer getLayer() { - return JLayeredPane.MODAL_LAYER; - } - - public void vanish() { - final Container parent = getParent(); - Rectangle bounds = getBounds(); - if (parent != null) { - parent.remove(this); - parent.repaint(bounds.x, bounds.y, bounds.width, bounds.height); - } - } - - protected abstract void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent); - } - - private abstract static class BaseTextHighlighter extends JWindow implements DropTargetHighlighter { - protected JLabel myLabel; - - public BaseTextHighlighter() { - myLabel = new JLabel("", JLabel.CENTER) { - protected void paintComponent(Graphics g) { - BaseTextHighlighter.this.paintComponent(g); - super.paintComponent(g); - } - }; - myLabel.setFont(myLabel.getFont().deriveFont(Font.BOLD)); - myLabel.setForeground(UIUtil.getToolTipForeground()); - - setFocusable(false); - - getContentPane().setLayout(new BorderLayout()); - getContentPane().add(myLabel, BorderLayout.CENTER); - } - - public void show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - myLabel.setText(aEvent.getExpectedDropResult()); - final Dimension prefSize = getPreferredSize(); - prefSize.width += 10; - prefSize.height += 4; - - int centerX = aRectangle.x + aRectangle.width / 2; - final Rectangle newBounds = new Rectangle(centerX - prefSize.width / 2, aRectangle.y - prefSize.height - 5, prefSize.width, prefSize.height); - newBounds.y = newBounds.y < 0 ? 0 : newBounds.y; - - Point location = newBounds.getLocation(); - SwingUtilities.convertPointToScreen(location, aPane); - newBounds.setLocation(location); - - setBounds(newBounds); - show(); - if (SystemInfo.isUnix) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - toFront(); - } - }); - } - } - - public void vanish() { - hide(); - } - - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - Object old = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - g.setColor(UIUtil.getToolTipBackground()); - g.fillRoundRect(0, 0, getSize().width - 1, getSize().height - 1, 6, 6); - - g.setColor(UIUtil.getToolTipForeground()); - g.drawRoundRect(0, 0, getSize().width - 1, getSize().height - 1, 6, 6); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old); - } - - protected Integer getLayer() { - return JLayeredPane.POPUP_LAYER; - } - - } - - private static class TextHighlighter extends BaseTextHighlighter { - public int getMask() { - return TEXT; - } - } - - private static class ErrorTextHighlighter extends BaseTextHighlighter { - public ErrorTextHighlighter() { - super(); - myLabel.setIcon(IconLoader.getIcon("/ide/dnd/error.png")); - } - - public int getMask() { - return ERROR_TEXT; - } - } - - private static class FilledRectangleHighlighter extends AbstractComponentHighlighter { - public FilledRectangleHighlighter() { - super(); - setOpaque(true); - setBorder(BorderFactory.createLineBorder(Color.red)); - setBackground(Color.red); - } - - protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - setBounds(aRectangle); - } - - public int getMask() { - return FILLED_RECTANGLE; - } - } - - private static class RectangleHighlighter extends AbstractComponentHighlighter { - public RectangleHighlighter() { - super(); - setOpaque(false); - setBorder(BorderFactory.createLineBorder(Color.red)); - } - - protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - setBounds(aRectangle); - } - - public int getMask() { - return RECTANGLE; - } - } - - private static class HorizontalLinesHighlighter extends AbstractComponentHighlighter { - private final Icon myLeft = IconLoader.getIcon("/ide/dnd/left.png"); - private final Icon myRight = IconLoader.getIcon("/ide/dnd/right.png"); - - protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - final Rectangle rectangle = new Rectangle(aRectangle.x - myLeft.getIconWidth(), aRectangle.y - myLeft.getIconHeight(), aRectangle.width + myLeft.getIconWidth() + myRight.getIconWidth(), aRectangle.height + myLeft.getIconHeight()); - setBounds(rectangle); - } - - protected void paintComponent(Graphics g) { - myLeft.paintIcon(this, g, 0, (getHeight() / 2)); - myRight.paintIcon(this, g, getWidth() - myRight.getIconWidth(), (getHeight() / 2)); - } - - public int getMask() { - return H_ARROWS; - } - } - - private static class VerticalLinesHighlighter extends AbstractComponentHighlighter { - private final Icon myTop = IconLoader.getIcon("/ide/dnd/top.png"); - private final Icon myBottom = IconLoader.getIcon("/ide/dnd/bottom.png"); - - protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { - final Rectangle rectangle = new Rectangle(aRectangle.x, aRectangle.y - myTop.getIconHeight(), aRectangle.width, aRectangle.height + myTop.getIconHeight() + myBottom.getIconHeight()); - setBounds(rectangle); - } - - protected void paintComponent(Graphics g) { - myTop.paintIcon(this, g, (getWidth() - myTop.getIconWidth()) / 2, 0); - myBottom.paintIcon(this, g, (getWidth() - myBottom.getIconWidth()) / 2, getHeight() - myBottom.getIconHeight()); - } - - public int getMask() { - return V_ARROWS; - } - } -} +/* + * 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.ide.dnd; + +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.ui.UIUtil; + +import javax.accessibility.Accessible; +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class Highlighters implements DnDEvent.DropTargetHighlightingType { + private static final List ourHightlighters = new ArrayList(); + + private static final List ourCurrentHighlighters = new ArrayList(); + + static { + ourHightlighters.add(new RectangleHighlighter()); + ourHightlighters.add(new FilledRectangleHighlighter()); + ourHightlighters.add(new HorizontalLinesHighlighter()); + ourHightlighters.add(new TextHighlighter()); + ourHightlighters.add(new ErrorTextHighlighter()); + ourHightlighters.add(new VerticalLinesHighlighter()); + } + + static void show(int aType, JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + List toShow = new ArrayList(); + for (Accessible ourHightlighter : ourHightlighters) { + DropTargetHighlighter each = (DropTargetHighlighter)ourHightlighter; + if ((each.getMask() & aType) != 0) { + toShow.add(each); + } + } + + for (int i = 0; i < toShow.size(); i++) { + DropTargetHighlighter each = toShow.get(i); + each.show(aPane, aRectangle, aEvent); + } + ourCurrentHighlighters.addAll(toShow); + } + + static void hideAllBut(int aType) { + for (int i = 0; i < ourCurrentHighlighters.size(); i++) { + final DropTargetHighlighter each = ourCurrentHighlighters.get(i); + if ((each.getMask() & aType) == 0) { + each.vanish(); + ourCurrentHighlighters.remove(each); + } + } + } + + static void hide() { + for (int i = 0; i < ourCurrentHighlighters.size(); i++) { + (ourCurrentHighlighters.get(i)).vanish(); + } + ourCurrentHighlighters.clear(); + } + + static void hide(int aType) { + for (int i = 0; i < ourCurrentHighlighters.size(); i++) { + final DropTargetHighlighter each = ourCurrentHighlighters.get(i); + if ((each.getMask() & aType) != 0) { + each.vanish(); + ourCurrentHighlighters.remove(each); + } + } + } + + static boolean isVisibleExcept(int type) { + int resultType = type; + for (int i = 0; i < ourCurrentHighlighters.size(); i++) { + final DropTargetHighlighter each = ourCurrentHighlighters.get(i); + resultType = resultType | each.getMask(); + } + + return type != resultType; + } + + static boolean isVisible() { + return ourCurrentHighlighters.size() > 0; + } + + private static abstract class AbstractComponentHighlighter extends JPanel implements DropTargetHighlighter { + + protected AbstractComponentHighlighter() { + setOpaque(false); + setLayout(new BorderLayout()); + } + + public final void show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + if (getParent() != aPane) { + vanish(); + aPane.add(this, getLayer()); + } + _show(aPane, aRectangle, aEvent); + } + + protected Integer getLayer() { + return JLayeredPane.MODAL_LAYER; + } + + public void vanish() { + final Container parent = getParent(); + Rectangle bounds = getBounds(); + if (parent != null) { + parent.remove(this); + parent.repaint(bounds.x, bounds.y, bounds.width, bounds.height); + } + } + + protected abstract void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent); + } + + private abstract static class BaseTextHighlighter extends JWindow implements DropTargetHighlighter { + protected JLabel myLabel; + + public BaseTextHighlighter() { + myLabel = new JLabel("", JLabel.CENTER) { + protected void paintComponent(Graphics g) { + BaseTextHighlighter.this.paintComponent(g); + super.paintComponent(g); + } + }; + myLabel.setFont(myLabel.getFont().deriveFont(Font.BOLD)); + myLabel.setForeground(UIUtil.getToolTipForeground()); + + setFocusable(false); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(myLabel, BorderLayout.CENTER); + } + + public void show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + myLabel.setText(aEvent.getExpectedDropResult()); + final Dimension prefSize = getPreferredSize(); + prefSize.width += 10; + prefSize.height += 4; + + int centerX = aRectangle.x + aRectangle.width / 2; + final Rectangle newBounds = new Rectangle(centerX - prefSize.width / 2, aRectangle.y - prefSize.height - 5, prefSize.width, prefSize.height); + newBounds.y = newBounds.y < 0 ? 0 : newBounds.y; + + Point location = newBounds.getLocation(); + SwingUtilities.convertPointToScreen(location, aPane); + newBounds.setLocation(location); + + setBounds(newBounds); + show(); + if (SystemInfo.isUnix) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + toFront(); + } + }); + } + } + + public void vanish() { + hide(); + } + + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + Object old = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g.setColor(UIUtil.getToolTipBackground()); + g.fillRoundRect(0, 0, getSize().width - 1, getSize().height - 1, 6, 6); + + g.setColor(UIUtil.getToolTipForeground()); + g.drawRoundRect(0, 0, getSize().width - 1, getSize().height - 1, 6, 6); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, old); + } + + protected Integer getLayer() { + return JLayeredPane.POPUP_LAYER; + } + + } + + private static class TextHighlighter extends BaseTextHighlighter { + public int getMask() { + return TEXT; + } + } + + private static class ErrorTextHighlighter extends BaseTextHighlighter { + public ErrorTextHighlighter() { + super(); + myLabel.setIcon(IconLoader.getIcon("/ide/dnd/error.png")); + } + + public int getMask() { + return ERROR_TEXT; + } + } + + private static class FilledRectangleHighlighter extends AbstractComponentHighlighter { + public FilledRectangleHighlighter() { + super(); + setOpaque(true); + setBorder(BorderFactory.createLineBorder(Color.red)); + setBackground(Color.red); + } + + protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + setBounds(aRectangle); + } + + public int getMask() { + return FILLED_RECTANGLE; + } + } + + private static class RectangleHighlighter extends AbstractComponentHighlighter { + public RectangleHighlighter() { + super(); + setOpaque(false); + setBorder(BorderFactory.createLineBorder(Color.red)); + } + + protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + setBounds(aRectangle); + } + + public int getMask() { + return RECTANGLE; + } + } + + private static class HorizontalLinesHighlighter extends AbstractComponentHighlighter { + private final Icon myLeft = IconLoader.getIcon("/ide/dnd/left.png"); + private final Icon myRight = IconLoader.getIcon("/ide/dnd/right.png"); + + protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + final Rectangle rectangle = new Rectangle(aRectangle.x - myLeft.getIconWidth(), aRectangle.y - myLeft.getIconHeight(), aRectangle.width + myLeft.getIconWidth() + myRight.getIconWidth(), aRectangle.height + myLeft.getIconHeight()); + setBounds(rectangle); + } + + protected void paintComponent(Graphics g) { + myLeft.paintIcon(this, g, 0, (getHeight() / 2)); + myRight.paintIcon(this, g, getWidth() - myRight.getIconWidth(), (getHeight() / 2)); + } + + public int getMask() { + return H_ARROWS; + } + } + + private static class VerticalLinesHighlighter extends AbstractComponentHighlighter { + private final Icon myTop = IconLoader.getIcon("/ide/dnd/top.png"); + private final Icon myBottom = IconLoader.getIcon("/ide/dnd/bottom.png"); + + protected void _show(JLayeredPane aPane, Rectangle aRectangle, DnDEvent aEvent) { + final Rectangle rectangle = new Rectangle(aRectangle.x, aRectangle.y - myTop.getIconHeight(), aRectangle.width, aRectangle.height + myTop.getIconHeight() + myBottom.getIconHeight()); + setBounds(rectangle); + } + + protected void paintComponent(Graphics g) { + myTop.paintIcon(this, g, (getWidth() - myTop.getIconWidth()) / 2, 0); + myBottom.paintIcon(this, g, (getWidth() - myBottom.getIconWidth()) / 2, getHeight() - myBottom.getIconHeight()); + } + + public int getMask() { + return V_ARROWS; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java index 2b0b4a335c89..28ce85c2b6a1 100644 --- a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java @@ -1,253 +1,253 @@ -/* - * 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.ide.impl; - -import com.intellij.CommonBundle; -import com.intellij.ide.GeneralSettings; -import com.intellij.ide.IdeBundle; -import com.intellij.ide.highlighter.ModuleFileType; -import com.intellij.ide.highlighter.ProjectFileType; -import com.intellij.ide.highlighter.WorkspaceFileType; -import com.intellij.openapi.components.StorageScheme; -import com.intellij.openapi.components.impl.stores.IProjectStore; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ex.ProjectEx; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.FocusCommand; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.projectImport.ProjectOpenProcessor; -import com.intellij.ui.AppIcon; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.File; -import java.io.IOException; - -/** - * @author Eugene Belyaev - */ -public class ProjectUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.ide.impl.ProjectUtil"); - @NonNls public static final String DIRECTORY_BASED_PROJECT_DIR = ".idea"; - - private ProjectUtil() { - } - - public static void updateLastProjectLocation(final String projectFilePath) { - File lastProjectLocation = new File(projectFilePath); - if (lastProjectLocation.isFile()) { - lastProjectLocation = lastProjectLocation.getParentFile(); //for directory based project storages - } - if (lastProjectLocation == null) { // the immediate parent of the ipr file - return; - } - lastProjectLocation = lastProjectLocation.getParentFile(); // the candidate directory to be saved - if (lastProjectLocation == null) { - return; - } - String path = lastProjectLocation.getPath(); - try { - path = FileUtil.resolveShortWindowsName(path); - } - catch (IOException e) { - LOG.info(e); - return; - } - GeneralSettings.getInstance().setLastProjectLocation(path.replace(File.separatorChar, '/')); - } - - /** - * @param project cannot be null - */ - public static boolean closeAndDispose(@NotNull final Project project) { - return ProjectManagerEx.getInstanceEx().closeAndDispose(project); - } - - /** - * @param path project file path - * @param projectToClose currently active project - * @param forceOpenInNewFrame forces opening in new frame - * @return project by path if the path was recognized as IDEA project file or one of the project formats supported by - * installed importers (regardless of opening/import result) - * null otherwise - */ - @Nullable - public static Project openOrImport(@NotNull final String path, final Project projectToClose, boolean forceOpenInNewFrame) { - final VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); - - if (virtualFile == null) return null; - - if (path.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION) || - virtualFile.isDirectory() && virtualFile.findChild(DIRECTORY_BASED_PROJECT_DIR) != null) { - return openProject(path, projectToClose, forceOpenInNewFrame); - } - if (virtualFile.isDirectory()) { - for (VirtualFile child : virtualFile.getChildren()) { - final String childPath = child.getPath(); - if (childPath.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION)) { - return openProject(childPath, projectToClose, forceOpenInNewFrame); - } - } - } - ProjectOpenProcessor provider = ProjectOpenProcessor.getImportProvider(virtualFile); - if (provider != null) { - return provider.doOpenProject(virtualFile, projectToClose, forceOpenInNewFrame); - } - return null; - } - - @Nullable - public static Project openProject(final String path, Project projectToClose, boolean forceOpenInNewFrame) { - File file = new File(path); - if (!file.exists()) { - Messages.showMessageDialog(IdeBundle.message("error.project.file.does.not.exist", path), CommonBundle.getErrorTitle(), - Messages.getErrorIcon()); - return null; - } - - if (file.isDirectory() && !new File(file, DIRECTORY_BASED_PROJECT_DIR).exists()) { - Messages.showMessageDialog(IdeBundle.message("error.project.file.does.not.exist", new File(file, DIRECTORY_BASED_PROJECT_DIR).getPath()), CommonBundle.getErrorTitle(), - Messages.getErrorIcon()); - return null; - } - - Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); - for (Project project : openProjects) { - if (isSameProject(path, project)) { - focusProjectWindow(project, false); - return project; - } - } - - if (!forceOpenInNewFrame && openProjects.length > 0) { - int exitCode = confirmOpenNewProject(); - if (exitCode == 1) { // "No" option - if (!closeAndDispose(projectToClose != null ? projectToClose : openProjects[openProjects.length - 1])) return null; - } - else if (exitCode != 0) { // not "Yes" - return null; - } - } - - ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); - Project project = null; - try { - project = projectManager.loadAndOpenProject(path); - } - catch (IOException e) { - Messages.showMessageDialog(IdeBundle.message("error.cannot.load.project", e.getMessage()), - IdeBundle.message("title.cannot.load.project"), Messages.getErrorIcon()); - } - catch (JDOMException e) { - LOG.info(e); - Messages.showMessageDialog(IdeBundle.message("error.project.file.is.corrupted"), IdeBundle.message("title.cannot.load.project"), - Messages.getErrorIcon()); - } - catch (InvalidDataException e) { - LOG.info(e); - Messages.showMessageDialog(IdeBundle.message("error.project.file.is.corrupted"), IdeBundle.message("title.cannot.load.project"), - Messages.getErrorIcon()); - } - return project; - } - - public static int confirmOpenNewProject() { - final GeneralSettings settings = GeneralSettings.getInstance(); - if (settings.getConfirmOpenNewProject() == GeneralSettings.OPEN_PROJECT_ASK) { - return Messages.showDialog(IdeBundle.message("prompt.open.project.in.new.frame"), IdeBundle.message("title.open.project"), - new String[]{IdeBundle.message("button.newframe"), IdeBundle.message("button.existingframe"), - CommonBundle.getCancelButtonText()}, 1, 0, Messages.getQuestionIcon(), - new ProjectNewWindowDoNotAskOption()); - } else { - return settings.getConfirmOpenNewProject(); - } - } - - private static boolean isSameProject(String path, Project p) { - final IProjectStore projectStore = ((ProjectEx)p).getStateStore(); - - String toOpen = FileUtil.toSystemIndependentName(path); - String existing = FileUtil.toSystemIndependentName(projectStore.getProjectFilePath()); - - final VirtualFile existingBaseDir = projectStore.getProjectBaseDir(); - if (existingBaseDir == null) return false; // could be null if not yet initialized - - final File openFile = new File(toOpen); - if (openFile.isDirectory()) { - return FileUtil.pathsEqual(toOpen, existingBaseDir.getPath()); - } else { - if (StorageScheme.DIRECTORY_BASED == projectStore.getStorageScheme()) { - // todo: check if IPR is located not under the project base dir - return FileUtil.pathsEqual(FileUtil.toSystemIndependentName(openFile.getParentFile().getPath()), existingBaseDir.getPath()); - } - } - - return FileUtil.pathsEqual(toOpen, existing); - } - - public static void focusProjectWindow(final Project p, boolean executeIfAppInactive) { - FocusCommand cmd = new FocusCommand() { - @Override - public ActionCallback run() { - JFrame f = WindowManager.getInstance().getFrame(p); - if (f != null) { - f.toFront(); - //f.requestFocus(); - } - return new ActionCallback.Done(); - } - }; - - if (executeIfAppInactive) { - AppIcon.getInstance().requestFocus((IdeFrame)WindowManager.getInstance().getFrame(p)); - cmd.run(); - } else { - IdeFocusManager.getInstance(p).requestFocus(cmd, false); - } - } - - public static boolean isProjectOrWorkspaceFile(final VirtualFile file) { - return isProjectOrWorkspaceFile(file, file.getFileType()); - } - - public static boolean isProjectOrWorkspaceFile(final VirtualFile file, - final FileType fileType) { - final boolean iprBased = fileType instanceof WorkspaceFileType || fileType instanceof ProjectFileType || fileType instanceof ModuleFileType; - if (iprBased) return true; - VirtualFile parent = file.getParent(); - if (parent != null) { - if (parent.getName().equals(DIRECTORY_BASED_PROJECT_DIR)) return true; - parent = parent.getParent(); - if (parent != null && parent.getName().equals(DIRECTORY_BASED_PROJECT_DIR)) return true; - } - - return false; - } -} +/* + * 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.ide.impl; + +import com.intellij.CommonBundle; +import com.intellij.ide.GeneralSettings; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.highlighter.ModuleFileType; +import com.intellij.ide.highlighter.ProjectFileType; +import com.intellij.ide.highlighter.WorkspaceFileType; +import com.intellij.openapi.components.StorageScheme; +import com.intellij.openapi.components.impl.stores.IProjectStore; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ex.ProjectEx; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.FocusCommand; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.projectImport.ProjectOpenProcessor; +import com.intellij.ui.AppIcon; +import org.jdom.JDOMException; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; + +/** + * @author Eugene Belyaev + */ +public class ProjectUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.ide.impl.ProjectUtil"); + @NonNls public static final String DIRECTORY_BASED_PROJECT_DIR = ".idea"; + + private ProjectUtil() { + } + + public static void updateLastProjectLocation(final String projectFilePath) { + File lastProjectLocation = new File(projectFilePath); + if (lastProjectLocation.isFile()) { + lastProjectLocation = lastProjectLocation.getParentFile(); //for directory based project storages + } + if (lastProjectLocation == null) { // the immediate parent of the ipr file + return; + } + lastProjectLocation = lastProjectLocation.getParentFile(); // the candidate directory to be saved + if (lastProjectLocation == null) { + return; + } + String path = lastProjectLocation.getPath(); + try { + path = FileUtil.resolveShortWindowsName(path); + } + catch (IOException e) { + LOG.info(e); + return; + } + GeneralSettings.getInstance().setLastProjectLocation(path.replace(File.separatorChar, '/')); + } + + /** + * @param project cannot be null + */ + public static boolean closeAndDispose(@NotNull final Project project) { + return ProjectManagerEx.getInstanceEx().closeAndDispose(project); + } + + /** + * @param path project file path + * @param projectToClose currently active project + * @param forceOpenInNewFrame forces opening in new frame + * @return project by path if the path was recognized as IDEA project file or one of the project formats supported by + * installed importers (regardless of opening/import result) + * null otherwise + */ + @Nullable + public static Project openOrImport(@NotNull final String path, final Project projectToClose, boolean forceOpenInNewFrame) { + final VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + + if (virtualFile == null) return null; + + if (path.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION) || + virtualFile.isDirectory() && virtualFile.findChild(DIRECTORY_BASED_PROJECT_DIR) != null) { + return openProject(path, projectToClose, forceOpenInNewFrame); + } + if (virtualFile.isDirectory()) { + for (VirtualFile child : virtualFile.getChildren()) { + final String childPath = child.getPath(); + if (childPath.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION)) { + return openProject(childPath, projectToClose, forceOpenInNewFrame); + } + } + } + ProjectOpenProcessor provider = ProjectOpenProcessor.getImportProvider(virtualFile); + if (provider != null) { + return provider.doOpenProject(virtualFile, projectToClose, forceOpenInNewFrame); + } + return null; + } + + @Nullable + public static Project openProject(final String path, Project projectToClose, boolean forceOpenInNewFrame) { + File file = new File(path); + if (!file.exists()) { + Messages.showMessageDialog(IdeBundle.message("error.project.file.does.not.exist", path), CommonBundle.getErrorTitle(), + Messages.getErrorIcon()); + return null; + } + + if (file.isDirectory() && !new File(file, DIRECTORY_BASED_PROJECT_DIR).exists()) { + Messages.showMessageDialog(IdeBundle.message("error.project.file.does.not.exist", new File(file, DIRECTORY_BASED_PROJECT_DIR).getPath()), CommonBundle.getErrorTitle(), + Messages.getErrorIcon()); + return null; + } + + Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + for (Project project : openProjects) { + if (isSameProject(path, project)) { + focusProjectWindow(project, false); + return project; + } + } + + if (!forceOpenInNewFrame && openProjects.length > 0) { + int exitCode = confirmOpenNewProject(); + if (exitCode == 1) { // "No" option + if (!closeAndDispose(projectToClose != null ? projectToClose : openProjects[openProjects.length - 1])) return null; + } + else if (exitCode != 0) { // not "Yes" + return null; + } + } + + ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); + Project project = null; + try { + project = projectManager.loadAndOpenProject(path); + } + catch (IOException e) { + Messages.showMessageDialog(IdeBundle.message("error.cannot.load.project", e.getMessage()), + IdeBundle.message("title.cannot.load.project"), Messages.getErrorIcon()); + } + catch (JDOMException e) { + LOG.info(e); + Messages.showMessageDialog(IdeBundle.message("error.project.file.is.corrupted"), IdeBundle.message("title.cannot.load.project"), + Messages.getErrorIcon()); + } + catch (InvalidDataException e) { + LOG.info(e); + Messages.showMessageDialog(IdeBundle.message("error.project.file.is.corrupted"), IdeBundle.message("title.cannot.load.project"), + Messages.getErrorIcon()); + } + return project; + } + + public static int confirmOpenNewProject() { + final GeneralSettings settings = GeneralSettings.getInstance(); + if (settings.getConfirmOpenNewProject() == GeneralSettings.OPEN_PROJECT_ASK) { + return Messages.showDialog(IdeBundle.message("prompt.open.project.in.new.frame"), IdeBundle.message("title.open.project"), + new String[]{IdeBundle.message("button.newframe"), IdeBundle.message("button.existingframe"), + CommonBundle.getCancelButtonText()}, 1, 0, Messages.getQuestionIcon(), + new ProjectNewWindowDoNotAskOption()); + } else { + return settings.getConfirmOpenNewProject(); + } + } + + private static boolean isSameProject(String path, Project p) { + final IProjectStore projectStore = ((ProjectEx)p).getStateStore(); + + String toOpen = FileUtil.toSystemIndependentName(path); + String existing = FileUtil.toSystemIndependentName(projectStore.getProjectFilePath()); + + final VirtualFile existingBaseDir = projectStore.getProjectBaseDir(); + if (existingBaseDir == null) return false; // could be null if not yet initialized + + final File openFile = new File(toOpen); + if (openFile.isDirectory()) { + return FileUtil.pathsEqual(toOpen, existingBaseDir.getPath()); + } else { + if (StorageScheme.DIRECTORY_BASED == projectStore.getStorageScheme()) { + // todo: check if IPR is located not under the project base dir + return FileUtil.pathsEqual(FileUtil.toSystemIndependentName(openFile.getParentFile().getPath()), existingBaseDir.getPath()); + } + } + + return FileUtil.pathsEqual(toOpen, existing); + } + + public static void focusProjectWindow(final Project p, boolean executeIfAppInactive) { + FocusCommand cmd = new FocusCommand() { + @Override + public ActionCallback run() { + JFrame f = WindowManager.getInstance().getFrame(p); + if (f != null) { + f.toFront(); + //f.requestFocus(); + } + return new ActionCallback.Done(); + } + }; + + if (executeIfAppInactive) { + AppIcon.getInstance().requestFocus((IdeFrame)WindowManager.getInstance().getFrame(p)); + cmd.run(); + } else { + IdeFocusManager.getInstance(p).requestFocus(cmd, false); + } + } + + public static boolean isProjectOrWorkspaceFile(final VirtualFile file) { + return isProjectOrWorkspaceFile(file, file.getFileType()); + } + + public static boolean isProjectOrWorkspaceFile(final VirtualFile file, + final FileType fileType) { + final boolean iprBased = fileType instanceof WorkspaceFileType || fileType instanceof ProjectFileType || fileType instanceof ModuleFileType; + if (iprBased) return true; + VirtualFile parent = file.getParent(); + if (parent != null) { + if (parent.getName().equals(DIRECTORY_BASED_PROJECT_DIR)) return true; + parent = parent.getParent(); + if (parent != null && parent.getName().equals(DIRECTORY_BASED_PROJECT_DIR)) return true; + } + + return false; + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/ui/LafManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/ui/LafManagerImpl.java index 121973dcd3bc..6772bb9a34b7 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/LafManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/LafManagerImpl.java @@ -1,704 +1,704 @@ -/* - * 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.ide.ui; - -import com.intellij.CommonBundle; -import com.intellij.ide.IdeBundle; -import com.intellij.idea.StartupUtil; -import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.components.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.popup.util.PopupUtil; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.ui.ColoredSideBorder; -import com.intellij.ui.IdeaBlueMetalTheme; -import com.intellij.ui.ScreenUtil; -import com.intellij.ui.mac.MacPopupMenuUI; -import com.intellij.ui.plaf.beg.*; -import com.intellij.util.ui.UIUtil; -import com.sun.java.swing.plaf.windows.WindowsLookAndFeel; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.EventListenerList; -import javax.swing.plaf.ColorUIResource; -import javax.swing.plaf.FontUIResource; -import javax.swing.plaf.metal.DefaultMetalTheme; -import javax.swing.plaf.metal.MetalLookAndFeel; -import javax.swing.text.DefaultEditorKit; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; - -/** - * @author Eugene Belyaev - * @author Vladimir Kondratyev - */ -@State( - name = "LafManager", - roamingType = RoamingType.PER_PLATFORM, - storages = {@Storage( - id = "other", - file = "$APP_CONFIG$/options.xml")}) -public final class LafManagerImpl extends LafManager implements ApplicationComponent, PersistentStateComponent { - private static final Logger LOG=Logger.getInstance("#com.intellij.ide.ui.LafManager"); - - @NonNls private static final String IDEA_LAF_CLASSNAME = "idea.laf.classname"; - - /** - * One of the possible values of -Didea.popup.weight property. Heavy weight means - * that all popups are shown inside the window. Under UNIXes it's possible to configure - * window manager "Focus follows mouse with Auto Raise". In this case popup window will - * be immediately closed after showing. - */ - @NonNls private static final String HEAVY_WEIGHT_POPUP="heavy"; - /** - * One of the possible values of -Didea.popup.weight property. Medium weight means - * that popup will be shouw inside the paren't JLayeredPane if it can be fit to it. - * Otherwise popup will be shown in the window. This mode is defaut for the Swing but - * it's very slow (much slower then heavy weight popups). - */ - @NonNls private static final String MEDIUM_WEIGHT_POPUP="medium"; - - private final EventListenerList myListenerList; - private final UIManager.LookAndFeelInfo[] myLafs; - private UIManager.LookAndFeelInfo myCurrentLaf; - - @NonNls private static final String[] ourPatcheableFontResources = new String[]{ - "Button.font", "ToggleButton.font", "RadioButton.font", "CheckBox.font", "ColorChooser.font", "ComboBox.font", - "Label.font", "List.font", "MenuBar.font", "MenuItem.font", "MenuItem.acceleratorFont", "RadioButtonMenuItem.font", - "CheckBoxMenuItem.font", "Menu.font", "PopupMenu.font", "OptionPane.font", "Panel.font", "ProgressBar.font", - "ScrollPane.font", "Viewport.font", "TabbedPane.font", "Table.font", "TableHeader.font", "TextField.font", - "PasswordField.font", "TextArea.font", "TextPane.font", "EditorPane.font", "TitledBorder.font", "ToolBar.font", - "ToolTip.font", "Tree.font" - }; - @NonNls private static final String[] ourFileChooserTextKeys = new String[] { - "FileChooser.viewMenuLabelText", "FileChooser.newFolderActionLabelText", "FileChooser.listViewActionLabelText", - "FileChooser.detailsViewActionLabelText", "FileChooser.refreshActionLabelText" - }; - - private final HashMap> myStoredDefaults = new HashMap>(); - private final UISettings myUiSettings; - - @NonNls private static final String ELEMENT_LAF = "laf"; - @NonNls private static final String ATTRIBUTE_CLASS_NAME = "class-name"; - - /** invoked by reflection - * @param uiSettings */ - LafManagerImpl(UISettings uiSettings){ - myUiSettings = uiSettings; - myListenerList=new EventListenerList(); - - IdeaLookAndFeelInfo ideaLaf=new IdeaLookAndFeelInfo(); - UIManager.LookAndFeelInfo[] installedLafs=UIManager.getInstalledLookAndFeels(); - - // Get all installed LAFs - myLafs=new UIManager.LookAndFeelInfo[1+installedLafs.length]; - myLafs[0]=ideaLaf; - System.arraycopy(installedLafs,0,myLafs,1,installedLafs.length); - Arrays.sort(myLafs,new MyComparator()); - - // Setup current LAF. Unfortunately it's system depended. - myCurrentLaf=getDefaultLaf(); - } - - /** - * Adds specified listener - */ - public void addLafManagerListener(@NotNull final LafManagerListener l){ - myListenerList.add(LafManagerListener.class, l); - } - - /** - * Removes specified listener - */ - public void removeLafManagerListener(@NotNull final LafManagerListener l){ - myListenerList.remove(LafManagerListener.class, l); - } - - private void fireLookAndFeelChanged(){ - LafManagerListener[] listeners = myListenerList.getListeners(LafManagerListener.class); - for (LafManagerListener listener : listeners) { - listener.lookAndFeelChanged(this); - } - } - - @NotNull - public String getComponentName(){ - return "LafManager"; - } - - public void initComponent() { - setCurrentLookAndFeel(findLaf(myCurrentLaf.getClassName())); // setup default LAF or one specfied by readExternal. - updateUI(); - } - - public void disposeComponent(){} - - public void loadState(final Element element) { - String className=null; - for (final Object o : element.getChildren()) { - Element child = (Element)o; - if (ELEMENT_LAF.equals(child.getName())) { - className = child.getAttributeValue(ATTRIBUTE_CLASS_NAME); - break; - } - } - - UIManager.LookAndFeelInfo laf=findLaf(className); - // If LAF is undefined (wrong class name or something else) we have set default LAF anyway. - if(laf==null){ - laf=getDefaultLaf(); - } - - if (myCurrentLaf != null && !laf.getClassName().equals(myCurrentLaf.getClassName())) { - setCurrentLookAndFeel(laf); - updateUI(); - } - - myCurrentLaf=laf; - } - - public Element getState() { - Element element = new Element("state"); - if(myCurrentLaf.getClassName()!=null){ - Element child=new Element(ELEMENT_LAF); - child.setAttribute(ATTRIBUTE_CLASS_NAME,myCurrentLaf.getClassName()); - element.addContent(child); - } - return element; - } - - public UIManager.LookAndFeelInfo[] getInstalledLookAndFeels(){ - return myLafs.clone(); - } - - public UIManager.LookAndFeelInfo getCurrentLookAndFeel(){ - return myCurrentLaf; - } - - public boolean isUnderAquaLookAndFeel() { - //noinspection HardCodedStringLiteral - return "Mac OS X".equals(getCurrentLookAndFeel().getName()); - } - - public boolean isUnderQuaquaLookAndFeel() { - //noinspection HardCodedStringLiteral - return "Quaqua".equals(getCurrentLookAndFeel().getName()); - } - - /** - * @return default LookAndFeelInfo for the running OS. For Win32 and - * Linux the method returns Alloy LAF or IDEA LAF if first not found, for Mac OS X it returns Aqua - * RubyMine uses Native L&F for linux as well - */ - private UIManager.LookAndFeelInfo getDefaultLaf(){ - if(SystemInfo.isMac) { - UIManager.LookAndFeelInfo laf=findLaf(UIManager.getSystemLookAndFeelClassName()); - LOG.assertTrue(laf!=null); - return laf; - } - else if (SystemInfo.isLinux && ApplicationNamesInfo.getInstance().getLowercaseProductName().equals("Rubymine")) { - UIManager.LookAndFeelInfo laf=findLaf(UIManager.getSystemLookAndFeelClassName()); - LOG.assertTrue(laf!=null); - return laf; - } - else { - String defaultLafName = StartupUtil.getDefaultLAF(); - if (defaultLafName != null) { - UIManager.LookAndFeelInfo defaultLaf = findLaf(defaultLafName); - if (defaultLaf != null) { - return defaultLaf; - } - } - return findLaf(IDEA_LAF_CLASSNAME); - } - } - - /** - * Finds LAF by its class name. - * will be returned. - */ - private UIManager.LookAndFeelInfo findLaf(String className){ - for (UIManager.LookAndFeelInfo laf : myLafs) { - if (Comparing.equal(laf.getClassName(), className)) { - return laf; - } - } - return null; - } - - /** - * Sets current LAF. The method doesn't update component hierarchy. - */ - public void setCurrentLookAndFeel(UIManager.LookAndFeelInfo lookAndFeelInfo){ - if(findLaf(lookAndFeelInfo.getClassName())==null){ - LOG.error("unknown LookAndFeel : "+lookAndFeelInfo); - return; - } - - // Set L&F - - if(IDEA_LAF_CLASSNAME.equals(lookAndFeelInfo.getClassName())){ // that is IDEA default LAF - IdeaLaf laf=new IdeaLaf(); - IdeaLaf.setCurrentTheme(new IdeaBlueMetalTheme()); - try { - UIManager.setLookAndFeel(laf); - } catch (Exception exc) { - Messages.showMessageDialog( - IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName()), - CommonBundle.getErrorTitle(), - Messages.getErrorIcon() - ); - return; - } - }else{ // non default LAF - try { - LookAndFeel laf=((LookAndFeel)Class.forName(lookAndFeelInfo.getClassName()).newInstance()); - if(laf instanceof MetalLookAndFeel){ - MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme()); - } - UIManager.setLookAndFeel(laf); - } catch(Exception exc) { - Messages.showMessageDialog( - IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName()), - CommonBundle.getErrorTitle(), - Messages.getErrorIcon() - ); - return; - } - } - myCurrentLaf=lookAndFeelInfo; - - // The following code is a trick! By default Swing uses lightweight and "medium" weight - // popups to show JPopupMenu. The code below force the creation of real heavyweight menus. - // It dramatically increases speed of popups. - - //noinspection HardCodedStringLiteral - String popupWeight=System.getProperty("idea.popup.weight"); - if(popupWeight==null){ // use defaults if popup weight isn't specified - if(SystemInfo.isWindows){ - popupWeight=HEAVY_WEIGHT_POPUP; - }else{ // UNIXes (Linux and MAC) go here - popupWeight=MEDIUM_WEIGHT_POPUP; - } - } else if (!HEAVY_WEIGHT_POPUP.equals(popupWeight) && !MEDIUM_WEIGHT_POPUP.equals(popupWeight)) { - throw new IllegalStateException("unknown value of property -Didea.popup.weight: " + popupWeight); - } - - if (SystemInfo.isMacOSLeopard) { - // Force heavy weight popups under Leopard, otherwise they don't have shadow or any kind of border. - popupWeight = HEAVY_WEIGHT_POPUP; - } - - popupWeight = popupWeight.trim(); - - PopupFactory popupFactory; - final PopupFactory oldFactory = PopupFactory.getSharedInstance(); - if (!(oldFactory instanceof OurPopupFactory)) { - popupFactory = new OurPopupFactory() { - public Popup getPopup( - Component owner, - Component contents, - int x, - int y - ) throws IllegalArgumentException { - final Point point = fixPopupLocation(contents, x, y); - - final int popupType = PopupUtil.getPopupType(this); - if (popupType >= 0) { - PopupUtil.setPopupType(oldFactory, popupType); - } - - return oldFactory.getPopup(owner, contents, point.x, point.y); - } - }; - - PopupUtil.setPopupType(popupFactory, HEAVY_WEIGHT_POPUP.equals(popupWeight) ? 2 : 1); - PopupFactory.setSharedInstance(popupFactory); - } - - // update ui for popup menu to get round corners - if (UIUtil.isUnderAquaLookAndFeel()) { - final UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults(); - uiDefaults.put("PopupMenuUI", MacPopupMenuUI.class.getCanonicalName()); - uiDefaults.put("Menu.invertedArrowIcon", getAquaMenuInvertedIcon()); - uiDefaults.put("Menu.disabledArrowIcon", getAquaMenuDisabledIcon()); - } - } - - @Nullable - private static Icon getAquaMenuDisabledIcon() { - final Icon arrowIcon = (Icon)UIManager.get("Menu.arrowIcon"); - if (arrowIcon != null) { - return IconLoader.getDisabledIcon(arrowIcon); - } - - return null; - } - - @Nullable - private static Icon getAquaMenuInvertedIcon() { - if (!UIUtil.isUnderAquaLookAndFeel()) return null; - final Icon arrow = (Icon) UIManager.get("Menu.arrowIcon"); - if (arrow == null) return null; - - try { - final Method method = arrow.getClass().getMethod("getInvertedIcon"); - if (method != null) { - method.setAccessible(true); - return (Icon) method.invoke(arrow); - } - - return null; - } - catch (NoSuchMethodException e1) { - return null; - } - catch (InvocationTargetException e1) { - return null; - } - catch (IllegalAccessException e1) { - return null; - } - } - - private Point fixPopupLocation(final Component contents, final int x, final int y) { - if (!(contents instanceof JToolTip)) return new Point(x, y); - - final PointerInfo info; - try { - info = MouseInfo.getPointerInfo(); - } - catch (InternalError e) { - // http://www.jetbrains.net/jira/browse/IDEADEV-21390 - // may happen under Mac OSX 10.5 - return new Point(x, y); - } - int deltaY = 0; - - if (info != null) { - final Point mouse = info.getLocation(); - deltaY = mouse.y - y; - } - - final Dimension size = contents.getPreferredSize(); - final Rectangle rec = new Rectangle(new Point(x, y), size); - ScreenUtil.moveRectangleToFitTheScreen(rec); - - if (rec.y < y) { - rec.y += deltaY; - } - - return rec.getLocation(); - } - - - /** - * Updates LAF of all windows. The method also updates font of components - * as it's configured in UISettings. - */ - public void updateUI(){ - UIDefaults lookAndFeelDefaults=UIManager.getLookAndFeelDefaults(); - initInputMapDefaults(lookAndFeelDefaults); - patchFileChooserStrings(lookAndFeelDefaults); - if (shouldPatchLAFFonts()) { - storeOriginalFontDefaults(lookAndFeelDefaults); - initFontDefaults(lookAndFeelDefaults); - } - else { - restoreOriginalFontDefaults(lookAndFeelDefaults); - } - - Frame[] frames=Frame.getFrames(); - for (Frame frame : frames) { - updateUI(frame); - } - fireLookAndFeelChanged(); - } - - private void patchFileChooserStrings(final UIDefaults defaults) { - if (!defaults.containsKey(ourFileChooserTextKeys [0])) { - // Alloy L&F does not define strings for names of context menu actions, so we have to patch them in here - for (String key : ourFileChooserTextKeys) { - defaults.put(key, IdeBundle.message(key)); - } - } - } - - private void restoreOriginalFontDefaults(UIDefaults defaults) { - UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel(); - HashMap lfDefaults = myStoredDefaults.get(lf); - if (lfDefaults != null) { - for (String resource : ourPatcheableFontResources) { - defaults.put(resource, lfDefaults.get(resource)); - } - } - } - - private void storeOriginalFontDefaults(UIDefaults defaults) { - UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel(); - HashMap lfDefaults = myStoredDefaults.get(lf); - if (lfDefaults == null) { - lfDefaults = new HashMap(); - for (String resource : ourPatcheableFontResources) { - lfDefaults.put(resource, defaults.get(resource)); - } - myStoredDefaults.put(lf, lfDefaults); - } - } - - private boolean shouldPatchLAFFonts() { - //noinspection HardCodedStringLiteral - return getCurrentLookAndFeel().getName().startsWith("IDEA") || UISettings.getInstance().OVERRIDE_NONIDEA_LAF_FONTS; - } - - private static void updateUI(Window window){ - if(!window.isDisplayable()){ - return; - } - SwingUtilities.updateComponentTreeUI(window); - Window[] children=window.getOwnedWindows(); - for (Window aChildren : children) { - updateUI(aChildren); - } - } - - /** - * Repaints all displayable window. - */ - public void repaintUI(){ - Frame[] frames=Frame.getFrames(); - for (Frame frame : frames) { - repaintUI(frame); - } - } - - private static void repaintUI(Window window){ - if(!window.isDisplayable()){ - return; - } - window.repaint(); - Window[] children=window.getOwnedWindows(); - for (Window aChildren : children) { - repaintUI(aChildren); - } - } - - private static void installCutCopyPasteShortcuts(InputMap inputMap, boolean useSimpleActionKeys){ - String copyActionKey = useSimpleActionKeys ? "copy" : DefaultEditorKit.copyAction; - String pasteActionKey = useSimpleActionKeys ? "paste" : DefaultEditorKit.pasteAction; - String cutActionKey = useSimpleActionKeys ? "cut" : DefaultEditorKit.cutAction; - // Ctrl+Ins, Shift+Ins, Shift+Del - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),copyActionKey); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT,KeyEvent.SHIFT_MASK|KeyEvent.SHIFT_DOWN_MASK),pasteActionKey); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,KeyEvent.SHIFT_MASK|KeyEvent.SHIFT_DOWN_MASK),cutActionKey); - // Ctrl+C, Ctrl+V, Ctrl+X - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),copyActionKey); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),pasteActionKey); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),DefaultEditorKit.cutAction); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private static void initInputMapDefaults(UIDefaults defaults){ - // Make ENTER work in JTrees - InputMap treeInputMap = (InputMap)defaults.get("Tree.focusInputMap"); - if(treeInputMap!=null){ // it's really possible. For examle, GTK+ doesn't have such map - treeInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0),"toggle"); - } - // Cut/Copy/Paste in JTextAreas - InputMap textAreaInputMap=(InputMap)defaults.get("TextArea.focusInputMap"); - if(textAreaInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) - installCutCopyPasteShortcuts(textAreaInputMap, false); - } - // Cut/Copy/Paste in JTextFields - InputMap textFieldInputMap=(InputMap)defaults.get("TextField.focusInputMap"); - if(textFieldInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) - installCutCopyPasteShortcuts(textFieldInputMap, false); - } - // Cut/Copy/Paste in JPAsswordField - InputMap passwordFieldInputMap=(InputMap)defaults.get("PasswordField.focusInputMap"); - if(passwordFieldInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) - installCutCopyPasteShortcuts(passwordFieldInputMap, false); - } - // Cut/Copy/Paste in JTables - InputMap tableInputMap=(InputMap)defaults.get("Table.ancestorInputMap"); - if(tableInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) - installCutCopyPasteShortcuts(tableInputMap, true); - } - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private void initFontDefaults(UIDefaults defaults) { - defaults.put("Tree.ancestorInputMap", null); - int uiFontSize = myUiSettings.FONT_SIZE; - String uiFontFace = myUiSettings.FONT_FACE; - FontUIResource font = new FontUIResource(uiFontFace, Font.PLAIN, uiFontSize); - FontUIResource font1 = new FontUIResource("Serif", Font.PLAIN, uiFontSize); - FontUIResource font3 = new FontUIResource("Monospaced", Font.PLAIN, uiFontSize); - - for (String fontResource : ourPatcheableFontResources) { - defaults.put(fontResource, font); - } - - defaults.put("PasswordField.font", font3); - defaults.put("TextArea.font", font3); - defaults.put("TextPane.font", font1); - defaults.put("EditorPane.font", font1); - defaults.put("TitledBorder.font", font); - } - - private static final class IdeaLookAndFeelInfo extends UIManager.LookAndFeelInfo{ - public IdeaLookAndFeelInfo(){ - super(IdeBundle.message("idea.default.look.and.feel"), IDEA_LAF_CLASSNAME); - } - - public boolean equals(Object obj){ - return (obj instanceof IdeaLookAndFeelInfo); - } - - public int hashCode(){ - return getName().hashCode(); - } - } - - private static final class MyComparator implements Comparator{ - public int compare(Object obj1,Object obj2){ - String name1=((UIManager.LookAndFeelInfo)obj1).getName(); - String name2=((UIManager.LookAndFeelInfo)obj2).getName(); - return name1.compareToIgnoreCase(name2); - } - } - - private static final class IdeaLaf extends MetalLookAndFeel{ - protected void initComponentDefaults(UIDefaults table) { - super.initComponentDefaults(table); - initInputMapDefaults(table); - initIdeaDefaults(table); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private static void initIdeaDefaults(UIDefaults defaults) { - defaults.put("Menu.maxGutterIconWidth", 18); - defaults.put("MenuItem.maxGutterIconWidth", 18); - // TODO[vova,anton] REMOVE!!! INVESTIGATE??? Borland??? - defaults.put("MenuItem.acceleratorDelimiter", "-"); - - defaults.put("TitledBorder.titleColor",IdeaBlueMetalTheme.primary1); - ColorUIResource col = new ColorUIResource(230, 230, 230); - defaults.put("ScrollBar.background", col); - defaults.put("ScrollBar.track", col); - -// Border scrollPaneBorder = new BorderUIResource(new BegBorders.ScrollPaneBorder()); -// defaults.put("ScrollPane.border", scrollPaneBorder); - defaults.put("TextField.border", BegBorders.getTextFieldBorder()); - defaults.put("PasswordField.border", BegBorders.getTextFieldBorder()); - Border popupMenuBorder = new BegPopupMenuBorder(); - defaults.put("PopupMenu.border", popupMenuBorder); - defaults.put("ScrollPane.border", BegBorders.getScrollPaneBorder()); - - defaults.put("ButtonUI", BegButtonUI.class.getName()); - defaults.put("ComboBoxUI", BegComboBoxUI.class.getName()); - defaults.put("RadioButtonUI", BegRadioButtonUI.class.getName()); - defaults.put("CheckBoxUI", BegCheckBoxUI.class.getName()); - defaults.put("TabbedPaneUI", BegTabbedPaneUI.class.getName()); - defaults.put("TableUI", BegTableUI.class.getName()); - defaults.put("TreeUI", BegTreeUI.class.getName()); -// defaults.put("ScrollPaneUI", BegScrollPaneUI.class.getName()); - - defaults.put("TabbedPane.tabInsets", new Insets(0, 4, 0, 4)); - defaults.put("ToolTip.background", new ColorUIResource(255, 255, 231)); - defaults.put("ToolTip.border", new ColoredSideBorder(Color.gray, Color.gray, Color.black, Color.black, 1)); - defaults.put("Tree.ancestorInputMap", null); - defaults.put("FileView.directoryIcon", IconLoader.getIcon("/nodes/folder.png")); - defaults.put("FileChooser.upFolderIcon", IconLoader.getIcon("/nodes/upFolder.png")); - defaults.put("FileChooser.newFolderIcon", IconLoader.getIcon("/nodes/newFolder.png")); - defaults.put("FileChooser.homeFolderIcon", IconLoader.getIcon("/nodes/homeFolder.png")); - defaults.put("OptionPane.errorIcon", IconLoader.getIcon("/general/errorDialog.png")); - defaults.put("OptionPane.informationIcon", IconLoader.getIcon("/general/informationDialog.png")); - defaults.put("OptionPane.warningIcon", IconLoader.getIcon("/general/warningDialog.png")); - defaults.put("OptionPane.questionIcon", IconLoader.getIcon("/general/questionDialog.png")); - defaults.put("Tree.openIcon", LookAndFeel.makeIcon(WindowsLookAndFeel.class, "icons/TreeOpen.gif")); - defaults.put("Tree.closedIcon", LookAndFeel.makeIcon(WindowsLookAndFeel.class, "icons/TreeClosed.gif")); - defaults.put("Tree.leafIcon", LookAndFeel.makeIcon(WindowsLookAndFeel.class, "icons/TreeLeaf.gif")); - defaults.put("Tree.expandedIcon", com.sun.java.swing.plaf.windows.WindowsTreeUI.ExpandedIcon.createExpandedIcon()); - defaults.put("Tree.collapsedIcon", com.sun.java.swing.plaf.windows.WindowsTreeUI.CollapsedIcon.createCollapsedIcon()); - defaults.put("Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { - "ctrl C", "copy", - "ctrl V", "paste", - "ctrl X", "cut", - "COPY", "copy", - "PASTE", "paste", - "CUT", "cut", - "control INSERT", "copy", - "shift INSERT", "paste", - "shift DELETE", "cut", - "RIGHT", "selectNextColumn", - "KP_RIGHT", "selectNextColumn", - "LEFT", "selectPreviousColumn", - "KP_LEFT", "selectPreviousColumn", - "DOWN", "selectNextRow", - "KP_DOWN", "selectNextRow", - "UP", "selectPreviousRow", - "KP_UP", "selectPreviousRow", - "shift RIGHT", "selectNextColumnExtendSelection", - "shift KP_RIGHT", "selectNextColumnExtendSelection", - "shift LEFT", "selectPreviousColumnExtendSelection", - "shift KP_LEFT", "selectPreviousColumnExtendSelection", - "shift DOWN", "selectNextRowExtendSelection", - "shift KP_DOWN", "selectNextRowExtendSelection", - "shift UP", "selectPreviousRowExtendSelection", - "shift KP_UP", "selectPreviousRowExtendSelection", - "PAGE_UP", "scrollUpChangeSelection", - "PAGE_DOWN", "scrollDownChangeSelection", - "HOME", "selectFirstColumn", - "END", "selectLastColumn", - "shift PAGE_UP", "scrollUpExtendSelection", - "shift PAGE_DOWN", "scrollDownExtendSelection", - "shift HOME", "selectFirstColumnExtendSelection", - "shift END", "selectLastColumnExtendSelection", - "ctrl PAGE_UP", "scrollLeftChangeSelection", - "ctrl PAGE_DOWN", "scrollRightChangeSelection", - "ctrl HOME", "selectFirstRow", - "ctrl END", "selectLastRow", - "ctrl shift PAGE_UP", "scrollRightExtendSelection", - "ctrl shift PAGE_DOWN", "scrollLeftExtendSelection", - "ctrl shift HOME", "selectFirstRowExtendSelection", - "ctrl shift END", "selectLastRowExtendSelection", - "TAB", "selectNextColumnCell", - "shift TAB", "selectPreviousColumnCell", - //"ENTER", "selectNextRowCell", - "shift ENTER", "selectPreviousRowCell", - "ctrl A", "selectAll", - //"ESCAPE", "cancel", - "F2", "startEditing" - })); - } - } - - private static class OurPopupFactory extends PopupFactory {} -} +/* + * 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.ide.ui; + +import com.intellij.CommonBundle; +import com.intellij.ide.IdeBundle; +import com.intellij.idea.StartupUtil; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.components.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.popup.util.PopupUtil; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.ui.ColoredSideBorder; +import com.intellij.ui.IdeaBlueMetalTheme; +import com.intellij.ui.ScreenUtil; +import com.intellij.ui.mac.MacPopupMenuUI; +import com.intellij.ui.plaf.beg.*; +import com.intellij.util.ui.UIUtil; +import com.sun.java.swing.plaf.windows.WindowsLookAndFeel; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.event.EventListenerList; +import javax.swing.plaf.ColorUIResource; +import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.metal.DefaultMetalTheme; +import javax.swing.plaf.metal.MetalLookAndFeel; +import javax.swing.text.DefaultEditorKit; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; + +/** + * @author Eugene Belyaev + * @author Vladimir Kondratyev + */ +@State( + name = "LafManager", + roamingType = RoamingType.PER_PLATFORM, + storages = {@Storage( + id = "other", + file = "$APP_CONFIG$/options.xml")}) +public final class LafManagerImpl extends LafManager implements ApplicationComponent, PersistentStateComponent { + private static final Logger LOG=Logger.getInstance("#com.intellij.ide.ui.LafManager"); + + @NonNls private static final String IDEA_LAF_CLASSNAME = "idea.laf.classname"; + + /** + * One of the possible values of -Didea.popup.weight property. Heavy weight means + * that all popups are shown inside the window. Under UNIXes it's possible to configure + * window manager "Focus follows mouse with Auto Raise". In this case popup window will + * be immediately closed after showing. + */ + @NonNls private static final String HEAVY_WEIGHT_POPUP="heavy"; + /** + * One of the possible values of -Didea.popup.weight property. Medium weight means + * that popup will be shouw inside the paren't JLayeredPane if it can be fit to it. + * Otherwise popup will be shown in the window. This mode is defaut for the Swing but + * it's very slow (much slower then heavy weight popups). + */ + @NonNls private static final String MEDIUM_WEIGHT_POPUP="medium"; + + private final EventListenerList myListenerList; + private final UIManager.LookAndFeelInfo[] myLafs; + private UIManager.LookAndFeelInfo myCurrentLaf; + + @NonNls private static final String[] ourPatcheableFontResources = new String[]{ + "Button.font", "ToggleButton.font", "RadioButton.font", "CheckBox.font", "ColorChooser.font", "ComboBox.font", + "Label.font", "List.font", "MenuBar.font", "MenuItem.font", "MenuItem.acceleratorFont", "RadioButtonMenuItem.font", + "CheckBoxMenuItem.font", "Menu.font", "PopupMenu.font", "OptionPane.font", "Panel.font", "ProgressBar.font", + "ScrollPane.font", "Viewport.font", "TabbedPane.font", "Table.font", "TableHeader.font", "TextField.font", + "PasswordField.font", "TextArea.font", "TextPane.font", "EditorPane.font", "TitledBorder.font", "ToolBar.font", + "ToolTip.font", "Tree.font" + }; + @NonNls private static final String[] ourFileChooserTextKeys = new String[] { + "FileChooser.viewMenuLabelText", "FileChooser.newFolderActionLabelText", "FileChooser.listViewActionLabelText", + "FileChooser.detailsViewActionLabelText", "FileChooser.refreshActionLabelText" + }; + + private final HashMap> myStoredDefaults = new HashMap>(); + private final UISettings myUiSettings; + + @NonNls private static final String ELEMENT_LAF = "laf"; + @NonNls private static final String ATTRIBUTE_CLASS_NAME = "class-name"; + + /** invoked by reflection + * @param uiSettings */ + LafManagerImpl(UISettings uiSettings){ + myUiSettings = uiSettings; + myListenerList=new EventListenerList(); + + IdeaLookAndFeelInfo ideaLaf=new IdeaLookAndFeelInfo(); + UIManager.LookAndFeelInfo[] installedLafs=UIManager.getInstalledLookAndFeels(); + + // Get all installed LAFs + myLafs=new UIManager.LookAndFeelInfo[1+installedLafs.length]; + myLafs[0]=ideaLaf; + System.arraycopy(installedLafs,0,myLafs,1,installedLafs.length); + Arrays.sort(myLafs,new MyComparator()); + + // Setup current LAF. Unfortunately it's system depended. + myCurrentLaf=getDefaultLaf(); + } + + /** + * Adds specified listener + */ + public void addLafManagerListener(@NotNull final LafManagerListener l){ + myListenerList.add(LafManagerListener.class, l); + } + + /** + * Removes specified listener + */ + public void removeLafManagerListener(@NotNull final LafManagerListener l){ + myListenerList.remove(LafManagerListener.class, l); + } + + private void fireLookAndFeelChanged(){ + LafManagerListener[] listeners = myListenerList.getListeners(LafManagerListener.class); + for (LafManagerListener listener : listeners) { + listener.lookAndFeelChanged(this); + } + } + + @NotNull + public String getComponentName(){ + return "LafManager"; + } + + public void initComponent() { + setCurrentLookAndFeel(findLaf(myCurrentLaf.getClassName())); // setup default LAF or one specfied by readExternal. + updateUI(); + } + + public void disposeComponent(){} + + public void loadState(final Element element) { + String className=null; + for (final Object o : element.getChildren()) { + Element child = (Element)o; + if (ELEMENT_LAF.equals(child.getName())) { + className = child.getAttributeValue(ATTRIBUTE_CLASS_NAME); + break; + } + } + + UIManager.LookAndFeelInfo laf=findLaf(className); + // If LAF is undefined (wrong class name or something else) we have set default LAF anyway. + if(laf==null){ + laf=getDefaultLaf(); + } + + if (myCurrentLaf != null && !laf.getClassName().equals(myCurrentLaf.getClassName())) { + setCurrentLookAndFeel(laf); + updateUI(); + } + + myCurrentLaf=laf; + } + + public Element getState() { + Element element = new Element("state"); + if(myCurrentLaf.getClassName()!=null){ + Element child=new Element(ELEMENT_LAF); + child.setAttribute(ATTRIBUTE_CLASS_NAME,myCurrentLaf.getClassName()); + element.addContent(child); + } + return element; + } + + public UIManager.LookAndFeelInfo[] getInstalledLookAndFeels(){ + return myLafs.clone(); + } + + public UIManager.LookAndFeelInfo getCurrentLookAndFeel(){ + return myCurrentLaf; + } + + public boolean isUnderAquaLookAndFeel() { + //noinspection HardCodedStringLiteral + return "Mac OS X".equals(getCurrentLookAndFeel().getName()); + } + + public boolean isUnderQuaquaLookAndFeel() { + //noinspection HardCodedStringLiteral + return "Quaqua".equals(getCurrentLookAndFeel().getName()); + } + + /** + * @return default LookAndFeelInfo for the running OS. For Win32 and + * Linux the method returns Alloy LAF or IDEA LAF if first not found, for Mac OS X it returns Aqua + * RubyMine uses Native L&F for linux as well + */ + private UIManager.LookAndFeelInfo getDefaultLaf(){ + if(SystemInfo.isMac) { + UIManager.LookAndFeelInfo laf=findLaf(UIManager.getSystemLookAndFeelClassName()); + LOG.assertTrue(laf!=null); + return laf; + } + else if (SystemInfo.isLinux && ApplicationNamesInfo.getInstance().getLowercaseProductName().equals("Rubymine")) { + UIManager.LookAndFeelInfo laf=findLaf(UIManager.getSystemLookAndFeelClassName()); + LOG.assertTrue(laf!=null); + return laf; + } + else { + String defaultLafName = StartupUtil.getDefaultLAF(); + if (defaultLafName != null) { + UIManager.LookAndFeelInfo defaultLaf = findLaf(defaultLafName); + if (defaultLaf != null) { + return defaultLaf; + } + } + return findLaf(IDEA_LAF_CLASSNAME); + } + } + + /** + * Finds LAF by its class name. + * will be returned. + */ + private UIManager.LookAndFeelInfo findLaf(String className){ + for (UIManager.LookAndFeelInfo laf : myLafs) { + if (Comparing.equal(laf.getClassName(), className)) { + return laf; + } + } + return null; + } + + /** + * Sets current LAF. The method doesn't update component hierarchy. + */ + public void setCurrentLookAndFeel(UIManager.LookAndFeelInfo lookAndFeelInfo){ + if(findLaf(lookAndFeelInfo.getClassName())==null){ + LOG.error("unknown LookAndFeel : "+lookAndFeelInfo); + return; + } + + // Set L&F + + if(IDEA_LAF_CLASSNAME.equals(lookAndFeelInfo.getClassName())){ // that is IDEA default LAF + IdeaLaf laf=new IdeaLaf(); + IdeaLaf.setCurrentTheme(new IdeaBlueMetalTheme()); + try { + UIManager.setLookAndFeel(laf); + } catch (Exception exc) { + Messages.showMessageDialog( + IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName()), + CommonBundle.getErrorTitle(), + Messages.getErrorIcon() + ); + return; + } + }else{ // non default LAF + try { + LookAndFeel laf=((LookAndFeel)Class.forName(lookAndFeelInfo.getClassName()).newInstance()); + if(laf instanceof MetalLookAndFeel){ + MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme()); + } + UIManager.setLookAndFeel(laf); + } catch(Exception exc) { + Messages.showMessageDialog( + IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName()), + CommonBundle.getErrorTitle(), + Messages.getErrorIcon() + ); + return; + } + } + myCurrentLaf=lookAndFeelInfo; + + // The following code is a trick! By default Swing uses lightweight and "medium" weight + // popups to show JPopupMenu. The code below force the creation of real heavyweight menus. + // It dramatically increases speed of popups. + + //noinspection HardCodedStringLiteral + String popupWeight=System.getProperty("idea.popup.weight"); + if(popupWeight==null){ // use defaults if popup weight isn't specified + if(SystemInfo.isWindows){ + popupWeight=HEAVY_WEIGHT_POPUP; + }else{ // UNIXes (Linux and MAC) go here + popupWeight=MEDIUM_WEIGHT_POPUP; + } + } else if (!HEAVY_WEIGHT_POPUP.equals(popupWeight) && !MEDIUM_WEIGHT_POPUP.equals(popupWeight)) { + throw new IllegalStateException("unknown value of property -Didea.popup.weight: " + popupWeight); + } + + if (SystemInfo.isMacOSLeopard) { + // Force heavy weight popups under Leopard, otherwise they don't have shadow or any kind of border. + popupWeight = HEAVY_WEIGHT_POPUP; + } + + popupWeight = popupWeight.trim(); + + PopupFactory popupFactory; + final PopupFactory oldFactory = PopupFactory.getSharedInstance(); + if (!(oldFactory instanceof OurPopupFactory)) { + popupFactory = new OurPopupFactory() { + public Popup getPopup( + Component owner, + Component contents, + int x, + int y + ) throws IllegalArgumentException { + final Point point = fixPopupLocation(contents, x, y); + + final int popupType = PopupUtil.getPopupType(this); + if (popupType >= 0) { + PopupUtil.setPopupType(oldFactory, popupType); + } + + return oldFactory.getPopup(owner, contents, point.x, point.y); + } + }; + + PopupUtil.setPopupType(popupFactory, HEAVY_WEIGHT_POPUP.equals(popupWeight) ? 2 : 1); + PopupFactory.setSharedInstance(popupFactory); + } + + // update ui for popup menu to get round corners + if (UIUtil.isUnderAquaLookAndFeel()) { + final UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults(); + uiDefaults.put("PopupMenuUI", MacPopupMenuUI.class.getCanonicalName()); + uiDefaults.put("Menu.invertedArrowIcon", getAquaMenuInvertedIcon()); + uiDefaults.put("Menu.disabledArrowIcon", getAquaMenuDisabledIcon()); + } + } + + @Nullable + private static Icon getAquaMenuDisabledIcon() { + final Icon arrowIcon = (Icon)UIManager.get("Menu.arrowIcon"); + if (arrowIcon != null) { + return IconLoader.getDisabledIcon(arrowIcon); + } + + return null; + } + + @Nullable + private static Icon getAquaMenuInvertedIcon() { + if (!UIUtil.isUnderAquaLookAndFeel()) return null; + final Icon arrow = (Icon) UIManager.get("Menu.arrowIcon"); + if (arrow == null) return null; + + try { + final Method method = arrow.getClass().getMethod("getInvertedIcon"); + if (method != null) { + method.setAccessible(true); + return (Icon) method.invoke(arrow); + } + + return null; + } + catch (NoSuchMethodException e1) { + return null; + } + catch (InvocationTargetException e1) { + return null; + } + catch (IllegalAccessException e1) { + return null; + } + } + + private Point fixPopupLocation(final Component contents, final int x, final int y) { + if (!(contents instanceof JToolTip)) return new Point(x, y); + + final PointerInfo info; + try { + info = MouseInfo.getPointerInfo(); + } + catch (InternalError e) { + // http://www.jetbrains.net/jira/browse/IDEADEV-21390 + // may happen under Mac OSX 10.5 + return new Point(x, y); + } + int deltaY = 0; + + if (info != null) { + final Point mouse = info.getLocation(); + deltaY = mouse.y - y; + } + + final Dimension size = contents.getPreferredSize(); + final Rectangle rec = new Rectangle(new Point(x, y), size); + ScreenUtil.moveRectangleToFitTheScreen(rec); + + if (rec.y < y) { + rec.y += deltaY; + } + + return rec.getLocation(); + } + + + /** + * Updates LAF of all windows. The method also updates font of components + * as it's configured in UISettings. + */ + public void updateUI(){ + UIDefaults lookAndFeelDefaults=UIManager.getLookAndFeelDefaults(); + initInputMapDefaults(lookAndFeelDefaults); + patchFileChooserStrings(lookAndFeelDefaults); + if (shouldPatchLAFFonts()) { + storeOriginalFontDefaults(lookAndFeelDefaults); + initFontDefaults(lookAndFeelDefaults); + } + else { + restoreOriginalFontDefaults(lookAndFeelDefaults); + } + + Frame[] frames=Frame.getFrames(); + for (Frame frame : frames) { + updateUI(frame); + } + fireLookAndFeelChanged(); + } + + private void patchFileChooserStrings(final UIDefaults defaults) { + if (!defaults.containsKey(ourFileChooserTextKeys [0])) { + // Alloy L&F does not define strings for names of context menu actions, so we have to patch them in here + for (String key : ourFileChooserTextKeys) { + defaults.put(key, IdeBundle.message(key)); + } + } + } + + private void restoreOriginalFontDefaults(UIDefaults defaults) { + UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel(); + HashMap lfDefaults = myStoredDefaults.get(lf); + if (lfDefaults != null) { + for (String resource : ourPatcheableFontResources) { + defaults.put(resource, lfDefaults.get(resource)); + } + } + } + + private void storeOriginalFontDefaults(UIDefaults defaults) { + UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel(); + HashMap lfDefaults = myStoredDefaults.get(lf); + if (lfDefaults == null) { + lfDefaults = new HashMap(); + for (String resource : ourPatcheableFontResources) { + lfDefaults.put(resource, defaults.get(resource)); + } + myStoredDefaults.put(lf, lfDefaults); + } + } + + private boolean shouldPatchLAFFonts() { + //noinspection HardCodedStringLiteral + return getCurrentLookAndFeel().getName().startsWith("IDEA") || UISettings.getInstance().OVERRIDE_NONIDEA_LAF_FONTS; + } + + private static void updateUI(Window window){ + if(!window.isDisplayable()){ + return; + } + SwingUtilities.updateComponentTreeUI(window); + Window[] children=window.getOwnedWindows(); + for (Window aChildren : children) { + updateUI(aChildren); + } + } + + /** + * Repaints all displayable window. + */ + public void repaintUI(){ + Frame[] frames=Frame.getFrames(); + for (Frame frame : frames) { + repaintUI(frame); + } + } + + private static void repaintUI(Window window){ + if(!window.isDisplayable()){ + return; + } + window.repaint(); + Window[] children=window.getOwnedWindows(); + for (Window aChildren : children) { + repaintUI(aChildren); + } + } + + private static void installCutCopyPasteShortcuts(InputMap inputMap, boolean useSimpleActionKeys){ + String copyActionKey = useSimpleActionKeys ? "copy" : DefaultEditorKit.copyAction; + String pasteActionKey = useSimpleActionKeys ? "paste" : DefaultEditorKit.pasteAction; + String cutActionKey = useSimpleActionKeys ? "cut" : DefaultEditorKit.cutAction; + // Ctrl+Ins, Shift+Ins, Shift+Del + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),copyActionKey); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT,KeyEvent.SHIFT_MASK|KeyEvent.SHIFT_DOWN_MASK),pasteActionKey); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,KeyEvent.SHIFT_MASK|KeyEvent.SHIFT_DOWN_MASK),cutActionKey); + // Ctrl+C, Ctrl+V, Ctrl+X + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),copyActionKey); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),pasteActionKey); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X,KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK),DefaultEditorKit.cutAction); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static void initInputMapDefaults(UIDefaults defaults){ + // Make ENTER work in JTrees + InputMap treeInputMap = (InputMap)defaults.get("Tree.focusInputMap"); + if(treeInputMap!=null){ // it's really possible. For examle, GTK+ doesn't have such map + treeInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0),"toggle"); + } + // Cut/Copy/Paste in JTextAreas + InputMap textAreaInputMap=(InputMap)defaults.get("TextArea.focusInputMap"); + if(textAreaInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) + installCutCopyPasteShortcuts(textAreaInputMap, false); + } + // Cut/Copy/Paste in JTextFields + InputMap textFieldInputMap=(InputMap)defaults.get("TextField.focusInputMap"); + if(textFieldInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) + installCutCopyPasteShortcuts(textFieldInputMap, false); + } + // Cut/Copy/Paste in JPAsswordField + InputMap passwordFieldInputMap=(InputMap)defaults.get("PasswordField.focusInputMap"); + if(passwordFieldInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) + installCutCopyPasteShortcuts(passwordFieldInputMap, false); + } + // Cut/Copy/Paste in JTables + InputMap tableInputMap=(InputMap)defaults.get("Table.ancestorInputMap"); + if(tableInputMap!=null){ // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) + installCutCopyPasteShortcuts(tableInputMap, true); + } + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private void initFontDefaults(UIDefaults defaults) { + defaults.put("Tree.ancestorInputMap", null); + int uiFontSize = myUiSettings.FONT_SIZE; + String uiFontFace = myUiSettings.FONT_FACE; + FontUIResource font = new FontUIResource(uiFontFace, Font.PLAIN, uiFontSize); + FontUIResource font1 = new FontUIResource("Serif", Font.PLAIN, uiFontSize); + FontUIResource font3 = new FontUIResource("Monospaced", Font.PLAIN, uiFontSize); + + for (String fontResource : ourPatcheableFontResources) { + defaults.put(fontResource, font); + } + + defaults.put("PasswordField.font", font3); + defaults.put("TextArea.font", font3); + defaults.put("TextPane.font", font1); + defaults.put("EditorPane.font", font1); + defaults.put("TitledBorder.font", font); + } + + private static final class IdeaLookAndFeelInfo extends UIManager.LookAndFeelInfo{ + public IdeaLookAndFeelInfo(){ + super(IdeBundle.message("idea.default.look.and.feel"), IDEA_LAF_CLASSNAME); + } + + public boolean equals(Object obj){ + return (obj instanceof IdeaLookAndFeelInfo); + } + + public int hashCode(){ + return getName().hashCode(); + } + } + + private static final class MyComparator implements Comparator{ + public int compare(Object obj1,Object obj2){ + String name1=((UIManager.LookAndFeelInfo)obj1).getName(); + String name2=((UIManager.LookAndFeelInfo)obj2).getName(); + return name1.compareToIgnoreCase(name2); + } + } + + private static final class IdeaLaf extends MetalLookAndFeel{ + protected void initComponentDefaults(UIDefaults table) { + super.initComponentDefaults(table); + initInputMapDefaults(table); + initIdeaDefaults(table); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static void initIdeaDefaults(UIDefaults defaults) { + defaults.put("Menu.maxGutterIconWidth", 18); + defaults.put("MenuItem.maxGutterIconWidth", 18); + // TODO[vova,anton] REMOVE!!! INVESTIGATE??? Borland??? + defaults.put("MenuItem.acceleratorDelimiter", "-"); + + defaults.put("TitledBorder.titleColor",IdeaBlueMetalTheme.primary1); + ColorUIResource col = new ColorUIResource(230, 230, 230); + defaults.put("ScrollBar.background", col); + defaults.put("ScrollBar.track", col); + +// Border scrollPaneBorder = new BorderUIResource(new BegBorders.ScrollPaneBorder()); +// defaults.put("ScrollPane.border", scrollPaneBorder); + defaults.put("TextField.border", BegBorders.getTextFieldBorder()); + defaults.put("PasswordField.border", BegBorders.getTextFieldBorder()); + Border popupMenuBorder = new BegPopupMenuBorder(); + defaults.put("PopupMenu.border", popupMenuBorder); + defaults.put("ScrollPane.border", BegBorders.getScrollPaneBorder()); + + defaults.put("ButtonUI", BegButtonUI.class.getName()); + defaults.put("ComboBoxUI", BegComboBoxUI.class.getName()); + defaults.put("RadioButtonUI", BegRadioButtonUI.class.getName()); + defaults.put("CheckBoxUI", BegCheckBoxUI.class.getName()); + defaults.put("TabbedPaneUI", BegTabbedPaneUI.class.getName()); + defaults.put("TableUI", BegTableUI.class.getName()); + defaults.put("TreeUI", BegTreeUI.class.getName()); +// defaults.put("ScrollPaneUI", BegScrollPaneUI.class.getName()); + + defaults.put("TabbedPane.tabInsets", new Insets(0, 4, 0, 4)); + defaults.put("ToolTip.background", new ColorUIResource(255, 255, 231)); + defaults.put("ToolTip.border", new ColoredSideBorder(Color.gray, Color.gray, Color.black, Color.black, 1)); + defaults.put("Tree.ancestorInputMap", null); + defaults.put("FileView.directoryIcon", IconLoader.getIcon("/nodes/folder.png")); + defaults.put("FileChooser.upFolderIcon", IconLoader.getIcon("/nodes/upFolder.png")); + defaults.put("FileChooser.newFolderIcon", IconLoader.getIcon("/nodes/newFolder.png")); + defaults.put("FileChooser.homeFolderIcon", IconLoader.getIcon("/nodes/homeFolder.png")); + defaults.put("OptionPane.errorIcon", IconLoader.getIcon("/general/errorDialog.png")); + defaults.put("OptionPane.informationIcon", IconLoader.getIcon("/general/informationDialog.png")); + defaults.put("OptionPane.warningIcon", IconLoader.getIcon("/general/warningDialog.png")); + defaults.put("OptionPane.questionIcon", IconLoader.getIcon("/general/questionDialog.png")); + defaults.put("Tree.openIcon", LookAndFeel.makeIcon(WindowsLookAndFeel.class, "icons/TreeOpen.gif")); + defaults.put("Tree.closedIcon", LookAndFeel.makeIcon(WindowsLookAndFeel.class, "icons/TreeClosed.gif")); + defaults.put("Tree.leafIcon", LookAndFeel.makeIcon(WindowsLookAndFeel.class, "icons/TreeLeaf.gif")); + defaults.put("Tree.expandedIcon", com.sun.java.swing.plaf.windows.WindowsTreeUI.ExpandedIcon.createExpandedIcon()); + defaults.put("Tree.collapsedIcon", com.sun.java.swing.plaf.windows.WindowsTreeUI.CollapsedIcon.createCollapsedIcon()); + defaults.put("Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { + "ctrl C", "copy", + "ctrl V", "paste", + "ctrl X", "cut", + "COPY", "copy", + "PASTE", "paste", + "CUT", "cut", + "control INSERT", "copy", + "shift INSERT", "paste", + "shift DELETE", "cut", + "RIGHT", "selectNextColumn", + "KP_RIGHT", "selectNextColumn", + "LEFT", "selectPreviousColumn", + "KP_LEFT", "selectPreviousColumn", + "DOWN", "selectNextRow", + "KP_DOWN", "selectNextRow", + "UP", "selectPreviousRow", + "KP_UP", "selectPreviousRow", + "shift RIGHT", "selectNextColumnExtendSelection", + "shift KP_RIGHT", "selectNextColumnExtendSelection", + "shift LEFT", "selectPreviousColumnExtendSelection", + "shift KP_LEFT", "selectPreviousColumnExtendSelection", + "shift DOWN", "selectNextRowExtendSelection", + "shift KP_DOWN", "selectNextRowExtendSelection", + "shift UP", "selectPreviousRowExtendSelection", + "shift KP_UP", "selectPreviousRowExtendSelection", + "PAGE_UP", "scrollUpChangeSelection", + "PAGE_DOWN", "scrollDownChangeSelection", + "HOME", "selectFirstColumn", + "END", "selectLastColumn", + "shift PAGE_UP", "scrollUpExtendSelection", + "shift PAGE_DOWN", "scrollDownExtendSelection", + "shift HOME", "selectFirstColumnExtendSelection", + "shift END", "selectLastColumnExtendSelection", + "ctrl PAGE_UP", "scrollLeftChangeSelection", + "ctrl PAGE_DOWN", "scrollRightChangeSelection", + "ctrl HOME", "selectFirstRow", + "ctrl END", "selectLastRow", + "ctrl shift PAGE_UP", "scrollRightExtendSelection", + "ctrl shift PAGE_DOWN", "scrollLeftExtendSelection", + "ctrl shift HOME", "selectFirstRowExtendSelection", + "ctrl shift END", "selectLastRowExtendSelection", + "TAB", "selectNextColumnCell", + "shift TAB", "selectPreviousColumnCell", + //"ENTER", "selectNextRowCell", + "shift ENTER", "selectPreviousRowCell", + "ctrl A", "selectAll", + //"ESCAPE", "cancel", + "F2", "startEditing" + })); + } + } + + private static class OurPopupFactory extends PopupFactory {} +} diff --git a/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java b/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java index 92ac510b4734..dab6e4eab014 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/ui/NotificationsListPanel.java @@ -1,861 +1,861 @@ -/* - * 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.notification.impl.ui; - -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.notification.impl.NotificationModelListener; -import com.intellij.notification.impl.NotificationsManagerImpl; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.*; -import com.intellij.openapi.ui.popup.util.MinimizeButton; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.wm.impl.content.GraphicsConfig; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.components.JBList; -import com.intellij.ui.components.panels.Wrapper; -import com.intellij.util.NotNullFunction; -import com.intellij.util.Processor; -import com.intellij.util.ui.BaseButtonBehavior; -import com.intellij.util.ui.TimedDeadzone; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.HyperlinkListener; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; -import java.awt.*; -import java.awt.event.*; -import java.awt.geom.RoundRectangle2D; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; - -/** - * @author spleaner - */ -public class NotificationsListPanel extends JPanel implements NotificationModelListener, Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.notification.impl.ui.NotificationsListPanel"); - private static final String REMOVE_KEY = "REMOVE"; - - private static final Icon CLOSE_ICON = IconLoader.getIcon("/general/balloonClose.png"); - - private Project myProject; - private final Wrapper myWrapper; - private JComponent myActiveComponent; - - private final JComponent myEmptyComponent; - private final JComponent myListComponent; - - public NotificationsListPanel(@Nullable final Project project) { - setLayout(new BorderLayout()); - myProject = project; - - myEmptyComponent = new JLabel("No new notifications.", JLabel.CENTER); - myListComponent = ItemsList.create(project, this); - - myWrapper = new Wrapper(); - myWrapper.setContent(getCurrentComponent(project)); - - setMinimumSize(new Dimension(350, 300)); - add(myWrapper, BorderLayout.CENTER); - } - - public void notificationsAdded(@NotNull Notification... notification) { - switchView(myProject); - } - - public void notificationsRemoved(@NotNull Notification... notification) { - switchView(myProject); - } - - public void notificationsRead(@NotNull Notification... notification) { - switchView(myProject); - } - - @Override - public void addNotify() { - super.addNotify(); - getManager().addListener(this); - } - - @Override - public void removeNotify() { - getManager().removeListener(this); - super.removeNotify(); - } - - private JComponent getCurrentComponent(@Nullable final Project project) { - final boolean empty = getManager().count(project) == 0; - final JComponent component = empty ? myEmptyComponent : myListComponent; - if (myActiveComponent == component) return null; - - myActiveComponent = component; - return myActiveComponent; - } - - protected void switchView(@Nullable final Project project) { - final JComponent component = getCurrentComponent(project); - if (component != null) { - myWrapper.setContent(component); - myWrapper.revalidate(); - myWrapper.repaint(); - } - } - - public void dispose() { - getManager().markRead(); - - myProject = null; - } - - private static NotificationsManagerImpl getManager() { - return NotificationsManagerImpl.getNotificationsManagerImpl(); - } - - static Dimension getMinSize() { - final Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); - size.width *= 0.1d; - size.height *= 0.1d; - return size; - } - - public JComponent getPreferredFocusedComponent() { - return myWrapper.getTargetComponent(); - } - - public static JBPopup show(@Nullable final Project project, @NotNull final JComponent parent) { - final NotificationsListPanel panel = new NotificationsListPanel(project); - final ComponentPopupBuilder builder = - JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel.getPreferredFocusedComponent()); - final JBPopup popup = builder.setResizable(true).setMinSize(getMinSize()).setDimensionServiceKey(null, "NotificationsPopup", true) - .setCancelOnClickOutside(false).setBelongsToGlobalPopupStack(false).setCancelButton(new MinimizeButton("Hide")).setMovable(true) - .setRequestFocus(true).setTitle("Notifications").createPopup(); - - popup.addListener(new JBPopupListener.Adapter() { - @Override - public void onClosed(LightweightWindowEvent event) { - Disposer.dispose(panel); - } - }); - - popup.showInCenterOf(SwingUtilities.getRootPane(parent)); - return popup; - } - - private static class NotificationsListRenderer extends JComponent implements ListCellRenderer { - private final JTextPane myText; - private boolean mySelected; - private boolean myHasFocus; - private final JLabel myIconLabel; - private Processor myProc; - private boolean myWasRead; - private JList myList; - private JTextPane myFakeTextPane; - private JViewport myFakeViewport; - - private NotificationsListRenderer() { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4)); - - setOpaque(false); - - myIconLabel = new JLabel(); - myIconLabel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); - myIconLabel.setOpaque(false); - - myText = new JTextPane() { - @Override - public void setCursor(Cursor cursor) { - super.setCursor(cursor); - onCursorChanged(cursor); - } - }; - - myFakeTextPane = new JTextPane(); - myText.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); - myFakeTextPane.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); - myText.setOpaque(false); - if (UIUtil.isUnderNimbusLookAndFeel()) { - myText.setBackground(new Color(0, 0, 0, 0)); - } - - myText.setEditable(false); - myFakeTextPane.setEditable(false); - myText.setEditorKit(UIUtil.getHTMLEditorKit()); - myFakeTextPane.setEditorKit(UIUtil.getHTMLEditorKit()); - - myFakeViewport = new JViewport(); - myFakeViewport.setView(myFakeTextPane); - - final Wrapper.North comp = new Wrapper.North(myIconLabel); - comp.setOpaque(false); - add(comp, BorderLayout.WEST); - add(myText, BorderLayout.CENTER); - } - - public JTextPane getText() { - return myText; - } - - public void setCursorHandler(Processor proc) { - myProc = proc; - } - - public void resetCursorHandler() { - myProc = null; - } - - public void onCursorChanged(Cursor cursor) { - if (myProc != null) myProc.process(cursor); - } - - @Override - public Dimension getPreferredSize() { - final Container parent = myList.getParent(); - if (parent != null) { - myFakeTextPane.setText(myText.getText()); - final Dimension size = parent.getSize(); - myFakeViewport.setSize(size); - final Dimension preferredSize = myFakeTextPane.getPreferredSize(); - - final Insets insets = getInsets(); - return new Dimension(Math.min(size.width - 20, preferredSize.width), preferredSize.height + insets.top + insets.bottom); - } - - return super.getPreferredSize(); - } - - @Override - protected void paintComponent(Graphics g) { - final Graphics2D g2d = (Graphics2D)g; - - final Rectangle bounds = getBounds(); - final Insets insets = getInsets(); - - final GraphicsConfig cfg = new GraphicsConfig(g); - cfg.setAntialiasing(true); - - final Shape shape = new RoundRectangle2D.Double(insets.left, insets.top, bounds.width - 1 - insets.left - insets.right, - bounds.height - 1 - insets.top - insets.bottom, 6, 6); - - if (mySelected) { - g2d.setColor(UIUtil.getListSelectionBackground()); - g2d.fillRect(0, 0, bounds.width, bounds.height); - } - - g2d.setColor(Color.WHITE); - g2d.fill(shape); - - - Color bgColor = getBackground(); - if (myWasRead) { - bgColor = new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), 60); - } - - g2d.setColor(bgColor); - g2d.fill(shape); - - g2d.setColor(myHasFocus || mySelected ? getBackground().darker().darker() : myWasRead ? getBackground() : getBackground().darker()); - g2d.draw(shape); - cfg.restore(); - - super.paintComponent(g); - } - - public Component getListCellRendererComponent(final JList list, - final Object value, - final int index, - final boolean isSelected, - final boolean cellHasFocus) { - LOG.assertTrue(value instanceof Notification); - final Notification notification = (Notification)value; - - myList = list; - - mySelected = isSelected; - myHasFocus = cellHasFocus; - - myText.setText(NotificationsUtil.buildHtml(notification)); - myIconLabel.setIcon(NotificationsUtil.getIcon(notification)); - myWasRead = NotificationsManagerImpl.getNotificationsManagerImpl().wasRead(notification); - - setBackground(NotificationsUtil.getBackground(notification)); - - return this; - } - } - - private static class NotificationsListModel extends AbstractListModel implements NotificationModelListener, Disposable { - private final List myNotifications = new ArrayList(); - private NotificationType myType; - private Project myProject; - private final NotNullFunction> myRebuildFunction; - private boolean myArchive; - - private NotificationsListModel(@Nullable Project project) { - myProject = project; - - myRebuildFunction = new NotNullFunction>() { - @NotNull - public Collection fun(Project project) { - return getManager().getByType(myType, project); - } - }; - - getManager().addListener(this); - rebuildList(); - } - - public void dispose() { - getManager().removeListener(this); - myProject = null; - } - - public int getSize() { - return myNotifications.size(); - } - - public Object getElementAt(final int index) { - return index < myNotifications.size() ? myNotifications.get(index) : null; - } - - private void rebuildList() { - myNotifications.clear(); - myNotifications.addAll(myRebuildFunction.fun(myProject)); - fireContentsChanged(this, 0, myNotifications.size() - 1); - } - - public void filter(final NotificationType type) { - myType = type; - rebuildList(); - } - - public void notificationsAdded(@NotNull Notification... notification) { - rebuildList(); - } - - public void notificationsRemoved(@NotNull Notification... notification) { - rebuildList(); - } - - public void notificationsRead(@NotNull Notification... notification) { - rebuildList(); - } - } - - private static class ItemsList extends JBList { - private ItemsList(final NotificationsListModel model) { - super(model); - setOpaque(false); - - setCellRenderer(new NotificationsListRenderer()); - getSelectionModel().setSelectionInterval(0, 0); - - getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), REMOVE_KEY); - getActionMap().put(REMOVE_KEY, new AbstractAction() { - public void actionPerformed(final ActionEvent e) { - removeSelected(); - } - }); - - setBackground(UIUtil.getPanelBackground()); - - addMouseMotionListener(new MouseMotionListener() { - public void mouseMoved(MouseEvent e) { - processMouse(e, false); - } - - public void mouseDragged(MouseEvent e) { - } - }); - - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - if (!e.isPopupTrigger()) { - processMouse(e, true); - } - } - }); - - ExpireButton.install(this); - } - - @Override - protected boolean shouldInstallItemTooltipExpander() { - return false; - } - - private void processMouse(final MouseEvent e, final boolean click) { - final int index = locationToIndex(e.getPoint()); - if (index > -1) { - final Object value = getModel().getElementAt(index); - if (value != null && value instanceof Notification) { - final Notification notification = (Notification)value; - final Component renderer = getCellRenderer().getListCellRendererComponent(this, value, index, false, false); - if (renderer instanceof NotificationsListRenderer) { - final Rectangle bounds = getCellBounds(index, index); - renderer.setBounds(bounds); - renderer.doLayout(); - - final JTextPane text = ((NotificationsListRenderer)renderer).getText(); - - Processor processor; - HyperlinkListener listener = null; - if (click) { - listener = NotificationsUtil.wrapListener(notification); - if (listener != null) text.addHyperlinkListener(listener); - } - else { - processor = new Processor() { - public boolean process(Cursor cursor) { - ItemsList.this.setCursor(cursor); - return true; - } - }; - - ((NotificationsListRenderer)renderer).setCursorHandler(processor); - } - - final Point point = e.getPoint(); - point.translate(-bounds.x, -bounds.y); - - final Rectangle r = text.getBounds(); - point.translate(-r.x, -r.y); - - final MouseEvent newEvent = - new MouseEvent(text, e.getID(), e.getWhen(), e.getModifiers(), point.x, point.y, e.getClickCount(), e.isPopupTrigger(), - e.getButton()); - - text.dispatchEvent(newEvent); - - ((NotificationsListRenderer)renderer).resetCursorHandler(); - if (listener != null) { - text.removeHyperlinkListener(listener); - } - } - } - } - } - - @SuppressWarnings({"ConstantConditions"}) - @Override - public NotificationsListModel getModel() { - final ListModel listModel = super.getModel(); - return listModel instanceof NotificationsListModel ? (NotificationsListModel)listModel : null; - } - - public void removeSelected() { - final ListSelectionModel model = getSelectionModel(); - final NotificationsListModel listModel = getModel(); - if (!model.isSelectionEmpty()) { - final int min = model.getMinSelectionIndex(); - final int max = model.getMaxSelectionIndex(); - - final List tbr = new ArrayList(); - for (int i = min; i <= max; i++) { - if (model.isSelectedIndex(i)) { - final Notification notification = (Notification)listModel.getElementAt(i); - if (notification != null) { - tbr.add(notification); - } - } - } - - if (tbr.size() > 0) { - getManager().remove(tbr.toArray(new Notification[tbr.size()])); - - final int toSelect = Math.min(min, listModel.getSize() - 1); - model.clearSelection(); - if (toSelect >= 0) { - model.setSelectionInterval(toSelect, toSelect); - scrollRectToVisible(getCellBounds(toSelect, toSelect)); - } - } - } - - revalidate(); - repaint(); - } - - private static void createFilterButton(final JPanel parent, - final ButtonGroup group, - final String title, - final ActionListener listener, - final NotNullFunction titleCallback, - final char mnemonic, - final boolean active) { - final StickyButton b = new MyButton(title, listener) { - @Override - public void updateTitle() { - setText(titleCallback.fun(this)); - } - }; - - parent.add(b); - group.add(b); - - b.setFocusable(false); - b.setSelected(active); - b.setMnemonic(mnemonic); - } - - private static void updateButtons(@NotNull final JComponent filterBar) { - final Component[] components = filterBar.getComponents(); - for (final Component c : components) { - if (c instanceof MyButton) { - ((MyButton)c).updateTitle(); - } - } - } - - private static JComponent buildFilterBar(final ItemsList list, final Project project) { - final JPanel box = new JPanel(); - box.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, box.getBackground().darker()), - BorderFactory.createEmptyBorder(3, 3, 3, 3))); - box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS)); - - final ButtonGroup buttonGroup = new ButtonGroup(); - - createFilterButton(box, buttonGroup, "All", new ActionListener() { - public void actionPerformed(final ActionEvent e) { - list.filter(null); - } - }, new NotNullFunction() { - @NotNull - public String fun(MyButton myButton) { - final int i = count(null, project); - if (i > 0) { - return String.format("All (%s)", i); - } - - return "All"; - } - }, 'A', true); - - createFilterButton(box, buttonGroup, "Error", new ActionListener() { - public void actionPerformed(final ActionEvent e) { - list.filter(NotificationType.ERROR); - } - }, new NotNullFunction() { - @NotNull - public String fun(MyButton myButton) { - final int i = count(NotificationType.ERROR, project); - myButton.setVisible(i > 0); - if (i > 0) { - return String.format("Error (%s)", i); - } - else if (myButton.isSelected()) { - switchToAll(buttonGroup); - } - - return "Error"; - } - }, 'E', false); - - createFilterButton(box, buttonGroup, "Warning", new ActionListener() { - public void actionPerformed(final ActionEvent e) { - list.filter(NotificationType.WARNING); - } - }, new NotNullFunction() { - @NotNull - public String fun(MyButton myButton) { - final int i = count(NotificationType.WARNING, project); - myButton.setVisible(i > 0); - if (i > 0) { - return String.format("Warning (%s)", i); - } - else if (myButton.isSelected()) { - switchToAll(buttonGroup); - } - - return "Warning"; - } - }, 'W', false); - - createFilterButton(box, buttonGroup, "Information", new ActionListener() { - public void actionPerformed(final ActionEvent e) { - list.filter(NotificationType.INFORMATION); - } - }, new NotNullFunction() { - @NotNull - public String fun(MyButton myButton) { - final int i = count(NotificationType.INFORMATION, project); - myButton.setVisible(i > 0); - if (i > 0) { - return String.format("Information (%s)", i); - } - else if (myButton.isSelected()) { - switchToAll(buttonGroup); - } - - return "Information"; - } - }, 'I', false); - - return box; - } - - private static void switchToAll(final ButtonGroup buttonGroup) { - final Enumeration enumeration = buttonGroup.getElements(); - while (enumeration.hasMoreElements()) { - final AbstractButton button = enumeration.nextElement(); - if (button.getText().startsWith("All")) { - button.doClick(); - return; - } - } - } - - private static int count(@Nullable final NotificationType type, @Nullable final Project project) { - return NotificationsManagerImpl.getNotificationsManagerImpl().getByType(type, project).size(); - } - - private void filter(@Nullable final NotificationType type) { - final NotificationsListModel listModel = getModel(); - listModel.filter(type); - if (listModel.getSize() > 0) setSelectedIndex(0); - } - - public static JComponent create(final Project project, final Disposable parentDisposable) { - final NotificationsListModel model = new NotificationsListModel(project); - Disposer.register(parentDisposable, model); - - // TODO: switch filter if removed all of the notifications from current one! - - final ItemsList list = new ItemsList(model); - list.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - list.setCellRenderer(new NotificationsListRenderer()); // request cell renderer size invalidation - } - }); - - final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(list); - scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); - - scrollPane.setBorder(null); - scrollPane.getViewport().setBackground(UIUtil.getPanelBackground()); - - final JComponent buttonBar = buildFilterBar(list, project); - model.addListDataListener(new ListDataListener() { - public void intervalAdded(ListDataEvent e) { - updateButtons(buttonBar); - } - - public void intervalRemoved(ListDataEvent e) { - updateButtons(buttonBar); - } - - public void contentsChanged(ListDataEvent e) { - updateButtons(buttonBar); - } - }); - - final JPanel panel = new JPanel(new BorderLayout()) { - @Override - public void requestFocus() { - updateButtons(buttonBar); - list.requestFocus(); - } - }; - - panel.add(buttonBar, BorderLayout.NORTH); - panel.add(scrollPane, BorderLayout.CENTER); - - return panel; - } - } - - private static class ExpireButton extends JComponent { - private int myIndex = -1; - private BaseButtonBehavior myBehavior; - private JList myList; - - private ExpireButton(final JList list) { - myList = list; - - final MouseAdapter adapter = new MouseAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - showAtPoint(e); - } - - @Override - public void mouseExited(MouseEvent e) { - hideCloseButton(e); - } - }; - - list.addMouseMotionListener(adapter); - list.addMouseListener(adapter); - - setOpaque(false); - setToolTipText("Delete (" + KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("DELETE")) + ")"); - - myBehavior = new BaseButtonBehavior(this, TimedDeadzone.NULL) { - @Override - protected void execute(final MouseEvent e) { - expire(); - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - showAtPoint(e); - } - }); - } - }; - } - - private void showAtPoint(MouseEvent e) { - final Point point = e.getSource() == myList ? e.getPoint() : SwingUtilities.convertPoint((Component)e.getSource(), e.getPoint(), myList); - final int index = myList.locationToIndex(point); - if (index > -1) { - final Object value = myList.getModel().getElementAt(index); - if (value != null && value instanceof Notification) { - final Rectangle bounds = myList.getCellBounds(index, index); - if (myList.getVisibleRect().contains(bounds) && bounds.contains(point)) { - toggle(index, false); - } - } - } - } - - private void expire() { - if (myIndex != -1) { - final ListModel model = myList.getModel(); - final Object o = model.getElementAt(myIndex); - if (o instanceof Notification) { - final Notification notification = (Notification)o; - if (!notification.isExpired()) { - notification.expire(); - } - } - } - - myIndex = -1; - setVisible(false); - } - - public void setIndex(final int index) { - myIndex = index; - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() { - return new Dimension(CLOSE_ICON.getIconWidth() + 2, CLOSE_ICON.getIconHeight() + 2); - } - - @Override - public void paint(Graphics g) { - final Rectangle r = getBounds(); - if (myBehavior.isPressedByMouse()) { - CLOSE_ICON.paintIcon(this, g, r.width - CLOSE_ICON.getIconWidth(), r.height - CLOSE_ICON.getIconHeight()); - } - else { - CLOSE_ICON.paintIcon(this, g, r.width - CLOSE_ICON.getIconWidth() - 2, r.height - CLOSE_ICON.getIconHeight() - 2); - } - } - - public static void install(final JList list) { - final ExpireButton button = new ExpireButton(list); - button.setVisible(false); - } - - public void hideCloseButton(final MouseEvent e) { - if (e != null) { - final Object source = e.getSource(); - if (source instanceof JComponent) { - final Container parent = getParent(); - if (parent != null) { - final Point point = SwingUtilities.convertPoint((Component) source, e.getPoint(), getParent()); - if (!getBounds().contains(point)) { - toggle(-1, true); - } - } - } - } - else { - toggle(-1, true); - } - } - - private void toggle(final int index, final boolean hide) { - if (hide) { - setIndex(index); - setVisible(false); - return; - } - - if (getParent() == null) { - final Window window = SwingUtilities.getWindowAncestor(myList); - if (!(window instanceof JDialog)) return; - final JLayeredPane layeredPane = ((JDialog)window).getLayeredPane(); - if (layeredPane == null) return; - layeredPane.add(this); - - layeredPane.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - toggle(-1, true); - } - }); - } - - if (getParent() != null) { - final Dimension preferredSize = getPreferredSize(); - final Rectangle cellBounds = myList.getCellBounds(index, index); - Point location = new Point(cellBounds.x + cellBounds.width - preferredSize.width, cellBounds.y); - location = SwingUtilities.convertPoint(myList, location, getParent()); - setIndex(index); - setBounds(location.x, location.y, preferredSize.width, preferredSize.height); - } - - setVisible(true); - } - - public int getIndex() { - return myIndex; - } - } - - private abstract static class MyButton extends StickyButton { - private MyButton(String text, ActionListener listener) { - super(text, listener); - } - - private MyButton(String text) { - super(text); - } - - public abstract void updateTitle(); - } -} +/* + * 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.notification.impl.ui; + +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.impl.NotificationModelListener; +import com.intellij.notification.impl.NotificationsManagerImpl; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.ui.popup.util.MinimizeButton; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.wm.impl.content.GraphicsConfig; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.panels.Wrapper; +import com.intellij.util.NotNullFunction; +import com.intellij.util.Processor; +import com.intellij.util.ui.BaseButtonBehavior; +import com.intellij.util.ui.TimedDeadzone; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.HyperlinkListener; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.RoundRectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; + +/** + * @author spleaner + */ +public class NotificationsListPanel extends JPanel implements NotificationModelListener, Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.notification.impl.ui.NotificationsListPanel"); + private static final String REMOVE_KEY = "REMOVE"; + + private static final Icon CLOSE_ICON = IconLoader.getIcon("/general/balloonClose.png"); + + private Project myProject; + private final Wrapper myWrapper; + private JComponent myActiveComponent; + + private final JComponent myEmptyComponent; + private final JComponent myListComponent; + + public NotificationsListPanel(@Nullable final Project project) { + setLayout(new BorderLayout()); + myProject = project; + + myEmptyComponent = new JLabel("No new notifications.", JLabel.CENTER); + myListComponent = ItemsList.create(project, this); + + myWrapper = new Wrapper(); + myWrapper.setContent(getCurrentComponent(project)); + + setMinimumSize(new Dimension(350, 300)); + add(myWrapper, BorderLayout.CENTER); + } + + public void notificationsAdded(@NotNull Notification... notification) { + switchView(myProject); + } + + public void notificationsRemoved(@NotNull Notification... notification) { + switchView(myProject); + } + + public void notificationsRead(@NotNull Notification... notification) { + switchView(myProject); + } + + @Override + public void addNotify() { + super.addNotify(); + getManager().addListener(this); + } + + @Override + public void removeNotify() { + getManager().removeListener(this); + super.removeNotify(); + } + + private JComponent getCurrentComponent(@Nullable final Project project) { + final boolean empty = getManager().count(project) == 0; + final JComponent component = empty ? myEmptyComponent : myListComponent; + if (myActiveComponent == component) return null; + + myActiveComponent = component; + return myActiveComponent; + } + + protected void switchView(@Nullable final Project project) { + final JComponent component = getCurrentComponent(project); + if (component != null) { + myWrapper.setContent(component); + myWrapper.revalidate(); + myWrapper.repaint(); + } + } + + public void dispose() { + getManager().markRead(); + + myProject = null; + } + + private static NotificationsManagerImpl getManager() { + return NotificationsManagerImpl.getNotificationsManagerImpl(); + } + + static Dimension getMinSize() { + final Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + size.width *= 0.1d; + size.height *= 0.1d; + return size; + } + + public JComponent getPreferredFocusedComponent() { + return myWrapper.getTargetComponent(); + } + + public static JBPopup show(@Nullable final Project project, @NotNull final JComponent parent) { + final NotificationsListPanel panel = new NotificationsListPanel(project); + final ComponentPopupBuilder builder = + JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel.getPreferredFocusedComponent()); + final JBPopup popup = builder.setResizable(true).setMinSize(getMinSize()).setDimensionServiceKey(null, "NotificationsPopup", true) + .setCancelOnClickOutside(false).setBelongsToGlobalPopupStack(false).setCancelButton(new MinimizeButton("Hide")).setMovable(true) + .setRequestFocus(true).setTitle("Notifications").createPopup(); + + popup.addListener(new JBPopupListener.Adapter() { + @Override + public void onClosed(LightweightWindowEvent event) { + Disposer.dispose(panel); + } + }); + + popup.showInCenterOf(SwingUtilities.getRootPane(parent)); + return popup; + } + + private static class NotificationsListRenderer extends JComponent implements ListCellRenderer { + private final JTextPane myText; + private boolean mySelected; + private boolean myHasFocus; + private final JLabel myIconLabel; + private Processor myProc; + private boolean myWasRead; + private JList myList; + private JTextPane myFakeTextPane; + private JViewport myFakeViewport; + + private NotificationsListRenderer() { + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4)); + + setOpaque(false); + + myIconLabel = new JLabel(); + myIconLabel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + myIconLabel.setOpaque(false); + + myText = new JTextPane() { + @Override + public void setCursor(Cursor cursor) { + super.setCursor(cursor); + onCursorChanged(cursor); + } + }; + + myFakeTextPane = new JTextPane(); + myText.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + myFakeTextPane.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + myText.setOpaque(false); + if (UIUtil.isUnderNimbusLookAndFeel()) { + myText.setBackground(new Color(0, 0, 0, 0)); + } + + myText.setEditable(false); + myFakeTextPane.setEditable(false); + myText.setEditorKit(UIUtil.getHTMLEditorKit()); + myFakeTextPane.setEditorKit(UIUtil.getHTMLEditorKit()); + + myFakeViewport = new JViewport(); + myFakeViewport.setView(myFakeTextPane); + + final Wrapper.North comp = new Wrapper.North(myIconLabel); + comp.setOpaque(false); + add(comp, BorderLayout.WEST); + add(myText, BorderLayout.CENTER); + } + + public JTextPane getText() { + return myText; + } + + public void setCursorHandler(Processor proc) { + myProc = proc; + } + + public void resetCursorHandler() { + myProc = null; + } + + public void onCursorChanged(Cursor cursor) { + if (myProc != null) myProc.process(cursor); + } + + @Override + public Dimension getPreferredSize() { + final Container parent = myList.getParent(); + if (parent != null) { + myFakeTextPane.setText(myText.getText()); + final Dimension size = parent.getSize(); + myFakeViewport.setSize(size); + final Dimension preferredSize = myFakeTextPane.getPreferredSize(); + + final Insets insets = getInsets(); + return new Dimension(Math.min(size.width - 20, preferredSize.width), preferredSize.height + insets.top + insets.bottom); + } + + return super.getPreferredSize(); + } + + @Override + protected void paintComponent(Graphics g) { + final Graphics2D g2d = (Graphics2D)g; + + final Rectangle bounds = getBounds(); + final Insets insets = getInsets(); + + final GraphicsConfig cfg = new GraphicsConfig(g); + cfg.setAntialiasing(true); + + final Shape shape = new RoundRectangle2D.Double(insets.left, insets.top, bounds.width - 1 - insets.left - insets.right, + bounds.height - 1 - insets.top - insets.bottom, 6, 6); + + if (mySelected) { + g2d.setColor(UIUtil.getListSelectionBackground()); + g2d.fillRect(0, 0, bounds.width, bounds.height); + } + + g2d.setColor(Color.WHITE); + g2d.fill(shape); + + + Color bgColor = getBackground(); + if (myWasRead) { + bgColor = new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), 60); + } + + g2d.setColor(bgColor); + g2d.fill(shape); + + g2d.setColor(myHasFocus || mySelected ? getBackground().darker().darker() : myWasRead ? getBackground() : getBackground().darker()); + g2d.draw(shape); + cfg.restore(); + + super.paintComponent(g); + } + + public Component getListCellRendererComponent(final JList list, + final Object value, + final int index, + final boolean isSelected, + final boolean cellHasFocus) { + LOG.assertTrue(value instanceof Notification); + final Notification notification = (Notification)value; + + myList = list; + + mySelected = isSelected; + myHasFocus = cellHasFocus; + + myText.setText(NotificationsUtil.buildHtml(notification)); + myIconLabel.setIcon(NotificationsUtil.getIcon(notification)); + myWasRead = NotificationsManagerImpl.getNotificationsManagerImpl().wasRead(notification); + + setBackground(NotificationsUtil.getBackground(notification)); + + return this; + } + } + + private static class NotificationsListModel extends AbstractListModel implements NotificationModelListener, Disposable { + private final List myNotifications = new ArrayList(); + private NotificationType myType; + private Project myProject; + private final NotNullFunction> myRebuildFunction; + private boolean myArchive; + + private NotificationsListModel(@Nullable Project project) { + myProject = project; + + myRebuildFunction = new NotNullFunction>() { + @NotNull + public Collection fun(Project project) { + return getManager().getByType(myType, project); + } + }; + + getManager().addListener(this); + rebuildList(); + } + + public void dispose() { + getManager().removeListener(this); + myProject = null; + } + + public int getSize() { + return myNotifications.size(); + } + + public Object getElementAt(final int index) { + return index < myNotifications.size() ? myNotifications.get(index) : null; + } + + private void rebuildList() { + myNotifications.clear(); + myNotifications.addAll(myRebuildFunction.fun(myProject)); + fireContentsChanged(this, 0, myNotifications.size() - 1); + } + + public void filter(final NotificationType type) { + myType = type; + rebuildList(); + } + + public void notificationsAdded(@NotNull Notification... notification) { + rebuildList(); + } + + public void notificationsRemoved(@NotNull Notification... notification) { + rebuildList(); + } + + public void notificationsRead(@NotNull Notification... notification) { + rebuildList(); + } + } + + private static class ItemsList extends JBList { + private ItemsList(final NotificationsListModel model) { + super(model); + setOpaque(false); + + setCellRenderer(new NotificationsListRenderer()); + getSelectionModel().setSelectionInterval(0, 0); + + getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), REMOVE_KEY); + getActionMap().put(REMOVE_KEY, new AbstractAction() { + public void actionPerformed(final ActionEvent e) { + removeSelected(); + } + }); + + setBackground(UIUtil.getPanelBackground()); + + addMouseMotionListener(new MouseMotionListener() { + public void mouseMoved(MouseEvent e) { + processMouse(e, false); + } + + public void mouseDragged(MouseEvent e) { + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + if (!e.isPopupTrigger()) { + processMouse(e, true); + } + } + }); + + ExpireButton.install(this); + } + + @Override + protected boolean shouldInstallItemTooltipExpander() { + return false; + } + + private void processMouse(final MouseEvent e, final boolean click) { + final int index = locationToIndex(e.getPoint()); + if (index > -1) { + final Object value = getModel().getElementAt(index); + if (value != null && value instanceof Notification) { + final Notification notification = (Notification)value; + final Component renderer = getCellRenderer().getListCellRendererComponent(this, value, index, false, false); + if (renderer instanceof NotificationsListRenderer) { + final Rectangle bounds = getCellBounds(index, index); + renderer.setBounds(bounds); + renderer.doLayout(); + + final JTextPane text = ((NotificationsListRenderer)renderer).getText(); + + Processor processor; + HyperlinkListener listener = null; + if (click) { + listener = NotificationsUtil.wrapListener(notification); + if (listener != null) text.addHyperlinkListener(listener); + } + else { + processor = new Processor() { + public boolean process(Cursor cursor) { + ItemsList.this.setCursor(cursor); + return true; + } + }; + + ((NotificationsListRenderer)renderer).setCursorHandler(processor); + } + + final Point point = e.getPoint(); + point.translate(-bounds.x, -bounds.y); + + final Rectangle r = text.getBounds(); + point.translate(-r.x, -r.y); + + final MouseEvent newEvent = + new MouseEvent(text, e.getID(), e.getWhen(), e.getModifiers(), point.x, point.y, e.getClickCount(), e.isPopupTrigger(), + e.getButton()); + + text.dispatchEvent(newEvent); + + ((NotificationsListRenderer)renderer).resetCursorHandler(); + if (listener != null) { + text.removeHyperlinkListener(listener); + } + } + } + } + } + + @SuppressWarnings({"ConstantConditions"}) + @Override + public NotificationsListModel getModel() { + final ListModel listModel = super.getModel(); + return listModel instanceof NotificationsListModel ? (NotificationsListModel)listModel : null; + } + + public void removeSelected() { + final ListSelectionModel model = getSelectionModel(); + final NotificationsListModel listModel = getModel(); + if (!model.isSelectionEmpty()) { + final int min = model.getMinSelectionIndex(); + final int max = model.getMaxSelectionIndex(); + + final List tbr = new ArrayList(); + for (int i = min; i <= max; i++) { + if (model.isSelectedIndex(i)) { + final Notification notification = (Notification)listModel.getElementAt(i); + if (notification != null) { + tbr.add(notification); + } + } + } + + if (tbr.size() > 0) { + getManager().remove(tbr.toArray(new Notification[tbr.size()])); + + final int toSelect = Math.min(min, listModel.getSize() - 1); + model.clearSelection(); + if (toSelect >= 0) { + model.setSelectionInterval(toSelect, toSelect); + scrollRectToVisible(getCellBounds(toSelect, toSelect)); + } + } + } + + revalidate(); + repaint(); + } + + private static void createFilterButton(final JPanel parent, + final ButtonGroup group, + final String title, + final ActionListener listener, + final NotNullFunction titleCallback, + final char mnemonic, + final boolean active) { + final StickyButton b = new MyButton(title, listener) { + @Override + public void updateTitle() { + setText(titleCallback.fun(this)); + } + }; + + parent.add(b); + group.add(b); + + b.setFocusable(false); + b.setSelected(active); + b.setMnemonic(mnemonic); + } + + private static void updateButtons(@NotNull final JComponent filterBar) { + final Component[] components = filterBar.getComponents(); + for (final Component c : components) { + if (c instanceof MyButton) { + ((MyButton)c).updateTitle(); + } + } + } + + private static JComponent buildFilterBar(final ItemsList list, final Project project) { + final JPanel box = new JPanel(); + box.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, box.getBackground().darker()), + BorderFactory.createEmptyBorder(3, 3, 3, 3))); + box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS)); + + final ButtonGroup buttonGroup = new ButtonGroup(); + + createFilterButton(box, buttonGroup, "All", new ActionListener() { + public void actionPerformed(final ActionEvent e) { + list.filter(null); + } + }, new NotNullFunction() { + @NotNull + public String fun(MyButton myButton) { + final int i = count(null, project); + if (i > 0) { + return String.format("All (%s)", i); + } + + return "All"; + } + }, 'A', true); + + createFilterButton(box, buttonGroup, "Error", new ActionListener() { + public void actionPerformed(final ActionEvent e) { + list.filter(NotificationType.ERROR); + } + }, new NotNullFunction() { + @NotNull + public String fun(MyButton myButton) { + final int i = count(NotificationType.ERROR, project); + myButton.setVisible(i > 0); + if (i > 0) { + return String.format("Error (%s)", i); + } + else if (myButton.isSelected()) { + switchToAll(buttonGroup); + } + + return "Error"; + } + }, 'E', false); + + createFilterButton(box, buttonGroup, "Warning", new ActionListener() { + public void actionPerformed(final ActionEvent e) { + list.filter(NotificationType.WARNING); + } + }, new NotNullFunction() { + @NotNull + public String fun(MyButton myButton) { + final int i = count(NotificationType.WARNING, project); + myButton.setVisible(i > 0); + if (i > 0) { + return String.format("Warning (%s)", i); + } + else if (myButton.isSelected()) { + switchToAll(buttonGroup); + } + + return "Warning"; + } + }, 'W', false); + + createFilterButton(box, buttonGroup, "Information", new ActionListener() { + public void actionPerformed(final ActionEvent e) { + list.filter(NotificationType.INFORMATION); + } + }, new NotNullFunction() { + @NotNull + public String fun(MyButton myButton) { + final int i = count(NotificationType.INFORMATION, project); + myButton.setVisible(i > 0); + if (i > 0) { + return String.format("Information (%s)", i); + } + else if (myButton.isSelected()) { + switchToAll(buttonGroup); + } + + return "Information"; + } + }, 'I', false); + + return box; + } + + private static void switchToAll(final ButtonGroup buttonGroup) { + final Enumeration enumeration = buttonGroup.getElements(); + while (enumeration.hasMoreElements()) { + final AbstractButton button = enumeration.nextElement(); + if (button.getText().startsWith("All")) { + button.doClick(); + return; + } + } + } + + private static int count(@Nullable final NotificationType type, @Nullable final Project project) { + return NotificationsManagerImpl.getNotificationsManagerImpl().getByType(type, project).size(); + } + + private void filter(@Nullable final NotificationType type) { + final NotificationsListModel listModel = getModel(); + listModel.filter(type); + if (listModel.getSize() > 0) setSelectedIndex(0); + } + + public static JComponent create(final Project project, final Disposable parentDisposable) { + final NotificationsListModel model = new NotificationsListModel(project); + Disposer.register(parentDisposable, model); + + // TODO: switch filter if removed all of the notifications from current one! + + final ItemsList list = new ItemsList(model); + list.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + list.setCellRenderer(new NotificationsListRenderer()); // request cell renderer size invalidation + } + }); + + final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(list); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + + scrollPane.setBorder(null); + scrollPane.getViewport().setBackground(UIUtil.getPanelBackground()); + + final JComponent buttonBar = buildFilterBar(list, project); + model.addListDataListener(new ListDataListener() { + public void intervalAdded(ListDataEvent e) { + updateButtons(buttonBar); + } + + public void intervalRemoved(ListDataEvent e) { + updateButtons(buttonBar); + } + + public void contentsChanged(ListDataEvent e) { + updateButtons(buttonBar); + } + }); + + final JPanel panel = new JPanel(new BorderLayout()) { + @Override + public void requestFocus() { + updateButtons(buttonBar); + list.requestFocus(); + } + }; + + panel.add(buttonBar, BorderLayout.NORTH); + panel.add(scrollPane, BorderLayout.CENTER); + + return panel; + } + } + + private static class ExpireButton extends JComponent { + private int myIndex = -1; + private BaseButtonBehavior myBehavior; + private JList myList; + + private ExpireButton(final JList list) { + myList = list; + + final MouseAdapter adapter = new MouseAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + showAtPoint(e); + } + + @Override + public void mouseExited(MouseEvent e) { + hideCloseButton(e); + } + }; + + list.addMouseMotionListener(adapter); + list.addMouseListener(adapter); + + setOpaque(false); + setToolTipText("Delete (" + KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke("DELETE")) + ")"); + + myBehavior = new BaseButtonBehavior(this, TimedDeadzone.NULL) { + @Override + protected void execute(final MouseEvent e) { + expire(); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + showAtPoint(e); + } + }); + } + }; + } + + private void showAtPoint(MouseEvent e) { + final Point point = e.getSource() == myList ? e.getPoint() : SwingUtilities.convertPoint((Component)e.getSource(), e.getPoint(), myList); + final int index = myList.locationToIndex(point); + if (index > -1) { + final Object value = myList.getModel().getElementAt(index); + if (value != null && value instanceof Notification) { + final Rectangle bounds = myList.getCellBounds(index, index); + if (myList.getVisibleRect().contains(bounds) && bounds.contains(point)) { + toggle(index, false); + } + } + } + } + + private void expire() { + if (myIndex != -1) { + final ListModel model = myList.getModel(); + final Object o = model.getElementAt(myIndex); + if (o instanceof Notification) { + final Notification notification = (Notification)o; + if (!notification.isExpired()) { + notification.expire(); + } + } + } + + myIndex = -1; + setVisible(false); + } + + public void setIndex(final int index) { + myIndex = index; + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public Dimension getMinimumSize() { + return new Dimension(CLOSE_ICON.getIconWidth() + 2, CLOSE_ICON.getIconHeight() + 2); + } + + @Override + public void paint(Graphics g) { + final Rectangle r = getBounds(); + if (myBehavior.isPressedByMouse()) { + CLOSE_ICON.paintIcon(this, g, r.width - CLOSE_ICON.getIconWidth(), r.height - CLOSE_ICON.getIconHeight()); + } + else { + CLOSE_ICON.paintIcon(this, g, r.width - CLOSE_ICON.getIconWidth() - 2, r.height - CLOSE_ICON.getIconHeight() - 2); + } + } + + public static void install(final JList list) { + final ExpireButton button = new ExpireButton(list); + button.setVisible(false); + } + + public void hideCloseButton(final MouseEvent e) { + if (e != null) { + final Object source = e.getSource(); + if (source instanceof JComponent) { + final Container parent = getParent(); + if (parent != null) { + final Point point = SwingUtilities.convertPoint((Component) source, e.getPoint(), getParent()); + if (!getBounds().contains(point)) { + toggle(-1, true); + } + } + } + } + else { + toggle(-1, true); + } + } + + private void toggle(final int index, final boolean hide) { + if (hide) { + setIndex(index); + setVisible(false); + return; + } + + if (getParent() == null) { + final Window window = SwingUtilities.getWindowAncestor(myList); + if (!(window instanceof JDialog)) return; + final JLayeredPane layeredPane = ((JDialog)window).getLayeredPane(); + if (layeredPane == null) return; + layeredPane.add(this); + + layeredPane.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + toggle(-1, true); + } + }); + } + + if (getParent() != null) { + final Dimension preferredSize = getPreferredSize(); + final Rectangle cellBounds = myList.getCellBounds(index, index); + Point location = new Point(cellBounds.x + cellBounds.width - preferredSize.width, cellBounds.y); + location = SwingUtilities.convertPoint(myList, location, getParent()); + setIndex(index); + setBounds(location.x, location.y, preferredSize.width, preferredSize.height); + } + + setVisible(true); + } + + public int getIndex() { + return myIndex; + } + } + + private abstract static class MyButton extends StickyButton { + private MyButton(String text, ActionListener listener) { + super(text, listener); + } + + private MyButton(String text) { + super(text); + } + + public abstract void updateTitle(); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButtonWithText.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButtonWithText.java index 66d88593e6b2..ffa6f65acb06 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButtonWithText.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButtonWithText.java @@ -1,97 +1,97 @@ -/* - * 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.actionSystem.impl; - -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.ActionButtonLook; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; - -public class ActionButtonWithText extends ActionButton { - private static final int ICON_TEXT_SPACE = 2; - - public ActionButtonWithText(final AnAction action, - final Presentation presentation, - final String place, - final Dimension minimumSize) { - super(action, presentation, place, minimumSize); - setFont(UIUtil.getLabelFont()); - } - - public Dimension getPreferredSize() { - final Dimension preferredSize = new Dimension(super.getPreferredSize()); - final String text = getText(); - final FontMetrics fontMetrics = getFontMetrics(getFont()); - preferredSize.width += ICON_TEXT_SPACE; - preferredSize.width += fontMetrics.stringWidth(text); - return preferredSize; - } - - public void paintComponent(Graphics g) { - final String text = getText(); - final Icon icon = getIcon(); - final FontMetrics fontMetrics = getFontMetrics(getFont()); - int x = (int)Math.ceil((getWidth() - icon.getIconWidth() - fontMetrics.stringWidth(text)) / 2); - int y = (int)Math.ceil((getHeight() - icon.getIconHeight()) / 2); - ActionButtonLook look = ActionButtonLook.IDEA_LOOK; - look.paintBackground(g, this); - look.paintIconAt(g, this, icon, x, y); - look.paintBorder(g, this); - final int textHeight = fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); - - UIUtil.applyRenderingHints(g); - g.setColor(isButtonEnabled() ? UIUtil.getLabelForeground() : UIUtil.getInactiveTextColor()); - final int iconTextDifference = (int)Math.ceil((icon.getIconHeight() - textHeight) / 2); - final int textStartX = x + icon.getIconWidth() + ICON_TEXT_SPACE; - g.drawString(text, textStartX, y + iconTextDifference + fontMetrics.getMaxAscent()); - final int mnemonicIndex = getMnemonicCharIndex(text); - if (mnemonicIndex >= 0) { - final char[] chars = text.toCharArray(); - final int startX = textStartX + fontMetrics.charsWidth(chars, 0, mnemonicIndex); - final int startY = y + iconTextDifference + fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); - final int endX = startX + fontMetrics.charWidth(text.charAt(mnemonicIndex)); - UIUtil.drawLine(g, startX, startY, endX, startY); - } - } - - private int getMnemonicCharIndex(String text) { - final ShortcutSet shortcutSet = myAction.getShortcutSet(); - final Shortcut[] shortcuts = shortcutSet.getShortcuts(); - for (int i = 0; i < shortcuts.length; i++) { - Shortcut shortcut = shortcuts[i]; - if (shortcut instanceof KeyboardShortcut) { - KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut; - if (keyboardShortcut.getSecondKeyStroke() == null) { // we are interested only in "mnemonic-like" shortcuts - final KeyStroke keyStroke = keyboardShortcut.getFirstKeyStroke(); - final int modifiers = keyStroke.getModifiers(); - if ((modifiers & KeyEvent.ALT_MASK) != 0) { - return (keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED)? text.indexOf(keyStroke.getKeyChar()) : text.indexOf(KeyEvent.getKeyText(keyStroke.getKeyCode())); - } - } - } - } - return -1; - } - - private String getText() { - final String text = myPresentation.getText(); - return text != null? text : ""; - } - -} +/* + * 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.actionSystem.impl; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionButtonLook; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; + +public class ActionButtonWithText extends ActionButton { + private static final int ICON_TEXT_SPACE = 2; + + public ActionButtonWithText(final AnAction action, + final Presentation presentation, + final String place, + final Dimension minimumSize) { + super(action, presentation, place, minimumSize); + setFont(UIUtil.getLabelFont()); + } + + public Dimension getPreferredSize() { + final Dimension preferredSize = new Dimension(super.getPreferredSize()); + final String text = getText(); + final FontMetrics fontMetrics = getFontMetrics(getFont()); + preferredSize.width += ICON_TEXT_SPACE; + preferredSize.width += fontMetrics.stringWidth(text); + return preferredSize; + } + + public void paintComponent(Graphics g) { + final String text = getText(); + final Icon icon = getIcon(); + final FontMetrics fontMetrics = getFontMetrics(getFont()); + int x = (int)Math.ceil((getWidth() - icon.getIconWidth() - fontMetrics.stringWidth(text)) / 2); + int y = (int)Math.ceil((getHeight() - icon.getIconHeight()) / 2); + ActionButtonLook look = ActionButtonLook.IDEA_LOOK; + look.paintBackground(g, this); + look.paintIconAt(g, this, icon, x, y); + look.paintBorder(g, this); + final int textHeight = fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); + + UIUtil.applyRenderingHints(g); + g.setColor(isButtonEnabled() ? UIUtil.getLabelForeground() : UIUtil.getInactiveTextColor()); + final int iconTextDifference = (int)Math.ceil((icon.getIconHeight() - textHeight) / 2); + final int textStartX = x + icon.getIconWidth() + ICON_TEXT_SPACE; + g.drawString(text, textStartX, y + iconTextDifference + fontMetrics.getMaxAscent()); + final int mnemonicIndex = getMnemonicCharIndex(text); + if (mnemonicIndex >= 0) { + final char[] chars = text.toCharArray(); + final int startX = textStartX + fontMetrics.charsWidth(chars, 0, mnemonicIndex); + final int startY = y + iconTextDifference + fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); + final int endX = startX + fontMetrics.charWidth(text.charAt(mnemonicIndex)); + UIUtil.drawLine(g, startX, startY, endX, startY); + } + } + + private int getMnemonicCharIndex(String text) { + final ShortcutSet shortcutSet = myAction.getShortcutSet(); + final Shortcut[] shortcuts = shortcutSet.getShortcuts(); + for (int i = 0; i < shortcuts.length; i++) { + Shortcut shortcut = shortcuts[i]; + if (shortcut instanceof KeyboardShortcut) { + KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut; + if (keyboardShortcut.getSecondKeyStroke() == null) { // we are interested only in "mnemonic-like" shortcuts + final KeyStroke keyStroke = keyboardShortcut.getFirstKeyStroke(); + final int modifiers = keyStroke.getModifiers(); + if ((modifiers & KeyEvent.ALT_MASK) != 0) { + return (keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED)? text.indexOf(keyStroke.getKeyChar()) : text.indexOf(KeyEvent.getKeyText(keyStroke.getKeyCode())); + } + } + } + } + return -1; + } + + private String getText() { + final String text = myPresentation.getText(); + return text != null? text : ""; + } + +} 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 c5a21f053d72..419124118200 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 @@ -1,1345 +1,1345 @@ -/* - * 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.actionSystem.impl; - -import com.intellij.CommonBundle; -import com.intellij.diagnostic.PluginException; -import com.intellij.ide.ActivityTracker; -import com.intellij.ide.DataManager; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.idea.IdeaLogger; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.actionSystem.ex.ActionUtil; -import com.intellij.openapi.actionSystem.ex.AnActionListener; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.keymap.ex.KeymapManagerEx; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ui.UIUtil; -import gnu.trove.THashMap; -import gnu.trove.THashSet; -import gnu.trove.TObjectIntHashMap; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; - -import javax.swing.*; -import javax.swing.Timer; -import java.awt.*; -import java.awt.event.*; -import java.lang.reflect.Constructor; -import java.util.*; -import java.util.List; - -public final class ActionManagerImpl extends ActionManagerEx implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.impl.ActionManagerImpl"); - private static final int TIMER_DELAY = 500; - private static final int UPDATE_DELAY_AFTER_TYPING = 500; - - private final Object myLock = new Object(); - private final THashMap myId2Action; - private final THashMap> myPlugin2Id; - private final TObjectIntHashMap myId2Index; - private final THashMap myAction2Id; - private final ArrayList myNotRegisteredInternalActionIds; - private MyTimer myTimer; - - private int myRegisteredActionsCount; - private final ArrayList myActionListeners; - private AnActionListener[] myCachedActionListeners; - private String myLastPreformedActionId; - private final KeymapManager myKeymapManager; - private final DataManager myDataManager; - private String myPrevPerformedActionId; - private long myLastTimeEditorWasTypedIn = 0; - @NonNls public static final String ACTION_ELEMENT_NAME = "action"; - @NonNls public static final String GROUP_ELEMENT_NAME = "group"; - @NonNls public static final String ACTIONS_ELEMENT_NAME = "actions"; - @NonNls public static final String CLASS_ATTR_NAME = "class"; - @NonNls public static final String ID_ATTR_NAME = "id"; - @NonNls public static final String INTERNAL_ATTR_NAME = "internal"; - @NonNls public static final String ICON_ATTR_NAME = "icon"; - @NonNls public static final String ADD_TO_GROUP_ELEMENT_NAME = "add-to-group"; - @NonNls public static final String SHORTCUT_ELEMENT_NAME = "keyboard-shortcut"; - @NonNls public static final String MOUSE_SHORTCUT_ELEMENT_NAME = "mouse-shortcut"; - @NonNls public static final String DESCRIPTION = "description"; - @NonNls public static final String TEXT_ATTR_NAME = "text"; - @NonNls public static final String POPUP_ATTR_NAME = "popup"; - @NonNls public static final String SEPARATOR_ELEMENT_NAME = "separator"; - @NonNls public static final String REFERENCE_ELEMENT_NAME = "reference"; - @NonNls public static final String GROUPID_ATTR_NAME = "group-id"; - @NonNls public static final String ANCHOR_ELEMENT_NAME = "anchor"; - @NonNls public static final String FIRST = "first"; - @NonNls public static final String LAST = "last"; - @NonNls public static final String BEFORE = "before"; - @NonNls public static final String AFTER = "after"; - @NonNls public static final String SECONDARY = "secondary"; - @NonNls public static final String RELATIVE_TO_ACTION_ATTR_NAME = "relative-to-action"; - @NonNls public static final String FIRST_KEYSTROKE_ATTR_NAME = "first-keystroke"; - @NonNls public static final String SECOND_KEYSTROKE_ATTR_NAME = "second-keystroke"; - @NonNls public static final String REMOVE_SHORTCUT_ATTR_NAME = "remove"; - @NonNls public static final String REPLACE_SHORTCUT_ATTR_NAME = "replace-all"; - @NonNls public static final String KEYMAP_ATTR_NAME = "keymap"; - @NonNls public static final String KEYSTROKE_ATTR_NAME = "keystroke"; - @NonNls public static final String REF_ATTR_NAME = "ref"; - @NonNls public static final String ACTIONS_BUNDLE = "messages.ActionsBundle"; - @NonNls public static final String USE_SHORTCUT_OF_ATTR_NAME = "use-shortcut-of"; - - private final List myPopups = new ArrayList(); - - private final Map myQueuedNotifications = new LinkedHashMap(); - private final Map myQueuedNotificationsEvents = new LinkedHashMap(); - - private Runnable myPreloadActionsRunnable; - private boolean myTransparrentOnlyUpdate; - - ActionManagerImpl(KeymapManager keymapManager, DataManager dataManager) { - myId2Action = new THashMap(); - myId2Index = new TObjectIntHashMap(); - myAction2Id = new THashMap(); - myPlugin2Id = new THashMap>(); - myNotRegisteredInternalActionIds = new ArrayList(); - myActionListeners = new ArrayList(); - myCachedActionListeners = null; - myKeymapManager = keymapManager; - myDataManager = dataManager; - - registerPluginActions(); - } - - public void initComponent() {} - - public void disposeComponent() { - if (myTimer != null) { - myTimer.stop(); - myTimer = null; - } - } - - public void addTimerListener(int delay, final TimerListener listener) { - _addTimerListener(delay, listener, false); - } - - public void removeTimerListener(TimerListener listener) { - _removeTimerListener(listener, false); - } - - @Override - public void addTransparrentTimerListener(int delay, TimerListener listener) { - _addTimerListener(delay, listener, true); - } - - @Override - public void removeTransparrentTimerListener(TimerListener listener) { - _removeTimerListener(listener, true); - } - - - private void _addTimerListener(int delay, final TimerListener listener, boolean transparrent) { - if (ApplicationManager.getApplication().isUnitTestMode()) return; - if (myTimer == null) { - myTimer = new MyTimer(); - myTimer.start(); - } - - myTimer.addTimerListener(listener, transparrent); - } - - private void _removeTimerListener(TimerListener listener, boolean transparrent) { - if (ApplicationManager.getApplication().isUnitTestMode()) return; - LOG.assertTrue(myTimer != null); - - myTimer.removeTimerListener(listener, transparrent); - } - - public ActionPopupMenu createActionPopupMenu(String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) { - return new ActionPopupMenuImpl(place, group, this, presentationFactory); - } - - public ActionPopupMenu createActionPopupMenu(String place, @NotNull ActionGroup group) { - return new ActionPopupMenuImpl(place, group, this, null); - } - - public ActionToolbar createActionToolbar(final String place, final ActionGroup group, final boolean horizontal) { - return new ActionToolbarImpl(place, group, horizontal, myDataManager, this, (KeymapManagerEx)myKeymapManager); - } - - - private void registerPluginActions() { - final Application app = ApplicationManager.getApplication(); - final IdeaPluginDescriptor[] plugins = app.getPlugins(); - for (IdeaPluginDescriptor plugin : plugins) { - if (PluginManager.shouldSkipPlugin(plugin)) continue; - final List elementList = plugin.getActionsDescriptionElements(); - if (elementList != null) { - for (Element e : elementList) { - processActionsChildElement(plugin.getPluginClassLoader(), plugin.getPluginId(), e); - } - } - } - } - - public AnAction getAction(@NotNull String id) { - return getActionImpl(id, false); - } - - private AnAction getActionImpl(String id, boolean canReturnStub) { - synchronized (myLock) { - AnAction action = (AnAction)myId2Action.get(id); - if (!canReturnStub && action instanceof ActionStub) { - action = convert((ActionStub)action); - } - return action; - } - } - - /** - * Converts action's stub to normal action. - */ - private AnAction convert(ActionStub stub) { - LOG.assertTrue(myAction2Id.contains(stub)); - myAction2Id.remove(stub); - - LOG.assertTrue(myId2Action.contains(stub.getId())); - - AnAction action = (AnAction)myId2Action.remove(stub.getId()); - LOG.assertTrue(action != null); - LOG.assertTrue(action.equals(stub)); - - Object obj; - String className = stub.getClassName(); - try { - Constructor constructor = Class.forName(className, true, stub.getLoader()).getDeclaredConstructor(); - constructor.setAccessible(true); - obj = constructor.newInstance(); - } - catch (ClassNotFoundException e) { - PluginId pluginId = stub.getPluginId(); - if (pluginId != null) { - throw new PluginException("class with name \"" + className + "\" not found", e, pluginId); - } - else { - throw new IllegalStateException("class with name \"" + className + "\" not found"); - } - } - catch(UnsupportedClassVersionError e) { - PluginId pluginId = stub.getPluginId(); - if (pluginId != null) { - throw new PluginException(e, pluginId); - } - else { - throw new IllegalStateException(e); - } - } - catch (Exception e) { - PluginId pluginId = stub.getPluginId(); - if (pluginId != null) { - throw new PluginException("cannot create class \"" + className + "\"", e, pluginId); - } - else { - throw new IllegalStateException("cannot create class \"" + className + "\"", e); - } - } - - if (!(obj instanceof AnAction)) { - throw new IllegalStateException("class with name \"" + className + "\" should be instance of " + AnAction.class.getName()); - } - - AnAction anAction = (AnAction)obj; - stub.initAction(anAction); - if (StringUtil.isNotEmpty(stub.getText())) { - anAction.getTemplatePresentation().setText(stub.getText()); - } - String iconPath = stub.getIconPath(); - if (iconPath != null) { - setIconFromClass(anAction.getClass(), anAction.getClass().getClassLoader(), iconPath, stub.getClassName(), anAction.getTemplatePresentation(), stub.getPluginId()); - } - - myId2Action.put(stub.getId(), obj); - myAction2Id.put(obj, stub.getId()); - - return anAction; - } - - public String getId(@NotNull AnAction action) { - LOG.assertTrue(!(action instanceof ActionStub)); - synchronized (myLock) { - return myAction2Id.get(action); - } - } - - public String[] getActionIds(@NotNull String idPrefix) { - synchronized (myLock) { - ArrayList idList = new ArrayList(); - for (String id : myId2Action.keySet()) { - if (id.startsWith(idPrefix)) { - idList.add(id); - } - } - return ArrayUtil.toStringArray(idList); - } - } - - public boolean isGroup(@NotNull String actionId) { - return getActionImpl(actionId, true) instanceof ActionGroup; - } - - public JComponent createButtonToolbar(final String actionPlace, final ActionGroup messageActionGroup) { - return new ButtonToolbarImpl(actionPlace, messageActionGroup, myDataManager, this); - } - - public AnAction getActionOrStub(String id) { - return getActionImpl(id, true); - } - - /** - * @return instance of ActionGroup or ActionStub. The method never returns real subclasses - * of AnAction. - */ - @Nullable - private AnAction processActionElement(Element element, final ClassLoader loader, PluginId pluginId) { - final Application app = ApplicationManager.getApplication(); - final IdeaPluginDescriptor plugin = app.getPlugin(pluginId); - ResourceBundle bundle = getActionsResourceBundle(loader, plugin); - - if (!ACTION_ELEMENT_NAME.equals(element.getName())) { - reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); - return null; - } - String className = element.getAttributeValue(CLASS_ATTR_NAME); - if (className == null || className.length() == 0) { - reportActionError(pluginId, "action element should have specified \"class\" attribute"); - return null; - } - // read ID and register loaded action - String id = element.getAttributeValue(ID_ATTR_NAME); - if (id == null || id.length() == 0) { - reportActionError(pluginId, "ID of the action cannot be an empty string"); - return null; - } - if (Boolean.valueOf(element.getAttributeValue(INTERNAL_ATTR_NAME)).booleanValue() && !ApplicationManagerEx.getApplicationEx().isInternal()) { - myNotRegisteredInternalActionIds.add(id); - return null; - } - - String text = loadTextForElement(element, bundle, id, ACTION_ELEMENT_NAME); - - String iconPath = element.getAttributeValue(ICON_ATTR_NAME); - - if (text == null) { - @NonNls String message = "'text' attribute is mandatory (action ID=" + id + ";" + - (plugin == null ? "" : " plugin path: "+plugin.getPath()) + ")"; - reportActionError(pluginId, message); - return null; - } - - ActionStub stub = new ActionStub(className, id, text, loader, pluginId, iconPath); - Presentation presentation = stub.getTemplatePresentation(); - presentation.setText(text); - - // description - - presentation.setDescription(loadDescriptionForElement(element, bundle, id, ACTION_ELEMENT_NAME)); - - // process all links and key bindings if any - for (final Object o : element.getChildren()) { - Element e = (Element)o; - if (ADD_TO_GROUP_ELEMENT_NAME.equals(e.getName())) { - processAddToGroupNode(stub, e, pluginId, isSecondary(e)); - } - else if (SHORTCUT_ELEMENT_NAME.equals(e.getName())) { - processKeyboardShortcutNode(e, id, pluginId); - } - else if (MOUSE_SHORTCUT_ELEMENT_NAME.equals(e.getName())) { - processMouseShortcutNode(e, id, pluginId); - } - else { - reportActionError(pluginId, "unexpected name of element \"" + e.getName() + "\""); - return null; - } - } - if (element.getAttributeValue(USE_SHORTCUT_OF_ATTR_NAME) != null) { - ((KeymapManagerEx)myKeymapManager).bindShortcuts(element.getAttributeValue(USE_SHORTCUT_OF_ATTR_NAME), id); - } - - // register action - registerAction(id, stub, pluginId); - return stub; - } - - private static ResourceBundle getActionsResourceBundle(ClassLoader loader, IdeaPluginDescriptor plugin) { - @NonNls final String resBundleName = plugin != null && !plugin.getPluginId().getIdString().equals("com.intellij") ? plugin.getResourceBundleBaseName() : ACTIONS_BUNDLE; - ResourceBundle bundle = null; - if (resBundleName != null) { - bundle = getBundle(loader, resBundleName); - } - return bundle; - } - - private static boolean isSecondary(Element element) { - return "true".equalsIgnoreCase(element.getAttributeValue(SECONDARY)); - } - - private static void setIcon(@Nullable final String iconPath, final String className, final ClassLoader loader, final Presentation presentation, - final PluginId pluginId) { - if (iconPath == null) return; - - try { - final Class actionClass = Class.forName(className, true, loader); - setIconFromClass(actionClass, loader, iconPath, className, presentation, pluginId); - } - catch (ClassNotFoundException e) { - LOG.error(e); - reportActionError(pluginId, "class with name \"" + className + "\" not found"); - } - catch (NoClassDefFoundError e) { - LOG.error(e); - reportActionError(pluginId, "class with name \"" + className + "\" not found"); - } - } - - private static void setIconFromClass(@NotNull final Class actionClass, @NotNull ClassLoader classLoader, @NotNull final String iconPath, final String className, - final Presentation presentation, final PluginId pluginId) { - //try to find icon in idea class path - Icon icon = IconLoader.findIcon(iconPath, actionClass); - if (icon == null) icon = IconLoader.findIcon(iconPath, classLoader); - if (icon == null) { - reportActionError(pluginId, "Icon cannot be found in '" + iconPath + "', action class='" + className + "'"); - } - else { - presentation.setIcon(icon); - } - } - - private static String loadDescriptionForElement(final Element element, final ResourceBundle bundle, final String id, String elementType) { - final String value = element.getAttributeValue(DESCRIPTION); - if (bundle != null) { - @NonNls final String key = elementType + "." + id + ".description"; - return CommonBundle.messageOrDefault(bundle, key, value == null ? "" : value); - } else { - return value; - } - } - - private static String loadTextForElement(final Element element, final ResourceBundle bundle, final String id, String elementType) { - final String value = element.getAttributeValue(TEXT_ATTR_NAME); - return CommonBundle.messageOrDefault(bundle, elementType + "." + id + "." + TEXT_ATTR_NAME, value == null ? "" : value); - } - - private AnAction processGroupElement(Element element, final ClassLoader loader, PluginId pluginId) { - final Application app = ApplicationManager.getApplication(); - final IdeaPluginDescriptor plugin = app.getPlugin(pluginId); - ResourceBundle bundle = getActionsResourceBundle(loader, plugin); - - if (!GROUP_ELEMENT_NAME.equals(element.getName())) { - reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); - return null; - } - String className = element.getAttributeValue(CLASS_ATTR_NAME); - if (className == null) { // use default group if class isn't specified - className = DefaultActionGroup.class.getName(); - } - try { - Class aClass = Class.forName(className, true, loader); - Object obj = new ConstructorInjectionComponentAdapter(className, aClass).getComponentInstance(ApplicationManager.getApplication().getPicoContainer()); - - if (!(obj instanceof ActionGroup)) { - reportActionError(pluginId, "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName()); - return null; - } - if (element.getChildren().size() != element.getChildren(ADD_TO_GROUP_ELEMENT_NAME).size() ) { // - if (!(obj instanceof DefaultActionGroup)) { - reportActionError(pluginId, "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() + - " because there are children specified"); - return null; - } - } - ActionGroup group = (ActionGroup)obj; - // read ID and register loaded group - String id = element.getAttributeValue(ID_ATTR_NAME); - if (id != null && id.length() == 0) { - reportActionError(pluginId, "ID of the group cannot be an empty string"); - return null; - } - if (Boolean.valueOf(element.getAttributeValue(INTERNAL_ATTR_NAME)).booleanValue() && !ApplicationManagerEx.getApplicationEx().isInternal()) { - myNotRegisteredInternalActionIds.add(id); - return null; - } - - if (id != null) { - registerAction(id, group); - } - Presentation presentation = group.getTemplatePresentation(); - - // text - String text = loadTextForElement(element, bundle, id, GROUP_ELEMENT_NAME); - // don't override value which was set in API with empty value from xml descriptor - if (!StringUtil.isEmpty(text) || presentation.getText() == null) { - presentation.setText(text); - } - - // description - String description = loadDescriptionForElement(element, bundle, id, GROUP_ELEMENT_NAME); - // don't override value which was set in API with empty value from xml descriptor - if (!StringUtil.isEmpty(description) || presentation.getDescription() == null) { - presentation.setDescription(description); - } - - // icon - setIcon(element.getAttributeValue(ICON_ATTR_NAME), className, loader, presentation, pluginId); - // popup - String popup = element.getAttributeValue(POPUP_ATTR_NAME); - if (popup != null) { - group.setPopup(Boolean.valueOf(popup).booleanValue()); - } - // process all group's children. There are other groups, actions, references and links - for (final Object o : element.getChildren()) { - Element child = (Element)o; - String name = child.getName(); - if (ACTION_ELEMENT_NAME.equals(name)) { - AnAction action = processActionElement(child, loader, pluginId); - if (action != null) { - assertActionIsGroupOrStub(action); - ((DefaultActionGroup)group).addAction(action, Constraints.LAST, this).setAsSecondary(isSecondary(child)); - } - } - else if (SEPARATOR_ELEMENT_NAME.equals(name)) { - processSeparatorNode((DefaultActionGroup)group, child, pluginId); - } - else if (GROUP_ELEMENT_NAME.equals(name)) { - AnAction action = processGroupElement(child, loader, pluginId); - if (action != null) { - ((DefaultActionGroup)group).add(action, this); - } - } - else if (ADD_TO_GROUP_ELEMENT_NAME.equals(name)) { - processAddToGroupNode(group, child, pluginId, isSecondary(child)); - } - else if (REFERENCE_ELEMENT_NAME.equals(name)) { - AnAction action = processReferenceElement(child, pluginId); - if (action != null) { - ((DefaultActionGroup)group).addAction(action, Constraints.LAST, this).setAsSecondary(isSecondary(child)); - } - } - else { - reportActionError(pluginId, "unexpected name of element \"" + name + "\n"); - return null; - } - } - return group; - } - catch (ClassNotFoundException e) { - reportActionError(pluginId, "class with name \"" + className + "\" not found"); - return null; - } - catch (NoClassDefFoundError e) { - reportActionError(pluginId, "class with name \"" + e.getMessage() + "\" not found"); - return null; - } - catch(UnsupportedClassVersionError e) { - reportActionError(pluginId, "unsupported class version for " + className); - return null; - } - catch (Exception e) { - final String message = "cannot create class \"" + className + "\""; - if (pluginId == null) { - LOG.error(message, e); - } - else { - LOG.error(new PluginException(message, e, pluginId)); - } - return null; - } - } - - private void processReferenceNode(final Element element, final PluginId pluginId) { - final AnAction action = processReferenceElement(element, pluginId); - - for (final Object o : element.getChildren()) { - Element child = (Element)o; - if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.getName())) { - processAddToGroupNode(action, child, pluginId, isSecondary(child)); - } - } - } - - private static final Map ourBundlesCache = new HashMap(); - - private static ResourceBundle getBundle(final ClassLoader loader, final String resBundleName) { - - if (ourBundlesCache.containsKey(resBundleName)) { - return ourBundlesCache.get(resBundleName); - } - - final ResourceBundle bundle = ResourceBundle.getBundle(resBundleName, Locale.getDefault(), loader); - - ourBundlesCache.put(resBundleName, bundle); - - return bundle; - } - - /**\ - * @param element description of link - * @param pluginId - * @param secondary - */ - private void processAddToGroupNode(AnAction action, Element element, final PluginId pluginId, boolean secondary) { - // Real subclasses of AnAction should not be here - if (!(action instanceof Separator)) { - assertActionIsGroupOrStub(action); - } - - String actionName = action instanceof ActionStub ? ((ActionStub)action).getClassName() : action.getClass().getName(); - - if (!ADD_TO_GROUP_ELEMENT_NAME.equals(element.getName())) { - reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); - return; - } - - // parent group - final AnAction parentGroup = getParentGroup(element.getAttributeValue(GROUPID_ATTR_NAME), actionName, pluginId); - if (parentGroup == null) { - return; - } - - // anchor attribute - final Anchor anchor = parseAnchor(element.getAttributeValue(ANCHOR_ELEMENT_NAME), - actionName, pluginId); - if (anchor == null) { - return; - } - - final String relativeToActionId = element.getAttributeValue(RELATIVE_TO_ACTION_ATTR_NAME); - if (!checkRelativeToAction(relativeToActionId, anchor, actionName, pluginId)) { - return; - } - final DefaultActionGroup group = (DefaultActionGroup)parentGroup; - group.addAction(action, new Constraints(anchor, relativeToActionId), this).setAsSecondary(secondary); - } - - public static boolean checkRelativeToAction(final String relativeToActionId, - @NotNull final Anchor anchor, - @NotNull final String actionName, - @Nullable final PluginId pluginId) { - if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) { - reportActionError(pluginId, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\""); - return false; - } - return true; - } - - @Nullable - public static Anchor parseAnchor(final String anchorStr, - @Nullable final String actionName, - @Nullable final PluginId pluginId) { - if (anchorStr == null) { - return Anchor.LAST; - } - - if (FIRST.equalsIgnoreCase(anchorStr)) { - return Anchor.FIRST; - } - else if (LAST.equalsIgnoreCase(anchorStr)) { - return Anchor.LAST; - } - else if (BEFORE.equalsIgnoreCase(anchorStr)) { - return Anchor.BEFORE; - } - else if (AFTER.equalsIgnoreCase(anchorStr)) { - return Anchor.AFTER; - } - else { - reportActionError(pluginId, actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\""); - return null; - } - } - - @Nullable - public AnAction getParentGroup(final String groupId, - @Nullable final String actionName, - @Nullable final PluginId pluginId) { - if (groupId == null || groupId.length() == 0) { - reportActionError(pluginId, actionName + ": attribute \"group-id\" should be defined"); - return null; - } - AnAction parentGroup = getActionImpl(groupId, true); - if (parentGroup == null) { - reportActionError(pluginId, actionName + ": action with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group"); - parentGroup = getActionImpl(IdeActions.GROUP_OTHER_MENU, true); - } - if (!(parentGroup instanceof DefaultActionGroup)) { - reportActionError(pluginId, actionName + ": action with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() + - " but was " + parentGroup.getClass()); - return null; - } - return parentGroup; - } - - /** - * @param parentGroup group wich is the parent of the separator. It can be null in that - * case separator will be added to group described in the subelement. - * @param element XML element which represent separator. - */ - private void processSeparatorNode(DefaultActionGroup parentGroup, Element element, PluginId pluginId) { - if (!SEPARATOR_ELEMENT_NAME.equals(element.getName())) { - reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); - return; - } - Separator separator = Separator.getInstance(); - if (parentGroup != null) { - parentGroup.add(separator, this); - } - // try to find inner tag - for (final Object o : element.getChildren()) { - Element child = (Element)o; - if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.getName())) { - processAddToGroupNode(separator, child, pluginId, isSecondary(child)); - } - } - } - - private void processKeyboardShortcutNode(Element element, String actionId, PluginId pluginId) { - String firstStrokeString = element.getAttributeValue(FIRST_KEYSTROKE_ATTR_NAME); - if (firstStrokeString == null) { - reportActionError(pluginId, "\"first-keystroke\" attribute must be specified for action with id=" + actionId); - return; - } - KeyStroke firstKeyStroke = getKeyStroke(firstStrokeString); - if (firstKeyStroke == null) { - reportActionError(pluginId, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId); - return; - } - - KeyStroke secondKeyStroke = null; - String secondStrokeString = element.getAttributeValue(SECOND_KEYSTROKE_ATTR_NAME); - if (secondStrokeString != null) { - secondKeyStroke = getKeyStroke(secondStrokeString); - if (secondKeyStroke == null) { - reportActionError(pluginId, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId); - return; - } - } - - String keymapName = element.getAttributeValue(KEYMAP_ATTR_NAME); - if (keymapName == null || keymapName.trim().length() == 0) { - reportActionError(pluginId, "attribute \"keymap\" should be defined"); - return; - } - Keymap keymap = myKeymapManager.getKeymap(keymapName); - if (keymap == null) { - reportActionError(pluginId, "keymap \"" + keymapName + "\" not found"); - return; - } - final String removeOption = element.getAttributeValue(REMOVE_SHORTCUT_ATTR_NAME); - final KeyboardShortcut shortcut = new KeyboardShortcut(firstKeyStroke, secondKeyStroke); - final String replaceOption = element.getAttributeValue(REPLACE_SHORTCUT_ATTR_NAME); - if (Boolean.valueOf(removeOption)) { - keymap.removeShortcut(actionId, shortcut); - } - if (Boolean.valueOf(replaceOption)) { - keymap.removeAllActionShortcuts(actionId); - } - if (!Boolean.valueOf(removeOption)) { - keymap.addShortcut(actionId, shortcut); - } - } - - private static void processMouseShortcutNode(Element element, String actionId, PluginId pluginId) { - String keystrokeString = element.getAttributeValue(KEYSTROKE_ATTR_NAME); - if (keystrokeString == null || keystrokeString.trim().length() == 0) { - reportActionError(pluginId, "\"keystroke\" attribute must be specified for action with id=" + actionId); - return; - } - MouseShortcut shortcut; - try { - shortcut = KeymapUtil.parseMouseShortcut(keystrokeString); - } - catch (Exception ex) { - reportActionError(pluginId, "\"keystroke\" attribute has invalid value for action with id=" + actionId); - return; - } - - String keymapName = element.getAttributeValue(KEYMAP_ATTR_NAME); - if (keymapName == null || keymapName.length() == 0) { - reportActionError(pluginId, "attribute \"keymap\" should be defined"); - return; - } - Keymap keymap = KeymapManager.getInstance().getKeymap(keymapName); - if (keymap == null) { - reportActionError(pluginId, "keymap \"" + keymapName + "\" not found"); - return; - } - - final String removeOption = element.getAttributeValue(REMOVE_SHORTCUT_ATTR_NAME); - if (Boolean.valueOf(removeOption)) { - keymap.removeShortcut(actionId, shortcut); - } else { - keymap.addShortcut(actionId, shortcut); - } - } - - @Nullable - private AnAction processReferenceElement(Element element, PluginId pluginId) { - if (!REFERENCE_ELEMENT_NAME.equals(element.getName())) { - reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); - return null; - } - String ref = element.getAttributeValue(REF_ATTR_NAME); - - if (ref==null) { - // support old style references by id - ref = element.getAttributeValue(ID_ATTR_NAME); - } - - if (ref == null || ref.length() == 0) { - reportActionError(pluginId, "ID of reference element should be defined"); - return null; - } - - AnAction action = getActionImpl(ref, true); - - if (action == null) { - if (!myNotRegisteredInternalActionIds.contains(ref)) { - reportActionError(pluginId, "action specified by reference isn't registered (ID=" + ref + ")"); - } - return null; - } - assertActionIsGroupOrStub(action); - return action; - } - - private void processActionsElement(Element element, ClassLoader loader, PluginId pluginId) { - if (!ACTIONS_ELEMENT_NAME.equals(element.getName())) { - reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); - return; - } - synchronized (myLock) { - for (final Object o : element.getChildren()) { - Element child = (Element)o; - processActionsChildElement(loader, pluginId, child); - } - } - } - - private void processActionsChildElement(final ClassLoader loader, final PluginId pluginId, final Element child) { - String name = child.getName(); - if (ACTION_ELEMENT_NAME.equals(name)) { - AnAction action = processActionElement(child, loader, pluginId); - if (action != null) { - assertActionIsGroupOrStub(action); - } - } - else if (GROUP_ELEMENT_NAME.equals(name)) { - processGroupElement(child, loader, pluginId); - } - else if (SEPARATOR_ELEMENT_NAME.equals(name)) { - processSeparatorNode(null, child, pluginId); - } - else if (REFERENCE_ELEMENT_NAME.equals(name)) { - processReferenceNode(child, pluginId); - } - else { - reportActionError(pluginId, "unexpected name of element \"" + name + "\n"); - } - } - - private static void assertActionIsGroupOrStub(final AnAction action) { - if (!(action instanceof ActionGroup || action instanceof ActionStub)) { - LOG.error("Action : " + action + "; class: " + action.getClass()); - } - } - - public void registerAction(@NotNull String actionId, @NotNull AnAction action, @Nullable PluginId pluginId) { - synchronized (myLock) { - if (myId2Action.containsKey(actionId)) { - reportActionError(pluginId, "action with the ID \"" + actionId + "\" was already registered. Action being registered is " + action.toString() + - "; Registered action is " + - myId2Action.get(actionId) + getPluginInfo(pluginId)); - return; - } - if (myAction2Id.containsKey(action)) { - reportActionError(pluginId, "action was already registered for another ID. ID is " + myAction2Id.get(action) + - getPluginInfo(pluginId)); - return; - } - myId2Action.put(actionId, action); - myId2Index.put(actionId, myRegisteredActionsCount++); - myAction2Id.put(action, actionId); - if (pluginId != null && !(action instanceof ActionGroup)){ - THashSet pluginActionIds = myPlugin2Id.get(pluginId); - if (pluginActionIds == null){ - pluginActionIds = new THashSet(); - myPlugin2Id.put(pluginId, pluginActionIds); - } - pluginActionIds.add(actionId); - } - action.registerCustomShortcutSet(new ProxyShortcutSet(actionId, myKeymapManager), null); - } - } - - private static void reportActionError(final PluginId pluginId, @NonNls final String message) { - if (pluginId == null) { - LOG.error(message); - } - else { - LOG.error(new PluginException(message, null, pluginId)); - } - } - - @NonNls - private static String getPluginInfo(@Nullable PluginId id) { - if (id != null) { - final IdeaPluginDescriptor plugin = ApplicationManager.getApplication().getPlugin(id); - if (plugin != null) { - String name = plugin.getName(); - if (name == null) { - name = id.getIdString(); - } - return " Plugin: " + name; - } - } - return ""; - } - - public void registerAction(@NotNull String actionId, @NotNull AnAction action) { - registerAction(actionId, action, null); - } - - public void unregisterAction(@NotNull String actionId) { - synchronized (myLock) { - if (!myId2Action.containsKey(actionId)) { - if (LOG.isDebugEnabled()) { - LOG.debug("action with ID " + actionId + " wasn't registered"); - return; - } - } - AnAction oldValue = (AnAction)myId2Action.remove(actionId); - myAction2Id.remove(oldValue); - myId2Index.remove(actionId); - for (PluginId pluginName : myPlugin2Id.keySet()) { - final THashSet pluginActions = myPlugin2Id.get(pluginName); - if (pluginActions != null) { - pluginActions.remove(actionId); - } - } - } - } - - @NotNull - public String getComponentName() { - return "ActionManager"; - } - - public Comparator getRegistrationOrderComparator() { - return new Comparator() { - public int compare(String id1, String id2) { - return myId2Index.get(id1) - myId2Index.get(id2); - } - }; - } - - public String[] getPluginActions(PluginId pluginName) { - if (myPlugin2Id.containsKey(pluginName)){ - final THashSet pluginActions = myPlugin2Id.get(pluginName); - return ArrayUtil.toStringArray(pluginActions); - } - return ArrayUtil.EMPTY_STRING_ARRAY; - } - - public void addActionPopup(final ActionPopupMenuImpl menu) { - myPopups.add(menu); - } - - public void removeActionPopup(final ActionPopupMenuImpl menu) { - final boolean removed = myPopups.remove(menu); - if (removed && myPopups.isEmpty()) { - flushActionPerformed(); - } - } - - public void queueActionPerformedEvent(final AnAction action, DataContext context, AnActionEvent event) { - if (!myPopups.isEmpty()) { - myQueuedNotifications.put(action, context); - } else { - fireAfterActionPerformed(action, context, event); - } - } - - - public boolean isActionPopupStackEmpty() { - return myPopups.isEmpty(); - } - - @Override - public boolean isTransparrentOnlyActionsUpdateNow() { - return myTransparrentOnlyUpdate; - } - - private void flushActionPerformed() { - final Set actions = myQueuedNotifications.keySet(); - for (final AnAction eachAction : actions) { - final DataContext eachContext = myQueuedNotifications.get(eachAction); - fireAfterActionPerformed(eachAction, eachContext, myQueuedNotificationsEvents.get(eachAction)); - } - myQueuedNotifications.clear(); - myQueuedNotificationsEvents.clear(); - } - - private AnActionListener[] getActionListeners() { - if (myCachedActionListeners == null) { - myCachedActionListeners = myActionListeners.toArray(new AnActionListener[myActionListeners.size()]); - } - - return myCachedActionListeners; - } - - public void addAnActionListener(AnActionListener listener) { - myActionListeners.add(listener); - myCachedActionListeners = null; - } - - public void addAnActionListener(final AnActionListener listener, final Disposable parentDisposable) { - addAnActionListener(listener); - Disposer.register(parentDisposable, new Disposable() { - public void dispose() { - removeAnActionListener(listener); - } - }); - } - - public void removeAnActionListener(AnActionListener listener) { - myActionListeners.remove(listener); - myCachedActionListeners = null; - } - - public void fireBeforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { - if (action != null) { - myPrevPerformedActionId = myLastPreformedActionId; - myLastPreformedActionId = getId(action); - IdeaLogger.ourLastActionId = myLastPreformedActionId; - } - AnActionListener[] listeners = getActionListeners(); - for (AnActionListener listener : listeners) { - listener.beforeActionPerformed(action, dataContext, event); - } - } - - public void fireAfterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { - if (action != null) { - myPrevPerformedActionId = myLastPreformedActionId; - myLastPreformedActionId = getId(action); - IdeaLogger.ourLastActionId = myLastPreformedActionId; - } - AnActionListener[] listeners = getActionListeners(); - for (AnActionListener listener : listeners) { - try { - listener.afterActionPerformed(action, dataContext, event); - } - catch(AbstractMethodError e) { - // ignore - } - } - } - - @Override - public KeyboardShortcut getKeyboardShortcut(@NotNull String actionId) { - AnAction action = ActionManager.getInstance().getAction(actionId); - final ShortcutSet shortcutSet = action.getShortcutSet(); - final Shortcut[] shortcuts = shortcutSet.getShortcuts(); - for (final Shortcut shortcut : shortcuts) { - // Shortcut can be MouseShortcut here. - // For example IdeaVIM often assigns them - if (shortcut instanceof KeyboardShortcut){ - final KeyboardShortcut kb = (KeyboardShortcut)shortcut; - if (kb.getSecondKeyStroke() == null) { - return (KeyboardShortcut)shortcut; - } - } - } - - return null; - } - - public void fireBeforeEditorTyping(char c, DataContext dataContext) { - myLastTimeEditorWasTypedIn = System.currentTimeMillis(); - AnActionListener[] listeners = getActionListeners(); - for (AnActionListener listener : listeners) { - listener.beforeEditorTyping(c, dataContext); - } - } - - public String getLastPreformedActionId() { - return myLastPreformedActionId; - } - - public String getPrevPreformedActionId() { - return myPrevPerformedActionId; - } - - public Set getActionIds(){ - return new HashSet(myId2Action.keySet()); - } - - private int myActionsPreloaded = 0; - - public void preloadActions() { - if (myPreloadActionsRunnable == null) { - myPreloadActionsRunnable = new Runnable() { - public void run() { - doPreloadActions(); - } - }; - ApplicationManager.getApplication().executeOnPooledThread(myPreloadActionsRunnable); - } - } - - private void doPreloadActions() { - try { - Thread.sleep(5000); // wait for project initialization to complete - } - catch (InterruptedException e) { - // ignore - } - preloadActionGroup(IdeActions.GROUP_EDITOR_POPUP); - preloadActionGroup(IdeActions.GROUP_EDITOR_TAB_POPUP); - preloadActionGroup(IdeActions.GROUP_PROJECT_VIEW_POPUP); - preloadActionGroup(IdeActions.GROUP_MAIN_MENU); - // TODO anything else? - LOG.debug("Actions preloading completed"); - } - - public void preloadActionGroup(final String groupId) { - final AnAction action = getAction(groupId); - if (action instanceof ActionGroup) { - preloadActionGroup((ActionGroup) action); - } - } - - private void preloadActionGroup(final ActionGroup group) { - final AnAction[] children = ApplicationManager.getApplication().runReadAction(new Computable() { - public AnAction[] compute() { - if (ApplicationManager.getApplication().isDisposed()) { - return AnAction.EMPTY_ARRAY; - } - - return group.getChildren(null); - } - }); - for (AnAction action : children) { - if (action instanceof PreloadableAction) { - ((PreloadableAction)action).preload(); - } - else if (action instanceof ActionGroup) { - preloadActionGroup((ActionGroup)action); - } - - myActionsPreloaded++; - if (myActionsPreloaded % 10 == 0) { - try { - Thread.sleep(300); - } - catch (InterruptedException e) { - // ignore - } - } - } - } - - private class MyTimer extends Timer implements ActionListener { - private final List myTimerListeners = Collections.synchronizedList(new ArrayList()); - private final List myTransparrentTimerListeners = Collections.synchronizedList(new ArrayList()); - private int myLastTimePerformed; - - MyTimer() { - super(TIMER_DELAY, null); - addActionListener(this); - setRepeats(true); - } - - public void addTimerListener(TimerListener listener, boolean transparent){ - if (transparent) { - myTransparrentTimerListeners.add(listener); - } else { - myTimerListeners.add(listener); - } - } - - public void removeTimerListener(TimerListener listener, boolean transparent){ - if (transparent) { - myTransparrentTimerListeners.remove(listener); - } else { - myTimerListeners.remove(listener); - } - } - - public void actionPerformed(ActionEvent e) { - if (myLastTimeEditorWasTypedIn + UPDATE_DELAY_AFTER_TYPING > System.currentTimeMillis()) { - return; - } - - if (IdeFocusManager.getInstance(null).isFocusBeingTransferred()) return; - - final int lastEventCount = myLastTimePerformed; - myLastTimePerformed = ActivityTracker.getInstance().getCount(); - - boolean transparentOnly = myLastTimePerformed == lastEventCount; - - try { - HashSet notified = new HashSet(); - myTransparrentOnlyUpdate = transparentOnly; - notifyListeners(myTransparrentTimerListeners, notified); - - if (transparentOnly) { - return; - } - - notifyListeners(myTimerListeners, notified); - } - finally { - myTransparrentOnlyUpdate = false; - } - } - - private void notifyListeners(final List timerListeners, final Set notified) { - final TimerListener[] listeners = timerListeners.toArray(new TimerListener[timerListeners.size()]); - for (TimerListener listener : listeners) { - if (timerListeners.contains(listener)) { - if (!notified.contains(listener)) { - notified.add(listener); - runListenerAction(listener); - } - } - } - } - - private void runListenerAction(final TimerListener listener) { - ModalityState modalityState = listener.getModalityState(); - if (modalityState == null) return; - if (!ModalityState.current().dominates(modalityState)) { - try { - listener.run(); - } - catch (ProcessCanceledException ex) { - // ignore - } - catch (Throwable e) { - LOG.error(e); - } - } - } - } - - public ActionCallback tryToExecute(@NotNull final AnAction action, @NotNull final InputEvent inputEvent, @Nullable final Component contextComponent, @Nullable final String place, - boolean now) { - - final Application app = ApplicationManager.getApplication(); - assert app.isDispatchThread(); - - final ActionCallback result = new ActionCallback(); - final Runnable doRunnable = new Runnable() { - public void run() { - tryToExecuteNow(action, inputEvent, contextComponent, place, result); - } - }; - - if (now) { - doRunnable.run(); - } else { - SwingUtilities.invokeLater(doRunnable); - } - - return result; - - } - - private void tryToExecuteNow(final AnAction action, final InputEvent inputEvent, final Component contextComponent, final String place, final ActionCallback result) { - final Presentation presenation = (Presentation)action.getTemplatePresentation().clone(); - - IdeFocusManager.findInstanceByContext(getContextBy(contextComponent)).doWhenFocusSettlesDown(new Runnable() { - public void run() { - final DataContext context = getContextBy(contextComponent); - - AnActionEvent event = new AnActionEvent( - inputEvent, context, - place != null ? place : ActionPlaces.UNKNOWN, - presenation, ActionManagerImpl.this, - inputEvent.getModifiersEx() - ); - - ActionUtil.performDumbAwareUpdate(action, event, false); - if (!event.getPresentation().isEnabled()) { - result.setRejected(); - return; - } - - ActionUtil.lastUpdateAndCheckDumb(action, event, false); - if (!event.getPresentation().isEnabled()) { - result.setRejected(); - return; - } - - Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(context); - if (component != null && !component.isShowing()) { - result.setRejected(); - return; - } - - fireBeforeActionPerformed(action, context, event); - - UIUtil.addAwtListener(new AWTEventListener() { - public void eventDispatched(AWTEvent event) { - if (event.getID() == WindowEvent.WINDOW_OPENED ||event.getID() == WindowEvent.WINDOW_ACTIVATED) { - if (!result.isProcessed()) { - final WindowEvent we = (WindowEvent)event; - IdeFocusManager.findInstanceByComponent(we.getWindow()).doWhenFocusSettlesDown(new Runnable() { - public void run() { - result.setDone(); - } - }); - } - } - } - }, WindowEvent.WINDOW_EVENT_MASK, result); - - action.actionPerformed(event); - result.setDone(); - queueActionPerformedEvent(action, context, event); - } - }); - } - - private static DataContext getContextBy(Component contextComponent) { - final DataManager dataManager = DataManager.getInstance(); - return contextComponent != null ? dataManager.getDataContext(contextComponent) : dataManager.getDataContext(); - } -} +/* + * 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.actionSystem.impl; + +import com.intellij.CommonBundle; +import com.intellij.diagnostic.PluginException; +import com.intellij.ide.ActivityTracker; +import com.intellij.ide.DataManager; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.idea.IdeaLogger; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.actionSystem.ex.ActionUtil; +import com.intellij.openapi.actionSystem.ex.AnActionListener; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.keymap.ex.KeymapManagerEx; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ui.UIUtil; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import gnu.trove.TObjectIntHashMap; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; + +import javax.swing.*; +import javax.swing.Timer; +import java.awt.*; +import java.awt.event.*; +import java.lang.reflect.Constructor; +import java.util.*; +import java.util.List; + +public final class ActionManagerImpl extends ActionManagerEx implements ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.impl.ActionManagerImpl"); + private static final int TIMER_DELAY = 500; + private static final int UPDATE_DELAY_AFTER_TYPING = 500; + + private final Object myLock = new Object(); + private final THashMap myId2Action; + private final THashMap> myPlugin2Id; + private final TObjectIntHashMap myId2Index; + private final THashMap myAction2Id; + private final ArrayList myNotRegisteredInternalActionIds; + private MyTimer myTimer; + + private int myRegisteredActionsCount; + private final ArrayList myActionListeners; + private AnActionListener[] myCachedActionListeners; + private String myLastPreformedActionId; + private final KeymapManager myKeymapManager; + private final DataManager myDataManager; + private String myPrevPerformedActionId; + private long myLastTimeEditorWasTypedIn = 0; + @NonNls public static final String ACTION_ELEMENT_NAME = "action"; + @NonNls public static final String GROUP_ELEMENT_NAME = "group"; + @NonNls public static final String ACTIONS_ELEMENT_NAME = "actions"; + @NonNls public static final String CLASS_ATTR_NAME = "class"; + @NonNls public static final String ID_ATTR_NAME = "id"; + @NonNls public static final String INTERNAL_ATTR_NAME = "internal"; + @NonNls public static final String ICON_ATTR_NAME = "icon"; + @NonNls public static final String ADD_TO_GROUP_ELEMENT_NAME = "add-to-group"; + @NonNls public static final String SHORTCUT_ELEMENT_NAME = "keyboard-shortcut"; + @NonNls public static final String MOUSE_SHORTCUT_ELEMENT_NAME = "mouse-shortcut"; + @NonNls public static final String DESCRIPTION = "description"; + @NonNls public static final String TEXT_ATTR_NAME = "text"; + @NonNls public static final String POPUP_ATTR_NAME = "popup"; + @NonNls public static final String SEPARATOR_ELEMENT_NAME = "separator"; + @NonNls public static final String REFERENCE_ELEMENT_NAME = "reference"; + @NonNls public static final String GROUPID_ATTR_NAME = "group-id"; + @NonNls public static final String ANCHOR_ELEMENT_NAME = "anchor"; + @NonNls public static final String FIRST = "first"; + @NonNls public static final String LAST = "last"; + @NonNls public static final String BEFORE = "before"; + @NonNls public static final String AFTER = "after"; + @NonNls public static final String SECONDARY = "secondary"; + @NonNls public static final String RELATIVE_TO_ACTION_ATTR_NAME = "relative-to-action"; + @NonNls public static final String FIRST_KEYSTROKE_ATTR_NAME = "first-keystroke"; + @NonNls public static final String SECOND_KEYSTROKE_ATTR_NAME = "second-keystroke"; + @NonNls public static final String REMOVE_SHORTCUT_ATTR_NAME = "remove"; + @NonNls public static final String REPLACE_SHORTCUT_ATTR_NAME = "replace-all"; + @NonNls public static final String KEYMAP_ATTR_NAME = "keymap"; + @NonNls public static final String KEYSTROKE_ATTR_NAME = "keystroke"; + @NonNls public static final String REF_ATTR_NAME = "ref"; + @NonNls public static final String ACTIONS_BUNDLE = "messages.ActionsBundle"; + @NonNls public static final String USE_SHORTCUT_OF_ATTR_NAME = "use-shortcut-of"; + + private final List myPopups = new ArrayList(); + + private final Map myQueuedNotifications = new LinkedHashMap(); + private final Map myQueuedNotificationsEvents = new LinkedHashMap(); + + private Runnable myPreloadActionsRunnable; + private boolean myTransparrentOnlyUpdate; + + ActionManagerImpl(KeymapManager keymapManager, DataManager dataManager) { + myId2Action = new THashMap(); + myId2Index = new TObjectIntHashMap(); + myAction2Id = new THashMap(); + myPlugin2Id = new THashMap>(); + myNotRegisteredInternalActionIds = new ArrayList(); + myActionListeners = new ArrayList(); + myCachedActionListeners = null; + myKeymapManager = keymapManager; + myDataManager = dataManager; + + registerPluginActions(); + } + + public void initComponent() {} + + public void disposeComponent() { + if (myTimer != null) { + myTimer.stop(); + myTimer = null; + } + } + + public void addTimerListener(int delay, final TimerListener listener) { + _addTimerListener(delay, listener, false); + } + + public void removeTimerListener(TimerListener listener) { + _removeTimerListener(listener, false); + } + + @Override + public void addTransparrentTimerListener(int delay, TimerListener listener) { + _addTimerListener(delay, listener, true); + } + + @Override + public void removeTransparrentTimerListener(TimerListener listener) { + _removeTimerListener(listener, true); + } + + + private void _addTimerListener(int delay, final TimerListener listener, boolean transparrent) { + if (ApplicationManager.getApplication().isUnitTestMode()) return; + if (myTimer == null) { + myTimer = new MyTimer(); + myTimer.start(); + } + + myTimer.addTimerListener(listener, transparrent); + } + + private void _removeTimerListener(TimerListener listener, boolean transparrent) { + if (ApplicationManager.getApplication().isUnitTestMode()) return; + LOG.assertTrue(myTimer != null); + + myTimer.removeTimerListener(listener, transparrent); + } + + public ActionPopupMenu createActionPopupMenu(String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) { + return new ActionPopupMenuImpl(place, group, this, presentationFactory); + } + + public ActionPopupMenu createActionPopupMenu(String place, @NotNull ActionGroup group) { + return new ActionPopupMenuImpl(place, group, this, null); + } + + public ActionToolbar createActionToolbar(final String place, final ActionGroup group, final boolean horizontal) { + return new ActionToolbarImpl(place, group, horizontal, myDataManager, this, (KeymapManagerEx)myKeymapManager); + } + + + private void registerPluginActions() { + final Application app = ApplicationManager.getApplication(); + final IdeaPluginDescriptor[] plugins = app.getPlugins(); + for (IdeaPluginDescriptor plugin : plugins) { + if (PluginManager.shouldSkipPlugin(plugin)) continue; + final List elementList = plugin.getActionsDescriptionElements(); + if (elementList != null) { + for (Element e : elementList) { + processActionsChildElement(plugin.getPluginClassLoader(), plugin.getPluginId(), e); + } + } + } + } + + public AnAction getAction(@NotNull String id) { + return getActionImpl(id, false); + } + + private AnAction getActionImpl(String id, boolean canReturnStub) { + synchronized (myLock) { + AnAction action = (AnAction)myId2Action.get(id); + if (!canReturnStub && action instanceof ActionStub) { + action = convert((ActionStub)action); + } + return action; + } + } + + /** + * Converts action's stub to normal action. + */ + private AnAction convert(ActionStub stub) { + LOG.assertTrue(myAction2Id.contains(stub)); + myAction2Id.remove(stub); + + LOG.assertTrue(myId2Action.contains(stub.getId())); + + AnAction action = (AnAction)myId2Action.remove(stub.getId()); + LOG.assertTrue(action != null); + LOG.assertTrue(action.equals(stub)); + + Object obj; + String className = stub.getClassName(); + try { + Constructor constructor = Class.forName(className, true, stub.getLoader()).getDeclaredConstructor(); + constructor.setAccessible(true); + obj = constructor.newInstance(); + } + catch (ClassNotFoundException e) { + PluginId pluginId = stub.getPluginId(); + if (pluginId != null) { + throw new PluginException("class with name \"" + className + "\" not found", e, pluginId); + } + else { + throw new IllegalStateException("class with name \"" + className + "\" not found"); + } + } + catch(UnsupportedClassVersionError e) { + PluginId pluginId = stub.getPluginId(); + if (pluginId != null) { + throw new PluginException(e, pluginId); + } + else { + throw new IllegalStateException(e); + } + } + catch (Exception e) { + PluginId pluginId = stub.getPluginId(); + if (pluginId != null) { + throw new PluginException("cannot create class \"" + className + "\"", e, pluginId); + } + else { + throw new IllegalStateException("cannot create class \"" + className + "\"", e); + } + } + + if (!(obj instanceof AnAction)) { + throw new IllegalStateException("class with name \"" + className + "\" should be instance of " + AnAction.class.getName()); + } + + AnAction anAction = (AnAction)obj; + stub.initAction(anAction); + if (StringUtil.isNotEmpty(stub.getText())) { + anAction.getTemplatePresentation().setText(stub.getText()); + } + String iconPath = stub.getIconPath(); + if (iconPath != null) { + setIconFromClass(anAction.getClass(), anAction.getClass().getClassLoader(), iconPath, stub.getClassName(), anAction.getTemplatePresentation(), stub.getPluginId()); + } + + myId2Action.put(stub.getId(), obj); + myAction2Id.put(obj, stub.getId()); + + return anAction; + } + + public String getId(@NotNull AnAction action) { + LOG.assertTrue(!(action instanceof ActionStub)); + synchronized (myLock) { + return myAction2Id.get(action); + } + } + + public String[] getActionIds(@NotNull String idPrefix) { + synchronized (myLock) { + ArrayList idList = new ArrayList(); + for (String id : myId2Action.keySet()) { + if (id.startsWith(idPrefix)) { + idList.add(id); + } + } + return ArrayUtil.toStringArray(idList); + } + } + + public boolean isGroup(@NotNull String actionId) { + return getActionImpl(actionId, true) instanceof ActionGroup; + } + + public JComponent createButtonToolbar(final String actionPlace, final ActionGroup messageActionGroup) { + return new ButtonToolbarImpl(actionPlace, messageActionGroup, myDataManager, this); + } + + public AnAction getActionOrStub(String id) { + return getActionImpl(id, true); + } + + /** + * @return instance of ActionGroup or ActionStub. The method never returns real subclasses + * of AnAction. + */ + @Nullable + private AnAction processActionElement(Element element, final ClassLoader loader, PluginId pluginId) { + final Application app = ApplicationManager.getApplication(); + final IdeaPluginDescriptor plugin = app.getPlugin(pluginId); + ResourceBundle bundle = getActionsResourceBundle(loader, plugin); + + if (!ACTION_ELEMENT_NAME.equals(element.getName())) { + reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); + return null; + } + String className = element.getAttributeValue(CLASS_ATTR_NAME); + if (className == null || className.length() == 0) { + reportActionError(pluginId, "action element should have specified \"class\" attribute"); + return null; + } + // read ID and register loaded action + String id = element.getAttributeValue(ID_ATTR_NAME); + if (id == null || id.length() == 0) { + reportActionError(pluginId, "ID of the action cannot be an empty string"); + return null; + } + if (Boolean.valueOf(element.getAttributeValue(INTERNAL_ATTR_NAME)).booleanValue() && !ApplicationManagerEx.getApplicationEx().isInternal()) { + myNotRegisteredInternalActionIds.add(id); + return null; + } + + String text = loadTextForElement(element, bundle, id, ACTION_ELEMENT_NAME); + + String iconPath = element.getAttributeValue(ICON_ATTR_NAME); + + if (text == null) { + @NonNls String message = "'text' attribute is mandatory (action ID=" + id + ";" + + (plugin == null ? "" : " plugin path: "+plugin.getPath()) + ")"; + reportActionError(pluginId, message); + return null; + } + + ActionStub stub = new ActionStub(className, id, text, loader, pluginId, iconPath); + Presentation presentation = stub.getTemplatePresentation(); + presentation.setText(text); + + // description + + presentation.setDescription(loadDescriptionForElement(element, bundle, id, ACTION_ELEMENT_NAME)); + + // process all links and key bindings if any + for (final Object o : element.getChildren()) { + Element e = (Element)o; + if (ADD_TO_GROUP_ELEMENT_NAME.equals(e.getName())) { + processAddToGroupNode(stub, e, pluginId, isSecondary(e)); + } + else if (SHORTCUT_ELEMENT_NAME.equals(e.getName())) { + processKeyboardShortcutNode(e, id, pluginId); + } + else if (MOUSE_SHORTCUT_ELEMENT_NAME.equals(e.getName())) { + processMouseShortcutNode(e, id, pluginId); + } + else { + reportActionError(pluginId, "unexpected name of element \"" + e.getName() + "\""); + return null; + } + } + if (element.getAttributeValue(USE_SHORTCUT_OF_ATTR_NAME) != null) { + ((KeymapManagerEx)myKeymapManager).bindShortcuts(element.getAttributeValue(USE_SHORTCUT_OF_ATTR_NAME), id); + } + + // register action + registerAction(id, stub, pluginId); + return stub; + } + + private static ResourceBundle getActionsResourceBundle(ClassLoader loader, IdeaPluginDescriptor plugin) { + @NonNls final String resBundleName = plugin != null && !plugin.getPluginId().getIdString().equals("com.intellij") ? plugin.getResourceBundleBaseName() : ACTIONS_BUNDLE; + ResourceBundle bundle = null; + if (resBundleName != null) { + bundle = getBundle(loader, resBundleName); + } + return bundle; + } + + private static boolean isSecondary(Element element) { + return "true".equalsIgnoreCase(element.getAttributeValue(SECONDARY)); + } + + private static void setIcon(@Nullable final String iconPath, final String className, final ClassLoader loader, final Presentation presentation, + final PluginId pluginId) { + if (iconPath == null) return; + + try { + final Class actionClass = Class.forName(className, true, loader); + setIconFromClass(actionClass, loader, iconPath, className, presentation, pluginId); + } + catch (ClassNotFoundException e) { + LOG.error(e); + reportActionError(pluginId, "class with name \"" + className + "\" not found"); + } + catch (NoClassDefFoundError e) { + LOG.error(e); + reportActionError(pluginId, "class with name \"" + className + "\" not found"); + } + } + + private static void setIconFromClass(@NotNull final Class actionClass, @NotNull ClassLoader classLoader, @NotNull final String iconPath, final String className, + final Presentation presentation, final PluginId pluginId) { + //try to find icon in idea class path + Icon icon = IconLoader.findIcon(iconPath, actionClass); + if (icon == null) icon = IconLoader.findIcon(iconPath, classLoader); + if (icon == null) { + reportActionError(pluginId, "Icon cannot be found in '" + iconPath + "', action class='" + className + "'"); + } + else { + presentation.setIcon(icon); + } + } + + private static String loadDescriptionForElement(final Element element, final ResourceBundle bundle, final String id, String elementType) { + final String value = element.getAttributeValue(DESCRIPTION); + if (bundle != null) { + @NonNls final String key = elementType + "." + id + ".description"; + return CommonBundle.messageOrDefault(bundle, key, value == null ? "" : value); + } else { + return value; + } + } + + private static String loadTextForElement(final Element element, final ResourceBundle bundle, final String id, String elementType) { + final String value = element.getAttributeValue(TEXT_ATTR_NAME); + return CommonBundle.messageOrDefault(bundle, elementType + "." + id + "." + TEXT_ATTR_NAME, value == null ? "" : value); + } + + private AnAction processGroupElement(Element element, final ClassLoader loader, PluginId pluginId) { + final Application app = ApplicationManager.getApplication(); + final IdeaPluginDescriptor plugin = app.getPlugin(pluginId); + ResourceBundle bundle = getActionsResourceBundle(loader, plugin); + + if (!GROUP_ELEMENT_NAME.equals(element.getName())) { + reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); + return null; + } + String className = element.getAttributeValue(CLASS_ATTR_NAME); + if (className == null) { // use default group if class isn't specified + className = DefaultActionGroup.class.getName(); + } + try { + Class aClass = Class.forName(className, true, loader); + Object obj = new ConstructorInjectionComponentAdapter(className, aClass).getComponentInstance(ApplicationManager.getApplication().getPicoContainer()); + + if (!(obj instanceof ActionGroup)) { + reportActionError(pluginId, "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName()); + return null; + } + if (element.getChildren().size() != element.getChildren(ADD_TO_GROUP_ELEMENT_NAME).size() ) { // + if (!(obj instanceof DefaultActionGroup)) { + reportActionError(pluginId, "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() + + " because there are children specified"); + return null; + } + } + ActionGroup group = (ActionGroup)obj; + // read ID and register loaded group + String id = element.getAttributeValue(ID_ATTR_NAME); + if (id != null && id.length() == 0) { + reportActionError(pluginId, "ID of the group cannot be an empty string"); + return null; + } + if (Boolean.valueOf(element.getAttributeValue(INTERNAL_ATTR_NAME)).booleanValue() && !ApplicationManagerEx.getApplicationEx().isInternal()) { + myNotRegisteredInternalActionIds.add(id); + return null; + } + + if (id != null) { + registerAction(id, group); + } + Presentation presentation = group.getTemplatePresentation(); + + // text + String text = loadTextForElement(element, bundle, id, GROUP_ELEMENT_NAME); + // don't override value which was set in API with empty value from xml descriptor + if (!StringUtil.isEmpty(text) || presentation.getText() == null) { + presentation.setText(text); + } + + // description + String description = loadDescriptionForElement(element, bundle, id, GROUP_ELEMENT_NAME); + // don't override value which was set in API with empty value from xml descriptor + if (!StringUtil.isEmpty(description) || presentation.getDescription() == null) { + presentation.setDescription(description); + } + + // icon + setIcon(element.getAttributeValue(ICON_ATTR_NAME), className, loader, presentation, pluginId); + // popup + String popup = element.getAttributeValue(POPUP_ATTR_NAME); + if (popup != null) { + group.setPopup(Boolean.valueOf(popup).booleanValue()); + } + // process all group's children. There are other groups, actions, references and links + for (final Object o : element.getChildren()) { + Element child = (Element)o; + String name = child.getName(); + if (ACTION_ELEMENT_NAME.equals(name)) { + AnAction action = processActionElement(child, loader, pluginId); + if (action != null) { + assertActionIsGroupOrStub(action); + ((DefaultActionGroup)group).addAction(action, Constraints.LAST, this).setAsSecondary(isSecondary(child)); + } + } + else if (SEPARATOR_ELEMENT_NAME.equals(name)) { + processSeparatorNode((DefaultActionGroup)group, child, pluginId); + } + else if (GROUP_ELEMENT_NAME.equals(name)) { + AnAction action = processGroupElement(child, loader, pluginId); + if (action != null) { + ((DefaultActionGroup)group).add(action, this); + } + } + else if (ADD_TO_GROUP_ELEMENT_NAME.equals(name)) { + processAddToGroupNode(group, child, pluginId, isSecondary(child)); + } + else if (REFERENCE_ELEMENT_NAME.equals(name)) { + AnAction action = processReferenceElement(child, pluginId); + if (action != null) { + ((DefaultActionGroup)group).addAction(action, Constraints.LAST, this).setAsSecondary(isSecondary(child)); + } + } + else { + reportActionError(pluginId, "unexpected name of element \"" + name + "\n"); + return null; + } + } + return group; + } + catch (ClassNotFoundException e) { + reportActionError(pluginId, "class with name \"" + className + "\" not found"); + return null; + } + catch (NoClassDefFoundError e) { + reportActionError(pluginId, "class with name \"" + e.getMessage() + "\" not found"); + return null; + } + catch(UnsupportedClassVersionError e) { + reportActionError(pluginId, "unsupported class version for " + className); + return null; + } + catch (Exception e) { + final String message = "cannot create class \"" + className + "\""; + if (pluginId == null) { + LOG.error(message, e); + } + else { + LOG.error(new PluginException(message, e, pluginId)); + } + return null; + } + } + + private void processReferenceNode(final Element element, final PluginId pluginId) { + final AnAction action = processReferenceElement(element, pluginId); + + for (final Object o : element.getChildren()) { + Element child = (Element)o; + if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.getName())) { + processAddToGroupNode(action, child, pluginId, isSecondary(child)); + } + } + } + + private static final Map ourBundlesCache = new HashMap(); + + private static ResourceBundle getBundle(final ClassLoader loader, final String resBundleName) { + + if (ourBundlesCache.containsKey(resBundleName)) { + return ourBundlesCache.get(resBundleName); + } + + final ResourceBundle bundle = ResourceBundle.getBundle(resBundleName, Locale.getDefault(), loader); + + ourBundlesCache.put(resBundleName, bundle); + + return bundle; + } + + /**\ + * @param element description of link + * @param pluginId + * @param secondary + */ + private void processAddToGroupNode(AnAction action, Element element, final PluginId pluginId, boolean secondary) { + // Real subclasses of AnAction should not be here + if (!(action instanceof Separator)) { + assertActionIsGroupOrStub(action); + } + + String actionName = action instanceof ActionStub ? ((ActionStub)action).getClassName() : action.getClass().getName(); + + if (!ADD_TO_GROUP_ELEMENT_NAME.equals(element.getName())) { + reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); + return; + } + + // parent group + final AnAction parentGroup = getParentGroup(element.getAttributeValue(GROUPID_ATTR_NAME), actionName, pluginId); + if (parentGroup == null) { + return; + } + + // anchor attribute + final Anchor anchor = parseAnchor(element.getAttributeValue(ANCHOR_ELEMENT_NAME), + actionName, pluginId); + if (anchor == null) { + return; + } + + final String relativeToActionId = element.getAttributeValue(RELATIVE_TO_ACTION_ATTR_NAME); + if (!checkRelativeToAction(relativeToActionId, anchor, actionName, pluginId)) { + return; + } + final DefaultActionGroup group = (DefaultActionGroup)parentGroup; + group.addAction(action, new Constraints(anchor, relativeToActionId), this).setAsSecondary(secondary); + } + + public static boolean checkRelativeToAction(final String relativeToActionId, + @NotNull final Anchor anchor, + @NotNull final String actionName, + @Nullable final PluginId pluginId) { + if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) { + reportActionError(pluginId, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\""); + return false; + } + return true; + } + + @Nullable + public static Anchor parseAnchor(final String anchorStr, + @Nullable final String actionName, + @Nullable final PluginId pluginId) { + if (anchorStr == null) { + return Anchor.LAST; + } + + if (FIRST.equalsIgnoreCase(anchorStr)) { + return Anchor.FIRST; + } + else if (LAST.equalsIgnoreCase(anchorStr)) { + return Anchor.LAST; + } + else if (BEFORE.equalsIgnoreCase(anchorStr)) { + return Anchor.BEFORE; + } + else if (AFTER.equalsIgnoreCase(anchorStr)) { + return Anchor.AFTER; + } + else { + reportActionError(pluginId, actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\""); + return null; + } + } + + @Nullable + public AnAction getParentGroup(final String groupId, + @Nullable final String actionName, + @Nullable final PluginId pluginId) { + if (groupId == null || groupId.length() == 0) { + reportActionError(pluginId, actionName + ": attribute \"group-id\" should be defined"); + return null; + } + AnAction parentGroup = getActionImpl(groupId, true); + if (parentGroup == null) { + reportActionError(pluginId, actionName + ": action with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group"); + parentGroup = getActionImpl(IdeActions.GROUP_OTHER_MENU, true); + } + if (!(parentGroup instanceof DefaultActionGroup)) { + reportActionError(pluginId, actionName + ": action with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() + + " but was " + parentGroup.getClass()); + return null; + } + return parentGroup; + } + + /** + * @param parentGroup group wich is the parent of the separator. It can be null in that + * case separator will be added to group described in the subelement. + * @param element XML element which represent separator. + */ + private void processSeparatorNode(DefaultActionGroup parentGroup, Element element, PluginId pluginId) { + if (!SEPARATOR_ELEMENT_NAME.equals(element.getName())) { + reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); + return; + } + Separator separator = Separator.getInstance(); + if (parentGroup != null) { + parentGroup.add(separator, this); + } + // try to find inner tag + for (final Object o : element.getChildren()) { + Element child = (Element)o; + if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.getName())) { + processAddToGroupNode(separator, child, pluginId, isSecondary(child)); + } + } + } + + private void processKeyboardShortcutNode(Element element, String actionId, PluginId pluginId) { + String firstStrokeString = element.getAttributeValue(FIRST_KEYSTROKE_ATTR_NAME); + if (firstStrokeString == null) { + reportActionError(pluginId, "\"first-keystroke\" attribute must be specified for action with id=" + actionId); + return; + } + KeyStroke firstKeyStroke = getKeyStroke(firstStrokeString); + if (firstKeyStroke == null) { + reportActionError(pluginId, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId); + return; + } + + KeyStroke secondKeyStroke = null; + String secondStrokeString = element.getAttributeValue(SECOND_KEYSTROKE_ATTR_NAME); + if (secondStrokeString != null) { + secondKeyStroke = getKeyStroke(secondStrokeString); + if (secondKeyStroke == null) { + reportActionError(pluginId, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId); + return; + } + } + + String keymapName = element.getAttributeValue(KEYMAP_ATTR_NAME); + if (keymapName == null || keymapName.trim().length() == 0) { + reportActionError(pluginId, "attribute \"keymap\" should be defined"); + return; + } + Keymap keymap = myKeymapManager.getKeymap(keymapName); + if (keymap == null) { + reportActionError(pluginId, "keymap \"" + keymapName + "\" not found"); + return; + } + final String removeOption = element.getAttributeValue(REMOVE_SHORTCUT_ATTR_NAME); + final KeyboardShortcut shortcut = new KeyboardShortcut(firstKeyStroke, secondKeyStroke); + final String replaceOption = element.getAttributeValue(REPLACE_SHORTCUT_ATTR_NAME); + if (Boolean.valueOf(removeOption)) { + keymap.removeShortcut(actionId, shortcut); + } + if (Boolean.valueOf(replaceOption)) { + keymap.removeAllActionShortcuts(actionId); + } + if (!Boolean.valueOf(removeOption)) { + keymap.addShortcut(actionId, shortcut); + } + } + + private static void processMouseShortcutNode(Element element, String actionId, PluginId pluginId) { + String keystrokeString = element.getAttributeValue(KEYSTROKE_ATTR_NAME); + if (keystrokeString == null || keystrokeString.trim().length() == 0) { + reportActionError(pluginId, "\"keystroke\" attribute must be specified for action with id=" + actionId); + return; + } + MouseShortcut shortcut; + try { + shortcut = KeymapUtil.parseMouseShortcut(keystrokeString); + } + catch (Exception ex) { + reportActionError(pluginId, "\"keystroke\" attribute has invalid value for action with id=" + actionId); + return; + } + + String keymapName = element.getAttributeValue(KEYMAP_ATTR_NAME); + if (keymapName == null || keymapName.length() == 0) { + reportActionError(pluginId, "attribute \"keymap\" should be defined"); + return; + } + Keymap keymap = KeymapManager.getInstance().getKeymap(keymapName); + if (keymap == null) { + reportActionError(pluginId, "keymap \"" + keymapName + "\" not found"); + return; + } + + final String removeOption = element.getAttributeValue(REMOVE_SHORTCUT_ATTR_NAME); + if (Boolean.valueOf(removeOption)) { + keymap.removeShortcut(actionId, shortcut); + } else { + keymap.addShortcut(actionId, shortcut); + } + } + + @Nullable + private AnAction processReferenceElement(Element element, PluginId pluginId) { + if (!REFERENCE_ELEMENT_NAME.equals(element.getName())) { + reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); + return null; + } + String ref = element.getAttributeValue(REF_ATTR_NAME); + + if (ref==null) { + // support old style references by id + ref = element.getAttributeValue(ID_ATTR_NAME); + } + + if (ref == null || ref.length() == 0) { + reportActionError(pluginId, "ID of reference element should be defined"); + return null; + } + + AnAction action = getActionImpl(ref, true); + + if (action == null) { + if (!myNotRegisteredInternalActionIds.contains(ref)) { + reportActionError(pluginId, "action specified by reference isn't registered (ID=" + ref + ")"); + } + return null; + } + assertActionIsGroupOrStub(action); + return action; + } + + private void processActionsElement(Element element, ClassLoader loader, PluginId pluginId) { + if (!ACTIONS_ELEMENT_NAME.equals(element.getName())) { + reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\""); + return; + } + synchronized (myLock) { + for (final Object o : element.getChildren()) { + Element child = (Element)o; + processActionsChildElement(loader, pluginId, child); + } + } + } + + private void processActionsChildElement(final ClassLoader loader, final PluginId pluginId, final Element child) { + String name = child.getName(); + if (ACTION_ELEMENT_NAME.equals(name)) { + AnAction action = processActionElement(child, loader, pluginId); + if (action != null) { + assertActionIsGroupOrStub(action); + } + } + else if (GROUP_ELEMENT_NAME.equals(name)) { + processGroupElement(child, loader, pluginId); + } + else if (SEPARATOR_ELEMENT_NAME.equals(name)) { + processSeparatorNode(null, child, pluginId); + } + else if (REFERENCE_ELEMENT_NAME.equals(name)) { + processReferenceNode(child, pluginId); + } + else { + reportActionError(pluginId, "unexpected name of element \"" + name + "\n"); + } + } + + private static void assertActionIsGroupOrStub(final AnAction action) { + if (!(action instanceof ActionGroup || action instanceof ActionStub)) { + LOG.error("Action : " + action + "; class: " + action.getClass()); + } + } + + public void registerAction(@NotNull String actionId, @NotNull AnAction action, @Nullable PluginId pluginId) { + synchronized (myLock) { + if (myId2Action.containsKey(actionId)) { + reportActionError(pluginId, "action with the ID \"" + actionId + "\" was already registered. Action being registered is " + action.toString() + + "; Registered action is " + + myId2Action.get(actionId) + getPluginInfo(pluginId)); + return; + } + if (myAction2Id.containsKey(action)) { + reportActionError(pluginId, "action was already registered for another ID. ID is " + myAction2Id.get(action) + + getPluginInfo(pluginId)); + return; + } + myId2Action.put(actionId, action); + myId2Index.put(actionId, myRegisteredActionsCount++); + myAction2Id.put(action, actionId); + if (pluginId != null && !(action instanceof ActionGroup)){ + THashSet pluginActionIds = myPlugin2Id.get(pluginId); + if (pluginActionIds == null){ + pluginActionIds = new THashSet(); + myPlugin2Id.put(pluginId, pluginActionIds); + } + pluginActionIds.add(actionId); + } + action.registerCustomShortcutSet(new ProxyShortcutSet(actionId, myKeymapManager), null); + } + } + + private static void reportActionError(final PluginId pluginId, @NonNls final String message) { + if (pluginId == null) { + LOG.error(message); + } + else { + LOG.error(new PluginException(message, null, pluginId)); + } + } + + @NonNls + private static String getPluginInfo(@Nullable PluginId id) { + if (id != null) { + final IdeaPluginDescriptor plugin = ApplicationManager.getApplication().getPlugin(id); + if (plugin != null) { + String name = plugin.getName(); + if (name == null) { + name = id.getIdString(); + } + return " Plugin: " + name; + } + } + return ""; + } + + public void registerAction(@NotNull String actionId, @NotNull AnAction action) { + registerAction(actionId, action, null); + } + + public void unregisterAction(@NotNull String actionId) { + synchronized (myLock) { + if (!myId2Action.containsKey(actionId)) { + if (LOG.isDebugEnabled()) { + LOG.debug("action with ID " + actionId + " wasn't registered"); + return; + } + } + AnAction oldValue = (AnAction)myId2Action.remove(actionId); + myAction2Id.remove(oldValue); + myId2Index.remove(actionId); + for (PluginId pluginName : myPlugin2Id.keySet()) { + final THashSet pluginActions = myPlugin2Id.get(pluginName); + if (pluginActions != null) { + pluginActions.remove(actionId); + } + } + } + } + + @NotNull + public String getComponentName() { + return "ActionManager"; + } + + public Comparator getRegistrationOrderComparator() { + return new Comparator() { + public int compare(String id1, String id2) { + return myId2Index.get(id1) - myId2Index.get(id2); + } + }; + } + + public String[] getPluginActions(PluginId pluginName) { + if (myPlugin2Id.containsKey(pluginName)){ + final THashSet pluginActions = myPlugin2Id.get(pluginName); + return ArrayUtil.toStringArray(pluginActions); + } + return ArrayUtil.EMPTY_STRING_ARRAY; + } + + public void addActionPopup(final ActionPopupMenuImpl menu) { + myPopups.add(menu); + } + + public void removeActionPopup(final ActionPopupMenuImpl menu) { + final boolean removed = myPopups.remove(menu); + if (removed && myPopups.isEmpty()) { + flushActionPerformed(); + } + } + + public void queueActionPerformedEvent(final AnAction action, DataContext context, AnActionEvent event) { + if (!myPopups.isEmpty()) { + myQueuedNotifications.put(action, context); + } else { + fireAfterActionPerformed(action, context, event); + } + } + + + public boolean isActionPopupStackEmpty() { + return myPopups.isEmpty(); + } + + @Override + public boolean isTransparrentOnlyActionsUpdateNow() { + return myTransparrentOnlyUpdate; + } + + private void flushActionPerformed() { + final Set actions = myQueuedNotifications.keySet(); + for (final AnAction eachAction : actions) { + final DataContext eachContext = myQueuedNotifications.get(eachAction); + fireAfterActionPerformed(eachAction, eachContext, myQueuedNotificationsEvents.get(eachAction)); + } + myQueuedNotifications.clear(); + myQueuedNotificationsEvents.clear(); + } + + private AnActionListener[] getActionListeners() { + if (myCachedActionListeners == null) { + myCachedActionListeners = myActionListeners.toArray(new AnActionListener[myActionListeners.size()]); + } + + return myCachedActionListeners; + } + + public void addAnActionListener(AnActionListener listener) { + myActionListeners.add(listener); + myCachedActionListeners = null; + } + + public void addAnActionListener(final AnActionListener listener, final Disposable parentDisposable) { + addAnActionListener(listener); + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + removeAnActionListener(listener); + } + }); + } + + public void removeAnActionListener(AnActionListener listener) { + myActionListeners.remove(listener); + myCachedActionListeners = null; + } + + public void fireBeforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { + if (action != null) { + myPrevPerformedActionId = myLastPreformedActionId; + myLastPreformedActionId = getId(action); + IdeaLogger.ourLastActionId = myLastPreformedActionId; + } + AnActionListener[] listeners = getActionListeners(); + for (AnActionListener listener : listeners) { + listener.beforeActionPerformed(action, dataContext, event); + } + } + + public void fireAfterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { + if (action != null) { + myPrevPerformedActionId = myLastPreformedActionId; + myLastPreformedActionId = getId(action); + IdeaLogger.ourLastActionId = myLastPreformedActionId; + } + AnActionListener[] listeners = getActionListeners(); + for (AnActionListener listener : listeners) { + try { + listener.afterActionPerformed(action, dataContext, event); + } + catch(AbstractMethodError e) { + // ignore + } + } + } + + @Override + public KeyboardShortcut getKeyboardShortcut(@NotNull String actionId) { + AnAction action = ActionManager.getInstance().getAction(actionId); + final ShortcutSet shortcutSet = action.getShortcutSet(); + final Shortcut[] shortcuts = shortcutSet.getShortcuts(); + for (final Shortcut shortcut : shortcuts) { + // Shortcut can be MouseShortcut here. + // For example IdeaVIM often assigns them + if (shortcut instanceof KeyboardShortcut){ + final KeyboardShortcut kb = (KeyboardShortcut)shortcut; + if (kb.getSecondKeyStroke() == null) { + return (KeyboardShortcut)shortcut; + } + } + } + + return null; + } + + public void fireBeforeEditorTyping(char c, DataContext dataContext) { + myLastTimeEditorWasTypedIn = System.currentTimeMillis(); + AnActionListener[] listeners = getActionListeners(); + for (AnActionListener listener : listeners) { + listener.beforeEditorTyping(c, dataContext); + } + } + + public String getLastPreformedActionId() { + return myLastPreformedActionId; + } + + public String getPrevPreformedActionId() { + return myPrevPerformedActionId; + } + + public Set getActionIds(){ + return new HashSet(myId2Action.keySet()); + } + + private int myActionsPreloaded = 0; + + public void preloadActions() { + if (myPreloadActionsRunnable == null) { + myPreloadActionsRunnable = new Runnable() { + public void run() { + doPreloadActions(); + } + }; + ApplicationManager.getApplication().executeOnPooledThread(myPreloadActionsRunnable); + } + } + + private void doPreloadActions() { + try { + Thread.sleep(5000); // wait for project initialization to complete + } + catch (InterruptedException e) { + // ignore + } + preloadActionGroup(IdeActions.GROUP_EDITOR_POPUP); + preloadActionGroup(IdeActions.GROUP_EDITOR_TAB_POPUP); + preloadActionGroup(IdeActions.GROUP_PROJECT_VIEW_POPUP); + preloadActionGroup(IdeActions.GROUP_MAIN_MENU); + // TODO anything else? + LOG.debug("Actions preloading completed"); + } + + public void preloadActionGroup(final String groupId) { + final AnAction action = getAction(groupId); + if (action instanceof ActionGroup) { + preloadActionGroup((ActionGroup) action); + } + } + + private void preloadActionGroup(final ActionGroup group) { + final AnAction[] children = ApplicationManager.getApplication().runReadAction(new Computable() { + public AnAction[] compute() { + if (ApplicationManager.getApplication().isDisposed()) { + return AnAction.EMPTY_ARRAY; + } + + return group.getChildren(null); + } + }); + for (AnAction action : children) { + if (action instanceof PreloadableAction) { + ((PreloadableAction)action).preload(); + } + else if (action instanceof ActionGroup) { + preloadActionGroup((ActionGroup)action); + } + + myActionsPreloaded++; + if (myActionsPreloaded % 10 == 0) { + try { + Thread.sleep(300); + } + catch (InterruptedException e) { + // ignore + } + } + } + } + + private class MyTimer extends Timer implements ActionListener { + private final List myTimerListeners = Collections.synchronizedList(new ArrayList()); + private final List myTransparrentTimerListeners = Collections.synchronizedList(new ArrayList()); + private int myLastTimePerformed; + + MyTimer() { + super(TIMER_DELAY, null); + addActionListener(this); + setRepeats(true); + } + + public void addTimerListener(TimerListener listener, boolean transparent){ + if (transparent) { + myTransparrentTimerListeners.add(listener); + } else { + myTimerListeners.add(listener); + } + } + + public void removeTimerListener(TimerListener listener, boolean transparent){ + if (transparent) { + myTransparrentTimerListeners.remove(listener); + } else { + myTimerListeners.remove(listener); + } + } + + public void actionPerformed(ActionEvent e) { + if (myLastTimeEditorWasTypedIn + UPDATE_DELAY_AFTER_TYPING > System.currentTimeMillis()) { + return; + } + + if (IdeFocusManager.getInstance(null).isFocusBeingTransferred()) return; + + final int lastEventCount = myLastTimePerformed; + myLastTimePerformed = ActivityTracker.getInstance().getCount(); + + boolean transparentOnly = myLastTimePerformed == lastEventCount; + + try { + HashSet notified = new HashSet(); + myTransparrentOnlyUpdate = transparentOnly; + notifyListeners(myTransparrentTimerListeners, notified); + + if (transparentOnly) { + return; + } + + notifyListeners(myTimerListeners, notified); + } + finally { + myTransparrentOnlyUpdate = false; + } + } + + private void notifyListeners(final List timerListeners, final Set notified) { + final TimerListener[] listeners = timerListeners.toArray(new TimerListener[timerListeners.size()]); + for (TimerListener listener : listeners) { + if (timerListeners.contains(listener)) { + if (!notified.contains(listener)) { + notified.add(listener); + runListenerAction(listener); + } + } + } + } + + private void runListenerAction(final TimerListener listener) { + ModalityState modalityState = listener.getModalityState(); + if (modalityState == null) return; + if (!ModalityState.current().dominates(modalityState)) { + try { + listener.run(); + } + catch (ProcessCanceledException ex) { + // ignore + } + catch (Throwable e) { + LOG.error(e); + } + } + } + } + + public ActionCallback tryToExecute(@NotNull final AnAction action, @NotNull final InputEvent inputEvent, @Nullable final Component contextComponent, @Nullable final String place, + boolean now) { + + final Application app = ApplicationManager.getApplication(); + assert app.isDispatchThread(); + + final ActionCallback result = new ActionCallback(); + final Runnable doRunnable = new Runnable() { + public void run() { + tryToExecuteNow(action, inputEvent, contextComponent, place, result); + } + }; + + if (now) { + doRunnable.run(); + } else { + SwingUtilities.invokeLater(doRunnable); + } + + return result; + + } + + private void tryToExecuteNow(final AnAction action, final InputEvent inputEvent, final Component contextComponent, final String place, final ActionCallback result) { + final Presentation presenation = (Presentation)action.getTemplatePresentation().clone(); + + IdeFocusManager.findInstanceByContext(getContextBy(contextComponent)).doWhenFocusSettlesDown(new Runnable() { + public void run() { + final DataContext context = getContextBy(contextComponent); + + AnActionEvent event = new AnActionEvent( + inputEvent, context, + place != null ? place : ActionPlaces.UNKNOWN, + presenation, ActionManagerImpl.this, + inputEvent.getModifiersEx() + ); + + ActionUtil.performDumbAwareUpdate(action, event, false); + if (!event.getPresentation().isEnabled()) { + result.setRejected(); + return; + } + + ActionUtil.lastUpdateAndCheckDumb(action, event, false); + if (!event.getPresentation().isEnabled()) { + result.setRejected(); + return; + } + + Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(context); + if (component != null && !component.isShowing()) { + result.setRejected(); + return; + } + + fireBeforeActionPerformed(action, context, event); + + UIUtil.addAwtListener(new AWTEventListener() { + public void eventDispatched(AWTEvent event) { + if (event.getID() == WindowEvent.WINDOW_OPENED ||event.getID() == WindowEvent.WINDOW_ACTIVATED) { + if (!result.isProcessed()) { + final WindowEvent we = (WindowEvent)event; + IdeFocusManager.findInstanceByComponent(we.getWindow()).doWhenFocusSettlesDown(new Runnable() { + public void run() { + result.setDone(); + } + }); + } + } + } + }, WindowEvent.WINDOW_EVENT_MASK, result); + + action.actionPerformed(event); + result.setDone(); + queueActionPerformedEvent(action, context, event); + } + }); + } + + private static DataContext getContextBy(Component contextComponent) { + final DataManager dataManager = DataManager.getInstance(); + return contextComponent != null ? dataManager.getDataContext(contextComponent) : dataManager.getDataContext(); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java b/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java index 929b3527d501..d2adb5695810 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java @@ -1,52 +1,52 @@ -/* - * 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.application.ex; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.CachedSingletonsRegistry; -import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.util.Disposer; -import org.jetbrains.annotations.NonNls; - -/** - * @author max - */ -public class ApplicationManagerEx extends ApplicationManager { - public static ApplicationEx getApplicationEx() { - return (ApplicationEx) ourApplication; - } - - public static void createApplication(boolean internal, boolean isUnitTestMode, boolean isHeadlessMode, boolean isCommandline, @NonNls String appName) { - new ApplicationImpl(internal, isUnitTestMode, isHeadlessMode, isCommandline, appName); - } - - public static void setApplication(Application instance) { - ourApplication = instance; - CachedSingletonsRegistry.cleanupCachedFields(); - } - - public static void setApplication(Application instance, Disposable parent) { - final Application old = ourApplication; - Disposer.register(parent, new Disposable() { - public void dispose() { - setApplication(old); - } - }); - setApplication(instance); - } -} +/* + * 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.application.ex; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.CachedSingletonsRegistry; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.util.Disposer; +import org.jetbrains.annotations.NonNls; + +/** + * @author max + */ +public class ApplicationManagerEx extends ApplicationManager { + public static ApplicationEx getApplicationEx() { + return (ApplicationEx) ourApplication; + } + + public static void createApplication(boolean internal, boolean isUnitTestMode, boolean isHeadlessMode, boolean isCommandline, @NonNls String appName) { + new ApplicationImpl(internal, isUnitTestMode, isHeadlessMode, isCommandline, appName); + } + + public static void setApplication(Application instance) { + ourApplication = instance; + CachedSingletonsRegistry.cleanupCachedFields(); + } + + public static void setApplication(Application instance, Disposable parent) { + final Application old = ourApplication; + Disposer.register(parent, new Disposable() { + public void dispose() { + setApplication(old); + } + }); + setApplication(instance); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ModalityInvokatorImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ModalityInvokatorImpl.java index bcbd6378db01..f557edee8075 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ModalityInvokatorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ModalityInvokatorImpl.java @@ -1,45 +1,45 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.openapi.application.impl; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityInvokator; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.Condition; -import org.jetbrains.annotations.NotNull; - -public class ModalityInvokatorImpl implements ModalityInvokator { - public ActionCallback invokeLater(Runnable runnable) { - return invokeLater(runnable, ApplicationManager.getApplication().getDisposed()); - } - - public ActionCallback invokeLater(final Runnable runnable, @NotNull final Condition expired) { - return LaterInvocator.invokeLater(runnable, expired); - } - - public ActionCallback invokeLater(final Runnable runnable, @NotNull final ModalityState state, @NotNull final Condition expired) { - return LaterInvocator.invokeLater(runnable, state, expired); - } - - public ActionCallback invokeLater(Runnable runnable, @NotNull ModalityState state) { - return invokeLater(runnable, state, ApplicationManager.getApplication().getDisposed()); - } +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.openapi.application.impl; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityInvokator; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Condition; +import org.jetbrains.annotations.NotNull; + +public class ModalityInvokatorImpl implements ModalityInvokator { + public ActionCallback invokeLater(Runnable runnable) { + return invokeLater(runnable, ApplicationManager.getApplication().getDisposed()); + } + + public ActionCallback invokeLater(final Runnable runnable, @NotNull final Condition expired) { + return LaterInvocator.invokeLater(runnable, expired); + } + + public ActionCallback invokeLater(final Runnable runnable, @NotNull final ModalityState state, @NotNull final Condition expired) { + return LaterInvocator.invokeLater(runnable, state, expired); + } + + public ActionCallback invokeLater(Runnable runnable, @NotNull ModalityState state) { + return invokeLater(runnable, state, ApplicationManager.getApplication().getDisposed()); + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/DummyProject.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/DummyProject.java index 370453fde0a1..d4c68497096a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/DummyProject.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/DummyProject.java @@ -1,172 +1,172 @@ -/* - * 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.command.impl; - -import com.intellij.openapi.components.BaseComponent; -import com.intellij.openapi.components.ComponentConfig; -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; -import com.intellij.util.messages.MessageBus; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.picocontainer.PicoContainer; - -/** - * @author max - */ -public class DummyProject extends UserDataHolderBase implements Project { - - private static class DummyProjectHolder { - private static final DummyProject ourInstance = new DummyProject(); - } - - public static Project getInstance() { - return DummyProjectHolder.ourInstance; - } - - private DummyProject() { - } - - public VirtualFile getProjectFile() { - return null; - } - - @NotNull - public String getName() { - return ""; - } - - @Nullable - @NonNls - public String getPresentableUrl() { - return null; - } - - @NotNull - @NonNls - public String getLocationHash() { - return "dummy"; - } - - @Nullable - @NonNls - public String getLocation() { - throw new UnsupportedOperationException("Method getLocation not implemented in " + getClass()); - } - - @NotNull - public String getProjectFilePath() { - return ""; - } - - public VirtualFile getWorkspaceFile() { - return null; - } - - @Nullable - public VirtualFile getBaseDir() { - return null; - } - - public void save() { - } - - public BaseComponent getComponent(String name) { - return null; - } - - public T getComponent(Class interfaceClass) { - return null; - } - - @NotNull - public Class[] getComponentInterfaces() { - return ArrayUtil.EMPTY_CLASS_ARRAY; - } - - public boolean hasComponent(@NotNull Class interfaceClass) { - return false; - } - - @NotNull - public T[] getComponents(Class baseClass) { - return (T[]) ArrayUtil.EMPTY_OBJECT_ARRAY; - } - - @NotNull - public PicoContainer getPicoContainer() { - throw new UnsupportedOperationException("getPicoContainer is not implement in : " + getClass()); - } - - public T getComponent(Class interfaceClass, T defaultImplementation) { - return null; - } - - public boolean isDisposed() { - return false; - } - - @NotNull - public Condition getDisposed() { - return new Condition() { - public boolean value(final Object o) { - return isDisposed(); - } - }; - } - - @NotNull - public ComponentConfig[] getComponentConfigurations() { - return new ComponentConfig[0]; - } - - @Nullable - public Object getComponent(final ComponentConfig componentConfig) { - return null; - } - - public boolean isOpen() { - return false; - } - - public boolean isInitialized() { - return false; - } - - public boolean isDefault() { - return false; - } - - public MessageBus getMessageBus() { - return null; - } - - public void dispose() { - } - - public T[] getExtensions(final ExtensionPointName extensionPointName) { - throw new UnsupportedOperationException("getExtensions()"); - } - - public ComponentConfig getConfig(Class componentImplementation) { - throw new UnsupportedOperationException("Method getConfig not implemented in " + getClass()); - } -} +/* + * 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.command.impl; + +import com.intellij.openapi.components.BaseComponent; +import com.intellij.openapi.components.ComponentConfig; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ArrayUtil; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.picocontainer.PicoContainer; + +/** + * @author max + */ +public class DummyProject extends UserDataHolderBase implements Project { + + private static class DummyProjectHolder { + private static final DummyProject ourInstance = new DummyProject(); + } + + public static Project getInstance() { + return DummyProjectHolder.ourInstance; + } + + private DummyProject() { + } + + public VirtualFile getProjectFile() { + return null; + } + + @NotNull + public String getName() { + return ""; + } + + @Nullable + @NonNls + public String getPresentableUrl() { + return null; + } + + @NotNull + @NonNls + public String getLocationHash() { + return "dummy"; + } + + @Nullable + @NonNls + public String getLocation() { + throw new UnsupportedOperationException("Method getLocation not implemented in " + getClass()); + } + + @NotNull + public String getProjectFilePath() { + return ""; + } + + public VirtualFile getWorkspaceFile() { + return null; + } + + @Nullable + public VirtualFile getBaseDir() { + return null; + } + + public void save() { + } + + public BaseComponent getComponent(String name) { + return null; + } + + public T getComponent(Class interfaceClass) { + return null; + } + + @NotNull + public Class[] getComponentInterfaces() { + return ArrayUtil.EMPTY_CLASS_ARRAY; + } + + public boolean hasComponent(@NotNull Class interfaceClass) { + return false; + } + + @NotNull + public T[] getComponents(Class baseClass) { + return (T[]) ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + @NotNull + public PicoContainer getPicoContainer() { + throw new UnsupportedOperationException("getPicoContainer is not implement in : " + getClass()); + } + + public T getComponent(Class interfaceClass, T defaultImplementation) { + return null; + } + + public boolean isDisposed() { + return false; + } + + @NotNull + public Condition getDisposed() { + return new Condition() { + public boolean value(final Object o) { + return isDisposed(); + } + }; + } + + @NotNull + public ComponentConfig[] getComponentConfigurations() { + return new ComponentConfig[0]; + } + + @Nullable + public Object getComponent(final ComponentConfig componentConfig) { + return null; + } + + public boolean isOpen() { + return false; + } + + public boolean isInitialized() { + return false; + } + + public boolean isDefault() { + return false; + } + + public MessageBus getMessageBus() { + return null; + } + + public void dispose() { + } + + public T[] getExtensions(final ExtensionPointName extensionPointName) { + throw new UnsupportedOperationException("getExtensions()"); + } + + public ComponentConfig getConfig(Class componentImplementation) { + throw new UnsupportedOperationException("Method getConfig not implemented in " + getClass()); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/ComponentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/ComponentManagerImpl.java index e5d42bb23bc3..8186d5401b87 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/ComponentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/ComponentManagerImpl.java @@ -1,711 +1,711 @@ -/* - * 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.components.impl; - -import com.intellij.diagnostic.PluginException; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.application.impl.ApplicationInfoImpl; -import com.intellij.openapi.components.BaseComponent; -import com.intellij.openapi.components.ComponentConfig; -import com.intellij.openapi.components.ComponentManager; -import com.intellij.openapi.components.StateStorage; -import com.intellij.openapi.components.ex.ComponentManagerEx; -import com.intellij.openapi.components.impl.stores.IComponentStore; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ReflectionCache; -import com.intellij.util.containers.ConcurrentHashMap; -import com.intellij.util.containers.HashMap; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.MessageBusFactory; -import com.intellij.util.pico.IdeaPicoContainer; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.picocontainer.*; -import org.picocontainer.defaults.CachingComponentAdapter; -import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; - -import java.io.IOException; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * @author mike - */ -public abstract class ComponentManagerImpl extends UserDataHolderBase implements ComponentManagerEx, Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.components.ComponentManager"); - - private final Map myInitializedComponents = new ConcurrentHashMap(4096); - - private boolean myComponentsCreated = false; - - private MutablePicoContainer myPicoContainer; - private volatile boolean myDisposed = false; - private volatile boolean myDisposeCompleted = false; - - private MessageBus myMessageBus; - - private final ComponentManagerConfigurator myConfigurator = new ComponentManagerConfigurator(this); - private final ComponentManager myParentComponentManager; - private IComponentStore myComponentStore; - private Boolean myHeadless; - private ComponentsRegistry myComponentsRegistry = new ComponentsRegistry(); - private boolean myHaveProgressManager = false; - private final Condition myDisposedCondition = new Condition() { - public boolean value(final Object o) { - return isDisposed(); - } - }; - - protected ComponentManagerImpl(ComponentManager parentComponentManager) { - myParentComponentManager = parentComponentManager; - boostrapPicoContainer(); - } - - //todo[mike] there are several init* methods. Make it just 1 - public void init() { - initComponents(); - } - - - @NotNull - public synchronized IComponentStore getStateStore() { - if (myComponentStore == null) { - assert myPicoContainer != null; - myComponentStore = (IComponentStore)myPicoContainer.getComponentInstance(IComponentStore.class); - } - return myComponentStore; - } - - public IComponentStore getComponentStore() { - return getStateStore(); - } - - - public MessageBus getMessageBus() { - assert !myDisposeCompleted && !myDisposed : "Already disposed"; - assert myMessageBus != null : "Not initialized yet"; - return myMessageBus; - } - - public boolean isComponentsCreated() { - return myComponentsCreated; - } - - private void createComponents() { - try { - myComponentsRegistry.loadClasses(); - - final Class[] componentInterfaces = myComponentsRegistry.getComponentInterfaces(); - for (Class componentInterface : componentInterfaces) { - if (myHaveProgressManager) { - ProgressManager.checkCanceled(); - } - try { - createComponent(componentInterface); - } - catch (StateStorage.StateStorageException e) { - throw e; - } - catch (ProcessCanceledException e) { - throw e; - } - catch(Exception e) { - LOG.error(e); - } - } - } - finally { - myComponentsCreated = true; - } - } - - private synchronized Object createComponent(Class componentInterface) { - final Object component = getPicoContainer().getComponentInstance(componentInterface.getName()); - assert component != null : "Can't instantiate component for: " + componentInterface; - return component; - } - - protected void disposeComponents() { - assert !myDisposeCompleted : "Already disposed!"; - - final List components = myComponentsRegistry.getRegisteredImplementations(); - myDisposed = true; - - for (int i = components.size() - 1; i >= 0; i--) { - Object component = components.get(i); - if (component instanceof BaseComponent) { - try { - ((BaseComponent)component).disposeComponent(); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - myComponentsCreated = false; - } - - @SuppressWarnings({"unchecked"}) - @Nullable - protected T getComponentFromContainer(Class interfaceClass) { - final T initializedComponent = (T)myInitializedComponents.get(interfaceClass); - if (initializedComponent != null) return initializedComponent; - - //if (!myComponentsCreated) { - // LOG.error("Component requests are not allowed before they are created"); - //} - - synchronized (this) { - if (!myComponentsRegistry.containsInterface(interfaceClass)) { - return null; - } - - Object lock = myComponentsRegistry.getComponentLock(interfaceClass); - - synchronized (lock) { - T dcl = (T)myInitializedComponents.get(interfaceClass); - if (dcl != null) return dcl; - - T component = (T)getPicoContainer().getComponentInstance(interfaceClass.getName()); - if (component == null) { - component = (T)createComponent(interfaceClass); - } - - if (component == null) { - throw new IncorrectOperationException("createComponent() returns null for: " + interfaceClass); - } - - myInitializedComponents.put(interfaceClass, component); - - if (component instanceof com.intellij.openapi.Disposable) { - Disposer.register(this, (com.intellij.openapi.Disposable)component); - } - - return component; - } - } - } - - public T getComponent(Class interfaceClass) { - assert !myDisposeCompleted : "Already disposed"; - return getComponent(interfaceClass, null); - } - - public T getComponent(Class interfaceClass, T defaultImplementation) { - final T fromContainer = getComponentFromContainer(interfaceClass); - if (fromContainer != null) return fromContainer; - if (defaultImplementation != null) return defaultImplementation; - return null; - } - - private void initComponent(Object component) { - if (myHaveProgressManager) { - final ProgressManager progressManager = ProgressManager.getInstance(); - - final ProgressIndicator indicator = progressManager != null ? progressManager.getProgressIndicator() : null; - if (indicator != null) { - String name = component instanceof BaseComponent ? ((BaseComponent)component).getComponentName() : component.getClass().getName(); - indicator.checkCanceled(); - indicator.setText2(name); - indicator.setIndeterminate(false); - indicator.setFraction(myComponentsRegistry.getPercentageOfComponentsLoaded()); - } - } - if (component instanceof ProgressManager) { - myHaveProgressManager = true; - } - - try { - getStateStore().initComponent(component, false); - if (component instanceof BaseComponent) { - ((BaseComponent)component).initComponent(); - } - } - catch (StateStorage.StateStorageException e) { - throw e; - } - catch (ProcessCanceledException e) { - throw e; - } - catch (Throwable ex) { - handleInitComponentError(ex, false, component.getClass().getName()); - } - } - - - protected void handleInitComponentError(final Throwable ex, final boolean fatal, final String componentClassName) { - LOG.error(ex); - } - - public void registerComponent(Class interfaceClass, Class implementationClass) { - registerComponent(interfaceClass, implementationClass, null); - - } - - @SuppressWarnings({"unchecked"}) - public void registerComponent(Class interfaceClass, Class implementationClass, Map options) { - LOG.warn("Deprecated method usage: registerComponent", new Throwable()); - - final ComponentConfig config = new ComponentConfig(); - config.implementationClass = implementationClass.getName(); - config.interfaceClass = interfaceClass.getName(); - config.options = options; - registerComponent(config); - } - - @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"}) - public synchronized void registerComponent(final ComponentConfig config, final IdeaPluginDescriptor pluginDescriptor) { - if (isHeadless()) { - String headlessImplClass = config.headlessImplementationClass; - if (headlessImplClass != null) { - if (headlessImplClass.trim().length() == 0) { - return; - } - config.implementationClass = headlessImplClass; - } - } - - config.implementationClass = config.implementationClass.trim(); - - if (config.interfaceClass == null) config.interfaceClass = config.implementationClass; - config.interfaceClass = config.interfaceClass.trim(); - - config.pluginDescriptor = pluginDescriptor; - myComponentsRegistry.registerComponent(config); - } - - @NotNull - public synchronized Class[] getComponentInterfaces() { - LOG.warn("Deprecated method usage: getComponentInterfaces", new Throwable()); - return myComponentsRegistry.getComponentInterfaces(); - } - - public synchronized boolean hasComponent(@NotNull Class interfaceClass) { - return myComponentsRegistry.containsInterface(interfaceClass); - } - - protected synchronized Object[] getComponents() { - Class[] componentClasses = myComponentsRegistry.getComponentInterfaces(); - ArrayList components = new ArrayList(componentClasses.length); - for (Class interfaceClass : componentClasses) { - if (myHaveProgressManager) { - ProgressManager.checkCanceled(); - } - Object component = getComponent(interfaceClass); - if (component != null) components.add(component); - } - return ArrayUtil.toObjectArray(components); - } - - @SuppressWarnings({"unchecked"}) - @NotNull - public synchronized T[] getComponents(Class baseClass) { - return myComponentsRegistry.getComponentsByType(baseClass); - } - - @NotNull - public MutablePicoContainer getPicoContainer() { - assert !myDisposeCompleted : "Already disposed"; - return myPicoContainer; - } - - protected MutablePicoContainer createPicoContainer() { - MutablePicoContainer result; - - if (myParentComponentManager != null) { - result = new IdeaPicoContainer(myParentComponentManager.getPicoContainer()); - } - else { - result = new IdeaPicoContainer(); - } - - return result; - } - - public synchronized BaseComponent getComponent(String name) { - return myComponentsRegistry.getComponentByName(name); - } - - protected boolean isComponentSuitable(Map options) { - return !isTrue(options, "internal") || ApplicationManagerEx.getApplicationEx().isInternal(); - } - - private static boolean isTrue(Map options, @NonNls final String option) { - return options != null && options.containsKey(option) && Boolean.valueOf(options.get(option)).booleanValue(); - } - - public synchronized void dispose() { - ApplicationManager.getApplication().assertIsDispatchThread(); - myDisposeCompleted = true; - - if (myMessageBus != null) { - myMessageBus.dispose(); - myMessageBus = null; - } - - myInitializedComponents.clear(); - myComponentsRegistry = null; - myComponentStore = null; - myPicoContainer = null; - } - - public boolean isDisposed() { - return myDisposed; - } - - - public void initComponents() { - createComponents(); - getComponents(); - } - - protected void loadComponentsConfiguration(ComponentConfig[] components, @Nullable final IdeaPluginDescriptor descriptor, final boolean defaultProject) { - myConfigurator.loadComponentsConfiguration(components, descriptor, defaultProject); - } - - protected void boostrapPicoContainer() { - myPicoContainer = createPicoContainer(); - - myMessageBus = MessageBusFactory.newMessageBus(this, myParentComponentManager != null ? myParentComponentManager.getMessageBus() : null); - final MutablePicoContainer picoContainer = getPicoContainer(); - picoContainer.registerComponentInstance(MessageBus.class, myMessageBus); - /* - picoContainer.registerComponentInstance(ExtensionInitializer.class, new ExtensionInitializer() { - public void initExtension(final Object extension) { - getComponentStore().initComponent(extension); - } - }); - */ - } - - - protected ComponentManager getParentComponentManager() { - return myParentComponentManager; - } - - private boolean isHeadless() { - if (myHeadless == null) { - myHeadless = ApplicationManager.getApplication().isHeadlessEnvironment(); - } - - return myHeadless.booleanValue(); - } - - - public void registerComponent(final ComponentConfig config) { - registerComponent(config, null); - } - - @NotNull - public ComponentConfig[] getComponentConfigurations() { - return myComponentsRegistry.getComponentConfigurations(); - } - - @Nullable - public Object getComponent(final ComponentConfig componentConfig) { - return getPicoContainer().getComponentInstance(componentConfig.interfaceClass); - } - - public ComponentConfig getConfig(Class componentImplementation) { - return myComponentsRegistry.getConfig(componentImplementation); - } - - @NotNull - public Condition getDisposed() { - return myDisposedCondition; - } - - private class ComponentsRegistry { - private final Map myInterfaceToLockMap = new HashMap(); - private final Map myInterfaceToClassMap = new HashMap(); - private final ArrayList myComponentInterfaces = new ArrayList(); // keeps order of component's registration - private final Map myNameToComponent = new HashMap(); - private final List myComponentConfigs = new ArrayList(); - private final List myImplementations = new ArrayList(); - private final Map myComponentClassToConfig = new java.util.HashMap(); - private boolean myClassesLoaded = false; - - private void loadClasses() { - assert !myClassesLoaded; - - for (ComponentConfig config : myComponentConfigs) { - loadClasses(config); - } - - myClassesLoaded = true; - } - - private void loadClasses(final ComponentConfig config) { - ClassLoader loader = config.getClassLoader(); - - try { - final Class interfaceClass = Class.forName(config.interfaceClass, true, loader); - final Class implementationClass = Class.forName(config.implementationClass, true, loader); - - if (myInterfaceToClassMap.get(interfaceClass) != null) { - throw new Error("ComponentSetup for component " + interfaceClass.getName() + " already registered"); - } - - getPicoContainer().registerComponent(new ComponentConfigComponentAdapter(config)); - myInterfaceToClassMap.put(interfaceClass, implementationClass); - myComponentClassToConfig.put(implementationClass, config); - myComponentInterfaces.add(interfaceClass); - } - catch (Exception e) { - @NonNls final String message = "Error while registering component: " + config; - - if (config.pluginDescriptor != null) { - LOG.error(message, new PluginException(e, config.pluginDescriptor.getPluginId())); - } - else { - LOG.error(message, e); - } - } - catch (Error e) { - if (config.pluginDescriptor != null) { - LOG.error(new PluginException(e, config.pluginDescriptor.getPluginId())); - } - else { - throw e; - } - } - } - - private Object getComponentLock(final Class componentClass) { - Object lock = myInterfaceToLockMap.get(componentClass); - if (lock == null) { - myInterfaceToLockMap.put(componentClass, lock = new Object()); - } - return lock; - } - - private Class[] getComponentInterfaces() { - assert myClassesLoaded; - return myComponentInterfaces.toArray(new Class[myComponentInterfaces.size()]); - } - - private boolean containsInterface(final Class interfaceClass) { - if (!myClassesLoaded) loadClasses(); - return myInterfaceToClassMap.containsKey(interfaceClass); - } - - public double getPercentageOfComponentsLoaded() { - return ((double)myImplementations.size()) / myComponentConfigs.size(); - } - - private void registerComponentInstance(final Object component) { - myImplementations.add(component); - - if (component instanceof BaseComponent) { - BaseComponent baseComponent = (BaseComponent)component; - final String componentName = baseComponent.getComponentName(); - - if (myNameToComponent.containsKey(componentName)) { - BaseComponent loadedComponent = myNameToComponent.get(componentName); - // component may have been already loaded by PicoContainer, so fire error only if components are really different - if (!component.equals(loadedComponent)) { - LOG.error("Component name collision: " + componentName + " " + loadedComponent.getClass() + " and " + component.getClass()); - } - } - else { - myNameToComponent.put(componentName, baseComponent); - } - } - } - - public List getRegisteredImplementations() { - return myImplementations; - } - - private void registerComponent(ComponentConfig config) { - myComponentConfigs.add(config); - - if (myClassesLoaded) { - loadClasses(config); - } - } - - private BaseComponent getComponentByName(final String name) { - return myNameToComponent.get(name); - } - - @SuppressWarnings({"unchecked"}) - public T[] getComponentsByType(final Class baseClass) { - ArrayList array = new ArrayList(); - - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < myComponentInterfaces.size(); i++) { - Class interfaceClass = myComponentInterfaces.get(i); - final Class implClass = myInterfaceToClassMap.get(interfaceClass); - if (ReflectionCache.isAssignable(baseClass, implClass)) { - array.add((T)getComponent(interfaceClass)); - } - } - - return array.toArray((T[])Array.newInstance(baseClass, array.size())); - } - - public ComponentConfig[] getComponentConfigurations() { - return myComponentConfigs.toArray(new ComponentConfig[myComponentConfigs.size()]); - } - - public ComponentConfig getConfig(final Class componentImplementation) { - return myComponentClassToConfig.get(componentImplementation); - } - } - - private class ComponentConfigComponentAdapter implements ComponentAdapter { - private final ComponentConfig myConfig; - private ComponentAdapter myDelegate; - private boolean myInitialized = false; - private boolean myInitializing = false; - - public ComponentConfigComponentAdapter(final ComponentConfig config) { - myConfig = config; - } - - public Object getComponentKey() { - return myConfig.interfaceClass; - } - - public Class getComponentImplementation() { - return getDelegate().getComponentImplementation(); - } - - public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException { - return getDelegate().getComponentInstance(container); - } - - public void verify(final PicoContainer container) throws PicoIntrospectionException { - getDelegate().verify(container); - } - - public void accept(final PicoVisitor visitor) { - visitor.visitComponentAdapter(this); - getDelegate().accept(visitor); - } - - private ComponentAdapter getDelegate() { - if (myDelegate == null) { - final Object componentKey = getComponentKey(); - - ClassLoader loader = myConfig.getClassLoader(); - - Class implementationClass = null; - - try { - implementationClass = Class.forName(myConfig.implementationClass, true, loader); - } - catch (Exception e) { - @NonNls final String message = "Error while registering component: " + myConfig; - - if (myConfig.pluginDescriptor != null) { - LOG.error(message, new PluginException(e, myConfig.pluginDescriptor.getPluginId())); - } - else { - LOG.error(message, e); - } - } - catch (Error e) { - if (myConfig.pluginDescriptor != null) { - LOG.error(new PluginException(e, myConfig.pluginDescriptor.getPluginId())); - } - else { - throw e; - } - } - - assert implementationClass != null; - - myDelegate = new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(componentKey, implementationClass, null, true)) { - public Object getComponentInstance(PicoContainer picoContainer) throws PicoInitializationException, PicoIntrospectionException { - Object componentInstance = null; - try { - long startTime = myInitialized ? 0 : System.nanoTime(); - componentInstance = super.getComponentInstance(picoContainer); - - if (!myInitialized) { - if (myInitializing) LOG.error(new Throwable("Cyclic component initialization: " + componentKey)); - myInitializing = true; - myComponentsRegistry.registerComponentInstance(componentInstance); - initComponent(componentInstance); - long endTime = System.nanoTime(); - long ms = (endTime - startTime) / 1000000; - if (ms > 10) { - if (ApplicationInfoImpl.getShadowInstance().isEAP()) { - LOG.info(componentInstance.getClass().getName() + " initialized in " + ms + " ms"); - } - else if (LOG.isDebugEnabled()) { - LOG.debug(componentInstance.getClass().getName() + " initialized in " + ms + " ms"); - } - } - myInitializing = false; - myInitialized = true; - } - } - catch (ProcessCanceledException e) { - throw e; - } - catch (StateStorage.StateStorageException e) { - throw e; - } - catch (Throwable t) { - handleInitComponentError(t, componentInstance == null, componentKey.toString()); - } - return componentInstance; - } - }; - } - - return myDelegate; - } - } - - protected void doSave() throws IOException { - IComponentStore.SaveSession session = null; - try { - session = getStateStore().startSave(); - session.save(); - } - finally { - if (session != null) { - session.finishSave(); - } - } - } - - public final int hashCode() { - return super.hashCode(); - } - - public final boolean equals(Object obj) { - return super.equals(obj); - } -} +/* + * 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.components.impl; + +import com.intellij.diagnostic.PluginException; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.application.impl.ApplicationInfoImpl; +import com.intellij.openapi.components.BaseComponent; +import com.intellij.openapi.components.ComponentConfig; +import com.intellij.openapi.components.ComponentManager; +import com.intellij.openapi.components.StateStorage; +import com.intellij.openapi.components.ex.ComponentManagerEx; +import com.intellij.openapi.components.impl.stores.IComponentStore; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ReflectionCache; +import com.intellij.util.containers.ConcurrentHashMap; +import com.intellij.util.containers.HashMap; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusFactory; +import com.intellij.util.pico.IdeaPicoContainer; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.picocontainer.*; +import org.picocontainer.defaults.CachingComponentAdapter; +import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author mike + */ +public abstract class ComponentManagerImpl extends UserDataHolderBase implements ComponentManagerEx, Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.components.ComponentManager"); + + private final Map myInitializedComponents = new ConcurrentHashMap(4096); + + private boolean myComponentsCreated = false; + + private MutablePicoContainer myPicoContainer; + private volatile boolean myDisposed = false; + private volatile boolean myDisposeCompleted = false; + + private MessageBus myMessageBus; + + private final ComponentManagerConfigurator myConfigurator = new ComponentManagerConfigurator(this); + private final ComponentManager myParentComponentManager; + private IComponentStore myComponentStore; + private Boolean myHeadless; + private ComponentsRegistry myComponentsRegistry = new ComponentsRegistry(); + private boolean myHaveProgressManager = false; + private final Condition myDisposedCondition = new Condition() { + public boolean value(final Object o) { + return isDisposed(); + } + }; + + protected ComponentManagerImpl(ComponentManager parentComponentManager) { + myParentComponentManager = parentComponentManager; + boostrapPicoContainer(); + } + + //todo[mike] there are several init* methods. Make it just 1 + public void init() { + initComponents(); + } + + + @NotNull + public synchronized IComponentStore getStateStore() { + if (myComponentStore == null) { + assert myPicoContainer != null; + myComponentStore = (IComponentStore)myPicoContainer.getComponentInstance(IComponentStore.class); + } + return myComponentStore; + } + + public IComponentStore getComponentStore() { + return getStateStore(); + } + + + public MessageBus getMessageBus() { + assert !myDisposeCompleted && !myDisposed : "Already disposed"; + assert myMessageBus != null : "Not initialized yet"; + return myMessageBus; + } + + public boolean isComponentsCreated() { + return myComponentsCreated; + } + + private void createComponents() { + try { + myComponentsRegistry.loadClasses(); + + final Class[] componentInterfaces = myComponentsRegistry.getComponentInterfaces(); + for (Class componentInterface : componentInterfaces) { + if (myHaveProgressManager) { + ProgressManager.checkCanceled(); + } + try { + createComponent(componentInterface); + } + catch (StateStorage.StateStorageException e) { + throw e; + } + catch (ProcessCanceledException e) { + throw e; + } + catch(Exception e) { + LOG.error(e); + } + } + } + finally { + myComponentsCreated = true; + } + } + + private synchronized Object createComponent(Class componentInterface) { + final Object component = getPicoContainer().getComponentInstance(componentInterface.getName()); + assert component != null : "Can't instantiate component for: " + componentInterface; + return component; + } + + protected void disposeComponents() { + assert !myDisposeCompleted : "Already disposed!"; + + final List components = myComponentsRegistry.getRegisteredImplementations(); + myDisposed = true; + + for (int i = components.size() - 1; i >= 0; i--) { + Object component = components.get(i); + if (component instanceof BaseComponent) { + try { + ((BaseComponent)component).disposeComponent(); + } + catch (Throwable e) { + LOG.error(e); + } + } + } + + myComponentsCreated = false; + } + + @SuppressWarnings({"unchecked"}) + @Nullable + protected T getComponentFromContainer(Class interfaceClass) { + final T initializedComponent = (T)myInitializedComponents.get(interfaceClass); + if (initializedComponent != null) return initializedComponent; + + //if (!myComponentsCreated) { + // LOG.error("Component requests are not allowed before they are created"); + //} + + synchronized (this) { + if (!myComponentsRegistry.containsInterface(interfaceClass)) { + return null; + } + + Object lock = myComponentsRegistry.getComponentLock(interfaceClass); + + synchronized (lock) { + T dcl = (T)myInitializedComponents.get(interfaceClass); + if (dcl != null) return dcl; + + T component = (T)getPicoContainer().getComponentInstance(interfaceClass.getName()); + if (component == null) { + component = (T)createComponent(interfaceClass); + } + + if (component == null) { + throw new IncorrectOperationException("createComponent() returns null for: " + interfaceClass); + } + + myInitializedComponents.put(interfaceClass, component); + + if (component instanceof com.intellij.openapi.Disposable) { + Disposer.register(this, (com.intellij.openapi.Disposable)component); + } + + return component; + } + } + } + + public T getComponent(Class interfaceClass) { + assert !myDisposeCompleted : "Already disposed"; + return getComponent(interfaceClass, null); + } + + public T getComponent(Class interfaceClass, T defaultImplementation) { + final T fromContainer = getComponentFromContainer(interfaceClass); + if (fromContainer != null) return fromContainer; + if (defaultImplementation != null) return defaultImplementation; + return null; + } + + private void initComponent(Object component) { + if (myHaveProgressManager) { + final ProgressManager progressManager = ProgressManager.getInstance(); + + final ProgressIndicator indicator = progressManager != null ? progressManager.getProgressIndicator() : null; + if (indicator != null) { + String name = component instanceof BaseComponent ? ((BaseComponent)component).getComponentName() : component.getClass().getName(); + indicator.checkCanceled(); + indicator.setText2(name); + indicator.setIndeterminate(false); + indicator.setFraction(myComponentsRegistry.getPercentageOfComponentsLoaded()); + } + } + if (component instanceof ProgressManager) { + myHaveProgressManager = true; + } + + try { + getStateStore().initComponent(component, false); + if (component instanceof BaseComponent) { + ((BaseComponent)component).initComponent(); + } + } + catch (StateStorage.StateStorageException e) { + throw e; + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Throwable ex) { + handleInitComponentError(ex, false, component.getClass().getName()); + } + } + + + protected void handleInitComponentError(final Throwable ex, final boolean fatal, final String componentClassName) { + LOG.error(ex); + } + + public void registerComponent(Class interfaceClass, Class implementationClass) { + registerComponent(interfaceClass, implementationClass, null); + + } + + @SuppressWarnings({"unchecked"}) + public void registerComponent(Class interfaceClass, Class implementationClass, Map options) { + LOG.warn("Deprecated method usage: registerComponent", new Throwable()); + + final ComponentConfig config = new ComponentConfig(); + config.implementationClass = implementationClass.getName(); + config.interfaceClass = interfaceClass.getName(); + config.options = options; + registerComponent(config); + } + + @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"}) + public synchronized void registerComponent(final ComponentConfig config, final IdeaPluginDescriptor pluginDescriptor) { + if (isHeadless()) { + String headlessImplClass = config.headlessImplementationClass; + if (headlessImplClass != null) { + if (headlessImplClass.trim().length() == 0) { + return; + } + config.implementationClass = headlessImplClass; + } + } + + config.implementationClass = config.implementationClass.trim(); + + if (config.interfaceClass == null) config.interfaceClass = config.implementationClass; + config.interfaceClass = config.interfaceClass.trim(); + + config.pluginDescriptor = pluginDescriptor; + myComponentsRegistry.registerComponent(config); + } + + @NotNull + public synchronized Class[] getComponentInterfaces() { + LOG.warn("Deprecated method usage: getComponentInterfaces", new Throwable()); + return myComponentsRegistry.getComponentInterfaces(); + } + + public synchronized boolean hasComponent(@NotNull Class interfaceClass) { + return myComponentsRegistry.containsInterface(interfaceClass); + } + + protected synchronized Object[] getComponents() { + Class[] componentClasses = myComponentsRegistry.getComponentInterfaces(); + ArrayList components = new ArrayList(componentClasses.length); + for (Class interfaceClass : componentClasses) { + if (myHaveProgressManager) { + ProgressManager.checkCanceled(); + } + Object component = getComponent(interfaceClass); + if (component != null) components.add(component); + } + return ArrayUtil.toObjectArray(components); + } + + @SuppressWarnings({"unchecked"}) + @NotNull + public synchronized T[] getComponents(Class baseClass) { + return myComponentsRegistry.getComponentsByType(baseClass); + } + + @NotNull + public MutablePicoContainer getPicoContainer() { + assert !myDisposeCompleted : "Already disposed"; + return myPicoContainer; + } + + protected MutablePicoContainer createPicoContainer() { + MutablePicoContainer result; + + if (myParentComponentManager != null) { + result = new IdeaPicoContainer(myParentComponentManager.getPicoContainer()); + } + else { + result = new IdeaPicoContainer(); + } + + return result; + } + + public synchronized BaseComponent getComponent(String name) { + return myComponentsRegistry.getComponentByName(name); + } + + protected boolean isComponentSuitable(Map options) { + return !isTrue(options, "internal") || ApplicationManagerEx.getApplicationEx().isInternal(); + } + + private static boolean isTrue(Map options, @NonNls final String option) { + return options != null && options.containsKey(option) && Boolean.valueOf(options.get(option)).booleanValue(); + } + + public synchronized void dispose() { + ApplicationManager.getApplication().assertIsDispatchThread(); + myDisposeCompleted = true; + + if (myMessageBus != null) { + myMessageBus.dispose(); + myMessageBus = null; + } + + myInitializedComponents.clear(); + myComponentsRegistry = null; + myComponentStore = null; + myPicoContainer = null; + } + + public boolean isDisposed() { + return myDisposed; + } + + + public void initComponents() { + createComponents(); + getComponents(); + } + + protected void loadComponentsConfiguration(ComponentConfig[] components, @Nullable final IdeaPluginDescriptor descriptor, final boolean defaultProject) { + myConfigurator.loadComponentsConfiguration(components, descriptor, defaultProject); + } + + protected void boostrapPicoContainer() { + myPicoContainer = createPicoContainer(); + + myMessageBus = MessageBusFactory.newMessageBus(this, myParentComponentManager != null ? myParentComponentManager.getMessageBus() : null); + final MutablePicoContainer picoContainer = getPicoContainer(); + picoContainer.registerComponentInstance(MessageBus.class, myMessageBus); + /* + picoContainer.registerComponentInstance(ExtensionInitializer.class, new ExtensionInitializer() { + public void initExtension(final Object extension) { + getComponentStore().initComponent(extension); + } + }); + */ + } + + + protected ComponentManager getParentComponentManager() { + return myParentComponentManager; + } + + private boolean isHeadless() { + if (myHeadless == null) { + myHeadless = ApplicationManager.getApplication().isHeadlessEnvironment(); + } + + return myHeadless.booleanValue(); + } + + + public void registerComponent(final ComponentConfig config) { + registerComponent(config, null); + } + + @NotNull + public ComponentConfig[] getComponentConfigurations() { + return myComponentsRegistry.getComponentConfigurations(); + } + + @Nullable + public Object getComponent(final ComponentConfig componentConfig) { + return getPicoContainer().getComponentInstance(componentConfig.interfaceClass); + } + + public ComponentConfig getConfig(Class componentImplementation) { + return myComponentsRegistry.getConfig(componentImplementation); + } + + @NotNull + public Condition getDisposed() { + return myDisposedCondition; + } + + private class ComponentsRegistry { + private final Map myInterfaceToLockMap = new HashMap(); + private final Map myInterfaceToClassMap = new HashMap(); + private final ArrayList myComponentInterfaces = new ArrayList(); // keeps order of component's registration + private final Map myNameToComponent = new HashMap(); + private final List myComponentConfigs = new ArrayList(); + private final List myImplementations = new ArrayList(); + private final Map myComponentClassToConfig = new java.util.HashMap(); + private boolean myClassesLoaded = false; + + private void loadClasses() { + assert !myClassesLoaded; + + for (ComponentConfig config : myComponentConfigs) { + loadClasses(config); + } + + myClassesLoaded = true; + } + + private void loadClasses(final ComponentConfig config) { + ClassLoader loader = config.getClassLoader(); + + try { + final Class interfaceClass = Class.forName(config.interfaceClass, true, loader); + final Class implementationClass = Class.forName(config.implementationClass, true, loader); + + if (myInterfaceToClassMap.get(interfaceClass) != null) { + throw new Error("ComponentSetup for component " + interfaceClass.getName() + " already registered"); + } + + getPicoContainer().registerComponent(new ComponentConfigComponentAdapter(config)); + myInterfaceToClassMap.put(interfaceClass, implementationClass); + myComponentClassToConfig.put(implementationClass, config); + myComponentInterfaces.add(interfaceClass); + } + catch (Exception e) { + @NonNls final String message = "Error while registering component: " + config; + + if (config.pluginDescriptor != null) { + LOG.error(message, new PluginException(e, config.pluginDescriptor.getPluginId())); + } + else { + LOG.error(message, e); + } + } + catch (Error e) { + if (config.pluginDescriptor != null) { + LOG.error(new PluginException(e, config.pluginDescriptor.getPluginId())); + } + else { + throw e; + } + } + } + + private Object getComponentLock(final Class componentClass) { + Object lock = myInterfaceToLockMap.get(componentClass); + if (lock == null) { + myInterfaceToLockMap.put(componentClass, lock = new Object()); + } + return lock; + } + + private Class[] getComponentInterfaces() { + assert myClassesLoaded; + return myComponentInterfaces.toArray(new Class[myComponentInterfaces.size()]); + } + + private boolean containsInterface(final Class interfaceClass) { + if (!myClassesLoaded) loadClasses(); + return myInterfaceToClassMap.containsKey(interfaceClass); + } + + public double getPercentageOfComponentsLoaded() { + return ((double)myImplementations.size()) / myComponentConfigs.size(); + } + + private void registerComponentInstance(final Object component) { + myImplementations.add(component); + + if (component instanceof BaseComponent) { + BaseComponent baseComponent = (BaseComponent)component; + final String componentName = baseComponent.getComponentName(); + + if (myNameToComponent.containsKey(componentName)) { + BaseComponent loadedComponent = myNameToComponent.get(componentName); + // component may have been already loaded by PicoContainer, so fire error only if components are really different + if (!component.equals(loadedComponent)) { + LOG.error("Component name collision: " + componentName + " " + loadedComponent.getClass() + " and " + component.getClass()); + } + } + else { + myNameToComponent.put(componentName, baseComponent); + } + } + } + + public List getRegisteredImplementations() { + return myImplementations; + } + + private void registerComponent(ComponentConfig config) { + myComponentConfigs.add(config); + + if (myClassesLoaded) { + loadClasses(config); + } + } + + private BaseComponent getComponentByName(final String name) { + return myNameToComponent.get(name); + } + + @SuppressWarnings({"unchecked"}) + public T[] getComponentsByType(final Class baseClass) { + ArrayList array = new ArrayList(); + + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < myComponentInterfaces.size(); i++) { + Class interfaceClass = myComponentInterfaces.get(i); + final Class implClass = myInterfaceToClassMap.get(interfaceClass); + if (ReflectionCache.isAssignable(baseClass, implClass)) { + array.add((T)getComponent(interfaceClass)); + } + } + + return array.toArray((T[])Array.newInstance(baseClass, array.size())); + } + + public ComponentConfig[] getComponentConfigurations() { + return myComponentConfigs.toArray(new ComponentConfig[myComponentConfigs.size()]); + } + + public ComponentConfig getConfig(final Class componentImplementation) { + return myComponentClassToConfig.get(componentImplementation); + } + } + + private class ComponentConfigComponentAdapter implements ComponentAdapter { + private final ComponentConfig myConfig; + private ComponentAdapter myDelegate; + private boolean myInitialized = false; + private boolean myInitializing = false; + + public ComponentConfigComponentAdapter(final ComponentConfig config) { + myConfig = config; + } + + public Object getComponentKey() { + return myConfig.interfaceClass; + } + + public Class getComponentImplementation() { + return getDelegate().getComponentImplementation(); + } + + public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException { + return getDelegate().getComponentInstance(container); + } + + public void verify(final PicoContainer container) throws PicoIntrospectionException { + getDelegate().verify(container); + } + + public void accept(final PicoVisitor visitor) { + visitor.visitComponentAdapter(this); + getDelegate().accept(visitor); + } + + private ComponentAdapter getDelegate() { + if (myDelegate == null) { + final Object componentKey = getComponentKey(); + + ClassLoader loader = myConfig.getClassLoader(); + + Class implementationClass = null; + + try { + implementationClass = Class.forName(myConfig.implementationClass, true, loader); + } + catch (Exception e) { + @NonNls final String message = "Error while registering component: " + myConfig; + + if (myConfig.pluginDescriptor != null) { + LOG.error(message, new PluginException(e, myConfig.pluginDescriptor.getPluginId())); + } + else { + LOG.error(message, e); + } + } + catch (Error e) { + if (myConfig.pluginDescriptor != null) { + LOG.error(new PluginException(e, myConfig.pluginDescriptor.getPluginId())); + } + else { + throw e; + } + } + + assert implementationClass != null; + + myDelegate = new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(componentKey, implementationClass, null, true)) { + public Object getComponentInstance(PicoContainer picoContainer) throws PicoInitializationException, PicoIntrospectionException { + Object componentInstance = null; + try { + long startTime = myInitialized ? 0 : System.nanoTime(); + componentInstance = super.getComponentInstance(picoContainer); + + if (!myInitialized) { + if (myInitializing) LOG.error(new Throwable("Cyclic component initialization: " + componentKey)); + myInitializing = true; + myComponentsRegistry.registerComponentInstance(componentInstance); + initComponent(componentInstance); + long endTime = System.nanoTime(); + long ms = (endTime - startTime) / 1000000; + if (ms > 10) { + if (ApplicationInfoImpl.getShadowInstance().isEAP()) { + LOG.info(componentInstance.getClass().getName() + " initialized in " + ms + " ms"); + } + else if (LOG.isDebugEnabled()) { + LOG.debug(componentInstance.getClass().getName() + " initialized in " + ms + " ms"); + } + } + myInitializing = false; + myInitialized = true; + } + } + catch (ProcessCanceledException e) { + throw e; + } + catch (StateStorage.StateStorageException e) { + throw e; + } + catch (Throwable t) { + handleInitComponentError(t, componentInstance == null, componentKey.toString()); + } + return componentInstance; + } + }; + } + + return myDelegate; + } + } + + protected void doSave() throws IOException { + IComponentStore.SaveSession session = null; + try { + session = getStateStore().startSave(); + session.save(); + } + finally { + if (session != null) { + session.finishSave(); + } + } + } + + public final int hashCode() { + return super.hashCode(); + } + + public final boolean equals(Object obj) { + return super.equals(obj); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/ex/DiffStatusBar.java b/platform/platform-impl/src/com/intellij/openapi/diff/ex/DiffStatusBar.java index 62a311322f04..6ad8c09a138d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/ex/DiffStatusBar.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/ex/DiffStatusBar.java @@ -1,166 +1,166 @@ -/* - * 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.diff.ex; - -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.VcsBundle; -import com.intellij.ui.IdeBorderFactory; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -/** - * @author Yura Cangea - */ -public class DiffStatusBar extends JPanel { - public static final List DEFAULT_TYPES = - Arrays.asList( - new LegendTypeDescriptorImpl(VcsBundle.message("diff.type.name.modified"), FileStatus.COLOR_MODIFIED), - new LegendTypeDescriptorImpl(VcsBundle.message("diff.type.name.added"), FileStatus.COLOR_ADDED), - new LegendTypeDescriptorImpl(VcsBundle.message("diff.type.name.deleted"), FileStatus.COLOR_MISSING)); - - private final Collection myLabels = new ArrayList(); - - private final JLabel myTextLabel = new JLabel(""); - private static final int COMP_HEIGHT = 30; - private EditorColorsScheme myColorScheme = null; - - public DiffStatusBar(List types) { - for (T differenceType : types) { - addDiffType(differenceType); - } - initGui(); - } - - private void addDiffType(final LegendTypeDescriptor diffType){ - addComponent(diffType); - } - - private void addComponent(final LegendTypeDescriptor diffType) { - JComponent component = new JPanel() { - public void paint(Graphics g) { - setBackground(UIUtil.getPanelBackground()); - super.paint(g); - FontMetrics metrics = getFontMetrics(getFont()); - - EditorColorsScheme colorScheme = myColorScheme != null - ? myColorScheme - : EditorColorsManager.getInstance().getGlobalScheme(); - g.setColor(diffType.getLegendColor(colorScheme)); - g.fill3DRect(10, (getHeight() - 10) / 2, 35, 10, true); - - Font font = g.getFont(); - if (font.getStyle() != Font.PLAIN) { - font = font.deriveFont(Font.PLAIN); - } - g.setFont(font); - g.setColor(UIUtil.getLabelForeground()); - int textBaseline = (getHeight() - metrics.getHeight()) / 2 + metrics.getAscent(); - g.drawString(diffType.getDisplayName(), 67, textBaseline); - } - - @Override - public Dimension getPreferredSize() { - FontMetrics metrics = getFontMetrics(getFont()); - return new Dimension((int)(70 + metrics.getStringBounds(diffType.getDisplayName(), getGraphics()).getWidth()), COMP_HEIGHT); - } - - @Override - public Dimension getMinimumSize() { - return getPreferredSize(); - } - }; - myLabels.add(component); - } - - public Dimension getMinimumSize() { - Dimension p = super.getPreferredSize(); - Dimension m = super.getMinimumSize(); - return new Dimension(m.width, p.height); - } - - public Dimension getMaximumSize() { - Dimension p = super.getPreferredSize(); - Dimension m = super.getMaximumSize(); - return new Dimension(m.width, p.height); - } - - public void setText(String text) { - myTextLabel.setText(text); - } - - private void initGui() { - JComponent filler = new JComponent() { - @Override - public Dimension getPreferredSize() { - return myTextLabel.getPreferredSize(); - } - }; - setLayout(new BorderLayout()); - setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(), - BorderFactory.createEmptyBorder(3, 20, 3, 20))); - - add(myTextLabel, BorderLayout.WEST); - Box box = Box.createHorizontalBox(); - box.add(Box.createHorizontalGlue()); - JPanel panel = new JPanel(new GridLayout(1, myLabels.size(), 0, 0)); - for (final JComponent myLabel : myLabels) { - panel.add(myLabel); - } - panel.setMaximumSize(panel.getPreferredSize()); - box.add(panel); - box.add(Box.createHorizontalGlue()); - add(box, BorderLayout.CENTER); - - add(filler, BorderLayout.EAST); - } - - public void setColorScheme(EditorColorsScheme colorScheme) { - EditorColorsScheme oldScheme = myColorScheme; - myColorScheme = colorScheme; - if (oldScheme != colorScheme) repaint(); - } - - public interface LegendTypeDescriptor { - String getDisplayName(); - Color getLegendColor(EditorColorsScheme colorScheme); - } - - static class LegendTypeDescriptorImpl implements LegendTypeDescriptor { - private final String myDisplayName; - private final Color myColor; - - LegendTypeDescriptorImpl(final String displayName, final Color color) { - myDisplayName = displayName; - myColor = color; - } - - public String getDisplayName() { - return myDisplayName; - } - - public Color getLegendColor(final EditorColorsScheme colorScheme) { - return myColor; - } - } -} +/* + * 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.diff.ex; + +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * @author Yura Cangea + */ +public class DiffStatusBar extends JPanel { + public static final List DEFAULT_TYPES = + Arrays.asList( + new LegendTypeDescriptorImpl(VcsBundle.message("diff.type.name.modified"), FileStatus.COLOR_MODIFIED), + new LegendTypeDescriptorImpl(VcsBundle.message("diff.type.name.added"), FileStatus.COLOR_ADDED), + new LegendTypeDescriptorImpl(VcsBundle.message("diff.type.name.deleted"), FileStatus.COLOR_MISSING)); + + private final Collection myLabels = new ArrayList(); + + private final JLabel myTextLabel = new JLabel(""); + private static final int COMP_HEIGHT = 30; + private EditorColorsScheme myColorScheme = null; + + public DiffStatusBar(List types) { + for (T differenceType : types) { + addDiffType(differenceType); + } + initGui(); + } + + private void addDiffType(final LegendTypeDescriptor diffType){ + addComponent(diffType); + } + + private void addComponent(final LegendTypeDescriptor diffType) { + JComponent component = new JPanel() { + public void paint(Graphics g) { + setBackground(UIUtil.getPanelBackground()); + super.paint(g); + FontMetrics metrics = getFontMetrics(getFont()); + + EditorColorsScheme colorScheme = myColorScheme != null + ? myColorScheme + : EditorColorsManager.getInstance().getGlobalScheme(); + g.setColor(diffType.getLegendColor(colorScheme)); + g.fill3DRect(10, (getHeight() - 10) / 2, 35, 10, true); + + Font font = g.getFont(); + if (font.getStyle() != Font.PLAIN) { + font = font.deriveFont(Font.PLAIN); + } + g.setFont(font); + g.setColor(UIUtil.getLabelForeground()); + int textBaseline = (getHeight() - metrics.getHeight()) / 2 + metrics.getAscent(); + g.drawString(diffType.getDisplayName(), 67, textBaseline); + } + + @Override + public Dimension getPreferredSize() { + FontMetrics metrics = getFontMetrics(getFont()); + return new Dimension((int)(70 + metrics.getStringBounds(diffType.getDisplayName(), getGraphics()).getWidth()), COMP_HEIGHT); + } + + @Override + public Dimension getMinimumSize() { + return getPreferredSize(); + } + }; + myLabels.add(component); + } + + public Dimension getMinimumSize() { + Dimension p = super.getPreferredSize(); + Dimension m = super.getMinimumSize(); + return new Dimension(m.width, p.height); + } + + public Dimension getMaximumSize() { + Dimension p = super.getPreferredSize(); + Dimension m = super.getMaximumSize(); + return new Dimension(m.width, p.height); + } + + public void setText(String text) { + myTextLabel.setText(text); + } + + private void initGui() { + JComponent filler = new JComponent() { + @Override + public Dimension getPreferredSize() { + return myTextLabel.getPreferredSize(); + } + }; + setLayout(new BorderLayout()); + setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(), + BorderFactory.createEmptyBorder(3, 20, 3, 20))); + + add(myTextLabel, BorderLayout.WEST); + Box box = Box.createHorizontalBox(); + box.add(Box.createHorizontalGlue()); + JPanel panel = new JPanel(new GridLayout(1, myLabels.size(), 0, 0)); + for (final JComponent myLabel : myLabels) { + panel.add(myLabel); + } + panel.setMaximumSize(panel.getPreferredSize()); + box.add(panel); + box.add(Box.createHorizontalGlue()); + add(box, BorderLayout.CENTER); + + add(filler, BorderLayout.EAST); + } + + public void setColorScheme(EditorColorsScheme colorScheme) { + EditorColorsScheme oldScheme = myColorScheme; + myColorScheme = colorScheme; + if (oldScheme != colorScheme) repaint(); + } + + public interface LegendTypeDescriptor { + String getDisplayName(); + Color getLegendColor(EditorColorsScheme colorScheme); + } + + static class LegendTypeDescriptorImpl implements LegendTypeDescriptor { + private final String myDisplayName; + private final Color myColor; + + LegendTypeDescriptorImpl(final String displayName, final Color color) { + myDisplayName = displayName; + myColor = color; + } + + public String getDisplayName() { + return myDisplayName; + } + + public Color getLegendColor(final EditorColorsScheme colorScheme) { + return myColor; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarComponent.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarComponent.java index 53b3908f9215..de271c29ff7e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarComponent.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarComponent.java @@ -1,46 +1,46 @@ -/* - * 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.diff.impl; - -import com.intellij.openapi.diff.DiffRequest; - -import javax.swing.*; -import java.awt.*; - -public class DiffToolbarComponent extends JPanel { - private final JComponent myWholeComponent; - private DiffToolbarImpl myToolbar; - - public DiffToolbarComponent(final JComponent wholeComponent) { - super(new BorderLayout()); - myWholeComponent = wholeComponent; - } - - public void resetToolbar(DiffRequest.ToolbarAddons toolBar) { - if (myToolbar != null) remove(myToolbar.getComponent()); - myToolbar = new DiffToolbarImpl(); - myToolbar.setTargetComponent(myWholeComponent); - myToolbar.reset(toolBar); - myToolbar.registerKeyboardActions(myWholeComponent); - add(myToolbar.getComponent(), BorderLayout.CENTER); - revalidate(); - repaint(); - } - - public DiffToolbarImpl getToolbar() { - return myToolbar; - } -} +/* + * 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.diff.impl; + +import com.intellij.openapi.diff.DiffRequest; + +import javax.swing.*; +import java.awt.*; + +public class DiffToolbarComponent extends JPanel { + private final JComponent myWholeComponent; + private DiffToolbarImpl myToolbar; + + public DiffToolbarComponent(final JComponent wholeComponent) { + super(new BorderLayout()); + myWholeComponent = wholeComponent; + } + + public void resetToolbar(DiffRequest.ToolbarAddons toolBar) { + if (myToolbar != null) remove(myToolbar.getComponent()); + myToolbar = new DiffToolbarImpl(); + myToolbar.setTargetComponent(myWholeComponent); + myToolbar.reset(toolBar); + myToolbar.registerKeyboardActions(myWholeComponent); + add(myToolbar.getComponent(), BorderLayout.CENTER); + revalidate(); + repaint(); + } + + public DiffToolbarImpl getToolbar() { + return myToolbar; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarImpl.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarImpl.java index ad65593ff577..621db6cdaf7c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/DiffToolbarImpl.java @@ -1,91 +1,91 @@ -/* - * 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.diff.impl; - -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.diff.DiffRequest; -import com.intellij.openapi.diff.DiffToolbar; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -public class DiffToolbarImpl implements DiffToolbar { - private final DefaultActionGroup myActionGroup = new DefaultActionGroup(); - private ActionToolbar myActionToolbar; - private JComponent myTargetComponent; - - public void registerKeyboardActions(JComponent registerActionsTo) { - AnAction[] actions = getAllActions(); - for (AnAction action : actions) { - action.registerCustomShortcutSet(action.getShortcutSet(), registerActionsTo); - } - } - - public AnAction[] getAllActions() { - return myActionGroup.getChildren(null); - } - - public boolean removeActionById(String actionId) { - AnAction[] allActions = getAllActions(); - for (AnAction action : allActions) { - if (actionId.equals(ActionManager.getInstance().getId(action))) { - removeAction(action); - return true; - } - } - return false; - } - - public void removeAction(AnAction action) { - myActionGroup.remove(action); - updateToolbar(); - } - - public JComponent getComponent() { - if (myActionToolbar == null) { - myActionToolbar = ActionManager.getInstance(). - createActionToolbar(ActionPlaces.UNKNOWN, myActionGroup, true); - myActionToolbar.setTargetComponent(myTargetComponent); - } - return myActionToolbar.getComponent(); - } - - public void addAction(@NotNull AnAction action) { - myActionGroup.add(action); - updateToolbar(); - } - - private void updateToolbar() { - if (myActionToolbar != null) myActionToolbar.updateActionsImmediately(); - } - - public void addSeparator() { - myActionGroup.addSeparator(); - updateToolbar(); - } - - public void reset(DiffRequest.ToolbarAddons toolBar) { - myActionGroup.removeAll(); - toolBar.customize(this); - } - - public void setTargetComponent(JComponent component) { - myTargetComponent = component; - if (myActionToolbar != null) { - myActionToolbar.setTargetComponent(component); - } - } -} +/* + * 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.diff.impl; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.diff.DiffRequest; +import com.intellij.openapi.diff.DiffToolbar; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +public class DiffToolbarImpl implements DiffToolbar { + private final DefaultActionGroup myActionGroup = new DefaultActionGroup(); + private ActionToolbar myActionToolbar; + private JComponent myTargetComponent; + + public void registerKeyboardActions(JComponent registerActionsTo) { + AnAction[] actions = getAllActions(); + for (AnAction action : actions) { + action.registerCustomShortcutSet(action.getShortcutSet(), registerActionsTo); + } + } + + public AnAction[] getAllActions() { + return myActionGroup.getChildren(null); + } + + public boolean removeActionById(String actionId) { + AnAction[] allActions = getAllActions(); + for (AnAction action : allActions) { + if (actionId.equals(ActionManager.getInstance().getId(action))) { + removeAction(action); + return true; + } + } + return false; + } + + public void removeAction(AnAction action) { + myActionGroup.remove(action); + updateToolbar(); + } + + public JComponent getComponent() { + if (myActionToolbar == null) { + myActionToolbar = ActionManager.getInstance(). + createActionToolbar(ActionPlaces.UNKNOWN, myActionGroup, true); + myActionToolbar.setTargetComponent(myTargetComponent); + } + return myActionToolbar.getComponent(); + } + + public void addAction(@NotNull AnAction action) { + myActionGroup.add(action); + updateToolbar(); + } + + private void updateToolbar() { + if (myActionToolbar != null) myActionToolbar.updateActionsImmediately(); + } + + public void addSeparator() { + myActionGroup.addSeparator(); + updateToolbar(); + } + + public void reset(DiffRequest.ToolbarAddons toolBar) { + myActionGroup.removeAll(); + toolBar.customize(this); + } + + public void setTargetComponent(JComponent component) { + myTargetComponent = component; + if (myActionToolbar != null) { + myActionToolbar.setTargetComponent(component); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/CompositeDiffTool.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/CompositeDiffTool.java index c3f5ed902644..851a9d06b290 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/CompositeDiffTool.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/CompositeDiffTool.java @@ -1,62 +1,62 @@ -/* - * 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.diff.impl.external; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.diff.DiffContent; -import com.intellij.openapi.diff.DiffRequest; -import com.intellij.openapi.diff.DiffTool; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -class CompositeDiffTool implements DiffTool { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.external.CompositeDiffTool"); - private final List myTools; - - public CompositeDiffTool(List tools) { - myTools = new ArrayList(tools); - } - - public void show(DiffRequest data) { - checkDiffData(data); - DiffTool tool = chooseTool(data); - if (tool != null) tool.show(data); - else LOG.error("No diff tool found which is able to handle request " + data); - } - - public boolean canShow(DiffRequest data) { - checkDiffData(data); - return chooseTool(data) != null; - } - - @Nullable - private DiffTool chooseTool(DiffRequest data) { - for (DiffTool tool : myTools) { - if (tool.canShow(data)) return tool; - } - return null; - } - - private static void checkDiffData(@NotNull DiffRequest data) { - DiffContent[] contents = data.getContents(); - for (DiffContent content : contents) { - LOG.assertTrue(content != null, "Null content in diff request"); - } - } -} +/* + * 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.diff.impl.external; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.diff.DiffContent; +import com.intellij.openapi.diff.DiffRequest; +import com.intellij.openapi.diff.DiffTool; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +class CompositeDiffTool implements DiffTool { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.external.CompositeDiffTool"); + private final List myTools; + + public CompositeDiffTool(List tools) { + myTools = new ArrayList(tools); + } + + public void show(DiffRequest data) { + checkDiffData(data); + DiffTool tool = chooseTool(data); + if (tool != null) tool.show(data); + else LOG.error("No diff tool found which is able to handle request " + data); + } + + public boolean canShow(DiffRequest data) { + checkDiffData(data); + return chooseTool(data) != null; + } + + @Nullable + private DiffTool chooseTool(DiffRequest data) { + for (DiffTool tool : myTools) { + if (tool.canShow(data)) return tool; + } + return null; + } + + private static void checkDiffData(@NotNull DiffRequest data) { + DiffContent[] contents = data.getContents(); + for (DiffContent content : contents) { + LOG.assertTrue(content != null, "Null content in diff request"); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtCompareFiles.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtCompareFiles.java index 785c341441f8..aa94de49c2b1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtCompareFiles.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtCompareFiles.java @@ -1,123 +1,123 @@ -/* - * 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.diff.impl.external; - -import com.intellij.openapi.diff.DiffContent; -import com.intellij.openapi.diff.DiffRequest; -import com.intellij.openapi.diff.impl.DiffUtil; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.FileTypes; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NonNls; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -class ExtCompareFiles extends BaseExternalTool { - public static final BaseExternalTool INSTANCE = new ExtCompareFiles(); - private ExtCompareFiles() { - super(DiffManagerImpl.ENABLE_FILES, DiffManagerImpl.FILES_TOOL); - } - - public boolean canShow(DiffRequest request) { - DiffContent[] contents = request.getContents(); - for (int i = 0; i < contents.length; i++) { - DiffContent content = contents[i]; - VirtualFile file = getLocalFile(content.getFile()); - if (file != null && file.isDirectory()) return false; - if (canExternalizeAsFile(file)) continue; - if (DiffUtil.isWritable(content)) return false; - } - return super.canShow(request); - } - - protected BaseExternalTool.ContentExternalizer externalize(final DiffRequest request, final int index) { - VirtualFile file = getLocalFile(request.getContents()[index].getFile()); - if (canExternalizeAsFile(file)) return LocalFileExternalizer.tryCreate(file); - return new MyContentExternalizer(request, index); - } - - private boolean canExternalizeAsFile(VirtualFile file) { - if (file == null || file.isDirectory()) return false; - FileType fileType = FileTypeManager.getInstance().getFileTypeByFile(file); - if (fileType.isBinary() && fileType != FileTypes.UNKNOWN) return false; - return true; - } - - private static class MyContentExternalizer implements BaseExternalTool.ContentExternalizer { - private final DiffRequest myRequest; - private final int myIndex; - @NonNls public static final String STD_PREFIX = "IJDiff"; - - public MyContentExternalizer(DiffRequest request, int index) { - myRequest = request; - myIndex = index; - } - - public File getContentFile() throws IOException { - String extension = chooseExtension(); - String name = chooseName(); - if (name.length() <= 3) name = "___" + name; - File tempFile; - try { - tempFile = FileUtil.createTempFile(name, extension); - } - catch (IOException e) { - tempFile = FileUtil.createTempFile(STD_PREFIX, extension); - } - FileOutputStream stream = null; - try { - stream = new FileOutputStream(tempFile); - stream.write(getContent().getBytes()); - } finally { - if (stream != null) stream.close(); - } - return tempFile; - } - - private String chooseName() { - String title = myRequest.getContentTitles()[myIndex]; - char[] chars = title.toCharArray(); - for (int i = 0; i < chars.length; i++) { - char aChar = chars[i]; - if (!Character.isLetterOrDigit(aChar)) chars[i] = '_'; - } - return new String(chars); - } - - private String chooseExtension() { - DiffContent content = getContent(); - VirtualFile contentFile = content.getFile(); - String extension; - if (contentFile != null) { - extension = "." + contentFile.getExtension(); - } - else { - FileType contentType = content.getContentType(); - if (contentType == null) contentType = DiffUtil.chooseContentTypes(myRequest.getContents())[myIndex]; - extension = contentType != null ? "." + contentType.getDefaultExtension() : null; - } - return extension; - } - - private DiffContent getContent() { - return myRequest.getContents()[myIndex]; - } - } -} +/* + * 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.diff.impl.external; + +import com.intellij.openapi.diff.DiffContent; +import com.intellij.openapi.diff.DiffRequest; +import com.intellij.openapi.diff.impl.DiffUtil; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +class ExtCompareFiles extends BaseExternalTool { + public static final BaseExternalTool INSTANCE = new ExtCompareFiles(); + private ExtCompareFiles() { + super(DiffManagerImpl.ENABLE_FILES, DiffManagerImpl.FILES_TOOL); + } + + public boolean canShow(DiffRequest request) { + DiffContent[] contents = request.getContents(); + for (int i = 0; i < contents.length; i++) { + DiffContent content = contents[i]; + VirtualFile file = getLocalFile(content.getFile()); + if (file != null && file.isDirectory()) return false; + if (canExternalizeAsFile(file)) continue; + if (DiffUtil.isWritable(content)) return false; + } + return super.canShow(request); + } + + protected BaseExternalTool.ContentExternalizer externalize(final DiffRequest request, final int index) { + VirtualFile file = getLocalFile(request.getContents()[index].getFile()); + if (canExternalizeAsFile(file)) return LocalFileExternalizer.tryCreate(file); + return new MyContentExternalizer(request, index); + } + + private boolean canExternalizeAsFile(VirtualFile file) { + if (file == null || file.isDirectory()) return false; + FileType fileType = FileTypeManager.getInstance().getFileTypeByFile(file); + if (fileType.isBinary() && fileType != FileTypes.UNKNOWN) return false; + return true; + } + + private static class MyContentExternalizer implements BaseExternalTool.ContentExternalizer { + private final DiffRequest myRequest; + private final int myIndex; + @NonNls public static final String STD_PREFIX = "IJDiff"; + + public MyContentExternalizer(DiffRequest request, int index) { + myRequest = request; + myIndex = index; + } + + public File getContentFile() throws IOException { + String extension = chooseExtension(); + String name = chooseName(); + if (name.length() <= 3) name = "___" + name; + File tempFile; + try { + tempFile = FileUtil.createTempFile(name, extension); + } + catch (IOException e) { + tempFile = FileUtil.createTempFile(STD_PREFIX, extension); + } + FileOutputStream stream = null; + try { + stream = new FileOutputStream(tempFile); + stream.write(getContent().getBytes()); + } finally { + if (stream != null) stream.close(); + } + return tempFile; + } + + private String chooseName() { + String title = myRequest.getContentTitles()[myIndex]; + char[] chars = title.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char aChar = chars[i]; + if (!Character.isLetterOrDigit(aChar)) chars[i] = '_'; + } + return new String(chars); + } + + private String chooseExtension() { + DiffContent content = getContent(); + VirtualFile contentFile = content.getFile(); + String extension; + if (contentFile != null) { + extension = "." + contentFile.getExtension(); + } + else { + FileType contentType = content.getContentType(); + if (contentType == null) contentType = DiffUtil.chooseContentTypes(myRequest.getContents())[myIndex]; + extension = contentType != null ? "." + contentType.getDefaultExtension() : null; + } + return extension; + } + + private DiffContent getContent() { + return myRequest.getContents()[myIndex]; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/CutLineEndAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/CutLineEndAction.java index 877018095304..154423330c7c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/CutLineEndAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/CutLineEndAction.java @@ -1,98 +1,98 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: max - * Date: May 14, 2002 - * Time: 6:29:03 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ -package com.intellij.openapi.editor.actions; - -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; -import com.intellij.openapi.ide.CopyPasteManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.ui.EmptyClipboardOwner; - -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; - -public class CutLineEndAction extends EditorAction { - public CutLineEndAction() { - super(new Handler(true)); - } - - static class Handler extends EditorWriteActionHandler { - private final boolean myCopyToClipboard; - - Handler(boolean copyToClipboard) { - myCopyToClipboard = copyToClipboard; - } - - public void executeWriteAction(Editor editor, DataContext dataContext) { - final Document doc = editor.getDocument(); - if (doc.getLineCount() == 0) return; - int caretOffset = editor.getCaretModel().getOffset(); - final int lineNumber = doc.getLineNumber(caretOffset); - int lineEndOffset = doc.getLineEndOffset(lineNumber); - - if (caretOffset >= lineEndOffset) { - DeleteLineAction.deleteLineAtCaret(editor); - return; - } - - if (myCopyToClipboard) { - copyToClipboard(doc, caretOffset, lineEndOffset, dataContext, editor); - } - - final int lineStartOffset = doc.getLineStartOffset(lineNumber); - if (StringUtil.isEmptyOrSpaces(doc.getCharsSequence().subSequence(lineStartOffset, lineEndOffset).toString())) { - DeleteLineAction.deleteLineAtCaret(editor); - } - else { - doc.deleteString(caretOffset, lineEndOffset); - } - } - - private static void copyToClipboard(final Document doc, - int caretOffset, - int lineEndOffset, - DataContext dataContext, - Editor editor) { - String s = doc.getCharsSequence().subSequence(caretOffset, lineEndOffset).toString(); - - s = StringUtil.convertLineSeparators(s); - StringSelection contents = new StringSelection(s); - - Project project = PlatformDataKeys.PROJECT.getData(dataContext); - if (project == null) { - Clipboard clipboard = editor.getComponent().getToolkit().getSystemClipboard(); - clipboard.setContents(contents, EmptyClipboardOwner.INSTANCE); - } - else { - CopyPasteManager.getInstance().setContents(contents); - } - } - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: max + * Date: May 14, 2002 + * Time: 6:29:03 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.openapi.editor.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ui.EmptyClipboardOwner; + +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; + +public class CutLineEndAction extends EditorAction { + public CutLineEndAction() { + super(new Handler(true)); + } + + static class Handler extends EditorWriteActionHandler { + private final boolean myCopyToClipboard; + + Handler(boolean copyToClipboard) { + myCopyToClipboard = copyToClipboard; + } + + public void executeWriteAction(Editor editor, DataContext dataContext) { + final Document doc = editor.getDocument(); + if (doc.getLineCount() == 0) return; + int caretOffset = editor.getCaretModel().getOffset(); + final int lineNumber = doc.getLineNumber(caretOffset); + int lineEndOffset = doc.getLineEndOffset(lineNumber); + + if (caretOffset >= lineEndOffset) { + DeleteLineAction.deleteLineAtCaret(editor); + return; + } + + if (myCopyToClipboard) { + copyToClipboard(doc, caretOffset, lineEndOffset, dataContext, editor); + } + + final int lineStartOffset = doc.getLineStartOffset(lineNumber); + if (StringUtil.isEmptyOrSpaces(doc.getCharsSequence().subSequence(lineStartOffset, lineEndOffset).toString())) { + DeleteLineAction.deleteLineAtCaret(editor); + } + else { + doc.deleteString(caretOffset, lineEndOffset); + } + } + + private static void copyToClipboard(final Document doc, + int caretOffset, + int lineEndOffset, + DataContext dataContext, + Editor editor) { + String s = doc.getCharsSequence().subSequence(caretOffset, lineEndOffset).toString(); + + s = StringUtil.convertLineSeparators(s); + StringSelection contents = new StringSelection(s); + + Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project == null) { + Clipboard clipboard = editor.getComponent().getToolkit().getSystemClipboard(); + clipboard.setContents(contents, EmptyClipboardOwner.INSTANCE); + } + else { + CopyPasteManager.getInstance().setContents(contents); + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteToLineEndAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteToLineEndAction.java index ed68aa508a4b..b178ba89728c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteToLineEndAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteToLineEndAction.java @@ -1,27 +1,27 @@ -/* - * 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.editor.actions; - -import com.intellij.openapi.editor.actionSystem.EditorAction; - -/** - * @author yole - */ -public class DeleteToLineEndAction extends EditorAction { - public DeleteToLineEndAction() { - super(new CutLineEndAction.Handler(false)); - } -} +/* + * 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.editor.actions; + +import com.intellij.openapi.editor.actionSystem.EditorAction; + +/** + * @author yole + */ +public class DeleteToLineEndAction extends EditorAction { + public DeleteToLineEndAction() { + super(new CutLineEndAction.Handler(false)); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java index b05af1e639ef..50749c166843 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java @@ -1,103 +1,103 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: max - * Date: May 14, 2002 - * Time: 7:18:30 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ -package com.intellij.openapi.editor.actions; - -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; -import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.util.Pair; - -public class DuplicateAction extends EditorAction { - public DuplicateAction() { - super(new Handler()); - } - - private static class Handler extends EditorWriteActionHandler { - public void executeWriteAction(Editor editor, DataContext dataContext) { - duplicateLineOrSelectedBlockAtCaret(editor); - } - - public boolean isEnabled(Editor editor, DataContext dataContext) { - return !editor.isOneLineMode() || editor.getSelectionModel().hasSelection(); - } - } - - private static void duplicateLineOrSelectedBlockAtCaret(Editor editor) { - Document document = editor.getDocument(); - CaretModel caretModel = editor.getCaretModel(); - ScrollingModel scrollingModel = editor.getScrollingModel(); - if(editor.getSelectionModel().hasSelection()) { - int start = editor.getSelectionModel().getSelectionStart(); - int end = editor.getSelectionModel().getSelectionEnd(); - String s = document.getCharsSequence().subSequence(start, end).toString(); - document.insertString(end, s); - caretModel.moveToOffset(end+s.length()); - scrollingModel.scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().removeSelection(); - editor.getSelectionModel().setSelection(end, end+s.length()); - } - else { - duplicateLinesRange(editor, document, caretModel.getVisualPosition(), caretModel.getVisualPosition()); - } - } - - static Pair duplicateLinesRange(Editor editor, Document document, VisualPosition rangeStart, VisualPosition rangeEnd) { - Pair lines = EditorUtil.calcCaretLinesRange(editor, rangeStart, rangeEnd); - int offset = editor.getCaretModel().getOffset(); - - LogicalPosition lineStart = lines.first; - LogicalPosition nextLineStart = lines.second; - int start = editor.logicalPositionToOffset(lineStart); - int end = editor.logicalPositionToOffset(nextLineStart); - String s = document.getCharsSequence().subSequence(start, end).toString(); - final int lineToCheck = nextLineStart.line - 1; - - int newOffset = end + offset - start; - if(lineToCheck == document.getLineCount () /*empty document*/ || - document.getLineSeparatorLength(lineToCheck) == 0) { - s = "\n"+s; - newOffset++; - } - document.insertString(end, s); - - editor.getCaretModel().moveToOffset(newOffset); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - return new Pair(end, end+s.length()); - } - - @Override - public void update(final Editor editor, final Presentation presentation, final DataContext dataContext) { - super.update(editor, presentation, dataContext); - if (editor.getSelectionModel().hasSelection()) { - presentation.setText(EditorBundle.message("action.duplicate.block"), true); - } - else { - presentation.setText(EditorBundle.message("action.duplicate.line"), true); - } - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: max + * Date: May 14, 2002 + * Time: 7:18:30 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.openapi.editor.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.util.Pair; + +public class DuplicateAction extends EditorAction { + public DuplicateAction() { + super(new Handler()); + } + + private static class Handler extends EditorWriteActionHandler { + public void executeWriteAction(Editor editor, DataContext dataContext) { + duplicateLineOrSelectedBlockAtCaret(editor); + } + + public boolean isEnabled(Editor editor, DataContext dataContext) { + return !editor.isOneLineMode() || editor.getSelectionModel().hasSelection(); + } + } + + private static void duplicateLineOrSelectedBlockAtCaret(Editor editor) { + Document document = editor.getDocument(); + CaretModel caretModel = editor.getCaretModel(); + ScrollingModel scrollingModel = editor.getScrollingModel(); + if(editor.getSelectionModel().hasSelection()) { + int start = editor.getSelectionModel().getSelectionStart(); + int end = editor.getSelectionModel().getSelectionEnd(); + String s = document.getCharsSequence().subSequence(start, end).toString(); + document.insertString(end, s); + caretModel.moveToOffset(end+s.length()); + scrollingModel.scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + editor.getSelectionModel().setSelection(end, end+s.length()); + } + else { + duplicateLinesRange(editor, document, caretModel.getVisualPosition(), caretModel.getVisualPosition()); + } + } + + static Pair duplicateLinesRange(Editor editor, Document document, VisualPosition rangeStart, VisualPosition rangeEnd) { + Pair lines = EditorUtil.calcCaretLinesRange(editor, rangeStart, rangeEnd); + int offset = editor.getCaretModel().getOffset(); + + LogicalPosition lineStart = lines.first; + LogicalPosition nextLineStart = lines.second; + int start = editor.logicalPositionToOffset(lineStart); + int end = editor.logicalPositionToOffset(nextLineStart); + String s = document.getCharsSequence().subSequence(start, end).toString(); + final int lineToCheck = nextLineStart.line - 1; + + int newOffset = end + offset - start; + if(lineToCheck == document.getLineCount () /*empty document*/ || + document.getLineSeparatorLength(lineToCheck) == 0) { + s = "\n"+s; + newOffset++; + } + document.insertString(end, s); + + editor.getCaretModel().moveToOffset(newOffset); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + return new Pair(end, end+s.length()); + } + + @Override + public void update(final Editor editor, final Presentation presentation, final DataContext dataContext) { + super.update(editor, presentation, dataContext); + if (editor.getSelectionModel().hasSelection()) { + presentation.setText(EditorBundle.message("action.duplicate.block"), true); + } + else { + presentation.setText(EditorBundle.message("action.duplicate.line"), true); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateLinesAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateLinesAction.java index 36e62022e60b..fc3c86c50aed 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateLinesAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateLinesAction.java @@ -1,51 +1,51 @@ -/* - * 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.editor.actions; - -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.VisualPosition; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; -import com.intellij.openapi.util.Pair; - -/** - * @author yole - */ -public class DuplicateLinesAction extends EditorAction { - public DuplicateLinesAction() { - super(new Handler()); - } - - private static class Handler extends EditorWriteActionHandler { - @Override - public void executeWriteAction(Editor editor, DataContext dataContext) { - if (editor.getSelectionModel().hasSelection()) { - int selStart = editor.getSelectionModel().getSelectionStart(); - int selEnd = editor.getSelectionModel().getSelectionEnd(); - VisualPosition rangeStart = editor.offsetToVisualPosition(Math.min(selStart, selEnd)); - VisualPosition rangeEnd = editor.offsetToVisualPosition(Math.max(selStart, selEnd)); - final Pair copiedRange = - DuplicateAction.duplicateLinesRange(editor, editor.getDocument(), rangeStart, rangeEnd); - editor.getSelectionModel().setSelection(copiedRange.first, copiedRange.second); - } - else { - VisualPosition caretPos = editor.getCaretModel().getVisualPosition(); - DuplicateAction.duplicateLinesRange(editor, editor.getDocument(), caretPos, caretPos); - } - } - } -} +/* + * 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.editor.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.openapi.util.Pair; + +/** + * @author yole + */ +public class DuplicateLinesAction extends EditorAction { + public DuplicateLinesAction() { + super(new Handler()); + } + + private static class Handler extends EditorWriteActionHandler { + @Override + public void executeWriteAction(Editor editor, DataContext dataContext) { + if (editor.getSelectionModel().hasSelection()) { + int selStart = editor.getSelectionModel().getSelectionStart(); + int selEnd = editor.getSelectionModel().getSelectionEnd(); + VisualPosition rangeStart = editor.offsetToVisualPosition(Math.min(selStart, selEnd)); + VisualPosition rangeEnd = editor.offsetToVisualPosition(Math.max(selStart, selEnd)); + final Pair copiedRange = + DuplicateAction.duplicateLinesRange(editor, editor.getDocument(), rangeStart, rangeEnd); + editor.getSelectionModel().setSelection(copiedRange.first, copiedRange.second); + } + else { + VisualPosition caretPos = editor.getCaretModel().getVisualPosition(); + DuplicateAction.duplicateLinesRange(editor, editor.getDocument(), caretPos, caretPos); + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/SimplePasteAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/SimplePasteAction.java index 563d57f24eec..bb62fcecc9f3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/SimplePasteAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/SimplePasteAction.java @@ -1,58 +1,58 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: max - * Date: May 13, 2002 - * Time: 7:50:36 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ -package com.intellij.openapi.editor.actions; - -import com.intellij.openapi.actionSystem.ActionPlaces; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorModificationUtil; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; -import com.intellij.openapi.editor.ex.EditorEx; - -public class SimplePasteAction extends EditorAction { - public SimplePasteAction() { - super(new Handler()); - } - - @Override - public void update(AnActionEvent e) { - super.update(e); - if (ActionPlaces.isPopupPlace(e.getPlace())) { - e.getPresentation().setVisible(e.getPresentation().isEnabled()); - } - } - - private static class Handler extends EditorWriteActionHandler { - public void executeWriteAction(Editor editor, DataContext dataContext) { - if (editor.isColumnMode()) { - EditorModificationUtil.pasteFromClipboardAsBlock(editor); - } else { - editor.putUserData(EditorEx.LAST_PASTED_REGION, EditorModificationUtil.pasteFromClipboard(editor)); - } - } - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: max + * Date: May 13, 2002 + * Time: 7:50:36 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.openapi.editor.actions; + +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; +import com.intellij.openapi.editor.ex.EditorEx; + +public class SimplePasteAction extends EditorAction { + public SimplePasteAction() { + super(new Handler()); + } + + @Override + public void update(AnActionEvent e) { + super.update(e); + if (ActionPlaces.isPopupPlace(e.getPlace())) { + e.getPresentation().setVisible(e.getPresentation().isEnabled()); + } + } + + private static class Handler extends EditorWriteActionHandler { + public void executeWriteAction(Editor editor, DataContext dataContext) { + if (editor.isColumnMode()) { + EditorModificationUtil.pasteFromClipboardAsBlock(editor); + } else { + editor.putUserData(EditorEx.LAST_PASTED_REGION, EditorModificationUtil.pasteFromClipboard(editor)); + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index 0b51c82923b1..4db50963e5b8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -1,574 +1,574 @@ -/* - * 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.editor.ex.util; - -import com.intellij.openapi.application.Result; -import com.intellij.openapi.application.WriteAction; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry; -import com.intellij.openapi.editor.impl.FontInfo; -import com.intellij.openapi.editor.impl.IterationState; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.NotNull; - -import java.awt.*; -import java.util.List; - -public class EditorUtil { - private EditorUtil() { } - - public static int getLastVisualLineColumnNumber(Editor editor, int line) { - Document document = editor.getDocument(); - int lastLine = document.getLineCount() - 1; - if (lastLine < 0) { - return 0; - } - - // Filter all lines that are not shown because of collapsed folding region. - VisualPosition visStart = new VisualPosition(line, 0); - LogicalPosition logStart = editor.visualToLogicalPosition(visStart); - int lastLogLine = logStart.line; - while (lastLogLine < document.getLineCount() - 1) { - logStart = new LogicalPosition(logStart.line + 1, logStart.column); - VisualPosition tryVisible = editor.logicalToVisualPosition(logStart); - if (tryVisible.line != visStart.line) break; - lastLogLine = logStart.line; - } - - int resultLogLine = Math.min(lastLogLine, lastLine); - VisualPosition resVisStart = editor.offsetToVisualPosition(document.getLineStartOffset(resultLogLine)); - VisualPosition resVisEnd = editor.offsetToVisualPosition(document.getLineEndOffset(resultLogLine)); - - // Target logical line is not soft wrap affected. - if (resVisStart.line == resVisEnd.line) { - return resVisEnd.column; - } - - int visualLinesToSkip = line - resVisStart.line; - List softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine); - for (int i = 0; i < softWraps.size(); i++) { - SoftWrap softWrap = softWraps.get(i); - CharSequence text = document.getCharsSequence(); - if (visualLinesToSkip <= 0) { - VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1); - int result = visual.column; - int x = editor.visualPositionToXY(visual).x; - // We need to add width of the next symbol because current result column points to the last symbol before the soft wrap. - return result + textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), x); - } - - int softWrapLineFeeds = StringUtil.countNewLines(softWrap.getText()); - if (softWrapLineFeeds < visualLinesToSkip) { - visualLinesToSkip -= softWrapLineFeeds; - continue; - } - - // Target visual column is located on the last visual line of the current soft wrap. - if (softWrapLineFeeds == visualLinesToSkip) { - if (i >= softWraps.size() - 1) { - return resVisEnd.column; - } - // We need to find visual column for line feed of the next soft wrap. - SoftWrap nextSoftWrap = softWraps.get(i + 1); - VisualPosition visual = editor.offsetToVisualPosition(nextSoftWrap.getStart() - 1); - int result = visual.column; - int x = editor.visualPositionToXY(visual).x; - - // We need to add symbol width because current column points to the last symbol before the next soft wrap; - result += textWidthInColumns(editor, text, nextSoftWrap.getStart() - 1, nextSoftWrap.getStart(), x); - - int lineFeedIndex = StringUtil.indexOf(nextSoftWrap.getText(), '\n'); - result += textWidthInColumns(editor, nextSoftWrap.getText(), 0, lineFeedIndex, 0); - return result; - } - - // Target visual column is the one before line feed introduced by the current soft wrap. - int softWrapStartOffset = 0; - int softWrapEndOffset = 0; - int softWrapTextLength = softWrap.getText().length(); - while (visualLinesToSkip-- > 0) { - softWrapStartOffset = softWrapEndOffset + 1; - if (softWrapStartOffset >= softWrapTextLength) { - assert false; - return resVisEnd.column; - } - softWrapEndOffset = StringUtil.indexOf(softWrap.getText(), '\n', softWrapStartOffset, softWrapTextLength); - if (softWrapEndOffset < 0) { - assert false; - return resVisEnd.column; - } - } - VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1); - int result = visual.column; // Column of the symbol just before the soft wrap - int x = editor.visualPositionToXY(visual).x; - - // Target visual column is located on the last visual line of the current soft wrap. - result += textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), x); - result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset); - return result; - } - - assert false : String.format( - "Target visual line: %d, mapped logical line: %d, visual lines range for the mapped logical line: %s-%s%nDocument text: '%s'", - line, resultLogLine, resVisStart, resVisEnd, editor.getDocument().getText() - ); - return resVisEnd.column; - } - - public static int getVisualLineEndOffset(@NotNull Editor editor, int line) { - VisualPosition endLineVisualPosition = new VisualPosition(line, getLastVisualLineColumnNumber(editor, line)); - LogicalPosition endLineLogicalPosition = editor.visualToLogicalPosition(endLineVisualPosition); - return editor.logicalPositionToOffset(endLineLogicalPosition); - } - - public static float calcVerticalScrollProportion(Editor editor) { - Rectangle viewArea = editor.getScrollingModel().getVisibleAreaOnScrollingFinished(); - if (viewArea.height == 0) { - return 0; - } - LogicalPosition pos = editor.getCaretModel().getLogicalPosition(); - Point location = editor.logicalPositionToXY(pos); - return (location.y - viewArea.y) / (float) viewArea.height; - } - - public static void setVerticalScrollProportion(Editor editor, float proportion) { - Rectangle viewArea = editor.getScrollingModel().getVisibleArea(); - LogicalPosition caretPosition = editor.getCaretModel().getLogicalPosition(); - Point caretLocation = editor.logicalPositionToXY(caretPosition); - int yPos = caretLocation.y; - yPos -= viewArea.height * proportion; - editor.getScrollingModel().scrollVertically(yPos); - } - - public static void fillVirtualSpaceUntilCaret(final Editor editor) { - final LogicalPosition position = editor.getCaretModel().getLogicalPosition(); - fillVirtualSpaceUntil(editor, position.column, position.line); - } - - public static void fillVirtualSpaceUntil(final Editor editor, int columnNumber, int lineNumber) { - final int offset = editor.logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber)); - final String filler = EditorModificationUtil.calcStringToFillVirtualSpace(editor); - if (filler.length() > 0) { - new WriteAction(){ - protected void run(final Result result) throws Throwable { - editor.getDocument().insertString(offset, filler); - editor.getCaretModel().moveToOffset(offset + filler.length()); - } - }.execute(); - } - } - - /** - * Allows to calculate offset of the given column assuming that it belongs to the given text line identified by the - * given [start; end) intervals. - * - * @param editor editor that is used for representing given text - * @param text target text - * @param start start offset of the logical line that holds target column (inclusive) - * @param end end offset of the logical line that holds target column (exclusive) - * @param columnNumber target column number - * @param tabSize number of desired visual columns to use for tabulation representation - * @return given text offset that identifies the same position that is pointed by the given visual column - */ - public static int calcOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize) { - final int maxScanIndex = Math.min(start + columnNumber + 1, end); - SoftWrapModel softWrapModel = editor.getSoftWrapModel(); - List softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex); - int startToUse = start; - int x = 0; - int[] currentColumn = {0}; - for (SoftWrap softWrap : softWraps) { - // There is a possible case that target column points inside soft wrap-introduced virtual space. - if (currentColumn[0] >= columnNumber) { - return startToUse; - } - int result = calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn); - if (result >= 0) { - return result; - } - - startToUse = softWrap.getStart(); - x = softWrap.getIndentInPixels(); - } - - // There is a possible case that target column points inside soft wrap-introduced virtual space. - if (currentColumn[0] >= columnNumber) { - return startToUse; - } - - int result = calcSoftWrapUnawareOffset(editor, text, startToUse, end, columnNumber, tabSize, x, currentColumn); - if (result >= 0) { - return result; - } - - // We assume that given column points to the virtual space after the line end if control flow reaches this place, - // hence, just return end of line offset then. - return end; - } - - /** - * Tries to match given logical column to the document offset assuming that it's located at [start; end) region. - * - * @param editor editor that is used to represent target document - * @param text target document text - * @param start start offset to check (inclusive) - * @param end end offset to check (exclusive) - * @param columnNumber target logical column number - * @param tabSize user-defined desired number of columns to use for tabulation symbol representation - * @param x 'x' coordinate that corresponds to the given 'start' offset - * @param currentColumn logical column that corresponds to the given 'start' offset - * @return target offset that belongs to the [start; end) range and points to the target logical - * column if any; -1 otherwise - */ - private static int calcSoftWrapUnawareOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize, int x, - int[] currentColumn) - { - // The main problem in a calculation is that target text may contain tabulation symbols and every such symbol may take different - // number of logical columns to represent. E.g. it takes two columns if tab size is four and current column is two; three columns - // if tab size is four and current column is one etc. So, first of all we check if there are tabulation symbols at the target - // text fragment. - boolean useOptimization = true; - boolean hasNonTabs = false; - boolean hasTabs = false; - for (int i = start; i < end; i++) { - if (text.charAt(i) == '\t') { - hasTabs = true; - if (hasNonTabs) { - useOptimization = false; - break; - } - } else { - hasNonTabs = true; - } - } - - // Perform optimized processing if possible. 'Optimized' here means the processing when we exactly know how many logical - // columns are occupied by tabulation symbols. - if (editor == null || useOptimization) { - if (!hasTabs) { - int result = start + columnNumber - currentColumn[0]; - if (result < end) { - return result; - } - else { - currentColumn[0] += end - start; - return -1; - } - } - - // This variable holds number of 'virtual' tab-introduced columns, e.g. there is a possible case that particular tab owns - // three columns, hence, it increases 'shift' by two (3 - 1). - int shift = 0; - int offset = start; - int prevX = x; - for (; offset < end && offset + shift + currentColumn[0] < start + columnNumber; offset++) { - if (text.charAt(offset) == '\t') { - int nextX = nextTabStop(prevX, editor, tabSize); - shift += columnsNumber(nextX - prevX, getSpaceWidth(Font.PLAIN, editor)) - 1; - prevX = nextX; - } - } - int diff = start + columnNumber - offset - shift - currentColumn[0]; - if (diff < 0) { - return offset - 1; - } - else if (diff == 0) { - return offset; - } - else { - currentColumn[0] += offset - start + shift; - return -1; - } - } - - // It means that there are tabulation symbols that can't be explicitly mapped to the occupied logical columns number, - // hence, we need to perform special calculations to get know that. - EditorEx editorImpl = (EditorEx)editor; - int offset = start; - IterationState state = new IterationState(editorImpl, offset, false); - int fontType = state.getMergedAttributes().getFontType(); - int column = currentColumn[0]; - int spaceSize = getSpaceWidth(fontType, editorImpl); - for (; column < columnNumber && offset < end; offset++) { - if (offset >= state.getEndOffset()) { - state.advance(); - fontType = state.getMergedAttributes().getFontType(); - } - - char c = text.charAt(offset); - if (c == '\t') { - int prevX = x; - x = nextTabStop(x, editorImpl); - column += columnsNumber(x - prevX, spaceSize); - } - else { - x += charWidth(c, fontType, editorImpl); - column++; - } - } - - if (column == columnNumber) { - return offset; - } - if (column > columnNumber && offset > 0 && text.charAt(offset - 1) == '\t') { - return offset - 1; - } - currentColumn[0] = column; - return -1; - } - - private static int getTabLength(int colNumber, int tabSize) { - if (tabSize <= 0) { - tabSize = 1; - } - return tabSize - colNumber % tabSize; - } - - public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset) { - return calcColumnNumber(editor, text, start, offset, getTabSize(editor)); - } - - public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset, int tabSize) { - boolean useOptimization = true; - if (editor != null) { - SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start); - useOptimization = softWrap == null; - } - if (useOptimization) { - boolean hasNonTabs = false; - for (int i = start; i < offset; i++) { - if (text.charAt(i) == '\t') { - if (hasNonTabs) { - useOptimization = false; - break; - } - } else { - hasNonTabs = true; - } - } - } - - if (editor == null || useOptimization) { - int shift = 0; - - for (int i = start; i < offset; i++) { - char c = text.charAt(i); - assert c != '\n' && c != '\r'; - if (c == '\t') { - shift += getTabLength(i + shift - start, tabSize) - 1; - } - } - return offset - start + shift; - } - - EditorEx editorImpl = (EditorEx) editor; - return editorImpl.calcColumnNumber(text, start, offset, tabSize); - } - - public static void setHandCursor(Editor view) { - Cursor c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); - // XXX: Workaround, simply view.getContentComponent().setCursor(c) doesn't work - if (view.getContentComponent().getCursor() != c) { - view.getContentComponent().setCursor(c); - } - } - - public static FontInfo fontForChar(final char c, int style, Editor editor) { - EditorColorsScheme colorsScheme = editor.getColorsScheme(); - return ComplementaryFontsRegistry.getFontAbleToDisplay(c, colorsScheme.getEditorFontSize(), style, colorsScheme.getEditorFontName()); - } - - public static int charWidth(char c, int fontType, Editor editor) { - return fontForChar(c, fontType, editor).charWidth(c, editor.getContentComponent()); - } - - public static int getSpaceWidth(int fontType, Editor editor) { - int width = charWidth(' ', fontType, editor); - return width > 0 ? width : 1; - } - - public static int getTabSize(Editor editor) { - return editor.getSettings().getTabSize(editor.getProject()); - } - - public static int nextTabStop(int x, Editor editor) { - int tabSize = getTabSize(editor); - if (tabSize <= 0) { - tabSize = 1; - } - return nextTabStop(x, editor, tabSize); - } - - public static int nextTabStop(int x, Editor editor, int tabSize) { - return nextTabStop(x, getSpaceWidth(Font.PLAIN, editor), tabSize); - } - - public static int nextTabStop(int x, int spaceWidth, int tabSize) { - if (tabSize <= 0) { - return x + spaceWidth; - } - tabSize *= spaceWidth; - - int nTabs = x / tabSize; - return (nTabs + 1) * tabSize; - } - - public static int textWidthInColumns(@NotNull Editor editor, CharSequence text, int start, int end, int x) { - int startToUse = start; - int lastTabSymbolIndex = -1; - - // Skip all lines except the last. - loop: - for (int i = end - 1; i >= start; i--) { - switch (text.charAt(i)) { - case '\n': startToUse = i + 1; break loop; - case '\t': if (lastTabSymbolIndex < 0) lastTabSymbolIndex = i; - } - } - - // Tabulation is assumed to be the only symbol which representation may take various number of visual columns, hence, - // we return eagerly if no such symbol is found. - if (lastTabSymbolIndex < 0) { - return end - startToUse; - } - - int result = 0; - int prevX; - int spaceSize = getSpaceWidth(Font.PLAIN, editor); - - // Calculate number of columns up to the latest tabulation symbol. - for (int i = startToUse; i <= lastTabSymbolIndex; i++) { - SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(i); - if (softWrap != null) { - x = softWrap.getIndentInPixels(); - } - char c = text.charAt(i); - prevX = x; - switch (c) { - case '\t': - x = nextTabStop(x, editor); - result += columnsNumber(x - prevX, spaceSize); - break; - case '\n': x = result = 0; break; - default: x += charWidth(c, Font.PLAIN, editor); result++; - } - } - - // Add remaining tabulation-free columns. - result += end - lastTabSymbolIndex - 1; - return result; - } - - /** - * Allows to answer how many columns are necessary for representation of the given char on a screen. - * - * @param c target char - * @param x 'x' coordinate of the line where given char is represented that indicates char end location - * @param prevX 'x' coordinate of the line where given char is represented that indicates char start location - * @param spaceSize 'space' symbol width - * @return number of columns necessary for representation of the given char on a screen. - */ - public static int columnsNumber(char c, int x, int prevX, int spaceSize) { - if (c != '\t') { - return 1; - } - int result = (x - prevX) / spaceSize; - if ((x - prevX) % spaceSize > 0) { - result++; - } - return result; - } - - /** - * Allows to answer how many visual columns are occupied by the given width. - * - * @param width target width - * @param spaceSize width of the single space symbol within the target editor - * @return number of visual columns are occupied by the given width - */ - public static int columnsNumber(int width, int spaceSize) { - int result = width / spaceSize; - if (width % spaceSize > 0) { - result++; - } - return result; - } - - /** - * Allows to answer what width in pixels is required to draw fragment of the given char array from [start; end) interval - * at the given editor. - *

- * Tabulation symbols is processed specially, i.e. it's ta - *

- * Note: it's assumed that target text fragment remains to the single line, i.e. line feed symbols within it are not - * treated specially. - * - * @param editor editor that will be used for target text representation - * @param text target text holder - * @param start offset within the given char array that points to target text start (inclusive) - * @param end offset within the given char array that points to target text end (exclusive) - * @param fontType font type to use for target text representation - * @param x 'x' coordinate that should be used as a starting point for target text representation. - * It's necessity is implied by the fact that IDEA editor may represent tabulation symbols in any range - * from [1; tab size] (check {@link #nextTabStop(int, Editor)} for more details) - * @return width in pixels required for target text representation - */ - public static int textWidth(@NotNull Editor editor, CharSequence text, int start, int end, int fontType, int x) { - int result = 0; - for (int i = start; i < end; i++) { - char c = text.charAt(i); - if (c != '\t') { - FontInfo font = fontForChar(c, fontType, editor); - result += font.charWidth(c, editor.getContentComponent()); - continue; - } - - result += nextTabStop(x + result, editor) - result - x; - } - return result; - } - - public static Pair calcCaretLinesRange(Editor editor) { - return calcCaretLinesRange(editor, editor.getCaretModel().getVisualPosition(), editor.getCaretModel().getVisualPosition()); - } - - /** - * Calculates the closest non-soft-wrapped logical positions for current caret position. - * - * @param editor target editor to use - * @return pair of non-soft-wrapped logical positions closest to the caret position of the given editor - */ - public static Pair calcCaretLinesRange(Editor editor, VisualPosition start, VisualPosition end) { - int visualLine = start.line; - - LogicalPosition lineStart = editor.visualToLogicalPosition(new VisualPosition(visualLine, 0)); - while (lineStart.softWrapLinesOnCurrentLogicalLine > 0) { - lineStart = editor.visualToLogicalPosition(new VisualPosition(--visualLine, 0)); - } - - visualLine = end.line + 1; - LogicalPosition nextLineStart = editor.visualToLogicalPosition(new VisualPosition(end.line + 1, 0)); - while (nextLineStart.line == lineStart.line) { - nextLineStart = editor.visualToLogicalPosition(new VisualPosition(++visualLine, 0)); - } - return new Pair(lineStart, nextLineStart); - } -} - - +/* + * 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.editor.ex.util; + +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry; +import com.intellij.openapi.editor.impl.FontInfo; +import com.intellij.openapi.editor.impl.IterationState; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.List; + +public class EditorUtil { + private EditorUtil() { } + + public static int getLastVisualLineColumnNumber(Editor editor, int line) { + Document document = editor.getDocument(); + int lastLine = document.getLineCount() - 1; + if (lastLine < 0) { + return 0; + } + + // Filter all lines that are not shown because of collapsed folding region. + VisualPosition visStart = new VisualPosition(line, 0); + LogicalPosition logStart = editor.visualToLogicalPosition(visStart); + int lastLogLine = logStart.line; + while (lastLogLine < document.getLineCount() - 1) { + logStart = new LogicalPosition(logStart.line + 1, logStart.column); + VisualPosition tryVisible = editor.logicalToVisualPosition(logStart); + if (tryVisible.line != visStart.line) break; + lastLogLine = logStart.line; + } + + int resultLogLine = Math.min(lastLogLine, lastLine); + VisualPosition resVisStart = editor.offsetToVisualPosition(document.getLineStartOffset(resultLogLine)); + VisualPosition resVisEnd = editor.offsetToVisualPosition(document.getLineEndOffset(resultLogLine)); + + // Target logical line is not soft wrap affected. + if (resVisStart.line == resVisEnd.line) { + return resVisEnd.column; + } + + int visualLinesToSkip = line - resVisStart.line; + List softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine); + for (int i = 0; i < softWraps.size(); i++) { + SoftWrap softWrap = softWraps.get(i); + CharSequence text = document.getCharsSequence(); + if (visualLinesToSkip <= 0) { + VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1); + int result = visual.column; + int x = editor.visualPositionToXY(visual).x; + // We need to add width of the next symbol because current result column points to the last symbol before the soft wrap. + return result + textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), x); + } + + int softWrapLineFeeds = StringUtil.countNewLines(softWrap.getText()); + if (softWrapLineFeeds < visualLinesToSkip) { + visualLinesToSkip -= softWrapLineFeeds; + continue; + } + + // Target visual column is located on the last visual line of the current soft wrap. + if (softWrapLineFeeds == visualLinesToSkip) { + if (i >= softWraps.size() - 1) { + return resVisEnd.column; + } + // We need to find visual column for line feed of the next soft wrap. + SoftWrap nextSoftWrap = softWraps.get(i + 1); + VisualPosition visual = editor.offsetToVisualPosition(nextSoftWrap.getStart() - 1); + int result = visual.column; + int x = editor.visualPositionToXY(visual).x; + + // We need to add symbol width because current column points to the last symbol before the next soft wrap; + result += textWidthInColumns(editor, text, nextSoftWrap.getStart() - 1, nextSoftWrap.getStart(), x); + + int lineFeedIndex = StringUtil.indexOf(nextSoftWrap.getText(), '\n'); + result += textWidthInColumns(editor, nextSoftWrap.getText(), 0, lineFeedIndex, 0); + return result; + } + + // Target visual column is the one before line feed introduced by the current soft wrap. + int softWrapStartOffset = 0; + int softWrapEndOffset = 0; + int softWrapTextLength = softWrap.getText().length(); + while (visualLinesToSkip-- > 0) { + softWrapStartOffset = softWrapEndOffset + 1; + if (softWrapStartOffset >= softWrapTextLength) { + assert false; + return resVisEnd.column; + } + softWrapEndOffset = StringUtil.indexOf(softWrap.getText(), '\n', softWrapStartOffset, softWrapTextLength); + if (softWrapEndOffset < 0) { + assert false; + return resVisEnd.column; + } + } + VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1); + int result = visual.column; // Column of the symbol just before the soft wrap + int x = editor.visualPositionToXY(visual).x; + + // Target visual column is located on the last visual line of the current soft wrap. + result += textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), x); + result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset); + return result; + } + + assert false : String.format( + "Target visual line: %d, mapped logical line: %d, visual lines range for the mapped logical line: %s-%s%nDocument text: '%s'", + line, resultLogLine, resVisStart, resVisEnd, editor.getDocument().getText() + ); + return resVisEnd.column; + } + + public static int getVisualLineEndOffset(@NotNull Editor editor, int line) { + VisualPosition endLineVisualPosition = new VisualPosition(line, getLastVisualLineColumnNumber(editor, line)); + LogicalPosition endLineLogicalPosition = editor.visualToLogicalPosition(endLineVisualPosition); + return editor.logicalPositionToOffset(endLineLogicalPosition); + } + + public static float calcVerticalScrollProportion(Editor editor) { + Rectangle viewArea = editor.getScrollingModel().getVisibleAreaOnScrollingFinished(); + if (viewArea.height == 0) { + return 0; + } + LogicalPosition pos = editor.getCaretModel().getLogicalPosition(); + Point location = editor.logicalPositionToXY(pos); + return (location.y - viewArea.y) / (float) viewArea.height; + } + + public static void setVerticalScrollProportion(Editor editor, float proportion) { + Rectangle viewArea = editor.getScrollingModel().getVisibleArea(); + LogicalPosition caretPosition = editor.getCaretModel().getLogicalPosition(); + Point caretLocation = editor.logicalPositionToXY(caretPosition); + int yPos = caretLocation.y; + yPos -= viewArea.height * proportion; + editor.getScrollingModel().scrollVertically(yPos); + } + + public static void fillVirtualSpaceUntilCaret(final Editor editor) { + final LogicalPosition position = editor.getCaretModel().getLogicalPosition(); + fillVirtualSpaceUntil(editor, position.column, position.line); + } + + public static void fillVirtualSpaceUntil(final Editor editor, int columnNumber, int lineNumber) { + final int offset = editor.logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber)); + final String filler = EditorModificationUtil.calcStringToFillVirtualSpace(editor); + if (filler.length() > 0) { + new WriteAction(){ + protected void run(final Result result) throws Throwable { + editor.getDocument().insertString(offset, filler); + editor.getCaretModel().moveToOffset(offset + filler.length()); + } + }.execute(); + } + } + + /** + * Allows to calculate offset of the given column assuming that it belongs to the given text line identified by the + * given [start; end) intervals. + * + * @param editor editor that is used for representing given text + * @param text target text + * @param start start offset of the logical line that holds target column (inclusive) + * @param end end offset of the logical line that holds target column (exclusive) + * @param columnNumber target column number + * @param tabSize number of desired visual columns to use for tabulation representation + * @return given text offset that identifies the same position that is pointed by the given visual column + */ + public static int calcOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize) { + final int maxScanIndex = Math.min(start + columnNumber + 1, end); + SoftWrapModel softWrapModel = editor.getSoftWrapModel(); + List softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex); + int startToUse = start; + int x = 0; + int[] currentColumn = {0}; + for (SoftWrap softWrap : softWraps) { + // There is a possible case that target column points inside soft wrap-introduced virtual space. + if (currentColumn[0] >= columnNumber) { + return startToUse; + } + int result = calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn); + if (result >= 0) { + return result; + } + + startToUse = softWrap.getStart(); + x = softWrap.getIndentInPixels(); + } + + // There is a possible case that target column points inside soft wrap-introduced virtual space. + if (currentColumn[0] >= columnNumber) { + return startToUse; + } + + int result = calcSoftWrapUnawareOffset(editor, text, startToUse, end, columnNumber, tabSize, x, currentColumn); + if (result >= 0) { + return result; + } + + // We assume that given column points to the virtual space after the line end if control flow reaches this place, + // hence, just return end of line offset then. + return end; + } + + /** + * Tries to match given logical column to the document offset assuming that it's located at [start; end) region. + * + * @param editor editor that is used to represent target document + * @param text target document text + * @param start start offset to check (inclusive) + * @param end end offset to check (exclusive) + * @param columnNumber target logical column number + * @param tabSize user-defined desired number of columns to use for tabulation symbol representation + * @param x 'x' coordinate that corresponds to the given 'start' offset + * @param currentColumn logical column that corresponds to the given 'start' offset + * @return target offset that belongs to the [start; end) range and points to the target logical + * column if any; -1 otherwise + */ + private static int calcSoftWrapUnawareOffset(Editor editor, CharSequence text, int start, int end, int columnNumber, int tabSize, int x, + int[] currentColumn) + { + // The main problem in a calculation is that target text may contain tabulation symbols and every such symbol may take different + // number of logical columns to represent. E.g. it takes two columns if tab size is four and current column is two; three columns + // if tab size is four and current column is one etc. So, first of all we check if there are tabulation symbols at the target + // text fragment. + boolean useOptimization = true; + boolean hasNonTabs = false; + boolean hasTabs = false; + for (int i = start; i < end; i++) { + if (text.charAt(i) == '\t') { + hasTabs = true; + if (hasNonTabs) { + useOptimization = false; + break; + } + } else { + hasNonTabs = true; + } + } + + // Perform optimized processing if possible. 'Optimized' here means the processing when we exactly know how many logical + // columns are occupied by tabulation symbols. + if (editor == null || useOptimization) { + if (!hasTabs) { + int result = start + columnNumber - currentColumn[0]; + if (result < end) { + return result; + } + else { + currentColumn[0] += end - start; + return -1; + } + } + + // This variable holds number of 'virtual' tab-introduced columns, e.g. there is a possible case that particular tab owns + // three columns, hence, it increases 'shift' by two (3 - 1). + int shift = 0; + int offset = start; + int prevX = x; + for (; offset < end && offset + shift + currentColumn[0] < start + columnNumber; offset++) { + if (text.charAt(offset) == '\t') { + int nextX = nextTabStop(prevX, editor, tabSize); + shift += columnsNumber(nextX - prevX, getSpaceWidth(Font.PLAIN, editor)) - 1; + prevX = nextX; + } + } + int diff = start + columnNumber - offset - shift - currentColumn[0]; + if (diff < 0) { + return offset - 1; + } + else if (diff == 0) { + return offset; + } + else { + currentColumn[0] += offset - start + shift; + return -1; + } + } + + // It means that there are tabulation symbols that can't be explicitly mapped to the occupied logical columns number, + // hence, we need to perform special calculations to get know that. + EditorEx editorImpl = (EditorEx)editor; + int offset = start; + IterationState state = new IterationState(editorImpl, offset, false); + int fontType = state.getMergedAttributes().getFontType(); + int column = currentColumn[0]; + int spaceSize = getSpaceWidth(fontType, editorImpl); + for (; column < columnNumber && offset < end; offset++) { + if (offset >= state.getEndOffset()) { + state.advance(); + fontType = state.getMergedAttributes().getFontType(); + } + + char c = text.charAt(offset); + if (c == '\t') { + int prevX = x; + x = nextTabStop(x, editorImpl); + column += columnsNumber(x - prevX, spaceSize); + } + else { + x += charWidth(c, fontType, editorImpl); + column++; + } + } + + if (column == columnNumber) { + return offset; + } + if (column > columnNumber && offset > 0 && text.charAt(offset - 1) == '\t') { + return offset - 1; + } + currentColumn[0] = column; + return -1; + } + + private static int getTabLength(int colNumber, int tabSize) { + if (tabSize <= 0) { + tabSize = 1; + } + return tabSize - colNumber % tabSize; + } + + public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset) { + return calcColumnNumber(editor, text, start, offset, getTabSize(editor)); + } + + public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset, int tabSize) { + boolean useOptimization = true; + if (editor != null) { + SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start); + useOptimization = softWrap == null; + } + if (useOptimization) { + boolean hasNonTabs = false; + for (int i = start; i < offset; i++) { + if (text.charAt(i) == '\t') { + if (hasNonTabs) { + useOptimization = false; + break; + } + } else { + hasNonTabs = true; + } + } + } + + if (editor == null || useOptimization) { + int shift = 0; + + for (int i = start; i < offset; i++) { + char c = text.charAt(i); + assert c != '\n' && c != '\r'; + if (c == '\t') { + shift += getTabLength(i + shift - start, tabSize) - 1; + } + } + return offset - start + shift; + } + + EditorEx editorImpl = (EditorEx) editor; + return editorImpl.calcColumnNumber(text, start, offset, tabSize); + } + + public static void setHandCursor(Editor view) { + Cursor c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + // XXX: Workaround, simply view.getContentComponent().setCursor(c) doesn't work + if (view.getContentComponent().getCursor() != c) { + view.getContentComponent().setCursor(c); + } + } + + public static FontInfo fontForChar(final char c, int style, Editor editor) { + EditorColorsScheme colorsScheme = editor.getColorsScheme(); + return ComplementaryFontsRegistry.getFontAbleToDisplay(c, colorsScheme.getEditorFontSize(), style, colorsScheme.getEditorFontName()); + } + + public static int charWidth(char c, int fontType, Editor editor) { + return fontForChar(c, fontType, editor).charWidth(c, editor.getContentComponent()); + } + + public static int getSpaceWidth(int fontType, Editor editor) { + int width = charWidth(' ', fontType, editor); + return width > 0 ? width : 1; + } + + public static int getTabSize(Editor editor) { + return editor.getSettings().getTabSize(editor.getProject()); + } + + public static int nextTabStop(int x, Editor editor) { + int tabSize = getTabSize(editor); + if (tabSize <= 0) { + tabSize = 1; + } + return nextTabStop(x, editor, tabSize); + } + + public static int nextTabStop(int x, Editor editor, int tabSize) { + return nextTabStop(x, getSpaceWidth(Font.PLAIN, editor), tabSize); + } + + public static int nextTabStop(int x, int spaceWidth, int tabSize) { + if (tabSize <= 0) { + return x + spaceWidth; + } + tabSize *= spaceWidth; + + int nTabs = x / tabSize; + return (nTabs + 1) * tabSize; + } + + public static int textWidthInColumns(@NotNull Editor editor, CharSequence text, int start, int end, int x) { + int startToUse = start; + int lastTabSymbolIndex = -1; + + // Skip all lines except the last. + loop: + for (int i = end - 1; i >= start; i--) { + switch (text.charAt(i)) { + case '\n': startToUse = i + 1; break loop; + case '\t': if (lastTabSymbolIndex < 0) lastTabSymbolIndex = i; + } + } + + // Tabulation is assumed to be the only symbol which representation may take various number of visual columns, hence, + // we return eagerly if no such symbol is found. + if (lastTabSymbolIndex < 0) { + return end - startToUse; + } + + int result = 0; + int prevX; + int spaceSize = getSpaceWidth(Font.PLAIN, editor); + + // Calculate number of columns up to the latest tabulation symbol. + for (int i = startToUse; i <= lastTabSymbolIndex; i++) { + SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(i); + if (softWrap != null) { + x = softWrap.getIndentInPixels(); + } + char c = text.charAt(i); + prevX = x; + switch (c) { + case '\t': + x = nextTabStop(x, editor); + result += columnsNumber(x - prevX, spaceSize); + break; + case '\n': x = result = 0; break; + default: x += charWidth(c, Font.PLAIN, editor); result++; + } + } + + // Add remaining tabulation-free columns. + result += end - lastTabSymbolIndex - 1; + return result; + } + + /** + * Allows to answer how many columns are necessary for representation of the given char on a screen. + * + * @param c target char + * @param x 'x' coordinate of the line where given char is represented that indicates char end location + * @param prevX 'x' coordinate of the line where given char is represented that indicates char start location + * @param spaceSize 'space' symbol width + * @return number of columns necessary for representation of the given char on a screen. + */ + public static int columnsNumber(char c, int x, int prevX, int spaceSize) { + if (c != '\t') { + return 1; + } + int result = (x - prevX) / spaceSize; + if ((x - prevX) % spaceSize > 0) { + result++; + } + return result; + } + + /** + * Allows to answer how many visual columns are occupied by the given width. + * + * @param width target width + * @param spaceSize width of the single space symbol within the target editor + * @return number of visual columns are occupied by the given width + */ + public static int columnsNumber(int width, int spaceSize) { + int result = width / spaceSize; + if (width % spaceSize > 0) { + result++; + } + return result; + } + + /** + * Allows to answer what width in pixels is required to draw fragment of the given char array from [start; end) interval + * at the given editor. + *

+ * Tabulation symbols is processed specially, i.e. it's ta + *

+ * Note: it's assumed that target text fragment remains to the single line, i.e. line feed symbols within it are not + * treated specially. + * + * @param editor editor that will be used for target text representation + * @param text target text holder + * @param start offset within the given char array that points to target text start (inclusive) + * @param end offset within the given char array that points to target text end (exclusive) + * @param fontType font type to use for target text representation + * @param x 'x' coordinate that should be used as a starting point for target text representation. + * It's necessity is implied by the fact that IDEA editor may represent tabulation symbols in any range + * from [1; tab size] (check {@link #nextTabStop(int, Editor)} for more details) + * @return width in pixels required for target text representation + */ + public static int textWidth(@NotNull Editor editor, CharSequence text, int start, int end, int fontType, int x) { + int result = 0; + for (int i = start; i < end; i++) { + char c = text.charAt(i); + if (c != '\t') { + FontInfo font = fontForChar(c, fontType, editor); + result += font.charWidth(c, editor.getContentComponent()); + continue; + } + + result += nextTabStop(x + result, editor) - result - x; + } + return result; + } + + public static Pair calcCaretLinesRange(Editor editor) { + return calcCaretLinesRange(editor, editor.getCaretModel().getVisualPosition(), editor.getCaretModel().getVisualPosition()); + } + + /** + * Calculates the closest non-soft-wrapped logical positions for current caret position. + * + * @param editor target editor to use + * @return pair of non-soft-wrapped logical positions closest to the caret position of the given editor + */ + public static Pair calcCaretLinesRange(Editor editor, VisualPosition start, VisualPosition end) { + int visualLine = start.line; + + LogicalPosition lineStart = editor.visualToLogicalPosition(new VisualPosition(visualLine, 0)); + while (lineStart.softWrapLinesOnCurrentLogicalLine > 0) { + lineStart = editor.visualToLogicalPosition(new VisualPosition(--visualLine, 0)); + } + + visualLine = end.line + 1; + LogicalPosition nextLineStart = editor.visualToLogicalPosition(new VisualPosition(end.line + 1, 0)); + while (nextLineStart.line == lineStart.line) { + nextLineStart = editor.visualToLogicalPosition(new VisualPosition(++visualLine, 0)); + } + return new Pair(lineStart, nextLineStart); + } +} + + diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index 2c96ea893ea3..72f79aad4f61 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -1,583 +1,583 @@ -/* - * 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. - */ - -/* - * Created by IntelliJ IDEA. - * User: max - * Date: Jun 18, 2002 - * Time: 9:12:05 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ -package com.intellij.openapi.editor.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.event.CaretEvent; -import com.intellij.openapi.editor.event.CaretListener; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.ex.*; -import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.editor.impl.event.DocumentEventImpl; -import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper; -import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.text.CharArrayUtil; - -import java.awt.*; -import java.util.List; - -public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.CaretModelImpl"); - private final EditorImpl myEditor; - private final List myCaretListeners = ContainerUtil.createEmptyCOWList(); - private LogicalPosition myLogicalCaret; - private VerticalInfo myCaretInfo; - private VisualPosition myVisibleCaret; - private int myOffset; - private int myVisualLineStart; - private int myVisualLineEnd; - private TextAttributes myTextAttributes; - private boolean myIsInUpdate; - private RangeMarker savedBeforeBulkCaretMarker; - - public CaretModelImpl(EditorImpl editor) { - myEditor = editor; - myLogicalCaret = new LogicalPosition(0, 0); - myVisibleCaret = new VisualPosition(0, 0); - myCaretInfo = new VerticalInfo(0, 0); - myOffset = 0; - myVisualLineStart = 0; - Document doc = editor.getDocument(); - myVisualLineEnd = doc.getLineCount() > 1 ? doc.getLineStartOffset(1) : doc.getLineCount() == 0 ? 0 : doc.getLineEndOffset(0); - DocumentBulkUpdateListener bulkUpdateListener = new DocumentBulkUpdateListener() { - @Override - public void updateStarted(Document doc) { - if (doc != myEditor.getDocument()) return; - savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset); - } - @Override - public void updateFinished(Document doc) { - if (doc != myEditor.getDocument() || myIsInUpdate) return; - if (savedBeforeBulkCaretMarker != null && savedBeforeBulkCaretMarker.isValid()) { - moveToOffset(savedBeforeBulkCaretMarker.getStartOffset()); - } - releaseBulkCaretMarker(); - } - }; - ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, bulkUpdateListener); - } - - private void releaseBulkCaretMarker() { - if (savedBeforeBulkCaretMarker != null) { - savedBeforeBulkCaretMarker.dispose(); - savedBeforeBulkCaretMarker = null; - } - } - - public void moveToVisualPosition(VisualPosition pos) { - assertIsDispatchThread(); - validateCallContext(); - int column = pos.column; - int line = pos.line; - - if (column < 0) column = 0; - - if (line < 0) line = 0; - - int lastLine = myEditor.getVisibleLineCount() - 1; - if (lastLine <= 0) { - lastLine = 0; - } - - if (line > lastLine) { - line = lastLine; - } - - EditorSettings editorSettings = myEditor.getSettings(); - - if (!editorSettings.isVirtualSpace() && line <= lastLine) { - int lineEndColumn = EditorUtil.getLastVisualLineColumnNumber(myEditor, line); - if (column > lineEndColumn) { - column = lineEndColumn; - } - - if (column < 0 && line > 0) { - line--; - column = EditorUtil.getLastVisualLineColumnNumber(myEditor, line); - } - } - - myVisibleCaret = new VisualPosition(line, column); - - VerticalInfo oldInfo = myCaretInfo; - LogicalPosition oldPosition = myLogicalCaret; - - setCurrentLogicalCaret(myEditor.visualToLogicalPosition(myVisibleCaret)); - myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); - LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); - - myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); - myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); - - ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); - - myEditor.setLastColumnNumber(myVisibleCaret.column); - myEditor.updateCaretCursor(); - requestRepaint(oldInfo); - - if (oldPosition.column != myLogicalCaret.column || oldPosition.line != myLogicalCaret.line) { - CaretEvent event = new CaretEvent(myEditor, oldPosition, myLogicalCaret); - - for (CaretListener listener : myCaretListeners) { - listener.caretPositionChanged(event); - } - } - } - - private void assertIsDispatchThread() { - myEditor.assertIsDispatchThread(); - } - - public void moveToOffset(int offset) { - moveToOffset(offset, false); - } - - public void moveToOffset(int offset, boolean locateBeforeSoftWrap) { - assertIsDispatchThread(); - validateCallContext(); - moveToLogicalPosition(myEditor.offsetToLogicalPosition(offset), locateBeforeSoftWrap); - if (!myEditor.offsetToLogicalPosition(myOffset).equals(myEditor.offsetToLogicalPosition(offset))) { - LOG.error("caret moved to wrong offset. Requested:" + offset + " but actual:" + myOffset); - } - } - - public void moveCaretRelatively(int columnShift, - int lineShift, - boolean withSelection, - boolean blockSelection, - boolean scrollToCaret) - { - assertIsDispatchThread(); - SelectionModel selectionModel = myEditor.getSelectionModel(); - int selectionStart = selectionModel.getLeadSelectionOffset(); - LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() - ? selectionModel.getBlockStart() - : getLogicalPosition(); - EditorSettings editorSettings = myEditor.getSettings(); - VisualPosition visualCaret = getVisualPosition(); - - int newColumnNumber = visualCaret.column + columnShift; - int newLineNumber = visualCaret.line + lineShift; - - Document document = myEditor.getDocument(); - if (!editorSettings.isVirtualSpace() && columnShift == 0) { - newColumnNumber = myEditor.getLastColumnNumber(); - } - else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) { - int lastLine = document.getLineCount() - 1; - if (lastLine < 0) lastLine = 0; - if (EditorModificationUtil.calcAfterLineEnd(myEditor) >= 0 && - newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) { - newColumnNumber = 0; - newLineNumber++; - } - } - else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == -1) { - if (newColumnNumber < 0 && newLineNumber > 0) { - newLineNumber--; - newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber); - } - } - - if (newColumnNumber < 0) newColumnNumber = 0; - - // There is a possible case that caret is located at the first line and use presses 'Shift+Up'. We want to select all text - // from the document start to the current caret position then. So, we have a dedicated flag for tracking that. - boolean selectToDocumentStart = false; - if (newLineNumber < 0) { - selectToDocumentStart = true; - newLineNumber = 0; - } - - VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber); - int lastColumnNumber = newColumnNumber; - if (!editorSettings.isCaretInsideTabs() && !myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { - LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber)); - int offset = myEditor.logicalPositionToOffset(log); - if (offset >= document.getTextLength() && myEditor.getSelectionModel().hasSelection()) { - int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column; - newColumnNumber = Math.max(lastOffsetColumn, newColumnNumber); - } - CharSequence text = document.getCharsSequence(); - if (offset >= 0 && offset < document.getTextLength()) { - if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) { - if (columnShift <= 0) { - newColumnNumber = myEditor.offsetToVisualPosition(offset).column; - } - else { - SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1); - // There is a possible case that tabulation symbol is the last document symbol represented on a visual line before - // soft wrap. We can't just use column from 'offset + 1' because it would point on a next visual line. - if (softWrap == null) { - newColumnNumber = myEditor.offsetToVisualPosition(offset + 1).column; - } - else { - newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber); - } - } - } - } - } - - pos = new VisualPosition(newLineNumber, newColumnNumber); - if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { - LogicalPosition logical = myEditor.visualToLogicalPosition(pos); - int softWrapOffset = myEditor.logicalPositionToOffset(logical); - if (columnShift >= 0) { - moveToOffset(softWrapOffset); - } - else { - int line = myEditor.offsetToVisualPosition(softWrapOffset - 1).line; - moveToVisualPosition(new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line))); - } - } - else { - moveToVisualPosition(pos); - if (!editorSettings.isVirtualSpace() && columnShift == 0) { - myEditor.setLastColumnNumber(lastColumnNumber); - } - } - - if (withSelection) { - if (blockSelection) { - selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition()); - } - else { - if (selectToDocumentStart) { - selectionModel.setSelection(selectionStart, 0); - } - else if (pos.line >= myEditor.getVisibleLineCount()) { - if (selectionStart < document.getTextLength()) { - selectionModel.setSelection(selectionStart, document.getTextLength()); - } - } - else { - selectionModel.setSelection(selectionStart, getOffset()); - } - } - } - else { - selectionModel.removeSelection(); - } - - if (scrollToCaret) { - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - } - - public void moveToLogicalPosition(LogicalPosition pos) { - moveToLogicalPosition(pos, false); - } - - private void moveToLogicalPosition(LogicalPosition pos, boolean locateBeforeSoftWrap) { - assertIsDispatchThread(); - validateCallContext(); - int column = pos.column; - int line = pos.line; - int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine; - int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine; - int softWrapColumns = pos.softWrapColumnDiff; - - Document doc = myEditor.getDocument(); - - if (column < 0) { - column = 0; - softWrapColumns = 0; - } - if (line < 0) { - line = 0; - softWrapLinesBefore = 0; - softWrapLinesCurrent = 0; - } - - int lineCount = doc.getLineCount(); - if (lineCount == 0) { - line = 0; - } - else if (line > lineCount - 1) { - line = lineCount - 1; - softWrapLinesBefore = 0; - softWrapLinesCurrent = 0; - } - - EditorSettings editorSettings = myEditor.getSettings(); - - if (!editorSettings.isVirtualSpace() && line < lineCount) { - int lineEndOffset = doc.getLineEndOffset(line); - int lineEndColumnNumber = myEditor.offsetToLogicalPosition(lineEndOffset).column; - if (column > lineEndColumnNumber) { - column = lineEndColumnNumber; - if (softWrapColumns != 0) { - softWrapColumns -= column - lineEndColumnNumber; - } - } - } - - ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); - - VerticalInfo oldInfo = myCaretInfo; - LogicalPosition oldCaretPosition = myLogicalCaret; - - LogicalPosition logicalPositionToUse; - if (pos.visualPositionAware) { - logicalPositionToUse = new LogicalPosition( - line, column, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff - ); - } - else { - logicalPositionToUse = new LogicalPosition(line, column); - } - setCurrentLogicalCaret(logicalPositionToUse); - - final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); - - FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(offset); - - if (collapsedAt != null && offset > collapsedAt.getStartOffset()) { - Runnable runnable = new Runnable() { - public void run() { - FoldRegion[] allCollapsedAt = ((FoldingModelImpl)myEditor.getFoldingModel()).fetchCollapsedAt(offset); - for (FoldRegion foldRange : allCollapsedAt) { - foldRange.setExpanded(true); - } - } - }; - - myEditor.getFoldingModel().runBatchFoldingOperation(runnable); - } - - myEditor.setLastColumnNumber(myLogicalCaret.column); - myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret); - - myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); - LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); - - myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); - myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); - - myEditor.updateCaretCursor(); - requestRepaint(oldInfo); - - if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(myEditor)) { - int lineToUse = myVisibleCaret.line - 1; - if (lineToUse >= 0) { - moveToVisualPosition(new VisualPosition(lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse))); - return; - } - } - - if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) { - CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret); - for (CaretListener listener : myCaretListeners) { - listener.caretPositionChanged(event); - } - } - } - - private void requestRepaint(VerticalInfo oldCaretInfo) { - int lineHeight = myEditor.getLineHeight(); - Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); - final EditorGutterComponentEx gutter = myEditor.getGutterComponentEx(); - final EditorComponentImpl content = myEditor.getContentComponent(); - - int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleArea.width; - if (Math.abs(myCaretInfo.y - oldCaretInfo.y) <= 2 * lineHeight) { - int minY = Math.min(oldCaretInfo.y, myCaretInfo.y); - int maxY = Math.max(oldCaretInfo.y + oldCaretInfo.height, myCaretInfo.y + myCaretInfo.height); - content.repaintEditorComponent(0, minY, updateWidth, maxY - minY); - gutter.repaint(0, minY, gutter.getWidth(), maxY - minY); - } - else { - content.repaintEditorComponent(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); - gutter.repaint(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); - content.repaintEditorComponent(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); - gutter.repaint(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); - } - } - - public LogicalPosition getLogicalPosition() { - validateCallContext(); - return myLogicalCaret; - } - - private void validateCallContext() { - LOG.assertTrue(!myIsInUpdate, "Caret model is in its update process. All requests are illegal at this point."); - } - - public VisualPosition getVisualPosition() { - validateCallContext(); - return myVisibleCaret; - } - - public int getOffset() { - validateCallContext(); - return myOffset; - } - - public int getVisualLineStart() { - return myVisualLineStart; - } - - public int getVisualLineEnd() { - return myVisualLineEnd; - } - - public void addCaretListener(CaretListener listener) { - myCaretListeners.add(listener); - } - - public void removeCaretListener(CaretListener listener) { - boolean success = myCaretListeners.remove(listener); - LOG.assertTrue(success); - } - - public TextAttributes getTextAttributes() { - if (myTextAttributes == null) { - myTextAttributes = new TextAttributes(); - myTextAttributes.setBackgroundColor(myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR)); - } - - return myTextAttributes; - } - - public void reinitSettings() { - myTextAttributes = null; - } - - public void documentChanged(DocumentEvent e) { - myIsInUpdate = false; - - DocumentEventImpl event = (DocumentEventImpl)e; - final Document document = myEditor.getDocument(); - boolean performSoftWrapAdjustment = e.getNewLength() > 0 // We want to put caret just after the last added symbol - // There is a possible case that the user removes text just before the soft wrap. We want to keep caret - // on a visual line with soft wrap start then. - || myEditor.getSoftWrapModel().getSoftWrap(e.getOffset()) != null; - - if (event.isWholeTextReplaced()) { - int newLength = document.getTextLength(); - if (myOffset == newLength - e.getNewLength() + e.getOldLength() || newLength == 0) { - moveToOffset(newLength, performSoftWrapAdjustment); - } - else { - final int line = event.translateLineViaDiff(myLogicalCaret.line); - moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column), performSoftWrapAdjustment); - } - } - else { - if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; - int startOffset = e.getOffset(); - int oldEndOffset = startOffset + e.getOldLength(); - - int newOffset = myOffset; - - if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(e)) { - newOffset += e.getNewLength() - e.getOldLength(); - } - else if (myOffset >= startOffset && myOffset <= oldEndOffset) { - newOffset = Math.min(newOffset, startOffset + e.getNewLength()); - } - - newOffset = Math.min(newOffset, document.getTextLength()); - - // if (newOffset != myOffset) { - moveToOffset(newOffset, performSoftWrapAdjustment); - //} - //else { - // moveToVisualPosition(oldPosition); - //} - } - - myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); - myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); - } - - private boolean needToShiftWhiteSpaces(final DocumentEvent e) { - if(!CharArrayUtil.containsOnlyWhiteSpaces(e.getNewFragment()) || CharArrayUtil.containLineBreaks(e.getNewFragment())) - return e.getOldLength() > 0; - if(e.getOffset() == 0) return false; - final char charBefore = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() - 1); - //final char charAfter = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() + e.getNewLength()); - return Character.isWhitespace(charBefore)/* || !Character.isWhitespace(charAfter)*/; - } - - public void beforeDocumentChange(DocumentEvent e) { - myIsInUpdate = true; - } - - public int getPriority() { - return EditorDocumentPriorities.CARET_MODEL; - } - - private void setCurrentLogicalCaret(LogicalPosition position) { - myLogicalCaret = position; - myCaretInfo = createVerticalInfo(position); - } - - private VerticalInfo createVerticalInfo(LogicalPosition position) { - Document document = myEditor.getDocument(); - int logicalLine = position.line; - int startOffset = document.getLineStartOffset(logicalLine); - int endOffset = document.getLineEndOffset(logicalLine); - - // There is a possible case that active logical line is represented on multiple lines due to soft wraps processing. - // We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for the logical line start - // and height in accordance with the number of occupied visual lines. - VisualPosition visualPosition = myEditor.offsetToVisualPosition(document.getLineStartOffset(logicalLine)); - int y = myEditor.visualPositionToXY(visualPosition).y; - int lineHeight = myEditor.getLineHeight(); - int height = lineHeight; - List softWraps = myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset); - for (SoftWrap softWrap : softWraps) { - height += StringUtil.countNewLines(softWrap.getText()) * lineHeight; - } - - return new VerticalInfo(y, height); - } - - @Override - public void dispose() { - releaseBulkCaretMarker(); - } - - /** - * Encapsulates information about target vertical range info - its 'y' coordinate and height in pixels. - */ - public static class VerticalInfo { - public final int y; - public final int height; - - private VerticalInfo(int y, int height) { - this.y = y; - this.height = height; - } - } -} +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: max + * Date: Jun 18, 2002 + * Time: 9:12:05 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.event.CaretEvent; +import com.intellij.openapi.editor.event.CaretListener; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.ex.*; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.editor.impl.event.DocumentEventImpl; +import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.CharArrayUtil; + +import java.awt.*; +import java.util.List; + +public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.CaretModelImpl"); + private final EditorImpl myEditor; + private final List myCaretListeners = ContainerUtil.createEmptyCOWList(); + private LogicalPosition myLogicalCaret; + private VerticalInfo myCaretInfo; + private VisualPosition myVisibleCaret; + private int myOffset; + private int myVisualLineStart; + private int myVisualLineEnd; + private TextAttributes myTextAttributes; + private boolean myIsInUpdate; + private RangeMarker savedBeforeBulkCaretMarker; + + public CaretModelImpl(EditorImpl editor) { + myEditor = editor; + myLogicalCaret = new LogicalPosition(0, 0); + myVisibleCaret = new VisualPosition(0, 0); + myCaretInfo = new VerticalInfo(0, 0); + myOffset = 0; + myVisualLineStart = 0; + Document doc = editor.getDocument(); + myVisualLineEnd = doc.getLineCount() > 1 ? doc.getLineStartOffset(1) : doc.getLineCount() == 0 ? 0 : doc.getLineEndOffset(0); + DocumentBulkUpdateListener bulkUpdateListener = new DocumentBulkUpdateListener() { + @Override + public void updateStarted(Document doc) { + if (doc != myEditor.getDocument()) return; + savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset); + } + @Override + public void updateFinished(Document doc) { + if (doc != myEditor.getDocument() || myIsInUpdate) return; + if (savedBeforeBulkCaretMarker != null && savedBeforeBulkCaretMarker.isValid()) { + moveToOffset(savedBeforeBulkCaretMarker.getStartOffset()); + } + releaseBulkCaretMarker(); + } + }; + ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, bulkUpdateListener); + } + + private void releaseBulkCaretMarker() { + if (savedBeforeBulkCaretMarker != null) { + savedBeforeBulkCaretMarker.dispose(); + savedBeforeBulkCaretMarker = null; + } + } + + public void moveToVisualPosition(VisualPosition pos) { + assertIsDispatchThread(); + validateCallContext(); + int column = pos.column; + int line = pos.line; + + if (column < 0) column = 0; + + if (line < 0) line = 0; + + int lastLine = myEditor.getVisibleLineCount() - 1; + if (lastLine <= 0) { + lastLine = 0; + } + + if (line > lastLine) { + line = lastLine; + } + + EditorSettings editorSettings = myEditor.getSettings(); + + if (!editorSettings.isVirtualSpace() && line <= lastLine) { + int lineEndColumn = EditorUtil.getLastVisualLineColumnNumber(myEditor, line); + if (column > lineEndColumn) { + column = lineEndColumn; + } + + if (column < 0 && line > 0) { + line--; + column = EditorUtil.getLastVisualLineColumnNumber(myEditor, line); + } + } + + myVisibleCaret = new VisualPosition(line, column); + + VerticalInfo oldInfo = myCaretInfo; + LogicalPosition oldPosition = myLogicalCaret; + + setCurrentLogicalCaret(myEditor.visualToLogicalPosition(myVisibleCaret)); + myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); + LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); + + myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); + myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); + + ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); + + myEditor.setLastColumnNumber(myVisibleCaret.column); + myEditor.updateCaretCursor(); + requestRepaint(oldInfo); + + if (oldPosition.column != myLogicalCaret.column || oldPosition.line != myLogicalCaret.line) { + CaretEvent event = new CaretEvent(myEditor, oldPosition, myLogicalCaret); + + for (CaretListener listener : myCaretListeners) { + listener.caretPositionChanged(event); + } + } + } + + private void assertIsDispatchThread() { + myEditor.assertIsDispatchThread(); + } + + public void moveToOffset(int offset) { + moveToOffset(offset, false); + } + + public void moveToOffset(int offset, boolean locateBeforeSoftWrap) { + assertIsDispatchThread(); + validateCallContext(); + moveToLogicalPosition(myEditor.offsetToLogicalPosition(offset), locateBeforeSoftWrap); + if (!myEditor.offsetToLogicalPosition(myOffset).equals(myEditor.offsetToLogicalPosition(offset))) { + LOG.error("caret moved to wrong offset. Requested:" + offset + " but actual:" + myOffset); + } + } + + public void moveCaretRelatively(int columnShift, + int lineShift, + boolean withSelection, + boolean blockSelection, + boolean scrollToCaret) + { + assertIsDispatchThread(); + SelectionModel selectionModel = myEditor.getSelectionModel(); + int selectionStart = selectionModel.getLeadSelectionOffset(); + LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() + ? selectionModel.getBlockStart() + : getLogicalPosition(); + EditorSettings editorSettings = myEditor.getSettings(); + VisualPosition visualCaret = getVisualPosition(); + + int newColumnNumber = visualCaret.column + columnShift; + int newLineNumber = visualCaret.line + lineShift; + + Document document = myEditor.getDocument(); + if (!editorSettings.isVirtualSpace() && columnShift == 0) { + newColumnNumber = myEditor.getLastColumnNumber(); + } + else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) { + int lastLine = document.getLineCount() - 1; + if (lastLine < 0) lastLine = 0; + if (EditorModificationUtil.calcAfterLineEnd(myEditor) >= 0 && + newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) { + newColumnNumber = 0; + newLineNumber++; + } + } + else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == -1) { + if (newColumnNumber < 0 && newLineNumber > 0) { + newLineNumber--; + newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber); + } + } + + if (newColumnNumber < 0) newColumnNumber = 0; + + // There is a possible case that caret is located at the first line and use presses 'Shift+Up'. We want to select all text + // from the document start to the current caret position then. So, we have a dedicated flag for tracking that. + boolean selectToDocumentStart = false; + if (newLineNumber < 0) { + selectToDocumentStart = true; + newLineNumber = 0; + } + + VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber); + int lastColumnNumber = newColumnNumber; + if (!editorSettings.isCaretInsideTabs() && !myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { + LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber)); + int offset = myEditor.logicalPositionToOffset(log); + if (offset >= document.getTextLength() && myEditor.getSelectionModel().hasSelection()) { + int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column; + newColumnNumber = Math.max(lastOffsetColumn, newColumnNumber); + } + CharSequence text = document.getCharsSequence(); + if (offset >= 0 && offset < document.getTextLength()) { + if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) { + if (columnShift <= 0) { + newColumnNumber = myEditor.offsetToVisualPosition(offset).column; + } + else { + SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1); + // There is a possible case that tabulation symbol is the last document symbol represented on a visual line before + // soft wrap. We can't just use column from 'offset + 1' because it would point on a next visual line. + if (softWrap == null) { + newColumnNumber = myEditor.offsetToVisualPosition(offset + 1).column; + } + else { + newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber); + } + } + } + } + } + + pos = new VisualPosition(newLineNumber, newColumnNumber); + if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { + LogicalPosition logical = myEditor.visualToLogicalPosition(pos); + int softWrapOffset = myEditor.logicalPositionToOffset(logical); + if (columnShift >= 0) { + moveToOffset(softWrapOffset); + } + else { + int line = myEditor.offsetToVisualPosition(softWrapOffset - 1).line; + moveToVisualPosition(new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line))); + } + } + else { + moveToVisualPosition(pos); + if (!editorSettings.isVirtualSpace() && columnShift == 0) { + myEditor.setLastColumnNumber(lastColumnNumber); + } + } + + if (withSelection) { + if (blockSelection) { + selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition()); + } + else { + if (selectToDocumentStart) { + selectionModel.setSelection(selectionStart, 0); + } + else if (pos.line >= myEditor.getVisibleLineCount()) { + if (selectionStart < document.getTextLength()) { + selectionModel.setSelection(selectionStart, document.getTextLength()); + } + } + else { + selectionModel.setSelection(selectionStart, getOffset()); + } + } + } + else { + selectionModel.removeSelection(); + } + + if (scrollToCaret) { + myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + } + + public void moveToLogicalPosition(LogicalPosition pos) { + moveToLogicalPosition(pos, false); + } + + private void moveToLogicalPosition(LogicalPosition pos, boolean locateBeforeSoftWrap) { + assertIsDispatchThread(); + validateCallContext(); + int column = pos.column; + int line = pos.line; + int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine; + int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine; + int softWrapColumns = pos.softWrapColumnDiff; + + Document doc = myEditor.getDocument(); + + if (column < 0) { + column = 0; + softWrapColumns = 0; + } + if (line < 0) { + line = 0; + softWrapLinesBefore = 0; + softWrapLinesCurrent = 0; + } + + int lineCount = doc.getLineCount(); + if (lineCount == 0) { + line = 0; + } + else if (line > lineCount - 1) { + line = lineCount - 1; + softWrapLinesBefore = 0; + softWrapLinesCurrent = 0; + } + + EditorSettings editorSettings = myEditor.getSettings(); + + if (!editorSettings.isVirtualSpace() && line < lineCount) { + int lineEndOffset = doc.getLineEndOffset(line); + int lineEndColumnNumber = myEditor.offsetToLogicalPosition(lineEndOffset).column; + if (column > lineEndColumnNumber) { + column = lineEndColumnNumber; + if (softWrapColumns != 0) { + softWrapColumns -= column - lineEndColumnNumber; + } + } + } + + ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); + + VerticalInfo oldInfo = myCaretInfo; + LogicalPosition oldCaretPosition = myLogicalCaret; + + LogicalPosition logicalPositionToUse; + if (pos.visualPositionAware) { + logicalPositionToUse = new LogicalPosition( + line, column, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff + ); + } + else { + logicalPositionToUse = new LogicalPosition(line, column); + } + setCurrentLogicalCaret(logicalPositionToUse); + + final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); + + FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(offset); + + if (collapsedAt != null && offset > collapsedAt.getStartOffset()) { + Runnable runnable = new Runnable() { + public void run() { + FoldRegion[] allCollapsedAt = ((FoldingModelImpl)myEditor.getFoldingModel()).fetchCollapsedAt(offset); + for (FoldRegion foldRange : allCollapsedAt) { + foldRange.setExpanded(true); + } + } + }; + + myEditor.getFoldingModel().runBatchFoldingOperation(runnable); + } + + myEditor.setLastColumnNumber(myLogicalCaret.column); + myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret); + + myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); + LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); + + myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); + myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); + + myEditor.updateCaretCursor(); + requestRepaint(oldInfo); + + if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(myEditor)) { + int lineToUse = myVisibleCaret.line - 1; + if (lineToUse >= 0) { + moveToVisualPosition(new VisualPosition(lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse))); + return; + } + } + + if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) { + CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret); + for (CaretListener listener : myCaretListeners) { + listener.caretPositionChanged(event); + } + } + } + + private void requestRepaint(VerticalInfo oldCaretInfo) { + int lineHeight = myEditor.getLineHeight(); + Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); + final EditorGutterComponentEx gutter = myEditor.getGutterComponentEx(); + final EditorComponentImpl content = myEditor.getContentComponent(); + + int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleArea.width; + if (Math.abs(myCaretInfo.y - oldCaretInfo.y) <= 2 * lineHeight) { + int minY = Math.min(oldCaretInfo.y, myCaretInfo.y); + int maxY = Math.max(oldCaretInfo.y + oldCaretInfo.height, myCaretInfo.y + myCaretInfo.height); + content.repaintEditorComponent(0, minY, updateWidth, maxY - minY); + gutter.repaint(0, minY, gutter.getWidth(), maxY - minY); + } + else { + content.repaintEditorComponent(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); + gutter.repaint(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); + content.repaintEditorComponent(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); + gutter.repaint(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); + } + } + + public LogicalPosition getLogicalPosition() { + validateCallContext(); + return myLogicalCaret; + } + + private void validateCallContext() { + LOG.assertTrue(!myIsInUpdate, "Caret model is in its update process. All requests are illegal at this point."); + } + + public VisualPosition getVisualPosition() { + validateCallContext(); + return myVisibleCaret; + } + + public int getOffset() { + validateCallContext(); + return myOffset; + } + + public int getVisualLineStart() { + return myVisualLineStart; + } + + public int getVisualLineEnd() { + return myVisualLineEnd; + } + + public void addCaretListener(CaretListener listener) { + myCaretListeners.add(listener); + } + + public void removeCaretListener(CaretListener listener) { + boolean success = myCaretListeners.remove(listener); + LOG.assertTrue(success); + } + + public TextAttributes getTextAttributes() { + if (myTextAttributes == null) { + myTextAttributes = new TextAttributes(); + myTextAttributes.setBackgroundColor(myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR)); + } + + return myTextAttributes; + } + + public void reinitSettings() { + myTextAttributes = null; + } + + public void documentChanged(DocumentEvent e) { + myIsInUpdate = false; + + DocumentEventImpl event = (DocumentEventImpl)e; + final Document document = myEditor.getDocument(); + boolean performSoftWrapAdjustment = e.getNewLength() > 0 // We want to put caret just after the last added symbol + // There is a possible case that the user removes text just before the soft wrap. We want to keep caret + // on a visual line with soft wrap start then. + || myEditor.getSoftWrapModel().getSoftWrap(e.getOffset()) != null; + + if (event.isWholeTextReplaced()) { + int newLength = document.getTextLength(); + if (myOffset == newLength - e.getNewLength() + e.getOldLength() || newLength == 0) { + moveToOffset(newLength, performSoftWrapAdjustment); + } + else { + final int line = event.translateLineViaDiff(myLogicalCaret.line); + moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column), performSoftWrapAdjustment); + } + } + else { + if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; + int startOffset = e.getOffset(); + int oldEndOffset = startOffset + e.getOldLength(); + + int newOffset = myOffset; + + if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(e)) { + newOffset += e.getNewLength() - e.getOldLength(); + } + else if (myOffset >= startOffset && myOffset <= oldEndOffset) { + newOffset = Math.min(newOffset, startOffset + e.getNewLength()); + } + + newOffset = Math.min(newOffset, document.getTextLength()); + + // if (newOffset != myOffset) { + moveToOffset(newOffset, performSoftWrapAdjustment); + //} + //else { + // moveToVisualPosition(oldPosition); + //} + } + + myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); + myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); + } + + private boolean needToShiftWhiteSpaces(final DocumentEvent e) { + if(!CharArrayUtil.containsOnlyWhiteSpaces(e.getNewFragment()) || CharArrayUtil.containLineBreaks(e.getNewFragment())) + return e.getOldLength() > 0; + if(e.getOffset() == 0) return false; + final char charBefore = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() - 1); + //final char charAfter = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() + e.getNewLength()); + return Character.isWhitespace(charBefore)/* || !Character.isWhitespace(charAfter)*/; + } + + public void beforeDocumentChange(DocumentEvent e) { + myIsInUpdate = true; + } + + public int getPriority() { + return EditorDocumentPriorities.CARET_MODEL; + } + + private void setCurrentLogicalCaret(LogicalPosition position) { + myLogicalCaret = position; + myCaretInfo = createVerticalInfo(position); + } + + private VerticalInfo createVerticalInfo(LogicalPosition position) { + Document document = myEditor.getDocument(); + int logicalLine = position.line; + int startOffset = document.getLineStartOffset(logicalLine); + int endOffset = document.getLineEndOffset(logicalLine); + + // There is a possible case that active logical line is represented on multiple lines due to soft wraps processing. + // We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for the logical line start + // and height in accordance with the number of occupied visual lines. + VisualPosition visualPosition = myEditor.offsetToVisualPosition(document.getLineStartOffset(logicalLine)); + int y = myEditor.visualPositionToXY(visualPosition).y; + int lineHeight = myEditor.getLineHeight(); + int height = lineHeight; + List softWraps = myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset); + for (SoftWrap softWrap : softWraps) { + height += StringUtil.countNewLines(softWrap.getText()) * lineHeight; + } + + return new VerticalInfo(y, height); + } + + @Override + public void dispose() { + releaseBulkCaretMarker(); + } + + /** + * Encapsulates information about target vertical range info - its 'y' coordinate and height in pixels. + */ + public static class VerticalInfo { + public final int y; + public final int height; + + private VerticalInfo(int y, int height) { + this.y = y; + this.height = height; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index a629652402d5..1759c91a9a98 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -1,805 +1,805 @@ -/* - * 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.editor.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; -import com.intellij.openapi.editor.actionSystem.ReadonlyFragmentModificationHandler; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.event.DocumentListener; -import com.intellij.openapi.editor.ex.*; -import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.impl.event.DocumentEventImpl; -import com.intellij.openapi.editor.markup.MarkupModel; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.LocalTimeCounter; -import com.intellij.util.Processor; -import com.intellij.util.containers.ConcurrentHashMap; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.text.CharArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ConcurrentMap; - -public class DocumentImpl extends UserDataHolderBase implements DocumentEx { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.DocumentImpl"); - - private final List myDocumentListeners = new ArrayList(); - private final RangeMarkerTree myRangeMarkers = new RangeMarkerTree(this); - private final List myGuardedBlocks = new ArrayList(); - private ReadonlyFragmentModificationHandler myReadonlyFragmentModificationHandler; - - private final LineSet myLineSet = new LineSet(); - private final CharArray myText = new MyCharArray(); - - private boolean myIsReadOnly = false; - private boolean isStripTrailingSpacesEnabled = true; - private volatile long myModificationStamp; - private final ConcurrentMap myProjectToMarkupModelMap = new ConcurrentHashMap(); - private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this); - - private volatile MarkupModelEx myMarkupModel; - private DocumentListener[] myCachedDocumentListeners; - private final List myReadOnlyListeners = new ArrayList(1); - - private int myCheckGuardedBlocks = 0; - private boolean myGuardsSuppressed = false; - private boolean myEventsHandling = false; - private final boolean myAssertWriteAccess; - private boolean myDoingBulkUpdate = false; - private static final Key> ourSomeEditorSyntaxHighlighter = Key.create("some editor highlighter"); - private boolean myAcceptSlashR = false; - - private DocumentImpl() { - setCyclicBufferSize(0); - setModificationStamp(LocalTimeCounter.currentTime()); - myAssertWriteAccess = true; - } - - public DocumentImpl(String text) { - this((CharSequence)text); - } - - public DocumentImpl(boolean forUseInNonAWTThread) { - setCyclicBufferSize(0); - setModificationStamp(LocalTimeCounter.currentTime()); - myAssertWriteAccess = !forUseInNonAWTThread; - } - - public boolean setAcceptSlashR(boolean accept) { - try { - return myAcceptSlashR; - } - finally { - myAcceptSlashR = accept; - } - } - - public DocumentImpl(CharSequence chars) { - this(); - assertValidSeparators(chars); - myText.setText(this, chars); - DocumentEvent event = new DocumentEventImpl(this, 0, null, null, -1, true); - myLineSet.documentCreated(event); - } - - public char[] getRawChars() { - return myText.getChars(); - } - - @NotNull - public char[] getChars() { - return CharArrayUtil.fromSequence(getCharsSequence()); - } - - @NotNull - public MarkupModel getMarkupModel() { - return getMarkupModel(null); - } - - public void setStripTrailingSpacesEnabled(boolean isEnabled) { - isStripTrailingSpacesEnabled = isEnabled; - } - - public boolean stripTrailingSpaces(boolean inChangedLinesOnly) { - if (!isStripTrailingSpacesEnabled) { - return true; - } - - Editor editor = ContainerUtil.find(EditorFactory.getInstance().getEditors(this), new Condition() { - @Override - public boolean value(Editor editor) { - return ((EditorEx)editor).isCaretActive(); - } - }); - - // when virtual space enabled, we can strip whitespace anywhere - boolean isVirtualSpaceEnabled = editor == null || editor.getSettings().isVirtualSpace(); - - VisualPosition visualCaret = editor == null ? null : editor.getCaretModel().getVisualPosition(); - int caretLine = editor == null ? -1 : editor.getCaretModel().getLogicalPosition().line; - int caretOffset = editor == null ? -1 : editor.getCaretModel().getOffset(); - - boolean markAsNeedsStrippingLater = false; - CharSequence text = myText.getCharArray(); - for (int line = 0; line < myLineSet.getLineCount(); line++) { - if (inChangedLinesOnly && !myLineSet.isModified(line)) continue; - int whiteSpaceStart = -1; - final int lineEnd = myLineSet.getLineEnd(line) - myLineSet.getSeparatorLength(line); - int lineStart = myLineSet.getLineStart(line); - for (int offset = lineEnd - 1; offset >= lineStart; offset--) { - char c = text.charAt(offset); - if (c != ' ' && c != '\t') { - break; - } - whiteSpaceStart = offset; - } - if (whiteSpaceStart == -1) continue; - if (!isVirtualSpaceEnabled && caretLine == line && whiteSpaceStart < caretOffset) { - // mark this as a document that needs stripping later - // otherwise the caret would jump madly - markAsNeedsStrippingLater = true; - } - else { - final int finalStart = whiteSpaceStart; - ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(this, editor == null ? null : editor.getProject()) { - public void run() { - CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { - public void run() { - deleteString(finalStart, lineEnd); - } - }); - } - }); - text = myText.getCharArray(); - } - } - - if (!ShutDownTracker.isShutdownHookRunning() && editor != null) { - editor.getCaretModel().moveToVisualPosition(visualCaret); - } - return !markAsNeedsStrippingLater; - } - - public void setReadOnly(boolean isReadOnly) { - if (myIsReadOnly != isReadOnly) { - myIsReadOnly = isReadOnly; - myPropertyChangeSupport.firePropertyChange(PROP_WRITABLE, !isReadOnly, isReadOnly); - } - } - - public ReadonlyFragmentModificationHandler getReadonlyFragmentModificationHandler() { - return myReadonlyFragmentModificationHandler; - } - - public void setReadonlyFragmentModificationHandler(final ReadonlyFragmentModificationHandler readonlyFragmentModificationHandler) { - myReadonlyFragmentModificationHandler = readonlyFragmentModificationHandler; - } - - public boolean isWritable() { - return !myIsReadOnly; - } - - public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - synchronized(myRangeMarkers) { - return myRangeMarkers.remove(rangeMarker); - } - } - - public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - synchronized(myRangeMarkers) { - RangeMarkerImpl marker = (RangeMarkerImpl)rangeMarker; - marker.setValid(true); - marker.myNode = myRangeMarkers.add(rangeMarker); - myRangeMarkers.checkMax(true); - } - } - - @TestOnly - public int getRangeMarkersSize() { - return myRangeMarkers.size(); - } - - @NotNull - public RangeMarker createGuardedBlock(int startOffset, int endOffset) { - LOG.assertTrue(startOffset <= endOffset, "Should be startOffset <= endOffset"); - RangeMarker block = createRangeMarker(startOffset, endOffset, true); - myGuardedBlocks.add(block); - return block; - } - - public void removeGuardedBlock(@NotNull RangeMarker block) { - myGuardedBlocks.remove(block); - } - - @NotNull - public List getGuardedBlocks() { - return myGuardedBlocks; - } - - @SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage is produced otherwise in AbstractList.iterator() - public RangeMarker getOffsetGuard(int offset) { - for (int i = 0; i < myGuardedBlocks.size(); i++) { - RangeMarker block = myGuardedBlocks.get(i); - if (offsetInRange(offset, block.getStartOffset(), block.getEndOffset())) return block; - } - - return null; - } - - public RangeMarker getRangeGuard(int start, int end) { - for (RangeMarker block : myGuardedBlocks) { - if (rangesIntersect(start, block.getStartOffset(), end, block.getEndOffset(), true, block.isGreedyToLeft(), true, block.isGreedyToRight())) { - return block; - } - } - - return null; - } - - public void startGuardedBlockChecking() { - myCheckGuardedBlocks++; - } - - public void stopGuardedBlockChecking() { - LOG.assertTrue(myCheckGuardedBlocks > 0, "Unpaired start/stopGuardedBlockChecking"); - myCheckGuardedBlocks--; - } - - private static boolean offsetInRange(int offset, int start, int end) { - return start <= offset && offset < end; - } - - private static boolean rangesIntersect(int start0, int start1, int end0, int end1, boolean leftInclusive0, boolean leftInclusive1, boolean rightInclusive0, boolean rightInclusive1) { - if (start0 > start1 || start0 == start1 && !leftInclusive0) { - return rangesIntersect(start1, start0, end1, end0, leftInclusive1, leftInclusive0, rightInclusive1, rightInclusive0); - } - if (end0 < start1) return false; - if (end0 > start1) return true; - - return leftInclusive1 && rightInclusive0; - } - - @NotNull - public RangeMarker createRangeMarker(int startOffset, int endOffset) { - return createRangeMarker(startOffset, endOffset, false); - } - - @NotNull - public RangeMarker createRangeMarker(int startOffset, int endOffset, boolean surviveOnExternalChange) { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - if (!(0 <= startOffset && startOffset <= endOffset && endOffset <= getTextLength())) { - LOG.error("Incorrect offsets startOffset=" + startOffset + ", endOffset=" + endOffset + ", text length=" + getTextLength()); - } - RangeMarkerImpl rangeMarker = surviveOnExternalChange - ? new PersistentRangeMarker(this, startOffset, endOffset) - : new RangeMarkerImpl(this, startOffset, endOffset); - rangeMarker.registerInDocument(); - return rangeMarker; - } - - public long getModificationStamp() { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - return myModificationStamp; - } - - public void setModificationStamp(long modificationStamp) { - myModificationStamp = modificationStamp; - } - - public void replaceText(@NotNull CharSequence chars, long newModificationStamp) { - replaceString(0, getTextLength(), chars, newModificationStamp, true); //TODO: optimization!!! - clearLineModificationFlags(); - } - - public int getListenersCount() { - return myDocumentListeners.size(); - } - - public void insertString(int offset, @NotNull CharSequence s) { - if (offset < 0) throw new IndexOutOfBoundsException("Wrong offset: " + offset); - if (offset > getTextLength()) { - throw new IndexOutOfBoundsException("Wrong offset: " + offset +"; documentLength: "+getTextLength()+ "; " + s.subSequence(Math.max(0, getTextLength() - 20), getTextLength())); - } - assertWriteAccess(); - assertValidSeparators(s); - - if (!isWritable()) throw new ReadOnlyModificationException(this); - if (s.length() == 0) return; - - RangeMarker marker = getRangeGuard(offset, offset); - if (marker != null) { - throwGuardedFragment(marker, offset, null, s.toString()); - } - - myText.insert(this, s, offset); - } - - public void deleteString(int startOffset, int endOffset) { - assertBounds(startOffset, endOffset); - - assertWriteAccess(); - if (!isWritable()) throw new ReadOnlyModificationException(this); - if (startOffset == endOffset) return; - CharSequence sToDelete = myText.substring(startOffset, endOffset); - - RangeMarker marker = getRangeGuard(startOffset, endOffset); - if (marker != null) { - throwGuardedFragment(marker, startOffset, sToDelete.toString(), null); - } - - myText.remove(this, startOffset, endOffset,sToDelete); - } - - public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) { - replaceString(startOffset, endOffset, s, LocalTimeCounter.currentTime(), startOffset==0 && endOffset==getTextLength()); - } - - private void replaceString(int startOffset, int endOffset, CharSequence s, final long newModificationStamp, boolean wholeTextReplaced) { - assertBounds(startOffset, endOffset); - - assertWriteAccess(); - assertValidSeparators(s); - - if (!isWritable()) { - throw new ReadOnlyModificationException(this); - } - final int newStringLength = s.length(); - final CharSequence chars = getCharsSequence(); - int newStartInString = 0; - int newEndInString = newStringLength; - while (newStartInString < newStringLength && - startOffset < endOffset && - s.charAt(newStartInString) == chars.charAt(startOffset)) { - startOffset++; - newStartInString++; - } - - while(endOffset > startOffset && - newEndInString > newStartInString && - s.charAt(newEndInString - 1) == chars.charAt(endOffset - 1)){ - newEndInString--; - endOffset--; - } - //if (newEndInString - newStartInString == 0 && startOffset == endOffset) { - //setModificationStamp(newModificationStamp); - //return; - //} - - s = s.subSequence(newStartInString, newEndInString); - CharSequence sToDelete = myText.substring(startOffset, endOffset); - RangeMarker guard = getRangeGuard(startOffset, endOffset); - if (guard != null) { - throwGuardedFragment(guard, startOffset, sToDelete.toString(), s.toString()); - } - - myText.replace(this, startOffset, endOffset, sToDelete, s,newModificationStamp, wholeTextReplaced); - } - - private void assertBounds(final int startOffset, final int endOffset) { - if (startOffset < 0 || startOffset > getTextLength()) { - throw new IndexOutOfBoundsException("Wrong startOffset: " + startOffset+"; documentLength: "+getTextLength()); - } - if (endOffset < 0 || endOffset > getTextLength()) { - throw new IndexOutOfBoundsException("Wrong endOffset: " + endOffset+"; documentLength: "+getTextLength()); - } - if (endOffset < startOffset) { - throw new IllegalArgumentException("endOffset < startOffset: " + endOffset + " < " + startOffset+"; documentLength: "+getTextLength()); - } - } - - private void assertWriteAccess() { - if (myAssertWriteAccess) { - final Application application = ApplicationManager.getApplication(); - if (application != null) { - application.assertWriteAccessAllowed(); - } - } - } - - private void assertValidSeparators(final CharSequence s) { - if (myAcceptSlashR) return; - StringUtil.assertValidSeparators(s); - } - - private void throwGuardedFragment(RangeMarker guard, int offset, String oldString, String newString) { - if (myCheckGuardedBlocks > 0 && !myGuardsSuppressed) { - DocumentEvent event = new DocumentEventImpl(this, offset, oldString, newString, myModificationStamp, false); - throw new ReadOnlyFragmentModificationException(event, guard); - } - } - - public void suppressGuardedExceptions() { - myGuardsSuppressed = true; - } - - public void unSuppressGuardedExceptions() { - myGuardsSuppressed = false; - } - - public boolean isInEventsHandling() { - return myEventsHandling; - } - - public void clearLineModificationFlags() { - myLineSet.clearModificationFlags(); - } - - private DocumentEvent beforeChangedUpdate(int offset, CharSequence oldString, CharSequence newString, boolean wholeTextReplaced) { - if (ShutDownTracker.isShutdownHookRunning()) { - return null; // suppress events in shutdown hook - } - DocumentEvent event = new DocumentEventImpl(this, offset, oldString, newString, myModificationStamp, wholeTextReplaced); - //System.out.printf("%nbefore change: offset=%d, old text='%n%s%n', new text='%n%s%n', document modification stamp=%d%nDocument:'%s'%n", - // event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getDocument().getModificationStamp(), - // event.getDocument().getText()); - - DocumentListener[] listeners = getCachedListeners(); - for (int i = listeners.length - 1; i >= 0; i--) { - try { - listeners[i].beforeDocumentChange(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - - myEventsHandling = true; - return event; - } - - private void changedUpdate(DocumentEvent event, long newModificationStamp) { - //System.out.printf("after change: new modification stamp=%d%ndocument='%s'%n", event.getDocument().getModificationStamp(), - // event.getDocument().getText()); - if (ShutDownTracker.isShutdownHookRunning()) { - return; // suppress events in shutdown hook - } - try { - if (LOG.isDebugEnabled()) LOG.debug(event.toString()); - - myLineSet.changedUpdate(event); - setModificationStamp(newModificationStamp); - - DocumentListener[] listeners = getCachedListeners(); - for (DocumentListener listener : listeners) { - try { - listener.documentChanged(event); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - finally{ - myEventsHandling = false; - } - } - - public String getText() { - assertReadAccessToDocumentsAllowed(); - return myText.toString(); - } - - @NotNull - @Override - public String getText(@NotNull TextRange range) { - assertReadAccessToDocumentsAllowed(); - return myText.substring(range.getStartOffset(), range.getEndOffset()).toString(); - } - - public int getTextLength() { - assertReadAccessToDocumentsAllowed(); - return myText.length(); - } - - private static void assertReadAccessToDocumentsAllowed() { - /* - final ApplicationEx application = ApplicationManagerEx.getApplicationEx(); - if (application != null) { - application.assertReadAccessToDocumentsAllowed(); - } - */ - } - -/* - This method should be used very carefully - only to read the array, and to be sure, that nobody changes - text, while this array is processed. - Really it is used only to optimize paint in Editor. - [Valentin] 25.04.2001: More really, it is used in 61 places in 29 files across the project :-))) -*/ - - CharSequence getCharsNoThreadCheck() { - return myText.getCharArray(); - } - - @NotNull - public CharSequence getCharsSequence() { - assertReadAccessToDocumentsAllowed(); - return myText.getCharArray(); - } - - - public void addDocumentListener(@NotNull DocumentListener listener) { - myCachedDocumentListeners = null; - LOG.assertTrue(!myDocumentListeners.contains(listener), listener); - myDocumentListeners.add(listener); - } - - public void addDocumentListener(@NotNull final DocumentListener listener, @NotNull Disposable parentDisposable) { - addDocumentListener(listener); - Disposer.register(parentDisposable, new Disposable() { - public void dispose() { - removeDocumentListener(listener); - } - }); - } - - public void removeDocumentListener(@NotNull DocumentListener listener) { - myCachedDocumentListeners = null; - boolean success = myDocumentListeners.remove(listener); - LOG.assertTrue(success); - } - - public int getLineNumber(int offset) { - assertReadAccessToDocumentsAllowed(); - int lineIndex = myLineSet.findLineIndex(offset); - assert lineIndex >= 0; - return lineIndex; - } - - @NotNull - public LineIterator createLineIterator() { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - return myLineSet.createIterator(); - } - - public final int getLineStartOffset(int line) { - assertReadAccessToDocumentsAllowed(); - if (line == 0) return 0; // otherwise it crashed for zero-length document - int lineStart = myLineSet.getLineStart(line); - assert lineStart >= 0; - return lineStart; - } - - public final int getLineEndOffset(int line) { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - if (getTextLength() == 0 && line == 0) return 0; - int result = myLineSet.getLineEnd(line) - getLineSeparatorLength(line); - assert result >= 0; - return result; - } - - public final int getLineSeparatorLength(int line) { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - int separatorLength = myLineSet.getSeparatorLength(line); - assert separatorLength >= 0; - return separatorLength; - } - - public final int getLineCount() { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - int lineCount = myLineSet.getLineCount(); - assert lineCount >= 0; - return lineCount; - } - - private DocumentListener[] getCachedListeners() { - if (myCachedDocumentListeners == null) { - Collections.sort(myDocumentListeners, PrioritizedDocumentListener.COMPARATOR); - myCachedDocumentListeners = myDocumentListeners.toArray(new DocumentListener[myDocumentListeners.size()]); - } - - return myCachedDocumentListeners; - } - - public void fireReadOnlyModificationAttempt() { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - EditReadOnlyListener[] listeners = myReadOnlyListeners.toArray( - new EditReadOnlyListener[myReadOnlyListeners.size()]); - for (EditReadOnlyListener listener : listeners) { - listener.readOnlyModificationAttempt(this); - } - } - - public void addEditReadOnlyListener(@NotNull EditReadOnlyListener listener) { - myReadOnlyListeners.add(listener); - } - - public void removeEditReadOnlyListener(@NotNull EditReadOnlyListener listener) { - myReadOnlyListeners.remove(listener); - } - - public void removeMarkupModel(Project project) { - MarkupModel model = myProjectToMarkupModelMap.remove(project); - if (model != null) { - ((MarkupModelEx)model).dispose(); - } - } - - @NotNull - public MarkupModel getMarkupModel(Project project) { - return getMarkupModel(project, true); - } - - public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { - myPropertyChangeSupport.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { - myPropertyChangeSupport.removePropertyChangeListener(listener); - } - - private final Object lock = new Object(); - public MarkupModel getMarkupModel(@Nullable Project project, boolean create) { - if (project == null) { - MarkupModelEx markupModel = myMarkupModel; - if (create && markupModel == null) { - synchronized (lock) { - markupModel = myMarkupModel; - if (markupModel == null) { - myMarkupModel = markupModel = new MarkupModelImpl(this); - } - } - } - return markupModel; - } - - final DocumentMarkupModelManager documentMarkupModelManager = project.isDisposed() ? null : DocumentMarkupModelManager.getInstance(project); - if (documentMarkupModelManager == null || documentMarkupModelManager.isDisposed()) { - return new EmptyMarkupModel(this); - } - - MarkupModel model = myProjectToMarkupModelMap.get(project); - if (create && model == null) { - synchronized (lock) { - model = myProjectToMarkupModelMap.get(project); - if (model == null) { - model = new MarkupModelImpl(this); - myProjectToMarkupModelMap.put(project, model); - documentMarkupModelManager.registerDocument(this); - } - } - } - - return model; - } - - public void setCyclicBufferSize(int bufferSize) { - myText.setBufferSize(bufferSize); - } - - public void setText(@NotNull final CharSequence text) { - Runnable runnable = new Runnable() { - public void run() { - replaceString(0, getTextLength(), text, LocalTimeCounter.currentTime(), true); - } - }; - if (CommandProcessor.getInstance().isUndoTransparentActionInProgress()) { - runnable.run(); - } - else { - CommandProcessor.getInstance().executeCommand(null, runnable, "", DocCommandGroupId.noneGroupId(this)); - } - - clearLineModificationFlags(); - } - - @NotNull - public RangeMarker createRangeMarker(@NotNull final TextRange textRange) { - return createRangeMarker(textRange.getStartOffset(), textRange.getEndOffset()); - } - - public final boolean isInBulkUpdate() { - return myDoingBulkUpdate; - } - - public final void setInBulkUpdate(boolean value) { - if (value) { - myDoingBulkUpdate = true; - getPublisher().updateStarted(this); - } - else { - myDoingBulkUpdate = false; - getPublisher().updateFinished(this); - normalizeRangeMarkers(); - } - } - - @Nullable - public EditorHighlighter getEditorHighlighterForCachesBuilding() { - final WeakReference editorHighlighterWeakReference = getUserData(ourSomeEditorSyntaxHighlighter); - final EditorHighlighter someEditorHighlighter = editorHighlighterWeakReference != null ? editorHighlighterWeakReference.get():null; - - if (someEditorHighlighter instanceof LexerEditorHighlighter) { - return someEditorHighlighter; - } - return null; - } - - public void rememberEditorHighlighterForCachesOptimization(@NotNull final EditorHighlighter highlighter) { - putUserData(ourSomeEditorSyntaxHighlighter, new WeakReference(highlighter)); - } - - private static class DocumentBulkUpdateListenerHolder { - private static final DocumentBulkUpdateListener ourBulkChangePublisher = - ApplicationManager.getApplication().getMessageBus().syncPublisher(DocumentBulkUpdateListener.TOPIC); - } - - private static DocumentBulkUpdateListener getPublisher() { - return DocumentBulkUpdateListenerHolder.ourBulkChangePublisher; - } - - public boolean processRangeMarkers(@NotNull Processor processor) { - return myRangeMarkers.process(processor); - } - - public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { - return myRangeMarkers.processOverlappingWith(start, end, processor); - } - - public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { - return myRangeMarkers.processOverlappingWith(offset, processor); - } - - public void normalizeRangeMarkers() { - myRangeMarkers.normalize(); - - MarkupModel model = getMarkupModel(null, false); - if (model != null) ((MarkupModelImpl)model).normalize(); - - for (MarkupModel markupModel : myProjectToMarkupModelMap.values()) { - ((MarkupModelImpl)markupModel).normalize(); - } - } - - private static class MyCharArray extends CharArray { - public MyCharArray() { - super(0); - } - - protected DocumentEvent beforeChangedUpdate(DocumentImpl subj, - int offset, - CharSequence oldString, - CharSequence newString, - boolean wholeTextReplaced) { - return subj.beforeChangedUpdate(offset, oldString, newString, wholeTextReplaced); - } - - protected void afterChangedUpdate(DocumentEvent event, long newModificationStamp) { - ((DocumentImpl)event.getDocument()).changedUpdate(event, newModificationStamp); - } - } -} - +/* + * 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.editor.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; +import com.intellij.openapi.editor.actionSystem.ReadonlyFragmentModificationHandler; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.editor.ex.*; +import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.impl.event.DocumentEventImpl; +import com.intellij.openapi.editor.markup.MarkupModel; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.LocalTimeCounter; +import com.intellij.util.Processor; +import com.intellij.util.containers.ConcurrentHashMap; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentMap; + +public class DocumentImpl extends UserDataHolderBase implements DocumentEx { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.DocumentImpl"); + + private final List myDocumentListeners = new ArrayList(); + private final RangeMarkerTree myRangeMarkers = new RangeMarkerTree(this); + private final List myGuardedBlocks = new ArrayList(); + private ReadonlyFragmentModificationHandler myReadonlyFragmentModificationHandler; + + private final LineSet myLineSet = new LineSet(); + private final CharArray myText = new MyCharArray(); + + private boolean myIsReadOnly = false; + private boolean isStripTrailingSpacesEnabled = true; + private volatile long myModificationStamp; + private final ConcurrentMap myProjectToMarkupModelMap = new ConcurrentHashMap(); + private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this); + + private volatile MarkupModelEx myMarkupModel; + private DocumentListener[] myCachedDocumentListeners; + private final List myReadOnlyListeners = new ArrayList(1); + + private int myCheckGuardedBlocks = 0; + private boolean myGuardsSuppressed = false; + private boolean myEventsHandling = false; + private final boolean myAssertWriteAccess; + private boolean myDoingBulkUpdate = false; + private static final Key> ourSomeEditorSyntaxHighlighter = Key.create("some editor highlighter"); + private boolean myAcceptSlashR = false; + + private DocumentImpl() { + setCyclicBufferSize(0); + setModificationStamp(LocalTimeCounter.currentTime()); + myAssertWriteAccess = true; + } + + public DocumentImpl(String text) { + this((CharSequence)text); + } + + public DocumentImpl(boolean forUseInNonAWTThread) { + setCyclicBufferSize(0); + setModificationStamp(LocalTimeCounter.currentTime()); + myAssertWriteAccess = !forUseInNonAWTThread; + } + + public boolean setAcceptSlashR(boolean accept) { + try { + return myAcceptSlashR; + } + finally { + myAcceptSlashR = accept; + } + } + + public DocumentImpl(CharSequence chars) { + this(); + assertValidSeparators(chars); + myText.setText(this, chars); + DocumentEvent event = new DocumentEventImpl(this, 0, null, null, -1, true); + myLineSet.documentCreated(event); + } + + public char[] getRawChars() { + return myText.getChars(); + } + + @NotNull + public char[] getChars() { + return CharArrayUtil.fromSequence(getCharsSequence()); + } + + @NotNull + public MarkupModel getMarkupModel() { + return getMarkupModel(null); + } + + public void setStripTrailingSpacesEnabled(boolean isEnabled) { + isStripTrailingSpacesEnabled = isEnabled; + } + + public boolean stripTrailingSpaces(boolean inChangedLinesOnly) { + if (!isStripTrailingSpacesEnabled) { + return true; + } + + Editor editor = ContainerUtil.find(EditorFactory.getInstance().getEditors(this), new Condition() { + @Override + public boolean value(Editor editor) { + return ((EditorEx)editor).isCaretActive(); + } + }); + + // when virtual space enabled, we can strip whitespace anywhere + boolean isVirtualSpaceEnabled = editor == null || editor.getSettings().isVirtualSpace(); + + VisualPosition visualCaret = editor == null ? null : editor.getCaretModel().getVisualPosition(); + int caretLine = editor == null ? -1 : editor.getCaretModel().getLogicalPosition().line; + int caretOffset = editor == null ? -1 : editor.getCaretModel().getOffset(); + + boolean markAsNeedsStrippingLater = false; + CharSequence text = myText.getCharArray(); + for (int line = 0; line < myLineSet.getLineCount(); line++) { + if (inChangedLinesOnly && !myLineSet.isModified(line)) continue; + int whiteSpaceStart = -1; + final int lineEnd = myLineSet.getLineEnd(line) - myLineSet.getSeparatorLength(line); + int lineStart = myLineSet.getLineStart(line); + for (int offset = lineEnd - 1; offset >= lineStart; offset--) { + char c = text.charAt(offset); + if (c != ' ' && c != '\t') { + break; + } + whiteSpaceStart = offset; + } + if (whiteSpaceStart == -1) continue; + if (!isVirtualSpaceEnabled && caretLine == line && whiteSpaceStart < caretOffset) { + // mark this as a document that needs stripping later + // otherwise the caret would jump madly + markAsNeedsStrippingLater = true; + } + else { + final int finalStart = whiteSpaceStart; + ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(this, editor == null ? null : editor.getProject()) { + public void run() { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + public void run() { + deleteString(finalStart, lineEnd); + } + }); + } + }); + text = myText.getCharArray(); + } + } + + if (!ShutDownTracker.isShutdownHookRunning() && editor != null) { + editor.getCaretModel().moveToVisualPosition(visualCaret); + } + return !markAsNeedsStrippingLater; + } + + public void setReadOnly(boolean isReadOnly) { + if (myIsReadOnly != isReadOnly) { + myIsReadOnly = isReadOnly; + myPropertyChangeSupport.firePropertyChange(PROP_WRITABLE, !isReadOnly, isReadOnly); + } + } + + public ReadonlyFragmentModificationHandler getReadonlyFragmentModificationHandler() { + return myReadonlyFragmentModificationHandler; + } + + public void setReadonlyFragmentModificationHandler(final ReadonlyFragmentModificationHandler readonlyFragmentModificationHandler) { + myReadonlyFragmentModificationHandler = readonlyFragmentModificationHandler; + } + + public boolean isWritable() { + return !myIsReadOnly; + } + + public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + synchronized(myRangeMarkers) { + return myRangeMarkers.remove(rangeMarker); + } + } + + public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + synchronized(myRangeMarkers) { + RangeMarkerImpl marker = (RangeMarkerImpl)rangeMarker; + marker.setValid(true); + marker.myNode = myRangeMarkers.add(rangeMarker); + myRangeMarkers.checkMax(true); + } + } + + @TestOnly + public int getRangeMarkersSize() { + return myRangeMarkers.size(); + } + + @NotNull + public RangeMarker createGuardedBlock(int startOffset, int endOffset) { + LOG.assertTrue(startOffset <= endOffset, "Should be startOffset <= endOffset"); + RangeMarker block = createRangeMarker(startOffset, endOffset, true); + myGuardedBlocks.add(block); + return block; + } + + public void removeGuardedBlock(@NotNull RangeMarker block) { + myGuardedBlocks.remove(block); + } + + @NotNull + public List getGuardedBlocks() { + return myGuardedBlocks; + } + + @SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage is produced otherwise in AbstractList.iterator() + public RangeMarker getOffsetGuard(int offset) { + for (int i = 0; i < myGuardedBlocks.size(); i++) { + RangeMarker block = myGuardedBlocks.get(i); + if (offsetInRange(offset, block.getStartOffset(), block.getEndOffset())) return block; + } + + return null; + } + + public RangeMarker getRangeGuard(int start, int end) { + for (RangeMarker block : myGuardedBlocks) { + if (rangesIntersect(start, block.getStartOffset(), end, block.getEndOffset(), true, block.isGreedyToLeft(), true, block.isGreedyToRight())) { + return block; + } + } + + return null; + } + + public void startGuardedBlockChecking() { + myCheckGuardedBlocks++; + } + + public void stopGuardedBlockChecking() { + LOG.assertTrue(myCheckGuardedBlocks > 0, "Unpaired start/stopGuardedBlockChecking"); + myCheckGuardedBlocks--; + } + + private static boolean offsetInRange(int offset, int start, int end) { + return start <= offset && offset < end; + } + + private static boolean rangesIntersect(int start0, int start1, int end0, int end1, boolean leftInclusive0, boolean leftInclusive1, boolean rightInclusive0, boolean rightInclusive1) { + if (start0 > start1 || start0 == start1 && !leftInclusive0) { + return rangesIntersect(start1, start0, end1, end0, leftInclusive1, leftInclusive0, rightInclusive1, rightInclusive0); + } + if (end0 < start1) return false; + if (end0 > start1) return true; + + return leftInclusive1 && rightInclusive0; + } + + @NotNull + public RangeMarker createRangeMarker(int startOffset, int endOffset) { + return createRangeMarker(startOffset, endOffset, false); + } + + @NotNull + public RangeMarker createRangeMarker(int startOffset, int endOffset, boolean surviveOnExternalChange) { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + if (!(0 <= startOffset && startOffset <= endOffset && endOffset <= getTextLength())) { + LOG.error("Incorrect offsets startOffset=" + startOffset + ", endOffset=" + endOffset + ", text length=" + getTextLength()); + } + RangeMarkerImpl rangeMarker = surviveOnExternalChange + ? new PersistentRangeMarker(this, startOffset, endOffset) + : new RangeMarkerImpl(this, startOffset, endOffset); + rangeMarker.registerInDocument(); + return rangeMarker; + } + + public long getModificationStamp() { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + return myModificationStamp; + } + + public void setModificationStamp(long modificationStamp) { + myModificationStamp = modificationStamp; + } + + public void replaceText(@NotNull CharSequence chars, long newModificationStamp) { + replaceString(0, getTextLength(), chars, newModificationStamp, true); //TODO: optimization!!! + clearLineModificationFlags(); + } + + public int getListenersCount() { + return myDocumentListeners.size(); + } + + public void insertString(int offset, @NotNull CharSequence s) { + if (offset < 0) throw new IndexOutOfBoundsException("Wrong offset: " + offset); + if (offset > getTextLength()) { + throw new IndexOutOfBoundsException("Wrong offset: " + offset +"; documentLength: "+getTextLength()+ "; " + s.subSequence(Math.max(0, getTextLength() - 20), getTextLength())); + } + assertWriteAccess(); + assertValidSeparators(s); + + if (!isWritable()) throw new ReadOnlyModificationException(this); + if (s.length() == 0) return; + + RangeMarker marker = getRangeGuard(offset, offset); + if (marker != null) { + throwGuardedFragment(marker, offset, null, s.toString()); + } + + myText.insert(this, s, offset); + } + + public void deleteString(int startOffset, int endOffset) { + assertBounds(startOffset, endOffset); + + assertWriteAccess(); + if (!isWritable()) throw new ReadOnlyModificationException(this); + if (startOffset == endOffset) return; + CharSequence sToDelete = myText.substring(startOffset, endOffset); + + RangeMarker marker = getRangeGuard(startOffset, endOffset); + if (marker != null) { + throwGuardedFragment(marker, startOffset, sToDelete.toString(), null); + } + + myText.remove(this, startOffset, endOffset,sToDelete); + } + + public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) { + replaceString(startOffset, endOffset, s, LocalTimeCounter.currentTime(), startOffset==0 && endOffset==getTextLength()); + } + + private void replaceString(int startOffset, int endOffset, CharSequence s, final long newModificationStamp, boolean wholeTextReplaced) { + assertBounds(startOffset, endOffset); + + assertWriteAccess(); + assertValidSeparators(s); + + if (!isWritable()) { + throw new ReadOnlyModificationException(this); + } + final int newStringLength = s.length(); + final CharSequence chars = getCharsSequence(); + int newStartInString = 0; + int newEndInString = newStringLength; + while (newStartInString < newStringLength && + startOffset < endOffset && + s.charAt(newStartInString) == chars.charAt(startOffset)) { + startOffset++; + newStartInString++; + } + + while(endOffset > startOffset && + newEndInString > newStartInString && + s.charAt(newEndInString - 1) == chars.charAt(endOffset - 1)){ + newEndInString--; + endOffset--; + } + //if (newEndInString - newStartInString == 0 && startOffset == endOffset) { + //setModificationStamp(newModificationStamp); + //return; + //} + + s = s.subSequence(newStartInString, newEndInString); + CharSequence sToDelete = myText.substring(startOffset, endOffset); + RangeMarker guard = getRangeGuard(startOffset, endOffset); + if (guard != null) { + throwGuardedFragment(guard, startOffset, sToDelete.toString(), s.toString()); + } + + myText.replace(this, startOffset, endOffset, sToDelete, s,newModificationStamp, wholeTextReplaced); + } + + private void assertBounds(final int startOffset, final int endOffset) { + if (startOffset < 0 || startOffset > getTextLength()) { + throw new IndexOutOfBoundsException("Wrong startOffset: " + startOffset+"; documentLength: "+getTextLength()); + } + if (endOffset < 0 || endOffset > getTextLength()) { + throw new IndexOutOfBoundsException("Wrong endOffset: " + endOffset+"; documentLength: "+getTextLength()); + } + if (endOffset < startOffset) { + throw new IllegalArgumentException("endOffset < startOffset: " + endOffset + " < " + startOffset+"; documentLength: "+getTextLength()); + } + } + + private void assertWriteAccess() { + if (myAssertWriteAccess) { + final Application application = ApplicationManager.getApplication(); + if (application != null) { + application.assertWriteAccessAllowed(); + } + } + } + + private void assertValidSeparators(final CharSequence s) { + if (myAcceptSlashR) return; + StringUtil.assertValidSeparators(s); + } + + private void throwGuardedFragment(RangeMarker guard, int offset, String oldString, String newString) { + if (myCheckGuardedBlocks > 0 && !myGuardsSuppressed) { + DocumentEvent event = new DocumentEventImpl(this, offset, oldString, newString, myModificationStamp, false); + throw new ReadOnlyFragmentModificationException(event, guard); + } + } + + public void suppressGuardedExceptions() { + myGuardsSuppressed = true; + } + + public void unSuppressGuardedExceptions() { + myGuardsSuppressed = false; + } + + public boolean isInEventsHandling() { + return myEventsHandling; + } + + public void clearLineModificationFlags() { + myLineSet.clearModificationFlags(); + } + + private DocumentEvent beforeChangedUpdate(int offset, CharSequence oldString, CharSequence newString, boolean wholeTextReplaced) { + if (ShutDownTracker.isShutdownHookRunning()) { + return null; // suppress events in shutdown hook + } + DocumentEvent event = new DocumentEventImpl(this, offset, oldString, newString, myModificationStamp, wholeTextReplaced); + //System.out.printf("%nbefore change: offset=%d, old text='%n%s%n', new text='%n%s%n', document modification stamp=%d%nDocument:'%s'%n", + // event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getDocument().getModificationStamp(), + // event.getDocument().getText()); + + DocumentListener[] listeners = getCachedListeners(); + for (int i = listeners.length - 1; i >= 0; i--) { + try { + listeners[i].beforeDocumentChange(event); + } + catch (Throwable e) { + LOG.error(e); + } + } + + myEventsHandling = true; + return event; + } + + private void changedUpdate(DocumentEvent event, long newModificationStamp) { + //System.out.printf("after change: new modification stamp=%d%ndocument='%s'%n", event.getDocument().getModificationStamp(), + // event.getDocument().getText()); + if (ShutDownTracker.isShutdownHookRunning()) { + return; // suppress events in shutdown hook + } + try { + if (LOG.isDebugEnabled()) LOG.debug(event.toString()); + + myLineSet.changedUpdate(event); + setModificationStamp(newModificationStamp); + + DocumentListener[] listeners = getCachedListeners(); + for (DocumentListener listener : listeners) { + try { + listener.documentChanged(event); + } + catch (Throwable e) { + LOG.error(e); + } + } + } + finally{ + myEventsHandling = false; + } + } + + public String getText() { + assertReadAccessToDocumentsAllowed(); + return myText.toString(); + } + + @NotNull + @Override + public String getText(@NotNull TextRange range) { + assertReadAccessToDocumentsAllowed(); + return myText.substring(range.getStartOffset(), range.getEndOffset()).toString(); + } + + public int getTextLength() { + assertReadAccessToDocumentsAllowed(); + return myText.length(); + } + + private static void assertReadAccessToDocumentsAllowed() { + /* + final ApplicationEx application = ApplicationManagerEx.getApplicationEx(); + if (application != null) { + application.assertReadAccessToDocumentsAllowed(); + } + */ + } + +/* + This method should be used very carefully - only to read the array, and to be sure, that nobody changes + text, while this array is processed. + Really it is used only to optimize paint in Editor. + [Valentin] 25.04.2001: More really, it is used in 61 places in 29 files across the project :-))) +*/ + + CharSequence getCharsNoThreadCheck() { + return myText.getCharArray(); + } + + @NotNull + public CharSequence getCharsSequence() { + assertReadAccessToDocumentsAllowed(); + return myText.getCharArray(); + } + + + public void addDocumentListener(@NotNull DocumentListener listener) { + myCachedDocumentListeners = null; + LOG.assertTrue(!myDocumentListeners.contains(listener), listener); + myDocumentListeners.add(listener); + } + + public void addDocumentListener(@NotNull final DocumentListener listener, @NotNull Disposable parentDisposable) { + addDocumentListener(listener); + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + removeDocumentListener(listener); + } + }); + } + + public void removeDocumentListener(@NotNull DocumentListener listener) { + myCachedDocumentListeners = null; + boolean success = myDocumentListeners.remove(listener); + LOG.assertTrue(success); + } + + public int getLineNumber(int offset) { + assertReadAccessToDocumentsAllowed(); + int lineIndex = myLineSet.findLineIndex(offset); + assert lineIndex >= 0; + return lineIndex; + } + + @NotNull + public LineIterator createLineIterator() { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + return myLineSet.createIterator(); + } + + public final int getLineStartOffset(int line) { + assertReadAccessToDocumentsAllowed(); + if (line == 0) return 0; // otherwise it crashed for zero-length document + int lineStart = myLineSet.getLineStart(line); + assert lineStart >= 0; + return lineStart; + } + + public final int getLineEndOffset(int line) { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + if (getTextLength() == 0 && line == 0) return 0; + int result = myLineSet.getLineEnd(line) - getLineSeparatorLength(line); + assert result >= 0; + return result; + } + + public final int getLineSeparatorLength(int line) { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + int separatorLength = myLineSet.getSeparatorLength(line); + assert separatorLength >= 0; + return separatorLength; + } + + public final int getLineCount() { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + int lineCount = myLineSet.getLineCount(); + assert lineCount >= 0; + return lineCount; + } + + private DocumentListener[] getCachedListeners() { + if (myCachedDocumentListeners == null) { + Collections.sort(myDocumentListeners, PrioritizedDocumentListener.COMPARATOR); + myCachedDocumentListeners = myDocumentListeners.toArray(new DocumentListener[myDocumentListeners.size()]); + } + + return myCachedDocumentListeners; + } + + public void fireReadOnlyModificationAttempt() { + ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); + EditReadOnlyListener[] listeners = myReadOnlyListeners.toArray( + new EditReadOnlyListener[myReadOnlyListeners.size()]); + for (EditReadOnlyListener listener : listeners) { + listener.readOnlyModificationAttempt(this); + } + } + + public void addEditReadOnlyListener(@NotNull EditReadOnlyListener listener) { + myReadOnlyListeners.add(listener); + } + + public void removeEditReadOnlyListener(@NotNull EditReadOnlyListener listener) { + myReadOnlyListeners.remove(listener); + } + + public void removeMarkupModel(Project project) { + MarkupModel model = myProjectToMarkupModelMap.remove(project); + if (model != null) { + ((MarkupModelEx)model).dispose(); + } + } + + @NotNull + public MarkupModel getMarkupModel(Project project) { + return getMarkupModel(project, true); + } + + public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { + myPropertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { + myPropertyChangeSupport.removePropertyChangeListener(listener); + } + + private final Object lock = new Object(); + public MarkupModel getMarkupModel(@Nullable Project project, boolean create) { + if (project == null) { + MarkupModelEx markupModel = myMarkupModel; + if (create && markupModel == null) { + synchronized (lock) { + markupModel = myMarkupModel; + if (markupModel == null) { + myMarkupModel = markupModel = new MarkupModelImpl(this); + } + } + } + return markupModel; + } + + final DocumentMarkupModelManager documentMarkupModelManager = project.isDisposed() ? null : DocumentMarkupModelManager.getInstance(project); + if (documentMarkupModelManager == null || documentMarkupModelManager.isDisposed()) { + return new EmptyMarkupModel(this); + } + + MarkupModel model = myProjectToMarkupModelMap.get(project); + if (create && model == null) { + synchronized (lock) { + model = myProjectToMarkupModelMap.get(project); + if (model == null) { + model = new MarkupModelImpl(this); + myProjectToMarkupModelMap.put(project, model); + documentMarkupModelManager.registerDocument(this); + } + } + } + + return model; + } + + public void setCyclicBufferSize(int bufferSize) { + myText.setBufferSize(bufferSize); + } + + public void setText(@NotNull final CharSequence text) { + Runnable runnable = new Runnable() { + public void run() { + replaceString(0, getTextLength(), text, LocalTimeCounter.currentTime(), true); + } + }; + if (CommandProcessor.getInstance().isUndoTransparentActionInProgress()) { + runnable.run(); + } + else { + CommandProcessor.getInstance().executeCommand(null, runnable, "", DocCommandGroupId.noneGroupId(this)); + } + + clearLineModificationFlags(); + } + + @NotNull + public RangeMarker createRangeMarker(@NotNull final TextRange textRange) { + return createRangeMarker(textRange.getStartOffset(), textRange.getEndOffset()); + } + + public final boolean isInBulkUpdate() { + return myDoingBulkUpdate; + } + + public final void setInBulkUpdate(boolean value) { + if (value) { + myDoingBulkUpdate = true; + getPublisher().updateStarted(this); + } + else { + myDoingBulkUpdate = false; + getPublisher().updateFinished(this); + normalizeRangeMarkers(); + } + } + + @Nullable + public EditorHighlighter getEditorHighlighterForCachesBuilding() { + final WeakReference editorHighlighterWeakReference = getUserData(ourSomeEditorSyntaxHighlighter); + final EditorHighlighter someEditorHighlighter = editorHighlighterWeakReference != null ? editorHighlighterWeakReference.get():null; + + if (someEditorHighlighter instanceof LexerEditorHighlighter) { + return someEditorHighlighter; + } + return null; + } + + public void rememberEditorHighlighterForCachesOptimization(@NotNull final EditorHighlighter highlighter) { + putUserData(ourSomeEditorSyntaxHighlighter, new WeakReference(highlighter)); + } + + private static class DocumentBulkUpdateListenerHolder { + private static final DocumentBulkUpdateListener ourBulkChangePublisher = + ApplicationManager.getApplication().getMessageBus().syncPublisher(DocumentBulkUpdateListener.TOPIC); + } + + private static DocumentBulkUpdateListener getPublisher() { + return DocumentBulkUpdateListenerHolder.ourBulkChangePublisher; + } + + public boolean processRangeMarkers(@NotNull Processor processor) { + return myRangeMarkers.process(processor); + } + + public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { + return myRangeMarkers.processOverlappingWith(start, end, processor); + } + + public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { + return myRangeMarkers.processOverlappingWith(offset, processor); + } + + public void normalizeRangeMarkers() { + myRangeMarkers.normalize(); + + MarkupModel model = getMarkupModel(null, false); + if (model != null) ((MarkupModelImpl)model).normalize(); + + for (MarkupModel markupModel : myProjectToMarkupModelMap.values()) { + ((MarkupModelImpl)markupModel).normalize(); + } + } + + private static class MyCharArray extends CharArray { + public MyCharArray() { + super(0); + } + + protected DocumentEvent beforeChangedUpdate(DocumentImpl subj, + int offset, + CharSequence oldString, + CharSequence newString, + boolean wholeTextReplaced) { + return subj.beforeChangedUpdate(offset, oldString, newString, wholeTextReplaced); + } + + protected void afterChangedUpdate(DocumentEvent event, long newModificationStamp) { + ((DocumentImpl)event.getDocument()).changedUpdate(event, newModificationStamp); + } + } +} + 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 84614b3deab8..33a40608216c 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 @@ -1,5264 +1,5264 @@ -/* - * 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.editor.impl; - -import com.intellij.Patches; -import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer; -import com.intellij.codeInsight.hint.EditorFragmentComponent; -import com.intellij.codeInsight.hint.TooltipController; -import com.intellij.codeInsight.hint.TooltipGroup; -import com.intellij.concurrency.JobScheduler; -import com.intellij.ide.*; -import com.intellij.ide.dnd.DnDManager; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.UndoConfirmationPolicy; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; -import com.intellij.openapi.editor.actionSystem.EditorAction; -import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.editor.actionSystem.EditorActionManager; -import com.intellij.openapi.editor.colors.*; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.editor.ex.*; -import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.highlighter.HighlighterClient; -import com.intellij.openapi.editor.impl.event.MarkupModelListener; -import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces; -import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType; -import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper; -import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Queryable; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.GuiUtils; -import com.intellij.ui.LightweightHint; -import com.intellij.ui.components.JBScrollBar; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.util.Alarm; -import com.intellij.util.IJSwingUtilities; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.text.CharArrayUtil; -import com.intellij.util.ui.ButtonlessScrollBarUI; -import com.intellij.util.ui.EmptyClipboardOwner; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.update.UiNotifyConnector; -import gnu.trove.TIntArrayList; -import gnu.trove.TIntHashSet; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import javax.swing.*; -import javax.swing.Timer; -import javax.swing.border.Border; -import javax.swing.border.EmptyBorder; -import javax.swing.plaf.ScrollBarUI; -import javax.swing.plaf.basic.BasicScrollBarUI; -import java.awt.*; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.dnd.DropTarget; -import java.awt.dnd.DropTargetAdapter; -import java.awt.dnd.DropTargetDragEvent; -import java.awt.dnd.DropTargetDropEvent; -import java.awt.event.*; -import java.awt.font.TextHitInfo; -import java.awt.im.InputMethodRequests; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.text.AttributedCharacterIterator; -import java.text.AttributedString; -import java.text.CharacterIterator; -import java.util.*; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -public final class EditorImpl extends UserDataHolderBase implements EditorEx, HighlighterClient, Queryable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl"); - private static final Key DND_COMMAND_KEY = Key.create("DndCommand"); - public static final Key DO_DOCUMENT_UPDATE_TEST = Key.create("DoDocumentUpdateTest"); - private final DocumentImpl myDocument; - - private final JPanel myPanel; - private final JScrollPane myScrollPane; - private final EditorComponentImpl myEditorComponent; - private final EditorGutterComponentImpl myGutterComponent; - - static { - @SuppressWarnings({"UnusedDeclaration"}) - ComplementaryFontsRegistry registry; // load costly font info - } - - private final CommandProcessor myCommandProcessor; - private final MyScrollBar myVerticalScrollBar; - - private final CopyOnWriteArrayList myMouseListeners = ContainerUtil.createEmptyCOWList(); - private final CopyOnWriteArrayList myMouseMotionListeners; - - private int myCharHeight = -1; - private int myLineHeight = -1; - private int myDescent = -1; - - private boolean myIsInsertMode = true; - - private final CaretCursor myCaretCursor; - private final ScrollingTimer myScrollingTimer = new ScrollingTimer(); - - private final Key MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup"); - - private final DocumentListener myEditorDocumentAdapter; - - private final SettingsImpl mySettings; - - private boolean isReleased = false; - - private MouseEvent myMousePressedEvent = null; - - private int mySavedSelectionStart = -1; - private int mySavedSelectionEnd = -1; - private int myLastColumnNumber = 0; - - private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this); - private MyEditable myEditable; - - private EditorColorsScheme myScheme; - private ArrowPainter myTabPainter; - private final boolean myIsViewer; - private final SelectionModelImpl mySelectionModel; - private final EditorMarkupModelImpl myMarkupModel; - private final FoldingModelImpl myFoldingModel; - private final ScrollingModelImpl myScrollingModel; - private final CaretModelImpl myCaretModel; - private final SoftWrapModelImpl mySoftWrapModel; - - private static final RepaintCursorCommand ourCaretBlinkingCommand; - -// private final BorderEffect myBorderEffect = new BorderEffect(); - - private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE; - private FoldRegion myMouseSelectedRegion = null; - - private static final int MOUSE_SELECTION_STATE_NONE = 0; - private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1; - private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2; - - private final MarkupModelListener myMarkupModelListener; - - private EditorHighlighter myHighlighter; - private final TextDrawingCallback myTextDrawingCallback = new MyTextDrawingCallback(); - - private int myScrollBarOrientation; - private boolean myMousePressedInsideSelection; - private FontMetrics myPlainFontMetrics; - private FontMetrics myBoldFontMetrics; - private FontMetrics myItalicFontMetrics; - private FontMetrics myBoldItalicFontMetrics; - - private static final int CACHED_CHARS_BUFFER_SIZE = 300; - - private final ArrayList myFontCache = new ArrayList(); - private FontInfo myCurrentFontType = null; - - private final EditorSizeContainer mySizeContainer = new EditorSizeContainer(); - - private boolean myUpdateCursor; - private int myCaretUpdateVShift; - private final Project myProject; - private long myMouseSelectionChangeTimestamp; - private int mySavedCaretOffsetForDNDUndoHack; - private final ArrayList myFocusListeners = new ArrayList(); - - private MyInputMethodHandler myInputMethodRequestsHandler; - private InputMethodRequests myInputMethodRequestsSwingWrapper; - private boolean myIsOneLineMode; - private boolean myIsRendererMode; - private VirtualFile myVirtualFile; - private boolean myIsColumnMode = false; - private Color myForcedBackground = null; - private Dimension myPreferredSize; - private Runnable myGutterSizeUpdater = null; - private boolean myGutterNeedsUpdate = false; - private Alarm myAppleRepaintAlarm; - private boolean myEmbeddedIntoDialogWrapper; - private CachedFontContent myLastCache; - private boolean mySpacesHaveSameWidth; - - /** - * There is a possible case that specific font is used for particular text drawing operation (e.g. for 'before' and 'after' - * soft wraps drawings). Hence, even if {@link #mySpacesHaveSameWidth} is true, space size for that specific - * font may be different. So, we define additional flag that should indicate that {@link #myLastCache} should be reset. - */ - private boolean myForceRefreshFont; - private boolean mySoftWrapsChanged; - - private Point myLastBackgroundPosition = null; - private Color myLastBackgroundColor = null; - private int myLastBackgroundWidth; - private static final boolean ourIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); - private final JPanel myHeaderPanel; - - private MouseEvent myInitialMouseEvent; - private boolean myIgnoreMouseEventsConsecutiveToInitial; - - private String myReleasedAt = null; - - private EditorDropHandler myDropHandler; - - private char[] myPrefixText; - private TextAttributes myPrefixAttributes; - private final IndentsModel myIndentsModel; - - static { - ourCaretBlinkingCommand = new RepaintCursorCommand(); - ourCaretBlinkingCommand.start(); - } - - EditorImpl(@NotNull Document document, boolean viewer, Project project) { - myProject = project; - myDocument = (DocumentImpl)document; - myScheme = createBoundColorSchemeDelegate(null); - initTabPainter(); - myIsViewer = viewer; - mySettings = new SettingsImpl(this); - - mySelectionModel = new SelectionModelImpl(this); - myMarkupModel = new EditorMarkupModelImpl(this); - myFoldingModel = new FoldingModelImpl(this); - myCaretModel = new CaretModelImpl(this); - mySoftWrapModel = new SoftWrapModelImpl(this); - mySizeContainer.reset(); - - myCommandProcessor = CommandProcessor.getInstance(); - - myEditorDocumentAdapter = new EditorDocumentAdapter(); - myMouseMotionListeners = ContainerUtil.createEmptyCOWList(); - - myMarkupModelListener = new MarkupModelListener() { - public void afterAdded(@NotNull RangeHighlighterEx highlighter) { - attributesChanged(highlighter); - } - - public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) { - attributesChanged(highlighter); - } - - public void attributesChanged(@NotNull RangeHighlighterEx highlighter) { - int start = highlighter.getAffectedAreaStartOffset(); - int end = highlighter.getAffectedAreaEndOffset(); - int startLine = myDocument.getLineNumber(start); - int endLine = myDocument.getLineNumber(end); - repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount())); - ((EditorMarkupModelImpl)getMarkupModel()).markDirtied(); - ((EditorMarkupModelImpl)getMarkupModel()).repaint(start, end); - GutterIconRenderer renderer = highlighter.getGutterIconRenderer(); - if (renderer != null) { - updateGutterSize(); - } - updateCaretCursor(); - } - }; - - ((MarkupModelEx)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener); - ((MarkupModelEx)getMarkupModel()).addMarkupModelListener(myMarkupModelListener); - - myDocument.addDocumentListener(myFoldingModel); - myDocument.addDocumentListener(myCaretModel); - myDocument.addDocumentListener(mySelectionModel); - myDocument.addDocumentListener(myEditorDocumentAdapter); - myDocument.addDocumentListener(mySoftWrapModel); - - myFoldingModel.addListener(mySoftWrapModel); - - myIndentsModel = new IndentsModelImpl(this); - myCaretModel.addCaretListener(new CaretListener() { - private LightweightHint myCurrentHint = null; - private IndentGuideDescriptor myCurrentCaretGuide = null; - - public void caretPositionChanged(CaretEvent e) { - final IndentGuideDescriptor newGuide = myIndentsModel.getCaretIndentGuide(); - if (!Comparing.equal(myCurrentCaretGuide, newGuide)) { - repaintGuide(newGuide); - repaintGuide(myCurrentCaretGuide); - myCurrentCaretGuide = newGuide; - - if (myCurrentHint != null) { - myCurrentHint.hide(); - myCurrentHint = null; - } - - if (newGuide != null) { - final Rectangle visibleArea = getScrollingModel().getVisibleArea(); - final int line = newGuide.startLine; - if (logicalLineToY(line) < visibleArea.y) { - TextRange textRange = new TextRange(myDocument.getLineStartOffset(line), myDocument.getLineEndOffset(line)); - - myCurrentHint = EditorFragmentComponent.showEditorFragmentHint(EditorImpl.this, textRange, false, false); - } - } - } - } - }); - - myCaretCursor = new CaretCursor(); - - myFoldingModel.flushCaretShift(); - myScrollBarOrientation = VERTICAL_SCROLLBAR_RIGHT; - - mySoftWrapModel.addSoftWrapChangeListener(new SoftWrapChangeListener() { - @Override - public void softWrapAdded(@NotNull SoftWrap softWrap) { - mySoftWrapsChanged = true; - int softWrapLine = myDocument.getLineNumber(softWrap.getStart()); - mySizeContainer.update(softWrapLine, softWrapLine, softWrapLine); - } - - @Override - public void softWrapsRemoved() { - mySoftWrapsChanged = true; - mySizeContainer.reset(); - } - }); - - EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT)); - setHighlighter(highlighter); - - myEditorComponent = new EditorComponentImpl(this); - myScrollPane = new MyScrollPane(); - myPanel = new JPanel(); - - myHeaderPanel = new MyHeaderPanel(); - myVerticalScrollBar = new MyScrollBar(Adjustable.VERTICAL); - myGutterComponent = new EditorGutterComponentImpl(this); - initComponent(); - - myScrollingModel = new ScrollingModelImpl(this); - - myGutterComponent.updateSize(); - Dimension preferredSize = getPreferredSize(); - myEditorComponent.setSize(preferredSize); - - if (Patches.APPLE_BUG_ID_3716835) { - myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() { - public void visibleAreaChanged(VisibleAreaEvent e) { - if (myAppleRepaintAlarm == null) { - myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - } - myAppleRepaintAlarm.cancelAllRequests(); - myAppleRepaintAlarm.addRequest(new Runnable() { - public void run() { - repaint(0, myDocument.getTextLength()); - } - }, 50, ModalityState.stateForComponent(myEditorComponent)); - } - }); - } - - updateCaretCursor(); - - // This hacks context layout problem where editor appears scrolled to the right just after it is created. - if (!ourIsUnitTestMode) { - UiNotifyConnector.doWhenFirstShown(myEditorComponent, new Runnable() { - public void run() { - if (!isDisposed() && !myScrollingModel.isScrollingNow()) { - myScrollingModel.disableAnimation(); - myScrollingModel.scrollHorizontally(0); - myScrollingModel.enableAnimation(); - } - } - }); - } - } - - public EditorColorsScheme createBoundColorSchemeDelegate(@Nullable final EditorColorsScheme customGlobalScheme) { - return new MyColorSchemeDelegate(customGlobalScheme); - } - - private void repaintGuide(IndentGuideDescriptor guide) { - if (guide != null) { - repaintLines(guide.startLine, guide.endLine); - } - } - - public void setPrefixTextAndAttributes(String prefixText, TextAttributes attributes) { - myPrefixText = prefixText == null? null: prefixText.toCharArray(); - myPrefixAttributes = attributes; - } - - public boolean isViewer() { - return myIsViewer || myIsRendererMode; - } - - public boolean isRendererMode() { - return myIsRendererMode; - } - - public void setRendererMode(boolean isRendererMode) { - myIsRendererMode = isRendererMode; - } - - public void setFile(VirtualFile vFile) { - myVirtualFile = vFile; - reinitSettings(); - } - - public VirtualFile getVirtualFile() { - return myVirtualFile; - } - - @Override - public void setSoftWrapAppliancePlace(@NotNull SoftWrapAppliancePlaces place) { - getSoftWrapModel().setPlace(place); - mySettings.setSoftWrapAppliancePlace(place); - } - - @NotNull - public SelectionModel getSelectionModel() { - return mySelectionModel; - } - - @NotNull - public MarkupModel getMarkupModel() { - return myMarkupModel; - } - - @NotNull - public FoldingModelEx getFoldingModel() { - return myFoldingModel; - } - - @NotNull - public CaretModelImpl getCaretModel() { - return myCaretModel; - } - - @NotNull - public ScrollingModel getScrollingModel() { - return myScrollingModel; - } - - @NotNull - public SoftWrapModelImpl getSoftWrapModel() { - return mySoftWrapModel; - } - - @NotNull - public EditorSettings getSettings() { - assertReadAccess(); - return mySettings; - } - - public void reinitSettings() { - assertIsDispatchThread(); - myCharHeight = -1; - myLineHeight = -1; - myDescent = -1; - myPlainFontMetrics = null; - - mySoftWrapModel.reinitSettings(); - myCaretModel.reinitSettings(); - mySelectionModel.reinitSettings(); - mySettings.reinitSettings(); - ourCaretBlinkingCommand.setBlinkCaret(mySettings.isBlinkCaret()); - ourCaretBlinkingCommand.setBlinkPeriod(mySettings.getCaretBlinkPeriod()); - mySizeContainer.reset(); - myFoldingModel.refreshSettings(); - myFoldingModel.rebuild(); - - if (myScheme instanceof MyColorSchemeDelegate) { - ((MyColorSchemeDelegate)myScheme).updateGlobalScheme(); - } - myHighlighter.setColorScheme(myScheme); - - myGutterComponent.reinitSettings(); - myGutterComponent.revalidate(); - - myEditorComponent.repaint(); - - initTabPainter(); - updateCaretCursor(); - - if (myInitialMouseEvent != null) { - myIgnoreMouseEventsConsecutiveToInitial = true; - } - - // There is a possible case that 'use soft wrap' setting value is changed and we need to repaint all affected lines then. - repaintToScreenBottom(getCaretModel().getLogicalPosition().line); - int y = getCaretModel().getVisualLineStart() * getLineHeight(); - myGutterComponent.repaint(0, y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - y); - getCaretModel().moveToOffset(getCaretModel().getOffset()); - } - - private void initTabPainter() { - myTabPainter = new ArrowPainter( - ColorProvider.byColorsScheme(myScheme, EditorColors.WHITESPACES_COLOR), - new Computable() { - @Override - public Integer compute() { - return getCharHeight(); - } - } - ); - } - - public void release() { - assertIsDispatchThread(); - if (isReleased) { - LOG.error("Double release. First released at: =====\n" + myReleasedAt+"\n======"); - } - - myReleasedAt = StringUtil.getThrowableText(new Throwable()); - - isReleased = true; - myDocument.removeDocumentListener(myHighlighter); - myDocument.removeDocumentListener(myEditorDocumentAdapter); - myDocument.removeDocumentListener(myFoldingModel); - myDocument.removeDocumentListener(myCaretModel); - myDocument.removeDocumentListener(mySelectionModel); - myDocument.removeDocumentListener(mySoftWrapModel); - - myFoldingModel.removeListener(mySoftWrapModel); - myFoldingModel.dispose(); - - mySoftWrapModel.release(); - - MarkupModelEx markupModel = (MarkupModelEx)myDocument.getMarkupModel(myProject, false); - if (markupModel instanceof MarkupModelImpl) { - markupModel.removeMarkupModelListener(myMarkupModelListener); - } - - myMarkupModel.dispose(); - - myLineHeight = -1; - myCharHeight = -1; - myDescent = -1; - myPlainFontMetrics = null; - myScrollingModel.dispose(); - myGutterComponent.dispose(); - Disposer.dispose(myCaretModel); - clearCaretThread(); - - myFocusListeners.clear(); - } - - private void clearCaretThread() { - synchronized (ourCaretBlinkingCommand) { - if (ourCaretBlinkingCommand.myEditor == this) { - ourCaretBlinkingCommand.myEditor = null; - } - } - } - - private void initComponent() { -// myStatusBar = new EditorStatusBarImpl(); - - //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS)); - myPanel.setLayout(new BorderLayout()); - - myPanel.add(myHeaderPanel, BorderLayout.NORTH); - - myGutterComponent.setOpaque(true); - - myScrollPane.setVerticalScrollBar(myVerticalScrollBar); - myScrollPane.setViewportView(myEditorComponent); - //myScrollPane.setBorder(null); - myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - - - myScrollPane.setRowHeaderView(myGutterComponent); - stopOptimizedScrolling(); - - myEditorComponent.setTransferHandler(new MyTransferHandler()); - myEditorComponent.setAutoscrolls(true); - -/* Default mode till 1.4.0 - * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE); - */ - - if (mayShowToolbar()) { - JLayeredPane layeredPane = new JLayeredPane() { - @Override - public void doLayout() { - final Component[] components = getComponents(); - final Rectangle r = getBounds(); - for (Component c : components) { - if (c instanceof JScrollPane) { - c.setBounds(0, 0, r.width, r.height); - } - else { - final Dimension d = c.getPreferredSize(); - final MyScrollBar scrollBar = getVerticalScrollBar(); - c.setBounds(r.width - d.width - scrollBar.getWidth() - 30, 20, d.width, d.height); - } - } - } - }; - - layeredPane.add(myScrollPane, JLayeredPane.DEFAULT_LAYER); - myPanel.add(layeredPane); - - new ContextMenuImpl(layeredPane, myScrollPane, this); - } - else { - myPanel.add(myScrollPane); - } - - //myPanel.add(myScrollPane); - - myEditorComponent.addKeyListener(new KeyAdapter() { - public void keyTyped(KeyEvent event) { - if (Patches.APPLE_BUG_ID_3337563) return; // Everything is going through InputMethods under MacOS X in JDK releases earlier than 1.4.2_03-117.1 - if (event.isConsumed()) { - return; - } - if (processKeyTyped(event)) { - event.consume(); - } - } - }); - - MyMouseAdapter mouseAdapter = new MyMouseAdapter(); - myEditorComponent.addMouseListener(mouseAdapter); - myGutterComponent.addMouseListener(mouseAdapter); - - MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener(); - myEditorComponent.addMouseMotionListener(mouseMotionListener); - myGutterComponent.addMouseMotionListener(mouseMotionListener); - - myEditorComponent.addFocusListener(new FocusAdapter() { - public void focusGained(FocusEvent e) { - myCaretCursor.activate(); - int caretLine = getCaretModel().getLogicalPosition().line; - repaintLines(caretLine, caretLine); - fireFocusGained(); - if (myGutterNeedsUpdate) { - updateGutterSize(); - } - } - - public void focusLost(FocusEvent e) { - clearCaretThread(); - int caretLine = getCaretModel().getLogicalPosition().line; - repaintLines(caretLine, caretLine); - fireFocusLost(); - } - }); - -// myBorderEffect.reset(); - try { - final DropTarget dropTarget = myEditorComponent.getDropTarget(); - if (dropTarget != null) { // might be null in headless environment - dropTarget.addDropTargetListener(new DropTargetAdapter() { - public void drop(DropTargetDropEvent e) { - } - - public void dragOver(DropTargetDragEvent e) { - Point location = e.getLocation(); - - moveCaretToScreenPos(location.x, location.y); - getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - }); - } - } - catch (TooManyListenersException e) { - LOG.error(e); - } - - myPanel.addComponentListener(new ComponentAdapter() { - public void componentResized(ComponentEvent e) { - myMarkupModel.repaint(0, myDocument.getTextLength()); - } - }); - } - - private boolean mayShowToolbar() { - return !isEmbeddedIntoDialogWrapper() && !isOneLineMode() && ContextMenuImpl.mayShowToolbar(myDocument); - } - - public void setFontSize(final int fontSize) { - int oldFontSize = myScheme.getEditorFontSize(); - myScheme.setEditorFontSize(fontSize); - myPropertyChangeSupport.firePropertyChange(PROP_FONT_SIZE, oldFontSize, fontSize); - } - - public ActionCallback type(final String text) { - final ActionCallback result = new ActionCallback(); - - Application app = ApplicationManager.getApplication(); - if (!app.isWriteAccessAllowed()) { - result.setRejected(); - } else { - app.runWriteAction(new Runnable() { - public void run() { - for (int i = 0; i < text.length(); i++) { - if (!processKeyTyped(text.charAt(i))) { - result.setRejected(); - return; - } - } - - result.setDone(); - } - }); - } - - return result; - } - - private boolean processKeyTyped(char c) { - // [vova] This is patch for Mac OS X. Under Mac "input methods" - // is handled before our EventQueue consume upcoming KeyEvents. - IdeEventQueue queue = IdeEventQueue.getInstance(); - if (queue.shouldNotTypeInEditor() || ProgressManager.getInstance().hasModalProgressIndicator()) { - return false; - } - ActionManagerEx actionManager = ActionManagerEx.getInstanceEx(); - DataContext dataContext = getDataContext(); - actionManager.fireBeforeEditorTyping(c, dataContext); - EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext); - - return true; - } - - private void fireFocusLost() { - FocusChangeListener[] listeners = getFocusListeners(); - for (FocusChangeListener listener : listeners) { - listener.focusLost(this); - } - } - - private FocusChangeListener[] getFocusListeners() { - return myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]); - } - - private void fireFocusGained() { - FocusChangeListener[] listeners = getFocusListeners(); - for (FocusChangeListener listener : listeners) { - listener.focusGained(this); - } - } - - public void setHighlighter(EditorHighlighter highlighter) { - assertIsDispatchThread(); - final Document document = getDocument(); - if (myHighlighter != null) { - document.removeDocumentListener(myHighlighter); - } - - document.addDocumentListener(highlighter); - highlighter.setEditor(this); - highlighter.setText(document.getCharsSequence()); - myHighlighter = highlighter; - if (document instanceof DocumentImpl) { - ((DocumentImpl)document).rememberEditorHighlighterForCachesOptimization(highlighter); - } - - if (myPanel != null) { - reinitSettings(); - } - } - - public EditorHighlighter getHighlighter() { - assertReadAccess(); - return myHighlighter; - } - - @NotNull - public EditorComponentImpl getContentComponent() { - return myEditorComponent; - } - - public EditorGutterComponentEx getGutterComponentEx() { - return myGutterComponent; - } - - public void addPropertyChangeListener(PropertyChangeListener listener) { - myPropertyChangeSupport.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(PropertyChangeListener listener) { - myPropertyChangeSupport.removePropertyChangeListener(listener); - } - - public void setInsertMode(boolean mode) { - assertIsDispatchThread(); - boolean oldValue = myIsInsertMode; - myIsInsertMode = mode; - myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, mode); - //Repaint the caret line by moving caret to the same place - LogicalPosition caretPosition = getCaretModel().getLogicalPosition(); - getCaretModel().moveToLogicalPosition(caretPosition); - } - - public boolean isInsertMode() { - return myIsInsertMode; - } - - public void setColumnMode(boolean mode) { - assertIsDispatchThread(); - boolean oldValue = myIsColumnMode; - myIsColumnMode = mode; - myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode); - } - - public boolean isColumnMode() { - return myIsColumnMode; - } - - private int yPositionToVisibleLineNumber(int y) { - return y / getLineHeight(); - } - - @NotNull - public VisualPosition xyToVisualPosition(@NotNull Point p) { - int line = yPositionToVisibleLineNumber(p.y); - int px = p.x; - if (line == 0 && myPrefixText != null) { - for (char c : myPrefixText) { - px -= EditorUtil.charWidth(c, myPrefixAttributes.getFontType(), this); - } - } - - LogicalPosition logicalPosition = visualToLogicalPosition(new VisualPosition(line, 0)); - int offset = logicalPositionToOffset(logicalPosition); - int textLength = myDocument.getTextLength(); - - if (offset >= textLength) return new VisualPosition(line, 0); - - // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. - // We mark that we're under such circumstances then. - boolean activeSoftWrapProcessed = true; - if (logicalPosition.softWrapLinesOnCurrentLogicalLine > 0) { - activeSoftWrapProcessed = false; - } - - int column = 0; - int prevX = 0; - CharSequence text = myDocument.getCharsNoThreadCheck(); - char c = ' '; - IterationState state = new IterationState(this, offset, false); - - int fontType = state.getMergedAttributes().getFontType(); - int spaceSize = EditorUtil.getSpaceWidth(fontType, this); - - int x = 0; - int charWidth; - boolean onSoftWrapDrawing = false; - outer: - while (true) { - charWidth = -1; - if (offset >= textLength) { - break; - } - - if (offset >= state.getEndOffset()) { - state.advance(); - fontType = state.getMergedAttributes().getFontType(); - } - - SoftWrap softWrap = mySoftWrapModel.getSoftWrap(offset); - if (softWrap != null) { - if (activeSoftWrapProcessed) { - prevX = x; - charWidth = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); - x += charWidth; - if (x >= px) { - onSoftWrapDrawing = true; - break outer; - } - } - else { - CharSequence softWrapText = softWrap.getText(); - for (int i = 1/*Assuming line feed is located at the first position*/; i < softWrapText.length(); i++) { - c = softWrapText.charAt(i); - prevX = x; - charWidth = charToVisibleWidth(c, fontType, x); - x += charWidth; - if (x >= px) { - break outer; - } - column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); - } - - // Process 'after soft wrap' sign. - prevX = x; - charWidth = mySoftWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); - x += charWidth; - if (x >= px) { - onSoftWrapDrawing = true; - break outer; - } - column++; - activeSoftWrapProcessed = true; - } - } - FoldRegion region = state.getCurrentFold(); - if (region != null) { - char[] placeholder = region.getPlaceholderText().toCharArray(); - for (char aPlaceholder : placeholder) { - c = aPlaceholder; - x += EditorUtil.charWidth(c, fontType, this); - if (x >= px) { - break outer; - } - column++; - } - offset = region.getEndOffset(); - } - else { - prevX = x; - c = text.charAt(offset); - charWidth = charToVisibleWidth(c, fontType, x); - if (charWidth == 0) { - break; - } - x += charWidth; - - if (x >= px) { - break; - } - column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); - - offset++; - } - } - - if (charWidth < 0) { - charWidth = EditorUtil.charWidth(c, fontType, this); - } - - if (x >= px && c == '\t' && !onSoftWrapDrawing) { - if (mySettings.isCaretInsideTabs()) { - column += (px - prevX) / spaceSize; - if ((px - prevX) % spaceSize > spaceSize / 2) column++; - } - else if ((x - px) * 2 < x - prevX) { - column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); - } - } - else { - if (x >= px) { - if ((x - px) * 2 < charWidth) column++; - } - else { - column += (px - x) / spaceSize; - if ((px - x) * 2 >= spaceSize) { - column++; - } - } - } - - return new VisualPosition(line, column); - } - - /** - * Allows to answer how much width requires given char to be represented on a screen. - * - * @param c target character - * @param fontType font type to use for representation of the given character - * @param currentX current 'x' position on a line where given character should be displayed - * @return width required to represent given char with the given settings on a screen; - * '0' if given char is a line break - */ - private int charToVisibleWidth(char c, int fontType, int currentX) { - if (c == '\n') { - return 0; - } - - if (c == '\t') { - return EditorUtil.nextTabStop(currentX, this) - currentX; - } - return EditorUtil.charWidth(c, fontType, this); - } - - @NotNull - public VisualPosition offsetToVisualPosition(int offset) { - return logicalToVisualPosition(offsetToLogicalPosition(offset)); - } - - @NotNull - public LogicalPosition offsetToLogicalPosition(int offset) { - return offsetToLogicalPosition(offset, true); - } - - @NotNull - @Override - public LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware) { - if (softWrapAware) { - return mySoftWrapModel.offsetToLogicalPosition(offset); - } - int line = calcLogicalLineNumber(offset, false); - int column = calcColumnNumber(offset, line, false); - return new LogicalPosition(line, column); - } - - @Override - public boolean isCaretActive() { - synchronized (ourCaretBlinkingCommand) { - return ourCaretBlinkingCommand.myEditor == this; - } - } - - @TestOnly - public void setCaretActive() { - synchronized (ourCaretBlinkingCommand) { - ourCaretBlinkingCommand.myEditor = this; - } - } - - public int offsetToVisualLine(int offset) { - return logicalToVisualPosition(offsetToLogicalPosition(offset)).line; - } - - @NotNull - public LogicalPosition xyToLogicalPosition(@NotNull Point p) { - final Point pp; - if (p.x >= 0 && p.y >= 0) { - pp = p; - } - else { - pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0)); - } - - return visualToLogicalPosition(xyToVisualPosition(pp)); - } - - private int logicalLineToY(int line) { - VisualPosition visible = logicalToVisualPosition(new LogicalPosition(line, 0)); - return visibleLineNumberToYPosition(visible.line); - } - - @NotNull - public Point logicalPositionToXY(@NotNull LogicalPosition pos) { - VisualPosition visible = logicalToVisualPosition(pos); - return visualPositionToXY(visible); - } - - @NotNull - public Point visualPositionToXY(@NotNull VisualPosition visible) { - int y = visibleLineNumberToYPosition(visible.line); - LogicalPosition logical = visualToLogicalPosition(new VisualPosition(visible.line, 0)); - int logLine = logical.line; - - int lineStartOffset = -1; - int reserved = 0; - int column = visible.column; - - if (logical.softWrapLinesOnCurrentLogicalLine > 0) { - int linesToSkip = logical.softWrapLinesOnCurrentLogicalLine; - List softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine); - for (SoftWrap softWrap : softWraps) { - if (myFoldingModel.isOffsetCollapsed(softWrap.getStart()) && myFoldingModel.isOffsetCollapsed(softWrap.getStart() - 1)) { - continue; - } - linesToSkip--; // Assuming here that every soft wrap has exactly one line feed - if (linesToSkip > 0) { - continue; - } - lineStartOffset = softWrap.getStart(); - int widthInColumns = softWrap.getIndentInColumns(); - int widthInPixels = softWrap.getIndentInPixels(); - if (widthInColumns <= column) { - column -= widthInColumns; - reserved = widthInPixels; - } - else { - char[] softWrapChars = softWrap.getChars(); - int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length); - int start = 0; - if (i >= 0) { - start = i + 1; - } - return new Point(EditorUtil.textWidth(this, softWrap.getText(), start, column + 1, Font.PLAIN, 0), y); - } - break; - } - } - - if (logLine < 0) { - lineStartOffset = 0; - } - else if (lineStartOffset < 0) { - if (logLine >= myDocument.getLineCount()) { - lineStartOffset = myDocument.getTextLength(); - } - else { - lineStartOffset = myDocument.getLineStartOffset(logLine); - } - } - - int x = getTabbedTextWidth(lineStartOffset, column, reserved); - return new Point(x, y); - } - - private int getTabbedTextWidth(int startOffset, int length, int xOffset) { - int x = xOffset; - if (startOffset == 0 && myPrefixText != null) { - for (char c : myPrefixText) { - x += EditorUtil.charWidth(c, myPrefixAttributes.getFontType(), this); - } - } - if (length <= 0) return x; - int offset = startOffset; - CharSequence text = myDocument.getCharsNoThreadCheck(); - int textLength = myDocument.getTextLength(); - IterationState state = new IterationState(this, offset, false); - int fontType = state.getMergedAttributes().getFontType(); - int spaceSize = EditorUtil.getSpaceWidth(fontType, this); - - int column = 0; - outer: - while (column < length) { - if (offset >= textLength) break; - - if (offset >= state.getEndOffset()) { - state.advance(); - fontType = state.getMergedAttributes().getFontType(); - } - // We need to consider 'before soft wrap drawing'. - SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offset); - if (softWrap != null && offset > startOffset) { - column++; - x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); - // Assuming that first soft wrap symbol is line feed or all soft wrap symbols before the first line feed are spaces. - break; - } - - FoldRegion region = state.getCurrentFold(); - - if (region != null) { - char[] placeholder = region.getPlaceholderText().toCharArray(); - for (char aPlaceholder : placeholder) { - x += EditorUtil.charWidth(aPlaceholder, fontType, this); - column++; - if (column >= length) break outer; - } - offset = region.getEndOffset(); - } - else { - char c = text.charAt(offset); - if (c == '\n') { - break; - } - if (c == '\t') { - int prevX = x; - x = EditorUtil.nextTabStop(x, this); - int columnDiff = (x - prevX) / spaceSize; - if ((x - prevX) % spaceSize > 0) { - // There is a possible case that tabulation symbol takes more than one visual column to represent and it's shown at - // soft-wrapped line. Soft wrap sign width may be not divisible by space size, hence, part of tabulation symbol represented - // as a separate visual column may take less space than space width. - columnDiff++; - } - column += columnDiff; - } - else { - x += EditorUtil.charWidth(c, fontType, this); - column++; - } - offset++; - } - } - - if (column != length) { - x += EditorUtil.getSpaceWidth(fontType, this) * (length - column); - } - - return x; - } - - public int visibleLineNumberToYPosition(int lineNum) { - if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum); - return lineNum * getLineHeight(); - } - - public void repaint(int startOffset, int endOffset) { - if (!isShowing() || myScrollPane == null || myDocument.isInBulkUpdate()) { - return; - } - - assertIsDispatchThread(); - - if (endOffset > myDocument.getTextLength()) { - endOffset = myDocument.getTextLength(); - } - if (startOffset < endOffset) { - int startLine = myDocument.getLineNumber(startOffset); - int endLine = myDocument.getLineNumber(endOffset); - repaintLines(startLine, endLine); - } - } - - private boolean isShowing() { - return myGutterComponent != null && myGutterComponent.isShowing(); - } - - private void repaintToScreenBottom(int startLine) { - Rectangle visibleArea = getScrollingModel().getVisibleArea(); - int yStartLine = logicalLineToY(startLine); - int yEndLine = visibleArea.y + visibleArea.height; - - myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, yEndLine - yStartLine); - myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine); - } - - /** - * Asks to repaint all logical lines from the given [start; end] range. - * - * @param startLine start logical line to repaint (inclusive) - * @param endLine end logical line to repaint (inclusive) - */ - public void repaintLines(int startLine, int endLine) { - if (!isShowing()) return; - - Rectangle visibleArea = getScrollingModel().getVisibleArea(); - int yStartLine = logicalLineToY(startLine); - int endVisLine; - if (myDocument.getTextLength() <= 0) { - endVisLine = 0; - } - else { - endVisLine = offsetToVisualPosition(myDocument.getLineEndOffset(Math.min(myDocument.getLineCount() - 1, endLine))).line; - } - int height = endVisLine * getLineHeight() - yStartLine + getLineHeight() + WAVE_HEIGHT; - - myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, height); - myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), height); - } - - private void beforeChangedUpdate(DocumentEvent e) { - if (!myDocument.isInBulkUpdate()) { - Rectangle visibleArea = getScrollingModel().getVisibleArea(); - Point pos = visualPositionToXY(getCaretModel().getVisualPosition()); - myCaretUpdateVShift = pos.y - visibleArea.y; - } - mySizeContainer.beforeChange(e); - } - - private void changedUpdate(DocumentEvent e) { - if (myScrollPane == null) return; - - stopOptimizedScrolling(); - mySelectionModel.removeBlockSelection(); - - mySizeContainer.changedUpdate(e); - validateSize(); - - int startLine = calcLogicalLineNumber(e.getOffset()); - int endLine = calcLogicalLineNumber(e.getOffset() + e.getNewLength()); - - boolean painted = false; - if (myDocument.getTextLength() > 0) { - int startDocLine = myDocument.getLineNumber(e.getOffset()); - int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength()); - if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine) { - updateGutterSize(); - } - - if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) { - // Lines removed. Need to repaint till the end of the screen - repaintToScreenBottom(startLine); - painted = true; - } - } - - updateCaretCursor(); - if (!painted) { - repaintLines(startLine, endLine); - } - - if (!myDocument.isInBulkUpdate()) { - Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition()); - int scrollOffset = caretLocation.y - myCaretUpdateVShift; - getScrollingModel().scrollVertically(scrollOffset); - } - } - - private static int countLineFeeds(CharSequence c) { - return StringUtil.countNewLines(c); - } - - private void updateGutterSize() { - if (myGutterSizeUpdater != null) return; - myGutterSizeUpdater = new Runnable() { - public void run() { - if (!isDisposed()) { - if (isShowing()) { - myGutterComponent.updateSize(); - myGutterNeedsUpdate = false; - } - else { - myGutterNeedsUpdate = true; - } - } - myGutterSizeUpdater = null; - } - }; - - SwingUtilities.invokeLater(myGutterSizeUpdater); - } - - void validateSize() { - Dimension dim = getPreferredSize(); - - if (!dim.equals(myPreferredSize) && !myDocument.isInBulkUpdate()) { - myPreferredSize = dim; - - stopOptimizedScrolling(); - int lineNum = Math.max(1, getDocument().getLineCount()); - myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNum + 2)) + 6); - - myEditorComponent.setSize(dim); - myEditorComponent.fireResized(); - - myMarkupModel.repaint(0, myDocument.getTextLength()); - } - } - - void recalculateSizeAndRepaint() { - mySizeContainer.reset(); - validateSize(); - myEditorComponent.repaintEditorComponent(); - } - - @NotNull - public Document getDocument() { - return myDocument; - } - - @NotNull - public JComponent getComponent() { - return myPanel; - } - - public void addEditorMouseListener(@NotNull EditorMouseListener listener) { - myMouseListeners.add(listener); - } - - public void removeEditorMouseListener(@NotNull EditorMouseListener listener) { - boolean success = myMouseListeners.remove(listener); - LOG.assertTrue(success); - } - - public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) { - myMouseMotionListeners.add(listener); - } - - public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) { - boolean success = myMouseMotionListeners.remove(listener); - LOG.assertTrue(success); - } - - public boolean isDisposed() { - return isReleased; - } - - void paint(Graphics g) { - startOptimizedScrolling(); - - if (myUpdateCursor) { - setCursorPosition(); - myUpdateCursor = false; - } - - Rectangle clip = getClipBounds(g); - - if (clip == null) { - return; - } - - Rectangle visibleArea = getScrollingModel().getVisibleArea(); - if (visibleArea == null) { - return; - } - - if (isReleased) { - g.setColor(new Color(128, 255, 128)); - g.fillRect(clip.x, clip.y, clip.width, clip.height); - return; - } - if (myProject != null && myProject.isDisposed()) return; - - paintBackgrounds(g, clip); - paintRectangularSelection(g); - paintRightMargin(g, clip); - paintCustomRenderers((Graphics2D)g, clip); - final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); - paintLineMarkersSeparators(g, clip, docMarkup); - paintLineMarkersSeparators(g, clip, myMarkupModel); - paintText(g, clip); - paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip); - BorderEffect borderEffect = new BorderEffect(this, g); - borderEffect.paintHighlighters(getHighlighter()); - borderEffect.paintHighlighters(docMarkup.getAllHighlighters()); - borderEffect.paintHighlighters(getMarkupModel().getAllHighlighters()); - paintCaretCursor(g); - - paintComposedTextDecoration((Graphics2D)g); - } - - private void paintCustomRenderers(Graphics2D g, Rectangle clip) { - MarkupModel mm = myMarkupModel; - - int clipStartOffset = logicalPositionToOffset(xyToLogicalPosition(new Point(0, clip.y))); - int clipEndOffset = logicalPositionToOffset(xyToLogicalPosition(new Point(0, clip.y + clip.height + getLineHeight()))); - - for (RangeHighlighter highlighter : mm.getAllHighlighters()) { - if (highlighter.isValid()) { - final CustomHighlighterRenderer customRenderer = highlighter.getCustomRenderer(); - if (customRenderer != null && clipStartOffset < highlighter.getEndOffset() && clipEndOffset > highlighter.getStartOffset()) { - customRenderer.paint(this, highlighter, g); - } - } - } - } - - public IndentsModel getIndentsModel() { - return myIndentsModel; - } - - public void setHeaderComponent(JComponent header) { - myHeaderPanel.removeAll(); - if (header != null) { - myHeaderPanel.add(header); - } - - myHeaderPanel.revalidate(); - } - - public boolean hasHeaderComponent() { - return myHeaderPanel.getComponentCount() > 0; - } - - @Nullable - public JComponent getHeaderComponent() { - if (hasHeaderComponent()) { - return (JComponent)myHeaderPanel.getComponent(0); - } - return null; - } - - public void setBackgroundColor(Color color) { - myScrollPane.setBackground(color); - - if (getBackgroundIgnoreForced().equals(color)) { - myForcedBackground = null; - return; - } - myForcedBackground = color; - } - - public Color getForegroundColor() { - return myScheme.getDefaultForeground(); - } - - public Color getBackgroundColor() { - if (myForcedBackground != null) return myForcedBackground; - - return getBackgroundIgnoreForced(); - } - - @Override - public TextDrawingCallback getTextDrawingCallback() { - return myTextDrawingCallback; - } - - private Color getBackgroundColor(final TextAttributes attributes) { - final Color attrColor = attributes.getBackgroundColor(); - return attrColor == myScheme.getDefaultBackground() ? getBackgroundColor() : attrColor; - } - - private Color getBackgroundIgnoreForced() { - Color color = myScheme.getDefaultBackground(); - if (myDocument.isWritable()) { - return color; - } - Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR); - return readOnlyColor != null ? readOnlyColor : color; - } - - private void paintComposedTextDecoration(Graphics2D g) { - if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) { - VisualPosition visStart = - offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength())); - int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1; - Point p1 = visualPositionToXY(visStart); - Point p2 = - logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength()))); - - Stroke saved = g.getStroke(); - BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2}, 0); - g.setStroke(dotted); - UIUtil.drawLine(g, p1.x, y, p2.x, y); - g.setStroke(saved); - } - } - - private static Rectangle getClipBounds(Graphics g) { - return g.getClipBounds(); - } - - private void paintRightMargin(Graphics g, Rectangle clip) { - Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR); - if (!mySettings.isRightMarginShown() || rightMargin == null) { - return; - } - int x = mySettings.getRightMargin(myProject) * EditorUtil.getSpaceWidth(Font.PLAIN, this); - if (x >= clip.x && x < clip.x + clip.width) { - g.setColor(rightMargin); - UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height); - } - } - - private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) { - if (myDocument.getLineCount() == 0) return; - int startLineNumber = yPositionToVisibleLineNumber(clip.y); - int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1; - - final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); - RangeHighlighter[] segmentHighlighters = docMarkup.getAllHighlighters(); - for (RangeHighlighter segmentHighlighter : segmentHighlighters) { - paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber); - } - - segmentHighlighters = getMarkupModel().getAllHighlighters(); - for (RangeHighlighter segmentHighlighter : segmentHighlighters) { - paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber); - } - } - - private void paintSegmentHighlighterAfterEndOfLine(Graphics g, - RangeHighlighterEx segmentHighlighter, - int startLineNumber, - int endLineNumber) { - if (!segmentHighlighter.isValid()) { - return; - } - if (segmentHighlighter.isAfterEndOfLine()) { - int startOffset = segmentHighlighter.getStartOffset(); - int visibleStartLine = offsetToVisualLine(startOffset); - - if (!getFoldingModel().isOffsetCollapsed(startOffset)) { - if (visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) { - int logStartLine = offsetToLogicalPosition(startOffset).line; - LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine)); - Point end = logicalPositionToXY(logPosition); - int charWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this); - int lineHeight = getLineHeight(); - TextAttributes attributes = segmentHighlighter.getTextAttributes(); - if (attributes != null && getBackgroundColor(attributes) != null) { - g.setColor(getBackgroundColor(attributes)); - g.fillRect(end.x, end.y, charWidth, lineHeight); - } - if (attributes != null && attributes.getEffectColor() != null) { - int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1; - g.setColor(attributes.getEffectColor()); - if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) { - drawWave(g, end.x, end.x + charWidth - 1, y); - } - else if (attributes.getEffectType() != EffectType.BOXED) { - UIUtil.drawLine(g, end.x, y, end.x + charWidth - 1, y); - } - } - } - } - } - } - - public int getMaxWidthInRange(int startOffset, int endOffset) { - int width = 0; - int start = offsetToVisualLine(startOffset); - int end = offsetToVisualLine(endOffset); - - for (int i = start; i <= end; i++) { - int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1; - int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x; - - if (lineWidth > width) { - width = lineWidth; - } - } - - return width; - } - - @SuppressWarnings({"StatementWithEmptyBody"}) - private void paintBackgrounds(Graphics g, Rectangle clip) { - Color defaultBackground = getBackgroundColor(); - g.setColor(defaultBackground); - g.fillRect(clip.x, clip.y, clip.width, clip.height); - - int lineHeight = getLineHeight(); - - int visibleLineNumber = clip.y / lineHeight; - - VisualPosition visualPosition = xyToVisualPosition(new Point(0, clip.y)); - LogicalPosition logicalPosition = visualToLogicalPosition(visualPosition); - - Point position = new Point(0, visibleLineNumber * lineHeight); - if (visualPosition.line == 0 && myPrefixText != null) { - position.x = drawBackground(g, myPrefixAttributes.getBackgroundColor(), new String(myPrefixText), position, myPrefixAttributes.getFontType(), - defaultBackground, clip); - } - - if (logicalPosition.line >= myDocument.getLineCount() || logicalPosition.line < 0) { - if (position.x > 0) flushBackground(g, clip); - return; - } - - boolean locateBeforeSoftWrap = !SoftWrapHelper.isCaretAfterSoftWrap(this); - int start = logicalPositionToOffset(logicalPosition); - getSoftWrapModel().registerSoftWrapsIfNecessary(clip, start); - - // There is a possible case that we need to draw background from the start of soft wrap-introduced visual line. Given position - // has valid 'y' coordinate then at it shouldn't be affected by soft wrap that corresponds to the visual line start offset. - // Hence, we store information about soft wrap to be skipped for further processing and adjust 'x' coordinate value if necessary. - TIntHashSet softWrapsToSkip = new TIntHashSet(); - SoftWrap softWrap = getSoftWrapModel().getSoftWrap(start); - if (softWrap != null) { - position.x = softWrap.getIndentInPixels(); - softWrapsToSkip.add(softWrap.getStart()); - } - - // There is a possible case that caret is located at soft-wrapped line. We don't need to paint caret row background - // on a last visual line of that soft-wrapped line then. Below is a holder for the flag that indicates if caret row - // background is already drawn. We don't achieve concurrency stuff here, AtomicBoolean is used just as a data holder. - AtomicBoolean caretRowPainted = new AtomicBoolean(); - - IterationState iterationState = new IterationState(this, start, paintSelection()); - - LineIterator lIterator = createLineIterator(); - lIterator.start(start); - if (lIterator.atEnd()) { - return; - } - - myLastBackgroundPosition = null; - myLastBackgroundColor = null; - - TextAttributes attributes = iterationState.getMergedAttributes(); - Color backColor = getBackgroundColor(attributes); - int fontType = attributes.getFontType(); - CharSequence text = myDocument.getCharsNoThreadCheck(); - int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1); - - while (!iterationState.atEnd() && !lIterator.atEnd()) { - int hEnd = iterationState.getEndOffset(); - int lEnd = lIterator.getEnd(); - - if (hEnd >= lEnd) { - FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start); - if (collapsedFolderAt == null) { - position.x = drawSoftWrapAwareBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType, - defaultBackground, clip, softWrapsToSkip, caretRowPainted); - - if (lIterator.getLineNumber() < lastLineIndex) { - if (backColor != null && !backColor.equals(defaultBackground)) { - g.setColor(backColor); - g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight); - } - } - else { - paintAfterFileEndBackground(iterationState, - g, - position, clip, - lineHeight, defaultBackground, caretRowPainted); - break; - } - - position.x = 0; - if (position.y > clip.y + clip.height) break; - position.y += lineHeight; - start = lEnd; - } - - lIterator.advance(); - } - else { - FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); - if (collapsedFolderAt != null) { - position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(), position, fontType, defaultBackground, clip); - } - else { - if (hEnd > lEnd - lIterator.getSeparatorLength()) { - position.x = drawSoftWrapAwareBackground( - g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType, - defaultBackground, clip, softWrapsToSkip, caretRowPainted - ); - } - else { - position.x = drawSoftWrapAwareBackground( - g, backColor, text, start, hEnd, position, fontType, defaultBackground, clip, softWrapsToSkip, caretRowPainted - ); - } - } - - iterationState.advance(); - attributes = iterationState.getMergedAttributes(); - backColor = getBackgroundColor(attributes); - fontType = attributes.getFontType(); - start = iterationState.getStartOffset(); - } - } - - flushBackground(g, clip); - - if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) { - paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground, caretRowPainted); - } - - // Perform additional activity if soft wrap is added or removed during repainting. - if (mySoftWrapsChanged) { - mySoftWrapsChanged = false; - validateSize(); - - // Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction. - repaintToScreenBottom(xyToLogicalPosition(position).line); - - // Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn - // in accordance with the newly introduced soft wrap(s). - myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y); - - // Ask caret model to update visual caret position. - getCaretModel().moveToOffset(getCaretModel().getOffset(), locateBeforeSoftWrap); - } - } - - private void paintRectangularSelection(Graphics g) { - final SelectionModel model = getSelectionModel(); - if (!model.hasBlockSelection()) return; - final LogicalPosition blockStart = model.getBlockStart(); - final LogicalPosition blockEnd = model.getBlockEnd(); - assert blockStart != null; - assert blockEnd != null; - - final Point start = logicalPositionToXY(blockStart); - final Point end = logicalPositionToXY(blockEnd); - g.setColor(myScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR)); - final int y; - final int height; - if (start.y <= end.y) { - y = start.y; - height = end.y - y + getLineHeight(); - } - else { - y = end.y; - height = start.y - end.y + getLineHeight(); - } - final int x = Math.min(start.x, end.x); - final int width = Math.max(2, Math.abs(end.x - start.x)); - g.fillRect(x, y, width, height); - } - - private void paintAfterFileEndBackground(IterationState iterationState, - Graphics g, - Point position, - Rectangle clip, - int lineHeight, - final Color defaultBackground, - AtomicBoolean caretRowPainted) { - Color backColor = iterationState.getPastFileEndBackground(); - if (backColor == null || backColor.equals(defaultBackground)) { - return; - } - if (caretRowPainted.get() && backColor.equals(getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR))) { - return; - } - g.setColor(backColor); - g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight); - } - - private int drawSoftWrapAwareBackground(Graphics g, Color backColor, CharSequence text, int start, int end, Point position, - int fontType, Color defaultBackground, Rectangle clip, TIntHashSet processSoftWrap, - AtomicBoolean caretRowPainted) - { - int startToUse = start; - List softWraps = getSoftWrapModel().getSoftWrapsForRange(start, end); - for (SoftWrap softWrap : softWraps) { - int softWrapStart = softWrap.getStart(); - if (processSoftWrap.contains(softWrapStart)) { - continue; - } - if (startToUse < softWrapStart) { - position.x = drawBackground(g, backColor, text.subSequence(startToUse, softWrapStart), position, fontType, defaultBackground, clip); - } - drawSoftWrap(g, softWrap, position, fontType, defaultBackground, clip, caretRowPainted); - startToUse = softWrapStart; - } - - if (startToUse < end) { - position.x = drawBackground(g, backColor, text.subSequence(startToUse, end), position, fontType, defaultBackground, clip); - } - return position.x; - } - - private void drawSoftWrap(Graphics g, SoftWrap softWrap, Point position, int fontType, Color defaultBackground, Rectangle clip, - AtomicBoolean caretRowPainted) { - // The main idea is to to do the following: - // *) update given drawing position coordinates in accordance with the current soft wrap; - // *) draw 'active line' background at soft wrap-introduced virtual space if necessary; - - CharSequence softWrapText = softWrap.getText(); - int activeRowY = getCaretModel().getVisualPosition().line * getLineHeight(); - if (position.y == activeRowY) { - // Draw 'active line' background after soft wrap. - Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR); - drawBackground(g, caretRowColor, clip.x + clip.width - position.x, position, defaultBackground, clip); - caretRowPainted.set(true); - } - - int i = CharArrayUtil.lastIndexOf(softWrapText, "\n", softWrapText.length()) + 1; - position.x = getTextSegmentWidth(softWrapText.subSequence(i, softWrapText.length()), 0, fontType, clip); - position.x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); - position.y += getLineHeight(); - - if (position.y == activeRowY) { - // Draw 'active line' background for the soft wrap-introduced virtual space. - Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR); - drawBackground(g, caretRowColor, position.x, new Point(0, activeRowY), defaultBackground, clip); - } - - // The code below draws background for soft wrap-introduced virtual space. It's is considered that we don't want - // to do that for now. Uncomment if that decision is changed. - - //position.x = drawBackground( - // g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED), position, - // defaultBackground, clip - //); - // - //CharSequence softWrapText = softWrap.getText(); - //int start = 0; - //for ( - // int end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n"); - // start < softWrapText.length() && end < softWrapText.length(); - // end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n")) - //{ - // drawBackground(g, backColor, softWrapText.subSequence(start, end), position, fontType, defaultBackground, clip); - // start = end + 1; - // position.x = 0; - // position.y += getLineHeight(); - //} - // - //if (start < softWrapText.length()) { - // position.x = drawBackground( - // g, backColor, softWrapText.subSequence(start, softWrapText.length()), position, fontType, defaultBackground, clip - // ); - //} - // - //position.x = drawBackground( - // g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP), position, - // defaultBackground, clip - //); - } - - private int drawBackground(Graphics g, Color backColor, CharSequence text, Point position, int fontType, Color defaultBackground, - Rectangle clip) - { - int width = getTextSegmentWidth(text, position.x, fontType, clip); - return drawBackground(g, backColor, width, position, defaultBackground, clip); - } - - private int drawBackground(Graphics g, - Color backColor, - int width, - Point position, - Color defaultBackground, - Rectangle clip) { - if (backColor != null && !backColor.equals(defaultBackground) && clip.intersects(position.x, position.y, width, getLineHeight())) { - if (backColor.equals(myLastBackgroundColor) && myLastBackgroundPosition.y == position.y && - myLastBackgroundPosition.x + myLastBackgroundWidth == position.x) { - myLastBackgroundWidth += width; - } - else { - flushBackground(g, clip); - myLastBackgroundColor = backColor; - myLastBackgroundPosition = new Point(position); - myLastBackgroundWidth = width; - } - } - - return position.x + width; - } - - private void flushBackground(Graphics g, final Rectangle clip) { - if (myLastBackgroundColor != null) { - final Point position = myLastBackgroundPosition; - final int w = myLastBackgroundWidth; - final int height = getLineHeight(); - if (clip.intersects(position.x, position.y, w, height)) { - g.setColor(myLastBackgroundColor); - g.fillRect(position.x, position.y, w, height); - } - myLastBackgroundColor = null; - } - } - - private LineIterator createLineIterator() { - return myDocument.createLineIterator(); - } - - private void paintText(Graphics g, Rectangle clip) { - myCurrentFontType = null; - myLastCache = null; - final int plainSpaceWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this); - final int boldSpaceWidth = EditorUtil.getSpaceWidth(Font.BOLD, this); - final int italicSpaceWidth = EditorUtil.getSpaceWidth(Font.ITALIC, this); - final int boldItalicSpaceWidth = EditorUtil.getSpaceWidth(Font.BOLD | Font.ITALIC, this); - mySpacesHaveSameWidth = - plainSpaceWidth == boldSpaceWidth && plainSpaceWidth == italicSpaceWidth && plainSpaceWidth == boldItalicSpaceWidth; - - int lineHeight = getLineHeight(); - - int visibleLineNumber = clip.y / lineHeight; - - // We use AtomicReference here just as a holder for LogicalPosition - // The main idea is that there is a possible case that we need to perform painting starting from soft-wrapped logical line. - // We may want to skip necessary number of visual lines then. Hence, we remember logical position that corresponds to the starting - // visual line in order to use it for further processing. As soon as necessary number of visual lines is skipped, logical - // position is expected to be set to null as an indication that no soft wrap-introduced visual lines should be skipped on - // current painting iteration. - AtomicReference logicalPosition = new AtomicReference(xyToLogicalPosition(new Point(0, clip.y))); - int startLineNumber = logicalPosition.get().line; - - Point position = new Point(0, visibleLineNumber * lineHeight); - if (startLineNumber == 0 && myPrefixText != null) { - position.x = drawStringWithSoftWraps(g, myPrefixText, 0, myPrefixText.length, position, clip, - myPrefixAttributes.getEffectColor(), myPrefixAttributes.getEffectType(), - myPrefixAttributes.getFontType(), myPrefixAttributes.getForegroundColor(), logicalPosition); - } - if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) { - if (position.x > 0) flushCachedChars(g); - return; - } - - int start = myDocument.getLineStartOffset(startLineNumber); - IterationState iterationState = new IterationState(this, start, paintSelection()); - LineIterator lIterator = createLineIterator(); - lIterator.start(start); - if (lIterator.atEnd()) { - return; - } - - TextAttributes attributes = iterationState.getMergedAttributes(); - Color currentColor = attributes.getForegroundColor(); - if (currentColor == null) { - currentColor = getForegroundColor(); - } - Color effectColor = attributes.getEffectColor(); - EffectType effectType = attributes.getEffectType(); - int fontType = attributes.getFontType(); - g.setColor(currentColor); - - final char[] chars = myDocument.getRawChars(); - - while (!iterationState.atEnd() && !lIterator.atEnd()) { - int hEnd = iterationState.getEndOffset(); - int lEnd = lIterator.getEnd(); - if (hEnd >= lEnd) { - FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start); - if (collapsedFolderAt == null) { - drawStringWithSoftWraps(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, - effectType, fontType, currentColor, logicalPosition); - position.x = 0; - if (position.y > clip.y + clip.height) { - break; - } - position.y += lineHeight; - start = lEnd; - } - -// myBorderEffect.eolReached(g, this); - lIterator.advance(); - } - else { - FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); - if (collapsedFolderAt != null) { - SoftWrap softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset()); - if (softWrap != null && logicalPosition.get() != null) { - position.x = drawStringWithSoftWraps( - g, chars, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, clip, effectColor, effectType, - fontType, currentColor, logicalPosition - ); - } - int foldingXStart = position.x; - position.x = drawString( - g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor - ); - //drawStringWithSoftWraps(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, - // fontType, currentColor, logicalPosition); - BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, position.x, getLineHeight(), effectColor, effectType); - - } - else { - if (hEnd > lEnd - lIterator.getSeparatorLength()) { - position.x = drawStringWithSoftWraps(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, - effectColor, effectType, fontType, currentColor, logicalPosition); - } - else { - position.x = drawStringWithSoftWraps(g, chars, start, hEnd, position, clip, effectColor, effectType, fontType, - currentColor, logicalPosition); - } - } - - iterationState.advance(); - attributes = iterationState.getMergedAttributes(); - - currentColor = attributes.getForegroundColor(); - if (currentColor == null) { - currentColor = getForegroundColor(); - } - - effectColor = attributes.getEffectColor(); - effectType = attributes.getEffectType(); - fontType = attributes.getFontType(); - - start = iterationState.getStartOffset(); - } - } - - FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); - if (collapsedFolderAt != null) { - int foldingXStart = position.x; - int foldingXEnd = - drawStringWithSoftWraps(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, - fontType, currentColor, logicalPosition); - BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, foldingXEnd, getLineHeight(), effectColor, effectType); -// myBorderEffect.collapsedFolderReached(g, this); - } - - flushCachedChars(g); - } - - private boolean paintSelection() { - return !isOneLineMode() || IJSwingUtilities.hasFocus(getContentComponent()); - } - - private class CachedFontContent { - final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][]; - final int[] starts = new int[CACHED_CHARS_BUFFER_SIZE]; - final int[] ends = new int[CACHED_CHARS_BUFFER_SIZE]; - final int[] x = new int[CACHED_CHARS_BUFFER_SIZE]; - final int[] y = new int[CACHED_CHARS_BUFFER_SIZE]; - final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE]; - - int myCount = 0; - final FontInfo myFontType; - - private char[] myLastData; - - private CachedFontContent(FontInfo fontInfo) { - myFontType = fontInfo; - } - - private void flushContent(Graphics g) { - if (myCount != 0) { - if (myCurrentFontType != myFontType) { - myCurrentFontType = myFontType; - g.setFont(myFontType.getFont()); - } - Color currentColor = null; - for (int i = 0; i < myCount; i++) { - if (!Comparing.equal(color[i], currentColor)) { - currentColor = color[i]; - g.setColor(currentColor != null ? currentColor : Color.black); - } - - drawChars(g, data[i], starts[i], ends[i], x[i], y[i]); - color[i] = null; - data[i] = null; - } - - myCount = 0; - myLastData = null; - } - } - - private void addContent(Graphics g, char[] _data, int _start, int _end, int _x, int _y, Color _color) { - final int count = myCount; - if (count > 0) { - final int lastCount = count - 1; - final Color lastColor = color[lastCount]; - if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color == lastColor) - && _y == y[lastCount] /* there is a possible case that vertical position is adjusted because of soft wrap */) - { - ends[lastCount] = _end; - if (lastColor == null) color[lastCount] = _color; - return; - } - } - - myLastData = _data; - data[count] = _data; - x[count] = _x; - y[count] = _y; - starts[count] = _start; - ends[count] = _end; - color[count] = _color; - - myCount++; - if (count >= CACHED_CHARS_BUFFER_SIZE - 1) { - flushContent(g); - } - } - } - - private void flushCachedChars(Graphics g) { - for (CachedFontContent cache : myFontCache) { - cache.flushContent(g); - } - myLastCache = null; - } - - private void paintCaretCursor(Graphics g) { - // There is a possible case that visual caret position is changed because of newly added or removed soft wraps. - // We check if that's the case and ask caret model to recalculate visual position if necessary. - - myCaretCursor.paint(g); - } - - private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) { - if (markupModel == null) return; - RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters(); - for (RangeHighlighter lineMarker : lineMarkers) { - paintLineMarkerSeparator(lineMarker, clip, g); - } - } - - private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) { - if (!marker.isValid()) { - return; - } - Color separatorColor = marker.getLineSeparatorColor(); - if (separatorColor != null) { - int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument() - .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset()); - if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) { - return; - } - - int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line); - if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) { - y += getLineHeight(); - } - - if (y < clip.y || y > clip.y + clip.height) return; - - int endShift = clip.x + clip.width; - g.setColor(separatorColor); - - if (mySettings.isRightMarginShown() && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) { - endShift = Math.min(endShift, mySettings.getRightMargin(myProject) * EditorUtil.getSpaceWidth(Font.PLAIN, this)); - } - - UIUtil.drawLine(g, 0, y - 1, endShift, y - 1); - } - } - - private int drawStringWithSoftWraps(Graphics g, - final String text, - Point position, - Rectangle clip, - Color effectColor, - EffectType effectType, - int fontType, - Color fontColor, - AtomicReference startDrawingLogicalPosition) - { - return drawStringWithSoftWraps(g, text.toCharArray(), 0, text.length(), position, clip, effectColor, effectType, - fontType, fontColor, startDrawingLogicalPosition); - } - - private int drawStringWithSoftWraps(Graphics g, - final char[] text, - int start, - int end, - Point position, - Rectangle clip, - Color effectColor, - EffectType effectType, - int fontType, - Color fontColor, - AtomicReference startDrawingLogicalPosition) - { - - int startToUse = start; - - // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. - // We need to skip necessary number of visual lines then. - int softWrapLinesToSkip = 0; - if (startDrawingLogicalPosition.get() != null) { - softWrapLinesToSkip = startDrawingLogicalPosition.get().softWrapLinesOnCurrentLogicalLine; - } - SoftWrap lastSkippedSoftWrap = null; - if (softWrapLinesToSkip > 0) { - List softWraps = getSoftWrapModel().getSoftWrapsForLine(startDrawingLogicalPosition.get().line); - for (SoftWrap softWrap : softWraps) { - softWrapLinesToSkip--; // Assuming that soft wrap has a single line feed all the time - if (softWrapLinesToSkip <= 0) { - lastSkippedSoftWrap = softWrap; - startToUse = softWrap.getStart(); - break; - } - } - - } - startToUse = Math.max(startToUse, start); - - if (startToUse >= end) { - return position.x; - } - startDrawingLogicalPosition.set(null); - - outer: - for (SoftWrap softWrap : getSoftWrapModel().getSoftWrapsForRange(startToUse, end)) { - char[] softWrapChars = softWrap.getChars(); - - if (softWrap.equals(lastSkippedSoftWrap)) { - // If we are here that means that we are located on soft wrap-introduced visual line just after soft wrap. Hence, we need - // to draw soft wrap indent if any and 'after soft wrap' sign. - int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length); - if (i < softWrapChars.length - 1) { - position.x = 0; // Soft wrap starts new visual line - position.x = drawString( - g, softWrapChars, i + 1, softWrapChars.length, position, clip, effectColor, effectType, fontType, fontColor - ); - } - position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight()); - myForceRefreshFont = true; - continue; - } - - // Draw token text before the wrap. - if (softWrap.getStart() > startToUse) { - position.x = drawString( - g, text, startToUse, softWrap.getStart(), position, clip, effectColor, effectType, fontType, fontColor - ); - } - - startToUse = softWrap.getStart(); - - // We don't draw every soft wrap symbol one-by-one but whole visual line. Current variable holds index that points - // to the first soft wrap symbol that is not drawn yet. - int softWrapSegmentStartIndex = 0; - for (int i = 0; i < softWrapChars.length; i++) { - // Delay soft wraps symbols drawing until EOL is found. - if (softWrapChars[i] != '\n') { - continue; - } - - // Draw soft wrap symbols on current visual line if any. - if (i - softWrapSegmentStartIndex > 0) { - drawString( - g, softWrapChars, softWrapSegmentStartIndex, i, position, clip, effectColor, effectType, fontType, fontColor - ); - } - mySoftWrapModel.paint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED, position.x, position.y, getLineHeight()); - myForceRefreshFont = true; - - // Reset 'x' coordinate because of new line start. - position.x = 0; - - // Stop the processing if we drew the whole clip. - if (position.y > clip.y + clip.height) { - break outer; - } - position.y += getLineHeight(); - softWrapSegmentStartIndex = i + 1; - } - - // Draw remaining soft wrap symbols from its last line if any. - if (softWrapSegmentStartIndex < softWrapChars.length - 1) { - position.x += drawString( - g, softWrapChars, softWrapSegmentStartIndex, softWrapChars.length, position, clip, effectColor, effectType, - fontType, fontColor - ); - } - position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight()); - myForceRefreshFont = true; - } - return position.x = drawString(g, text, startToUse, end, position, clip, effectColor, effectType, fontType, fontColor); - } - - private int drawString(Graphics g, - final char[] text, - int start, - int end, - Point position, - Rectangle clip, - Color effectColor, - EffectType effectType, - int fontType, - Color fontColor) { - if (start >= end) return position.x; - - boolean isInClip = getLineHeight() + position.y >= clip.y && position.y <= clip.y + clip.height; - - if (!isInClip) return position.x; - - int y = getLineHeight() - getDescent() + position.y; - int x = position.x; - return drawTabbedString(g, text, start, end, x, y, effectColor, effectType, fontType, fontColor, clip); - } - - private int drawString(Graphics g, - String text, - Point position, - Rectangle clip, - Color effectColor, - EffectType effectType, - int fontType, - Color fontColor) { - boolean isInClip = getLineHeight() + position.y >= clip.y && position.y <= clip.y + clip.height; - - if (!isInClip) return position.x; - - int y = getLineHeight() - getDescent() + position.y; - int x = position.x; - - return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType, fontColor, clip); - } - - private int drawTabbedString(Graphics g, - char[] text, - int start, - int end, - int x, - int y, - Color effectColor, - EffectType effectType, - int fontType, - Color fontColor, - final Rectangle clip) { - int xStart = x; - - for (int i = start; i < end; i++) { - if (text[i] != '\t') continue; - - x = drawTablessString(text, start, i, g, x, y, fontType, fontColor, clip); - - int x1 = EditorUtil.nextTabStop(x, this); - drawTabPlacer(g, y, x, x1); - x = x1; - start = i + 1; - } - - x = drawTablessString(text, start, end, g, x, y, fontType, fontColor, clip); - - if (effectColor != null) { - final Color savedColor = g.getColor(); - -// myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor); - int xEnd = x; - if (xStart < clip.x && xEnd < clip.x || xStart > clip.x + clip.width && xEnd > clip.x + clip.width) { - return x; - } - - if (xEnd > clip.x + clip.width) { - xEnd = clip.x + clip.width; - } - if (xStart < clip.x) { - xStart = clip.x; - } - - if (effectType == EffectType.LINE_UNDERSCORE) { - g.setColor(effectColor); - UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1); - g.setColor(savedColor); - } - else if (effectType == EffectType.BOLD_LINE_UNDERSCORE) { - g.setColor(effectColor); - UIUtil.drawLine(g, xStart, y, xEnd, y); - UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1); - g.setColor(savedColor); - } - else if (effectType == EffectType.STRIKEOUT) { - g.setColor(effectColor); - int y1 = y - getCharHeight() / 2; - UIUtil.drawLine(g, xStart, y1, xEnd, y1); - g.setColor(savedColor); - } - else if (effectType == EffectType.WAVE_UNDERSCORE) { - g.setColor(effectColor); - drawWave(g, xStart, xEnd, y + 1); - g.setColor(savedColor); - } - else if (effectType == EffectType.BOLD_DOTTED_LINE) { - final Color bgColor = getBackgroundColor(); - final int dottedAt = SystemInfo.isMac ? y : y + 1; - UIUtil.drawBoldDottedLine((Graphics2D)g, xStart, xEnd, dottedAt, bgColor, effectColor, false); - } - } - - return x; - } - - private int drawTablessString(final char[] text, - int start, - final int end, - final Graphics g, - int x, - final int y, - final int fontType, - final Color fontColor, - final Rectangle clip) { - int endX = x; - if (start < end) { - FontInfo font = EditorUtil.fontForChar(text[start], fontType, this); - for (int j = start; j < end; j++) { - final char c = text[j]; - FontInfo newFont = EditorUtil.fontForChar(c, fontType, this); - if (font != newFont || endX > clip.x + clip.width) { - if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) { - drawCharsCached(g, text, start, j, x, y, fontType, fontColor); - } - start = j; - x = endX; - font = newFont; - } - if (x < clip.x && endX < clip.x) { - start = j; - x = endX; - font = newFont; - } - else if (x > clip.x + clip.width) { - return endX; - } - endX += font.charWidth(c, myEditorComponent); - } - - if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) { - drawCharsCached(g, text, start, end, x, y, fontType, fontColor); - } - } - - return endX; - } - - private void drawTabPlacer(Graphics g, int y, int start, int stop) { - if (mySettings.isWhitespacesShown()) { - myTabPainter.paint(g, y, start, stop); - } - } - - private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, int fontType, Color color) { - if (!myForceRefreshFont && mySpacesHaveSameWidth && myLastCache != null && spacesOnly(data, start, end)) { - myLastCache.addContent(g, data, start, end, x, y, null); - } - else { - myForceRefreshFont = false; - FontInfo fnt = EditorUtil.fontForChar(data[start], fontType, this); - drawCharsCached(g, data, start, end, x, y, fnt, color); - } - } - - private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, FontInfo fnt, Color color) { - CachedFontContent cache = null; - for (CachedFontContent fontCache : myFontCache) { - if (fontCache.myFontType == fnt) { - cache = fontCache; - break; - } - } - if (cache == null) { - cache = new CachedFontContent(fnt); - myFontCache.add(cache); - } - - myLastCache = cache; - cache.addContent(g, data, start, end, x, y, color); - } - - private static boolean spacesOnly(char[] chars, int start, int end) { - for (int i = start; i < end; i++) { - if (chars[i] != ' ') return false; - } - return true; - } - - private void drawChars(Graphics g, char[] data, int start, int end, int x, int y) { - g.drawChars(data, start, end - start, x, y); - - if (mySettings.isWhitespacesShown()) { - Color oldColor = g.getColor(); - g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR)); - final FontMetrics metrics = g.getFontMetrics(); - int halfSpaceWidth = metrics.charWidth(' ') / 2; - for (int i = start; i < end; i++) { - if (data[i] == ' ') { - g.fillRect(x + halfSpaceWidth, y, 1, 1); - } - x += metrics.charWidth(data[i]); - } - g.setColor(oldColor); - } - } - - private static final int WAVE_HEIGHT = 2; - private static final int WAVE_SEGMENT_LENGTH = 4; - - private static void drawWave(Graphics g, int xStart, int xEnd, int y) { - int startSegment = xStart / WAVE_SEGMENT_LENGTH; - int endSegment = xEnd / WAVE_SEGMENT_LENGTH; - for (int i = startSegment; i < endSegment; i++) { - drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y); - } - - int x = WAVE_SEGMENT_LENGTH * endSegment; - UIUtil.drawLine(g, x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y); - } - - private static void drawWaveSegment(Graphics g, int x, int y) { - UIUtil.drawLine(g, x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y); - UIUtil.drawLine(g, x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT); - } - - private int getTextSegmentWidth(CharSequence text, int xStart, int fontType, Rectangle clip) { - int x = xStart; - - final int textLength = text.length(); - for (int i = 0; i < textLength && xStart < clip.x + clip.width; i++) { - if (text.charAt(i) == '\t') { - x = EditorUtil.nextTabStop(x, this); - } - else { - x += EditorUtil.charWidth(text.charAt(i), fontType, this); - } - if (x > clip.x + clip.width) { - break; - } - } - return x - xStart; - } - - public int getLineHeight() { - if (myLineHeight != -1) return myLineHeight; - - assertReadAccess(); - - FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); - myLineHeight = (int)(fontMetrics.getHeight() * (isOneLineMode() ? 1 : myScheme.getLineSpacing())); - if (myLineHeight == 0) { - myLineHeight = fontMetrics.getHeight(); - if (myLineHeight == 0) { - myLineHeight = 12; - } - } - - return myLineHeight; - } - - int getDescent() { - if (myDescent != -1) { - return myDescent; - } - FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); - myDescent = fontMetrics.getDescent(); - return myDescent; - } - - FontMetrics getFontMetrics(int fontType) { - if (myPlainFontMetrics == null) { - assertIsDispatchThread(); - myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); - myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD)); - myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC)); - myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC)); - } - - if (fontType == Font.PLAIN) return myPlainFontMetrics; - if (fontType == Font.BOLD) return myBoldFontMetrics; - if (fontType == Font.ITALIC) return myItalicFontMetrics; - if (fontType == Font.BOLD + Font.ITALIC) return myBoldItalicFontMetrics; - - LOG.error("Unknown font type: " + fontType); - - return myPlainFontMetrics; - } - - private int getCharHeight() { - if (myCharHeight == -1) { - assertIsDispatchThread(); - FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); - myCharHeight = fontMetrics.charWidth('a'); - } - return myCharHeight; - } - - public int getPreferredHeight() { - if (ourIsUnitTestMode && getUserData(DO_DOCUMENT_UPDATE_TEST) == null) { - return 1; - } - - return getHeightWithoutCaret(); - } - - public Dimension getPreferredSize() { - if (ourIsUnitTestMode && getUserData(DO_DOCUMENT_UPDATE_TEST) == null) { - return new Dimension(1, 1); - } - - final Dimension draft = getSizeWithoutCaret(); - final int additionalSpace = mySettings.getAdditionalColumnsCount() * EditorUtil.getSpaceWidth(Font.PLAIN, this); - - if (!myDocument.isInBulkUpdate()) { - int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x; - draft.width = Math.max(caretX, draft.width) + additionalSpace; - } - else { - draft.width += additionalSpace; - } - return draft; - } - - private Dimension getSizeWithoutCaret() { - Dimension size = mySizeContainer.getContentSize(); - if (isOneLineMode()) return new Dimension(size.width, getLineHeight()); - if (mySettings.isAdditionalPageAtBottom()) { - int lineHeight = getLineHeight(); - return new Dimension(size.width, size.height + Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight)); - } - - return getContentSize(); - } - - private int getHeightWithoutCaret() { - if (isOneLineMode()) return getLineHeight(); - int size = mySizeContainer.getContentHeight(); - if (mySettings.isAdditionalPageAtBottom()) { - int lineHeight = getLineHeight(); - return size + Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight); - } - - return size + mySettings.getAdditionalLinesCount() * getLineHeight(); - } - - public Dimension getContentSize() { - Dimension size = mySizeContainer.getContentSize(); - return new Dimension(size.width, size.height + mySettings.getAdditionalLinesCount() * getLineHeight()); - } - - public JScrollPane getScrollPane() { - return myScrollPane; - } - - public void setBorder(Border border) { - myScrollPane.setBorder(border); - } - - public int logicalPositionToOffset(@NotNull LogicalPosition pos) { - assertReadAccess(); - assertIsDispatchThread(); - if (myDocument.getLineCount() == 0) return 0; - - if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line); - if (pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column); - - if (pos.line >= myDocument.getLineCount()) { - return myDocument.getTextLength(); - } - - int start = myDocument.getLineStartOffset(pos.line); - int end = myDocument.getLineEndOffset(pos.line); - - CharSequence text = myDocument.getCharsNoThreadCheck(); - - if (pos.column == 0) return start; - return EditorUtil.calcOffset(this, text, start, end, pos.column, EditorUtil.getTabSize(this)); - } - - public void setLastColumnNumber(int val) { - assertIsDispatchThread(); - myLastColumnNumber = val; - } - - public int getLastColumnNumber() { - assertReadAccess(); - return myLastColumnNumber; - } - - /** - * @return information about total number of lines that can be viewed by user. I.e. this is a number of all document - * lines (considering that single logical document line may be represented on multiple visual lines because of - * soft wraps appliance) minus number of folded lines - */ - int getVisibleLineCount() { - return getVisibleLogicalLinesCount() + getSoftWrapModel().getSoftWrapsIntroducedLinesNumber(); - } - - /** - * @return number of visible logical lines. Generally, that is a total logical lines number minus number of folded lines - */ - private int getVisibleLogicalLinesCount() { - return getDocument().getLineCount() - myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1); - } - - @Override - @NotNull - public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) { - return logicalToVisualPosition(logicalPos, true); - } - - @Override - @NotNull - public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos, boolean softWrapAware) { - assertReadAccess(); - if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) { - return new VisualPosition(logicalPos.line, logicalPos.column); - } - - int offset = logicalPositionToOffset(logicalPos); - - FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset); - if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) { - if (offset < getDocument().getTextLength()) { - offset = outermostCollapsed.getStartOffset(); - LogicalPosition foldStart = offsetToLogicalPosition(offset); - return logicalToVisualPosition(foldStart); - } - else { - offset = outermostCollapsed.getEndOffset() + 3; // WTF? - } - } - - int line = logicalPos.line; - int column = logicalPos.column; - - line -= myFoldingModel.getFoldedLinesCountBefore(offset); - - FoldRegion[] topLevel = myFoldingModel.fetchTopLevel(); - LogicalPosition anchorFoldingPosition = logicalPos; - for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) { - FoldRegion region = topLevel[idx]; - if (region.isValid()) { - if (region.getDocument().getLineNumber(region.getEndOffset()) == anchorFoldingPosition.line && region.getEndOffset() <= offset) { - LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset()); - LogicalPosition foldEnd = offsetToLogicalPosition(region.getEndOffset()); - column += foldStart.column + region.getPlaceholderText().length() - foldEnd.column; - offset = region.getStartOffset(); - anchorFoldingPosition = foldStart; - } - else { - break; - } - } - } - - LOG.assertTrue(line >= 0); - - VisualPosition softWrapUnawarePosition = new VisualPosition(line, Math.max(0, column)); - if (softWrapAware) { - return mySoftWrapModel.adjustVisualPosition(logicalPos, softWrapUnawarePosition); - } - else { - return softWrapUnawarePosition; - } - } - - @Nullable - private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) { - FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel(); - - if (topLevelCollapsed == null) return null; - - int start = 0; - int end = topLevelCollapsed.length - 1; - int i = 0; - - while (start <= end) { - i = (start + end) / 2; - FoldRegion region = topLevelCollapsed[i]; - LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1); - VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd); - if (visFoldEnd.line < visual.line) { - start = i + 1; - } - else { - if (visFoldEnd.line > visual.line) { - end = i - 1; - } - else { - if (visFoldEnd.column < visual.column) { - start = i + 1; - } - else { - if (visFoldEnd.column > visual.column) { - end = i - 1; - } - else { - i--; - break; - } - } - } - } - } - - while (i >= 0 && i < topLevelCollapsed.length) { - if (topLevelCollapsed[i].isValid()) break; - i--; - } - - if (i >= 0 && i < topLevelCollapsed.length) { - FoldRegion region = topLevelCollapsed[i]; - LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1); - VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd); - if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) { - i--; - if (i >= 0) { - return topLevelCollapsed[i]; - } - else { - return null; - } - } - return region; - } - - return null; - } - - @NotNull - public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) { - return visualToLogicalPosition(visiblePos, true); - } - - @NotNull - public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos, boolean softWrapAware) { - if (softWrapAware) { - return mySoftWrapModel.visualToLogicalPosition(visiblePos); - } - assertReadAccess(); - if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column); - - int line = visiblePos.line; - int column = visiblePos.column; - - FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos); - - if (lastCollapsedBefore != null) { - LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset(), false); - VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd, false); - - line = logFoldEnd.line + visiblePos.line - visFoldEnd.line; - if (visFoldEnd.line == visiblePos.line) { - if (visiblePos.column >= visFoldEnd.column) { - column = logFoldEnd.column + visiblePos.column - visFoldEnd.column; - } - else { - return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset(), false); - } - } - } - - if (column < 0) column = 0; - - return new LogicalPosition(line, column); - } - - private int calcLogicalLineNumber(int offset) { - return calcLogicalLineNumber(offset, true); - } - - private int calcLogicalLineNumber(int offset, boolean softWrapAware) { - int textLength = myDocument.getTextLength(); - if (textLength == 0) return 0; - - if (offset > textLength || offset < 0) { - throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength); - } - - int lineIndex = myDocument.getLineNumber(offset); - LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount()); - - if (softWrapAware && getSoftWrapModel().isSoftWrappingEnabled()) { - int column = calcColumnNumber(offset, lineIndex, false); - return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(lineIndex, column), offset).line; - } - else { - return lineIndex; - } - } - - public int calcColumnNumber(int offset, int lineIndex) { - return calcColumnNumber(offset, lineIndex, true); - } - - public int calcColumnNumber(int offset, int lineIndex, boolean softWrapAware) { - if (myDocument.getTextLength() == 0) return 0; - - CharSequence text = myDocument.getCharsSequence(); - int start = myDocument.getLineStartOffset(lineIndex); - if (start == offset) return 0; - int column = EditorUtil.calcColumnNumber(this, text, start, offset, EditorUtil.getTabSize(this)); - - if (softWrapAware) { - int line = calcLogicalLineNumber(offset, false); - return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(line, column), offset).column; - } - else { - return column; - } - } - - private void moveCaretToScreenPos(int x, int y) { - if (x < 0) { - x = 0; - } - - LogicalPosition pos = xyToLogicalPosition(new Point(x, y)); - - int columnNumber = pos.column; - int lineNumber = pos.line; - int softWrapLinesBeforeTargetLogicalLine = pos.softWrapLinesBeforeCurrentLogicalLine; - int softWrapLinesOnTargetLogicalLine = pos.softWrapLinesOnCurrentLogicalLine; - int softWrapColumns = pos.softWrapColumnDiff; - - if (lineNumber < 0) { - lineNumber = 0; - columnNumber = 0; - softWrapLinesBeforeTargetLogicalLine = 0; - softWrapLinesOnTargetLogicalLine = 0; - softWrapColumns = 0; - } - - final int totalLines = myDocument.getLineCount(); - if (totalLines <= 0) { - getCaretModel().moveToOffset(0); - return; - } - - if (lineNumber >= totalLines && totalLines > 0) { - int visibleLineCount = getVisibleLineCount(); - int newY = visibleLineCount > 0 ? visibleLineNumberToYPosition(visibleLineCount - 1) : 0; - if (newY > 0 && newY == y) { - newY = visibleLineNumberToYPosition(getVisibleLogicalLinesCount()); - } - moveCaretToScreenPos(x, newY); - return; - } - - if (!mySettings.isVirtualSpace()) { - int lineEndOffset = myDocument.getLineEndOffset(lineNumber); - int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber); - if (columnNumber > lineEndColumnNumber) { - columnNumber = lineEndColumnNumber; - if (softWrapColumns != 0) { - softWrapColumns -= columnNumber - lineEndColumnNumber; - } - } - } - - if (!mySettings.isCaretInsideTabs()) { - int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber)); - CharSequence text = myDocument.getCharsSequence(); - if (offset >= 0 && offset < myDocument.getTextLength()) { - if (text.charAt(offset) == '\t') { - columnNumber = calcColumnNumber(offset, lineNumber); - } - } - } - LogicalPosition pos1 = new LogicalPosition( - lineNumber, columnNumber, softWrapLinesBeforeTargetLogicalLine, softWrapLinesOnTargetLogicalLine, softWrapColumns, - pos.foldedLines, pos.foldingColumnDiff - ); - getCaretModel().moveToLogicalPosition(pos1); - } - - private boolean checkIgnore(MouseEvent e, boolean isFinalCheck) { - if (!myIgnoreMouseEventsConsecutiveToInitial) { - myInitialMouseEvent = null; - return false; - } - - if (e.getComponent() != myInitialMouseEvent.getComponent() || !e.getPoint().equals(myInitialMouseEvent.getPoint())) { - myIgnoreMouseEventsConsecutiveToInitial = false; - myInitialMouseEvent = null; - return false; - } - - if (isFinalCheck) { - myIgnoreMouseEventsConsecutiveToInitial = false; - myInitialMouseEvent = null; - } - - e.consume(); - - return true; - } - - private void processMouseReleased(MouseEvent e) { - if (checkIgnore(e, true)) return; - - if (e.getSource() == myGutterComponent) { - myGutterComponent.mouseReleased(e); - } - - if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) { - return; - } - -// if (myMousePressedInsideSelection) getSelectionModel().removeSelection(); - final FoldRegion region = getFoldingModel().getFoldingPlaceholderAt(e.getPoint()); - if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) { - getFoldingModel().runBatchFoldingOperation(new Runnable() { - public void run() { - myFoldingModel.flushCaretShift(); - region.setExpanded(true); - } - }); - - // The call below is performed because gutter's height is not updated sometimes, i.e. it sticks to the value that corresponds - // to the situation when fold region is collapsed. That causes bottom of the gutter to not be repainted and that looks really ugly. - getGutterComponentEx().invalidate(); - } - - if (myMousePressedEvent != null && myMousePressedEvent.getClickCount() == 1 && myMousePressedInsideSelection - && !myMousePressedEvent.isShiftDown()) - { - getSelectionModel().removeSelection(); - } - } - - public DataContext getDataContext() { - return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent())); - } - - private DataContext getProjectAwareDataContext(final DataContext original) { - if (PlatformDataKeys.PROJECT.getData(original) == myProject) return original; - - return new DataContext() { - public Object getData(String dataId) { - if (PlatformDataKeys.PROJECT.is(dataId)) { - return myProject; - } - return original.getData(dataId); - } - }; - } - - - public EditorMouseEventArea getMouseEventArea(@NotNull MouseEvent e) { - if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA; - - int x = myGutterComponent.convertX(e.getX()); - - if (x >= myGutterComponent.getLineNumberAreaOffset() && - x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) { - return EditorMouseEventArea.LINE_NUMBERS_AREA; - } - - if (x >= myGutterComponent.getAnnotationsAreaOffset() && - x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) { - return EditorMouseEventArea.ANNOTATIONS_AREA; - } - - if (x >= myGutterComponent.getLineMarkerAreaOffset() && - x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) { - return EditorMouseEventArea.LINE_MARKERS_AREA; - } - - if (x >= myGutterComponent.getFoldingAreaOffset() && - x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) { - return EditorMouseEventArea.FOLDING_OUTLINE_AREA; - } - - return null; - } - - private void requestFocus() { - myEditorComponent.requestFocus(); - } - - private void validateMousePointer(MouseEvent e) { - if (e.getSource() == myGutterComponent) { - FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY()); - myGutterComponent.setActiveFoldRegion(foldingAtCursor); - if (foldingAtCursor != null) { - myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - else { - myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } - else { - myGutterComponent.setActiveFoldRegion(null); - if (getSelectionModel().hasSelection() && (e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK)) == 0) { - int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint())); - if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) { - myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return; - } - } - myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); - } - } - - private void runMouseDraggedCommand(final MouseEvent e) { - if (myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) { - return; - } - myCommandProcessor.executeCommand(myProject, new Runnable() { - public void run() { - processMouseDragged(e); - } - }, "", MOUSE_DRAGGED_GROUP, UndoConfirmationPolicy.DEFAULT, getDocument()); - } - - private void processMouseDragged(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - return; - } - Rectangle visibleArea = getScrollingModel().getVisibleArea(); - - int x = e.getX(); - - if (e.getSource() == myGutterComponent) { - x = 0; - } - - int dx = 0; - if (x < visibleArea.x && visibleArea.x > 0) { - dx = x - visibleArea.x; - } - else { - if (x > visibleArea.x + visibleArea.width) { - dx = x - visibleArea.x - visibleArea.width; - } - } - - int dy = 0; - int y = e.getY(); - if (y < visibleArea.y && visibleArea.y > 0) { - dy = y - visibleArea.y; - } - else { - if (y > visibleArea.y + visibleArea.height) { - dy = y - visibleArea.y - visibleArea.height; - } - } - if (dx == 0 && dy == 0) { - myScrollingTimer.stop(); - - SelectionModel selectionModel = getSelectionModel(); - int oldSelectionStart = selectionModel.getLeadSelectionOffset(); - int oldCaretOffset = getCaretModel().getOffset(); - LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition(); - moveCaretToScreenPos(x, y); - getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - - int newCaretOffset = getCaretModel().getOffset(); - int caretShift = newCaretOffset - mySavedSelectionStart; - - if (myMousePressedEvent != null && getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA && - getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) { - selectionModel.setSelection(oldSelectionStart, newCaretOffset); - } - else { - if (isColumnMode() || e.isAltDown()) { - final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret; - selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition()); - } - else { - if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { - if (caretShift < 0) { - int newSelection = newCaretOffset; - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { - newSelection = mySelectionModel.getWordAtCaretStart(); - } - else { - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { - newSelection = - logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))); - } - } - if (newSelection < 0) newSelection = newCaretOffset; - selectionModel.setSelection(mySavedSelectionEnd, newSelection); - getCaretModel().moveToOffset(newSelection); - } - else { - int newSelection = newCaretOffset; - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { - newSelection = mySelectionModel.getWordAtCaretEnd(); - } - else { - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { - newSelection = - logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))); - } - } - if (newSelection < 0) newSelection = newCaretOffset; - selectionModel.setSelection(mySavedSelectionStart, newSelection); - getCaretModel().moveToOffset(newSelection); - } - return; - } - - if (!myMousePressedInsideSelection) { - selectionModel.setSelection(oldSelectionStart, newCaretOffset); - } - else { - if (caretShift != 0) { - if (myMousePressedEvent != null) { - if (mySettings.isDndEnabled()) { - boolean isCopy = UIUtil.isControlKeyDown(e) || isViewer() || !getDocument().isWritable(); - mySavedCaretOffsetForDNDUndoHack = oldCaretOffset; - getContentComponent().getTransferHandler() - .exportAsDrag(getContentComponent(), e, isCopy ? TransferHandler.COPY : TransferHandler.MOVE); - } - else { - selectionModel.removeSelection(); - } - myMousePressedEvent = null; - } - } - } - } - } - } - else { - myScrollingTimer.start(dx, dy); - } - } - - private static class RepaintCursorCommand implements Runnable { - private long mySleepTime = 500; - private boolean myIsBlinkCaret = true; - private EditorImpl myEditor = null; - private final MyRepaintRunnable myRepaintRunnable; - private ScheduledFuture mySchedulerHandle; - - private RepaintCursorCommand() { - myRepaintRunnable = new MyRepaintRunnable(); - } - - private class MyRepaintRunnable implements Runnable { - public void run() { - if (myEditor != null) { - myEditor.myCaretCursor.repaint(); - } - } - } - - public void start() { - if (mySchedulerHandle != null) { - mySchedulerHandle.cancel(false); - } - mySchedulerHandle = JobScheduler.getScheduler().scheduleAtFixedRate(this, mySleepTime, mySleepTime, TimeUnit.MILLISECONDS); - } - - private void setBlinkPeriod(int blinkPeriod) { - mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10; - start(); - } - - private void setBlinkCaret(boolean value) { - myIsBlinkCaret = value; - } - - public void run() { - if (myEditor != null) { - CaretCursor activeCursor = myEditor.myCaretCursor; - - long time = System.currentTimeMillis(); - time -= activeCursor.myStartTime; - - if (time > mySleepTime) { - boolean toRepaint = true; - if (myIsBlinkCaret) { - activeCursor.myIsShown = !activeCursor.myIsShown; - } - else { - toRepaint = !activeCursor.myIsShown; - activeCursor.myIsShown = true; - } - - if (toRepaint) { - SwingUtilities.invokeLater(myRepaintRunnable); - } - } - } - } - } - - void updateCaretCursor() { - if (!ourIsUnitTestMode && !IJSwingUtilities.hasFocus(getContentComponent())) { - stopOptimizedScrolling(); - } - - myUpdateCursor = true; - } - - private void setCursorPosition() { - VisualPosition caretPosition = getCaretModel().getVisualPosition(); - Point pos1 = visualPositionToXY(caretPosition); - Point pos2 = visualPositionToXY(new VisualPosition(caretPosition.line, caretPosition.column + 1)); - myCaretCursor.setPosition(pos1, pos2.x - pos1.x); - } - - public boolean setCaretVisible(boolean b) { - boolean old = myCaretCursor.isActive(); - if (b) { - myCaretCursor.activate(); - } - else { - myCaretCursor.passivate(); - } - return old; - } - - public boolean setCaretEnabled(boolean enabled) { - boolean old = myCaretCursor.isEnabled(); - myCaretCursor.setEnabled(enabled); - return old; - } - - public void addFocusListener(FocusChangeListener listener) { - myFocusListeners.add(listener); - } - - public Project getProject() { - return myProject; - } - - public boolean isOneLineMode() { - return myIsOneLineMode; - } - - public boolean isEmbeddedIntoDialogWrapper() { - return myEmbeddedIntoDialogWrapper; - } - - public void setEmbeddedIntoDialogWrapper(boolean b) { - assertIsDispatchThread(); - - myEmbeddedIntoDialogWrapper = b; - myScrollPane.setFocusable(!b); - myEditorComponent.setFocusCycleRoot(!b); - myEditorComponent.setFocusable(b); - } - - public void setOneLineMode(boolean isOneLineMode) { - myIsOneLineMode = isOneLineMode; - getScrollPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); - reinitSettings(); - } - - public void stopOptimizedScrolling() { - myEditorComponent.setOpaque(false); - } - - private void startOptimizedScrolling() { - myEditorComponent.setOpaque(true); - } - - private class CaretCursor { - private Point myLocation; - private int myWidth; - private boolean myEnabled; - - @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) - private boolean myIsShown = false; - private long myStartTime = 0; - - private CaretCursor() { - myLocation = new Point(0, 0); - setEnabled(true); - } - - public boolean isEnabled() { - return myEnabled; - } - - public void setEnabled(boolean enabled) { - myEnabled = enabled; - } - - private void activate() { - final boolean blink = mySettings.isBlinkCaret(); - final int blinkPeriod = mySettings.getCaretBlinkPeriod(); - synchronized (ourCaretBlinkingCommand) { - ourCaretBlinkingCommand.myEditor = EditorImpl.this; - ourCaretBlinkingCommand.setBlinkCaret(blink); - ourCaretBlinkingCommand.setBlinkPeriod(blinkPeriod); - myIsShown = true; - } - } - - public boolean isActive() { - synchronized (ourCaretBlinkingCommand) { - return myIsShown; - } - } - - private void passivate() { - synchronized (ourCaretBlinkingCommand) { - myIsShown = false; - } - } - - private void setPosition(Point location, int width) { - myStartTime = System.currentTimeMillis(); - myLocation = location; - myWidth = Math.max(width, 2); - myIsShown = true; - repaint(); - } - - private void repaint() { - myEditorComponent.repaintEditorComponent(myLocation.x, myLocation.y, myWidth, getLineHeight()); - } - - private void paint(Graphics g) { - if (!isEnabled() || !myIsShown || !IJSwingUtilities.hasFocus(getContentComponent()) || isRendererMode()) return; - - int x = myLocation.x; - int lineHeight = getLineHeight(); - int y = myLocation.y; - - Rectangle viewRectangle = getScrollingModel().getVisibleArea(); - if (x - viewRectangle.x < 0) { - return; - } - - - g.setColor(myScheme.getColor(EditorColors.CARET_COLOR)); - - if (myIsInsertMode != mySettings.isBlockCursor()) { - for (int i = 0; i < mySettings.getLineCursorWidth(); i++) { - UIUtil.drawLine(g, x + i, y, x + i, y + lineHeight - 1); - } - } - else { - Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR); - if (background == null) background = getBackgroundColor(); - g.setXORMode(background); - - g.fillRect(x, y, myWidth, lineHeight - 1); - - g.setPaintMode(); - } - } - } - - private class ScrollingTimer { - Timer myTimer; - private static final int TIMER_PERIOD = 100; - private static final int CYCLE_SIZE = 20; - private int myXCycles; - private int myYCycles; - private int myDx; - private int myDy; - private int xPassedCycles = 0; - private int yPassedCycles = 0; - - private void start(int dx, int dy) { - myDx = 0; - myDy = 0; - if (dx > 0) { - myXCycles = CYCLE_SIZE / dx + 1; - myDx = 1 + dx / CYCLE_SIZE; - } - else { - if (dx < 0) { - myXCycles = -CYCLE_SIZE / dx + 1; - myDx = -1 + dx / CYCLE_SIZE; - } - } - - if (dy > 0) { - myYCycles = CYCLE_SIZE / dy + 1; - myDy = 1 + dy / CYCLE_SIZE; - } - else { - if (dy < 0) { - myYCycles = -CYCLE_SIZE / dy + 1; - myDy = -1 + dy / CYCLE_SIZE; - } - } - - if (myTimer != null) { - return; - } - - - myTimer = new Timer(TIMER_PERIOD, new ActionListener() { - public void actionPerformed(ActionEvent e) { - myCommandProcessor.executeCommand(myProject, new DocumentRunnable(myDocument, myProject) { - public void run() { - int oldSelectionStart = mySelectionModel.getLeadSelectionOffset(); - VisualPosition caretPosition = getCaretModel().getVisualPosition(); - int columnNumber = caretPosition.column; - xPassedCycles++; - if (xPassedCycles >= myXCycles) { - xPassedCycles = 0; - columnNumber += myDx; - } - - int lineNumber = caretPosition.line; - yPassedCycles++; - if (yPassedCycles >= myYCycles) { - yPassedCycles = 0; - lineNumber += myDy; - } - - lineNumber = Math.max(0, lineNumber); - columnNumber = Math.max(0, columnNumber); - VisualPosition pos = new VisualPosition(lineNumber, columnNumber); - getCaretModel().moveToVisualPosition(pos); - getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - - int newCaretOffset = getCaretModel().getOffset(); - int caretShift = newCaretOffset - mySavedSelectionStart; - - if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { - if (caretShift < 0) { - int newSelection = newCaretOffset; - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { - newSelection = mySelectionModel.getWordAtCaretStart(); - } - else { - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { - newSelection = - logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))); - } - } - if (newSelection < 0) newSelection = newCaretOffset; - mySelectionModel.setSelection(validateOffset(mySavedSelectionEnd), newSelection); - getCaretModel().moveToOffset(newSelection); - } - else { - int newSelection = newCaretOffset; - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { - newSelection = mySelectionModel.getWordAtCaretEnd(); - } - else { - if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { - newSelection = logicalPositionToOffset( - visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))); - } - } - if (newSelection < 0) newSelection = newCaretOffset; - mySelectionModel.setSelection(validateOffset(mySavedSelectionStart), newSelection); - getCaretModel().moveToOffset(newSelection); - } - return; - } - - if (mySelectionModel.hasBlockSelection()) { - mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition()); - } - else { - mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset()); - } - } - }, EditorBundle.message("move.cursor.command.name"), DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument()); - } - }); - myTimer.start(); - } - - private void stop() { - if (myTimer != null) { - myTimer.stop(); - myTimer = null; - } - } - - private int validateOffset(int offset) { - if (offset < 0) return 0; - if (offset > myDocument.getTextLength()) return myDocument.getTextLength(); - return offset; - } - } - - private static final Field decrButtonField; - private static final Field incrButtonField; - static { - try { - decrButtonField = BasicScrollBarUI.class.getDeclaredField("decrButton"); - decrButtonField.setAccessible(true); - - incrButtonField = BasicScrollBarUI.class.getDeclaredField("incrButton"); - incrButtonField.setAccessible(true); - } - catch (NoSuchFieldException e) { - throw new IllegalStateException(e); - } - } - - class MyScrollBar extends JBScrollBar { - @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS = "apple.laf.AquaScrollBarUI"; - private ScrollBarUI myPersistentUI; - - private MyScrollBar(int orientation) { - super(orientation); - } - - void setPersistentUI(ScrollBarUI ui) { - myPersistentUI = ui; - setUI(ui); - } - - @Override - public void setUI(ScrollBarUI ui) { - if (myPersistentUI == null) myPersistentUI = ui; - super.setUI(myPersistentUI); - } - - /** - * This is helper method. It returns height of the top (decrease) scroll bar - * button. Please note, that it's possible to return real height only if scroll bar - * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) - * value. - */ - int getDecScrollButtonHeight() { - ScrollBarUI barUI = getUI(); - Insets insets = getInsets(); - if (barUI instanceof BasicScrollBarUI) { - try { - JButton decrButtonValue = (JButton)decrButtonField.get(barUI); - LOG.assertTrue(decrButtonValue != null); - return insets.top + decrButtonValue.getHeight(); - } - catch (Exception exc) { - throw new IllegalStateException(exc); - } - } - else { - return insets.top + 15; - } - } - - /** - * This is helper method. It returns height of the bottom (increase) scroll bar - * button. Please note, that it's possible to return real height only if scroll bar - * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) - * value. - */ - int getIncScrollButtonHeight() { - ScrollBarUI barUI = getUI(); - Insets insets = getInsets(); - if (barUI instanceof BasicScrollBarUI) { - try { - JButton incrButtonValue = (JButton)incrButtonField.get(barUI); - LOG.assertTrue(incrButtonValue != null); - return insets.bottom + incrButtonValue.getHeight(); - } - catch (Exception exc) { - throw new IllegalStateException(exc.getMessage()); - } - } - else if (APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS.equals(barUI.getClass().getName())) { - return insets.bottom + 30; - } - else { - return insets.bottom + 15; - } - } - - public int getUnitIncrement(int direction) { - JViewport vp = myScrollPane.getViewport(); - Rectangle vr = vp.getViewRect(); - return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction); - } - - public int getBlockIncrement(int direction) { - JViewport vp = myScrollPane.getViewport(); - Rectangle vr = vp.getViewRect(); - return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction); - } - } - - private MyEditable getViewer() { - if (myEditable == null) { - myEditable = new MyEditable(); - } - return myEditable; - } - - public CopyProvider getCopyProvider() { - return getViewer(); - } - - public CutProvider getCutProvider() { - return getViewer(); - } - - public PasteProvider getPasteProvider() { - - return getViewer(); - } - - public DeleteProvider getDeleteProvider() { - return getViewer(); - } - - private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider { - public void performCopy(DataContext dataContext) { - executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext); - } - - public boolean isCopyEnabled(DataContext dataContext) { - return true; - } - - public boolean isCopyVisible(DataContext dataContext) { - return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection(); - } - - public void performCut(DataContext dataContext) { - executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext); - } - - public boolean isCutEnabled(DataContext dataContext) { - return !isViewer() && getDocument().isWritable(); - } - - public boolean isCutVisible(DataContext dataContext) { - return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection(); - } - - public void performPaste(DataContext dataContext) { - executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext); - } - - public boolean isPastePossible(DataContext dataContext) { - // Copy of isPasteEnabled. See interface method javadoc. - return !isViewer() && getDocument().isWritable(); - } - - public boolean isPasteEnabled(DataContext dataContext) { - return !isViewer() && getDocument().isWritable(); - } - - public void deleteElement(DataContext dataContext) { - executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext); - } - - public boolean canDeleteElement(DataContext dataContext) { - return !isViewer() && getDocument().isWritable(); - } - - private void executeAction(String actionId, DataContext dataContext) { - EditorAction action = (EditorAction)ActionManager.getInstance().getAction(actionId); - if (action != null) { - action.actionPerformed(EditorImpl.this, dataContext); - } - } - } - - public void setColorsScheme(@NotNull EditorColorsScheme scheme) { - assertIsDispatchThread(); - myScheme = scheme; - reinitSettings(); - } - - @NotNull - public EditorColorsScheme getColorsScheme() { - assertReadAccess(); - return myScheme; - } - - void assertIsDispatchThread() { - ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(myEditorComponent); - } - private static void assertReadAccess() { - ApplicationManagerEx.getApplicationEx().assertReadAccessAllowed(); - } - - public void setVerticalScrollbarOrientation(int type) { - assertIsDispatchThread(); - int currentHorOffset = myScrollingModel.getHorizontalScrollOffset(); - myScrollBarOrientation = type; - if (type == VERTICAL_SCROLLBAR_LEFT) { - myScrollPane.setLayout(new LeftHandScrollbarLayout()); - } - else { - myScrollPane.setLayout(new ScrollPaneLayout()); - } - myScrollingModel.scrollHorizontally(currentHorOffset); - } - - public void setVerticalScrollbarVisible(boolean b) { - if (b) { - myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - } - else { - myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); - } - } - - public void setHorizontalScrollbarVisible(boolean b) { - if (b) { - myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - } - else { - myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - } - } - - int getVerticalScrollbarOrientation() { - return myScrollBarOrientation; - } - - MyScrollBar getVerticalScrollBar() { - return myVerticalScrollBar; - } - - private int getMouseSelectionState() { - return myMouseSelectionState; - } - - private void setMouseSelectionState(int mouseSelectionState) { - myMouseSelectionState = mouseSelectionState; - myMouseSelectionChangeTimestamp = System.currentTimeMillis(); - } - - - void replaceInputMethodText(InputMethodEvent e) { - getInputMethodRequests(); - myInputMethodRequestsHandler.replaceInputMethodText(e); - } - - void inputMethodCaretPositionChanged(InputMethodEvent e) { - getInputMethodRequests(); - myInputMethodRequestsHandler.setInputMethodCaretPosition(e); - } - - InputMethodRequests getInputMethodRequests() { - if (myInputMethodRequestsHandler == null) { - myInputMethodRequestsHandler = new MyInputMethodHandler(); - myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler); - } - return myInputMethodRequestsSwingWrapper; - } - - public boolean processKeyTyped(KeyEvent e) { - if (e.getID() != KeyEvent.KEY_TYPED) return false; - char c = e.getKeyChar(); - if (UIUtil.isReallyTypedEvent(e)) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction - processKeyTyped(c); - return true; - } - else { - return false; - } - } - - void beforeModalityStateChanged() { - myScrollingModel.beforeModalityStateChanged(); - } - - public EditorDropHandler getDropHandler() { - return myDropHandler; - } - - public void setDropHandler(EditorDropHandler dropHandler) { - myDropHandler = dropHandler; - } - - private static class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests { - private final InputMethodRequests myDelegate; - - private MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) { - myDelegate = delegate; - } - - public Rectangle getTextLocation(final TextHitInfo offset) { - if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getTextLocation(offset); - - final Rectangle[] r = new Rectangle[1]; - try { - GuiUtils.invokeAndWait(new Runnable() { - public void run() { - r[0] = myDelegate.getTextLocation(offset); - } - }); - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (InvocationTargetException e) { - LOG.error(e); - } - return r[0]; - } - - public TextHitInfo getLocationOffset(final int x, final int y) { - if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getLocationOffset(x, y); - - final TextHitInfo[] r = new TextHitInfo[1]; - try { - GuiUtils.invokeAndWait(new Runnable() { - public void run() { - r[0] = myDelegate.getLocationOffset(x, y); - } - }); - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (InvocationTargetException e) { - LOG.error(e); - } - return r[0]; - } - - public int getInsertPositionOffset() { - if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getInsertPositionOffset(); - - final int[] r = new int[1]; - try { - GuiUtils.invokeAndWait(new Runnable() { - public void run() { - r[0] = myDelegate.getInsertPositionOffset(); - } - }); - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (InvocationTargetException e) { - LOG.error(e); - } - return r[0]; - } - - public AttributedCharacterIterator getCommittedText(final int beginIndex, - final int endIndex, - final AttributedCharacterIterator.Attribute[] attributes) { - if (ApplicationManager.getApplication().isDispatchThread()) { - return myDelegate.getCommittedText(beginIndex, endIndex, attributes); - } - final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1]; - try { - GuiUtils.invokeAndWait(new Runnable() { - public void run() { - r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes); - } - }); - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (InvocationTargetException e) { - LOG.error(e); - } - return r[0]; - } - - public int getCommittedTextLength() { - if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getCommittedTextLength(); - final int[] r = new int[1]; - try { - GuiUtils.invokeAndWait(new Runnable() { - public void run() { - r[0] = myDelegate.getCommittedTextLength(); - } - }); - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (InvocationTargetException e) { - LOG.error(e); - } - return r[0]; - } - - @Nullable - public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) { - return null; - } - - public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) { - if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getSelectedText(attributes); - - final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1]; - try { - GuiUtils.invokeAndWait(new Runnable() { - public void run() { - r[0] = myDelegate.getSelectedText(attributes); - } - }); - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (InvocationTargetException e) { - LOG.error(e); - } - return r[0]; - } - } - - private class MyInputMethodHandler implements InputMethodRequests { - private String composedText; - private int composedTextStart; - private int composedTextEnd; - - public Rectangle getTextLocation(TextHitInfo offset) { - Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition()); - Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight())); - Point p = getContentComponent().getLocationOnScreen(); - r.translate(p.x, p.y); - - return r; - } - - @Nullable - public TextHitInfo getLocationOffset(int x, int y) { - if (composedText != null) { - Point p = getContentComponent().getLocationOnScreen(); - p.x = x - p.x; - p.y = y - p.y; - int pos = logicalPositionToOffset(xyToLogicalPosition(p)); - if (pos >= composedTextStart && pos <= composedTextEnd) { - return TextHitInfo.leading(pos - composedTextStart); - } - } - return null; - } - - public int getInsertPositionOffset() { - int composedStartIndex = 0; - int composedEndIndex = 0; - if (composedText != null) { - composedStartIndex = composedTextStart; - composedEndIndex = composedTextEnd; - } - - int caretIndex = getCaretModel().getOffset(); - - if (caretIndex < composedStartIndex) { - return caretIndex; - } - else { - if (caretIndex < composedEndIndex) { - return composedStartIndex; - } - else { - return caretIndex - (composedEndIndex - composedStartIndex); - } - } - } - - private String getText(int startIdx, int endIdx) { - CharSequence chars = getDocument().getCharsSequence(); - return chars.subSequence(startIdx, endIdx).toString(); - } - - public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex, AttributedCharacterIterator.Attribute[] attributes) { - int composedStartIndex = 0; - int composedEndIndex = 0; - if (composedText != null) { - composedStartIndex = composedTextStart; - composedEndIndex = composedTextEnd; - } - - String committed; - if (beginIndex < composedStartIndex) { - if (endIndex <= composedStartIndex) { - committed = getText(beginIndex, endIndex - beginIndex); - } - else { - int firstPartLength = composedStartIndex - beginIndex; - committed = getText(beginIndex, firstPartLength) + getText(composedEndIndex, endIndex - beginIndex - firstPartLength); - } - } - else { - committed = getText(beginIndex + composedEndIndex - composedStartIndex, endIndex - beginIndex); - } - - return new AttributedString(committed).getIterator(); - } - - public int getCommittedTextLength() { - int length = getDocument().getTextLength(); - if (composedText != null) { - length -= composedText.length(); - } - return length; - } - - @Nullable - public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) { - return null; - } - - @Nullable - public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) { - String text = getSelectionModel().getSelectedText(); - return text == null ? null : new AttributedString(text).getIterator(); - } - - private void createComposedString(int composedIndex, AttributedCharacterIterator text) { - StringBuffer strBuf = new StringBuffer(); - - // create attributed string with no attributes - for (char c = text.setIndex(composedIndex); c != CharacterIterator.DONE; c = text.next()) { - strBuf.append(c); - } - - composedText = new String(strBuf); - } - - private void setInputMethodCaretPosition(InputMethodEvent e) { - if (composedText != null) { - int dot = composedTextStart; - - TextHitInfo caretPos = e.getCaret(); - if (caretPos != null) { - dot += caretPos.getInsertionIndex(); - } - - getCaretModel().moveToOffset(dot); - getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - } - - private void runUndoTransparent(final Runnable runnable) { - CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { - public void run() { - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(runnable); - } - }, "", getDocument(), UndoConfirmationPolicy.DEFAULT, getDocument()); - } - }); - } - - private void replaceInputMethodText(InputMethodEvent e) { - int commitCount = e.getCommittedCharacterCount(); - AttributedCharacterIterator text = e.getText(); - - // old composed text deletion - final Document doc = getDocument(); - - if (composedText != null) { - if (!isViewer() && doc.isWritable()) { - runUndoTransparent(new Runnable() { - public void run() { - doc.deleteString(Math.max(0, composedTextStart), Math.min(composedTextEnd, doc.getTextLength())); - } - }); - } - composedText = null; - } - - if (text != null) { - text.first(); - - // committed text insertion - if (commitCount > 0) { - //noinspection ForLoopThatDoesntUseLoopVariable - for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) { - if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction - processKeyTyped(c); - } - } - } - - // new composed text insertion - if (!isViewer() && doc.isWritable()) { - int composedTextIndex = text.getIndex(); - if (composedTextIndex < text.getEndIndex()) { - createComposedString(composedTextIndex, text); - - runUndoTransparent(new Runnable() { - public void run() { - EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false); - } - }); - - composedTextStart = getCaretModel().getOffset(); - composedTextEnd = getCaretModel().getOffset() + composedText.length(); - } - } - } - } - } - - private class MyMouseAdapter extends MouseAdapter { - - private boolean mySelectionTweaked; - - public void mousePressed(MouseEvent e) { - requestFocus(); - runMousePressedCommand(e); - } - - public void mouseReleased(MouseEvent e) { - runMouseReleasedCommand(e); - if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() && - Math.abs(e.getX() - myMousePressedEvent.getX()) < EditorUtil.getSpaceWidth(Font.PLAIN, EditorImpl.this) && - Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) { - runMouseClickedCommand(e); - } - myMousePressedEvent = null; - } - - public void mouseEntered(MouseEvent e) { - runMouseEnteredCommand(e); - } - - public void mouseExited(MouseEvent e) { - runMouseExitedCommand(e); - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) { - myGutterComponent.mouseExited(e); - } - - TooltipController.getInstance().cancelTooltip(FOLDING_TOOLTIP_GROUP, e, true); - } - private void runMousePressedCommand(final MouseEvent e) { - mySelectionTweaked = false; - myMousePressedEvent = e; - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - - for (EditorMouseListener mouseListener : myMouseListeners) { - mouseListener.mousePressed(event); - } - - // On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event. - // But this trigger is always consumed by popup handler. In that case we have to - // also move caret. - if (event.isConsumed() && !(event.getMouseEvent().isPopupTrigger() || event.getArea() == EditorMouseEventArea.EDITING_AREA)) { - return; - } - - if (myCommandProcessor != null) { - Runnable runnable = new Runnable() { - public void run() { - processMousePressed(e); - } - }; - myCommandProcessor.executeCommand(myProject, runnable, "", DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument()); - } - else { - processMousePressed(e); - } - } - - private void runMouseClickedCommand(final MouseEvent e) { - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - for (EditorMouseListener listener : myMouseListeners) { - listener.mouseClicked(event); - if (event.isConsumed()) { - e.consume(); - return; - } - } - } - - private void runMouseReleasedCommand(final MouseEvent e) { - if (!mySelectionTweaked) { - tweakSelectionIfNecessary(e); - } - if (e.isConsumed()) { - return; - } - - myScrollingTimer.stop(); - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - for (EditorMouseListener listener : myMouseListeners) { - listener.mouseReleased(event); - if (event.isConsumed()) { - e.consume(); - return; - } - } - - if (myCommandProcessor != null) { - Runnable runnable = new Runnable() { - public void run() { - processMouseReleased(e); - } - }; - myCommandProcessor.executeCommand(myProject, runnable, "", DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument()); - } - else { - processMouseReleased(e); - } - } - - private void runMouseEnteredCommand(MouseEvent e) { - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - for (EditorMouseListener listener : myMouseListeners) { - listener.mouseEntered(event); - if (event.isConsumed()) { - e.consume(); - return; - } - } - } - - private void runMouseExitedCommand(MouseEvent e) { - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - for (EditorMouseListener listener : myMouseListeners) { - listener.mouseExited(event); - if (event.isConsumed()) { - e.consume(); - return; - } - } - } - - private void processMousePressed(MouseEvent e) { - myInitialMouseEvent = e; - - if (myMouseSelectionState != MOUSE_SELECTION_STATE_NONE && System.currentTimeMillis() - myMouseSelectionChangeTimestamp > 1000) { - setMouseSelectionState(MOUSE_SELECTION_STATE_NONE); - } - - int x = e.getX(); - int y = e.getY(); - - if (x < 0) x = 0; - if (y < 0) y = 0; - - final EditorMouseEventArea eventArea = getMouseEventArea(e); - if (eventArea == EditorMouseEventArea.FOLDING_OUTLINE_AREA) { - final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y); - if (range != null) { - final boolean expansion = !range.isExpanded(); - - int scrollShift = y - getScrollingModel().getVerticalScrollOffset(); - Runnable processor = new Runnable() { - public void run() { - myFoldingModel.flushCaretShift(); - range.setExpanded(expansion); - } - }; - getFoldingModel().runBatchFoldingOperation(processor); - y = myGutterComponent.getHeadCenterY(range); - getScrollingModel().scrollVertically(y - scrollShift); - return; - } - } - - if (e.getSource() == myGutterComponent) { - if (eventArea == EditorMouseEventArea.LINE_MARKERS_AREA || eventArea == EditorMouseEventArea.ANNOTATIONS_AREA || eventArea == EditorMouseEventArea.LINE_NUMBERS_AREA) { - if (tweakSelectionIfNecessary(e)) { - mySelectionTweaked = true; - } - else { - myGutterComponent.mousePressed(e); - } - if (e.isConsumed()) return; - } - x = 0; - } - - int oldSelectionStart = mySelectionModel.getLeadSelectionOffset(); - moveCaretToScreenPos(x, y); - - if (e.isPopupTrigger()) return; - - requestFocus(); - - int caretOffset = getCaretModel().getOffset(); - - myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y)); - myMousePressedInsideSelection = mySelectionModel.hasSelection() && caretOffset >= mySelectionModel.getSelectionStart() && - caretOffset <= mySelectionModel.getSelectionEnd(); - - if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) { - int[] starts = mySelectionModel.getBlockSelectionStarts(); - int[] ends = mySelectionModel.getBlockSelectionEnds(); - for (int i = 0; i < starts.length; i++) { - if (caretOffset >= starts[i] && caretOffset < ends[i]) { - myMousePressedInsideSelection = true; - break; - } - } - } - - if (getMouseEventArea(e) == EditorMouseEventArea.LINE_NUMBERS_AREA && e.getClickCount() == 1) { - mySelectionModel.selectLineAtCaret(); - setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED); - mySavedSelectionStart = mySelectionModel.getSelectionStart(); - mySavedSelectionEnd = mySelectionModel.getSelectionEnd(); - return; - } - - if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) { - if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { - if (caretOffset < mySavedSelectionStart) { - mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset); - } - else { - mySelectionModel.setSelection(mySavedSelectionStart, caretOffset); - } - } - else { - mySelectionModel.setSelection(oldSelectionStart, caretOffset); - } - } - else { - if (!myMousePressedInsideSelection && (getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection())) { - setMouseSelectionState(MOUSE_SELECTION_STATE_NONE); - mySelectionModel.setSelection(caretOffset, caretOffset); - } - else { - if (!e.isPopupTrigger()) { - switch (e.getClickCount()) { - case 2: - mySelectionModel.selectWordAtCaret(mySettings.isMouseClickSelectionHonorsCamelWords()); - setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED); - mySavedSelectionStart = mySelectionModel.getSelectionStart(); - mySavedSelectionEnd = mySelectionModel.getSelectionEnd(); - getCaretModel().moveToOffset(mySavedSelectionEnd); - break; - - case 3: - mySelectionModel.selectLineAtCaret(); - setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED); - mySavedSelectionStart = mySelectionModel.getSelectionStart(); - mySavedSelectionEnd = mySelectionModel.getSelectionEnd(); - break; - } - } - } - } - } - } - - /** - * Allows to answer if given event should tweak editor selection. - * - * @param e event for occurred mouse action - * @return true if action that produces given event will trigger editor selection change; false otherwise - */ - private boolean tweakSelectionEvent(@NotNull MouseEvent e) { - return getSelectionModel().hasSelection() && e.getButton() == MouseEvent.BUTTON1 && e.isShiftDown() - && e.getComponent() == getGutterComponentEx(); - } - - /** - * Checks if editor selection should be changed because of click at the given point at gutter and proceeds if necessary. - *

- * The main idea is that selection can be changed during left mouse clicks with hold Shift button and we want - * to distinguish that situation from 'close line numbers gutter' action (activated by 'Shift+click' - * on gutter area). - * - * @param e event for mouse click on gutter area - * @return true if editor's selection is changed because of the click; false otherwise - */ - private boolean tweakSelectionIfNecessary(@NotNull MouseEvent e) { - if (!tweakSelectionEvent(e)) { - return false; - } - - int startSelectionOffset = getSelectionModel().getSelectionStart(); - int startVisLine = offsetToVisualLine(startSelectionOffset); - - int endSelectionOffset = getSelectionModel().getSelectionEnd(); - int endVisLine = offsetToVisualLine(endSelectionOffset - 1); - - int clickVisLine = xyToVisualPosition(e.getPoint()).line; - - if (clickVisLine < startVisLine) { - // Expand selection at backward direction. - int startOffset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(clickVisLine, 0))); - getSelectionModel().setSelection(startOffset, endSelectionOffset); - getCaretModel().moveToOffset(startOffset); - } - else if (clickVisLine > endVisLine) { - // Expand selection at forward direction. - int endLineOffset = EditorUtil.getVisualLineEndOffset(this, clickVisLine); - getSelectionModel().setSelection(getSelectionModel().getSelectionStart(), endLineOffset); - getCaretModel().moveToOffset(endLineOffset, true); - } - else if (startVisLine == endVisLine) { - // Remove selection - getSelectionModel().removeSelection(); - } - else { - // Reduce selection in backward direction. - if (getSelectionModel().getLeadSelectionOffset() == endSelectionOffset) { - if (clickVisLine == startVisLine) { - clickVisLine++; - } - int startOffset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(clickVisLine, 0))); - getSelectionModel().setSelection(startOffset, endSelectionOffset); - getCaretModel().moveToOffset(startOffset); - } - else { - // Reduce selection is forward direction. - if (clickVisLine == endVisLine) { - clickVisLine--; - } - int endLineOffset = EditorUtil.getVisualLineEndOffset(this, clickVisLine); - getSelectionModel().setSelection(startSelectionOffset, endLineOffset); - getCaretModel().moveToOffset(endLineOffset); - } - } - e.consume(); - return true; - } - - private static final TooltipGroup FOLDING_TOOLTIP_GROUP = new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10); - - private class MyMouseMotionListener implements MouseMotionListener { - public void mouseDragged(MouseEvent e) { - validateMousePointer(e); - runMouseDraggedCommand(e); - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) { - myGutterComponent.mouseDragged(e); - } - - for (EditorMouseMotionListener listener : myMouseMotionListeners) { - listener.mouseDragged(event); - } - } - - public void mouseMoved(MouseEvent e) { - validateMousePointer(e); - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); - if (e.getSource() == myGutterComponent) { - myGutterComponent.mouseMoved(e); - } - - if (event.getArea() == EditorMouseEventArea.EDITING_AREA) { - FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint()); - TooltipController controller = TooltipController.getInstance(); - if (fold != null) { - DocumentFragment range = createDocumentFragment(fold); - final Point p = - SwingUtilities.convertPoint((Component)e.getSource(), e.getPoint(), getComponent().getRootPane().getLayeredPane()); - controller.showTooltip(EditorImpl.this, p, new DocumentFragmentTooltipRenderer(range), false, FOLDING_TOOLTIP_GROUP); - } - else { - controller.cancelTooltip(FOLDING_TOOLTIP_GROUP, e, true); - } - } - - for (EditorMouseMotionListener listener : myMouseMotionListeners) { - listener.mouseMoved(event); - } - } - - private DocumentFragment createDocumentFragment(FoldRegion fold) { - final FoldingGroup group = fold.getGroup(); - final int foldStart = fold.getStartOffset(); - if (group != null) { - final int endOffset = myFoldingModel.getEndOffset(group); - if (offsetToVisualLine(endOffset) == offsetToVisualLine(foldStart)) { - return new DocumentFragment(myDocument, foldStart, endOffset); - } - } - - final int oldEnd = fold.getEndOffset(); - return new DocumentFragment(myDocument, foldStart, oldEnd); - } - } - - private class MyColorSchemeDelegate implements EditorColorsScheme { - private final HashMap myOwnAttributes = new HashMap(); - private final HashMap myOwnColors = new HashMap(); - private final EditorColorsScheme myCustomGlobalScheme; - private Map myFontsMap = null; - private int myFontSize = -1; - private String myFaceName = null; - private EditorColorsScheme myGlobalScheme; - - private MyColorSchemeDelegate(@Nullable final EditorColorsScheme globalScheme) { - myCustomGlobalScheme = globalScheme; - updateGlobalScheme(); - } - - private EditorColorsScheme getGlobal() { - return myGlobalScheme; - } - - public String getName() { - return getGlobal().getName(); - } - - - protected void initFonts() { - String editorFontName = getEditorFontName(); - int editorFontSize = getEditorFontSize(); - - myFontsMap = new EnumMap(EditorFontType.class); - - Font plainFont = new Font(editorFontName, Font.PLAIN, editorFontSize); - Font boldFont = new Font(editorFontName, Font.BOLD, editorFontSize); - Font italicFont = new Font(editorFontName, Font.ITALIC, editorFontSize); - Font boldItalicFont = new Font(editorFontName, Font.BOLD + Font.ITALIC, editorFontSize); - - myFontsMap.put(EditorFontType.PLAIN, plainFont); - myFontsMap.put(EditorFontType.BOLD, boldFont); - myFontsMap.put(EditorFontType.ITALIC, italicFont); - myFontsMap.put(EditorFontType.BOLD_ITALIC, boldItalicFont); - - reinitSettings(); - } - - public void setName(String name) { - getGlobal().setName(name); - } - - public TextAttributes getAttributes(TextAttributesKey key) { - if (myOwnAttributes.containsKey(key)) return myOwnAttributes.get(key); - return getGlobal().getAttributes(key); - } - - public void setAttributes(TextAttributesKey key, TextAttributes attributes) { - myOwnAttributes.put(key, attributes); - } - - public Color getDefaultBackground() { - return getGlobal().getDefaultBackground(); - } - - public Color getDefaultForeground() { - return getGlobal().getDefaultForeground(); - } - - public Color getColor(ColorKey key) { - if (myOwnColors.containsKey(key)) return myOwnColors.get(key); - return getGlobal().getColor(key); - } - - public void setColor(ColorKey key, Color color) { - myOwnColors.put(key, color); - - // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit - // settings in this case. - myCaretModel.reinitSettings(); - mySelectionModel.reinitSettings(); - } - - public int getEditorFontSize() { - if (myFontSize == -1) { - return getGlobal().getEditorFontSize(); - } - return myFontSize; - } - - public void setEditorFontSize(int fontSize) { - if (fontSize < 8) fontSize = 8; - if (fontSize > 20) fontSize = 20; - myFontSize = fontSize; - initFonts(); - } - - public String getEditorFontName() { - if (myFaceName == null) { - return getGlobal().getEditorFontName(); - } - return myFaceName; - } - - public void setEditorFontName(String fontName) { - myFaceName = fontName; - initFonts(); - } - - public Font getFont(EditorFontType key) { - if (myFontsMap != null) { - Font font = myFontsMap.get(key); - if (font != null) return font; - } - return getGlobal().getFont(key); - } - - public void setFont(EditorFontType key, Font font) { - if (myFontsMap == null) { - initFonts(); - } - myFontsMap.put(key, font); - reinitSettings(); - } - - public float getLineSpacing() { - return getGlobal().getLineSpacing(); - } - - public void setLineSpacing(float lineSpacing) { - getGlobal().setLineSpacing(lineSpacing); - } - - @Nullable - public Object clone() { - return null; - } - - public void readExternal(Element element) throws InvalidDataException { - } - - public void writeExternal(Element element) throws WriteExternalException { - } - - public void updateGlobalScheme() { - myGlobalScheme = myCustomGlobalScheme != null ? myCustomGlobalScheme - : EditorColorsManager.getInstance().getGlobalScheme(); - } - } - - private static class MyTransferHandler extends TransferHandler { - private RangeMarker myDraggedRange = null; - - private static Editor getEditor(JComponent comp) { - EditorComponentImpl editorComponent = (EditorComponentImpl)comp; - return editorComponent.getEditor(); - } - - public boolean importData(final JComponent comp, final Transferable t) { - final EditorImpl editor = (EditorImpl)getEditor(comp); - - final EditorDropHandler dropHandler = editor.getDropHandler(); - if (dropHandler != null && dropHandler.canHandleDrop(t.getTransferDataFlavors())) { - dropHandler.handleDrop(t, editor.getProject()); - return true; - } - - final int caretOffset = editor.getCaretModel().getOffset(); - if (myDraggedRange != null && myDraggedRange.getStartOffset() <= caretOffset && caretOffset < myDraggedRange.getEndOffset()) { - return false; - } - - if (myDraggedRange != null) { - editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack); - } - - CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - editor.getSelectionModel().removeSelection(); - final int offset; - if (myDraggedRange != null) { - editor.getCaretModel().moveToOffset(caretOffset); - offset = caretOffset; - } - else { - offset = editor.getCaretModel().getOffset(); - } - if (editor.getDocument().getRangeGuard(offset, offset) != null) return; - - EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE); - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - - Transferable backup = null; - try { - backup = clipboard.getContents(this); - clipboard.setContents(t, EmptyClipboardOwner.INSTANCE); - } - catch (Exception e) { - LOG.info("Error communicating with system clipboard", e); - } - - editor.putUserData(LAST_PASTED_REGION, null); - pasteHandler.execute(editor, editor.getDataContext()); - try { - if (backup != null) { - clipboard.setContents(backup, EmptyClipboardOwner.INSTANCE); - } - } - catch (IllegalStateException e) { - LOG.info(e); - } - - TextRange range = editor.getUserData(LAST_PASTED_REGION); - if (range != null) { - editor.getCaretModel().moveToOffset(range.getStartOffset()); - editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()); - } - } - catch (Exception exception) { - LOG.error(exception); - } - } - }); - } - }, EditorBundle.message("paste.command.name"), DND_COMMAND_KEY, UndoConfirmationPolicy.DEFAULT, editor.getDocument()); - - return true; - } - - public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { - Editor editor = getEditor(comp); - final EditorDropHandler dropHandler = ((EditorImpl)editor).getDropHandler(); - if (dropHandler != null && dropHandler.canHandleDrop(transferFlavors)) { - return true; - } - if (editor.isViewer()) return false; - - int offset = editor.getCaretModel().getOffset(); - if (editor.getDocument().getRangeGuard(offset, offset) != null) return false; - - for (DataFlavor transferFlavor : transferFlavors) { - if (transferFlavor.equals(DataFlavor.stringFlavor)) return true; - } - - return false; - } - - @Nullable - protected Transferable createTransferable(JComponent c) { - Editor editor = getEditor(c); - String s = editor.getSelectionModel().getSelectedText(); - if (s == null) return null; - int selectionStart = editor.getSelectionModel().getSelectionStart(); - int selectionEnd = editor.getSelectionModel().getSelectionEnd(); - myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd); - - return new StringSelection(s); - } - - public int getSourceActions(JComponent c) { - return COPY_OR_MOVE; - } - - protected void exportDone(final JComponent source, Transferable data, int action) { - if (data == null) return; - - final Component last = DnDManager.getInstance().getLastDropHandler(); - - if (last != null && !(last instanceof EditorComponentImpl)) return; - - final Editor editor = getEditor(source); - if (action == MOVE && !editor.isViewer()) { - if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), editor.getProject())) { - return; - } - CommandProcessor.getInstance().executeCommand(((EditorImpl)editor).myProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - Document doc = editor.getDocument(); - doc.startGuardedBlockChecking(); - try { - doc.deleteString(myDraggedRange.getStartOffset(), myDraggedRange.getEndOffset()); - } - catch (ReadOnlyFragmentModificationException e) { - EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e); - } - finally { - doc.stopGuardedBlockChecking(); - } - } - }); - } - }, EditorBundle.message("move.selection.command.name"), DND_COMMAND_KEY, UndoConfirmationPolicy.DEFAULT, editor.getDocument()); - } - - myDraggedRange = null; - } - } - - class EditorDocumentAdapter implements PrioritizedDocumentListener { - public void beforeDocumentChange(DocumentEvent e) { - beforeChangedUpdate(e); - } - - public void documentChanged(DocumentEvent e) { - changedUpdate(e); - } - - public int getPriority() { - return EditorDocumentPriorities.EDITOR_DOCUMENT_ADAPTER; - } - } - - @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) - private class EditorSizeContainer { - - /** Holds logical line widths in pixels. */ - private TIntArrayList myLineWidths; - - /** Holds value that indicates if line widths recalculation should be performed. */ - private volatile boolean myIsDirty; - - /** Holds number of the last logical line affected by the last document change. */ - private int myOldEndLine; - - private Dimension mySize; - - private final Object lock = new Object(); - private int myMaxWidth = -1; - - public synchronized void reset() { - int lineCount = getDocument().getLineCount(); - myLineWidths = new TIntArrayList(lineCount + 300); - int[] values = new int[lineCount]; - Arrays.fill(values, -1); - myLineWidths.add(values); - myIsDirty = true; - } - - @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"}) - public synchronized void beforeChange(DocumentEvent e) { - if (myDocument.isInBulkUpdate()) { - myMaxWidth = mySize != null ? mySize.width : -1; - } - - myOldEndLine = offsetToLogicalPosition(e.getOffset() + e.getOldLength()).line; - } - - // Commented as nobody uses this method. - //private int getVisualPositionLine(int offset) { - // // Do round up of offset to the nearest line start (valid since we need only line) - // // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation - // - // int line = calcLogicalLineNumber(offset); - // return logicalToVisualLine(line); - //} - - public synchronized void update(int startLine, int newEndLine, int oldEndLine) { - final int lineWidthSize = myLineWidths.size(); - if (lineWidthSize == 0 || myDocument.getTextLength() <= 0) { - reset(); - } - else { - final int min = Math.min(oldEndLine, newEndLine); - final boolean toAddNewLines = min >= lineWidthSize; - - if (toAddNewLines) { - final int[] delta = new int[min - lineWidthSize + 1]; - myLineWidths.insert(lineWidthSize, delta); - } - - for (int i = startLine; i <= min; i++) myLineWidths.set(i, -1); - if (newEndLine > oldEndLine) { - int[] delta = new int[newEndLine - oldEndLine]; - Arrays.fill(delta, -1); - myLineWidths.insert(oldEndLine + 1, delta); - } - else if (oldEndLine > newEndLine && !toAddNewLines && newEndLine + 1 < lineWidthSize) { - myLineWidths.remove(newEndLine + 1, Math.min(oldEndLine, lineWidthSize) - newEndLine - 1); - } - myIsDirty = true; - } - } - - public synchronized void changedUpdate(DocumentEvent e) { - int startLine = e.getOldLength() == 0 ? myOldEndLine : offsetToLogicalPosition(e.getOffset()).line; - int newEndLine = e.getNewLength() == 0 ? startLine : offsetToLogicalPosition(e.getOffset() + e.getNewLength()).line; - int oldEndLine = myOldEndLine; - - update(startLine, newEndLine, oldEndLine); - } - - @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext", "AssignmentToForLoopParameter"}) - private void validateSizes() { - if (!myIsDirty) return; - - synchronized (lock) { - if (!myIsDirty) return; - int lineCount = Math.min(myLineWidths.size(), myDocument.getLineCount()); - - if (myMaxWidth != -1 && myDocument.isInBulkUpdate()) { - mySize = new Dimension(myMaxWidth, getLineHeight() * lineCount); - myIsDirty = false; - return; - } - - final CharSequence text = myDocument.getCharsNoThreadCheck(); - int end = myDocument.getTextLength(); - int x = 0; - final int fontSize = myScheme.getEditorFontSize(); - final String fontName = myScheme.getEditorFontName(); - - List softWraps = getSoftWrapModel().getRegisteredSoftWraps(); - int softWrapsIndex = -1; - - for (int line = 0; line < lineCount; line++) { - if (myLineWidths.getQuick(line) != -1) continue; - x = 0; - int offset = myDocument.getLineStartOffset(line); - - if (offset >= myDocument.getTextLength()) { - myLineWidths.set(line, 0); - break; - } - - if (softWrapsIndex < 0) { - softWrapsIndex = getSoftWrapModel().getSoftWrapIndex(offset); - if (softWrapsIndex < 0) { - softWrapsIndex = -softWrapsIndex - 1; - } - } - - IterationState state = new IterationState(EditorImpl.this, offset, false); - int fontType = state.getMergedAttributes().getFontType(); - - int maxPreviousSoftWrappedWidth = -1; - - while (offset < end && line < lineCount) { - char c = text.charAt(offset); - if (offset >= state.getEndOffset()) { - state.advance(); - fontType = state.getMergedAttributes().getFontType(); - } - - while (softWrapsIndex < softWraps.size() && line < lineCount) { - SoftWrap softWrap = softWraps.get(softWrapsIndex); - if (softWrap.getStart() > offset) { - break; - } - softWrapsIndex++; - if (softWrap.getStart() == offset) { - maxPreviousSoftWrappedWidth = Math.max(maxPreviousSoftWrappedWidth, x); - x = softWrap.getIndentInPixels(); - } - } - if (line + 1 >= lineCount) { - myLineWidths.set(line, Math.max(x, maxPreviousSoftWrappedWidth)); - break; - } - - FoldRegion collapsed = state.getCurrentFold(); - if (collapsed != null) { - String placeholder = collapsed.getPlaceholderText(); - for (int i = 0; i < placeholder.length(); i++) { - x += EditorUtil.charWidth(placeholder.charAt(i), fontType, EditorImpl.this); - } - offset = collapsed.getEndOffset(); - line = myDocument.getLineNumber(offset); - } - else { - if (c == '\t') { - x = EditorUtil.nextTabStop(x, EditorImpl.this); - offset++; - } - else { - if (c == '\n') { - myLineWidths.set(line, Math.max(x, maxPreviousSoftWrappedWidth)); - if (line + 1 >= lineCount || myLineWidths.getQuick(line + 1) != -1) break; - offset++; - x = 0; - //noinspection AssignmentToForLoopParameter - line++; - } - else { - x += ComplementaryFontsRegistry.getFontAbleToDisplay(c, fontSize, fontType, fontName).charWidth(c, myEditorComponent); - offset++; - } - } - } - } - } - - if (lineCount > 0) { - myLineWidths.set(lineCount - 1, - x); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's - } - - // There is a following possible situation: - // 1. Big document is opened at editor; - // 2. Soft wraps are calculated for the current visible area; - // 2. The user scrolled down; - // 3. The user significantly reduced visible area width (say, reduced it twice); - // 4. Soft wraps are calculated for the current visible area; - // We need to consider only the widths for the logical lines that are completely shown at the current visible area then. - // I.e. we shouldn't use widths of the lines that are not shown for max width calculation because previous widths are calculated - // for another visible area width. - int startToUse = 0; - int endToUse = Math.min(lineCount, myLineWidths.size()); - if (endToUse > 0 && getSoftWrapModel().isSoftWrappingEnabled()) { - Rectangle visibleArea = getScrollingModel().getVisibleArea(); - startToUse = xyToLogicalPosition(visibleArea.getLocation()).line + 1; - endToUse = Math.min(endToUse, xyToLogicalPosition(new Point(0, visibleArea.y + visibleArea.height)).line); - } - int maxWidth = 0; - for (int i = startToUse; i < endToUse; i++) { - maxWidth = Math.max(maxWidth, myLineWidths.getQuick(i)); - } - - mySize = new Dimension(maxWidth, getLineHeight() * getVisibleLineCount()); - - myIsDirty = false; - } - } - - private Dimension getContentSize() { - validateSizes(); - return mySize; - } - - private int getContentHeight() { - return getVisibleLineCount() * getLineHeight(); - } - } - - @NotNull - public EditorGutter getGutter() { - return getGutterComponentEx(); - } - - public int calcColumnNumber(CharSequence text, int start, int offset, int tabSize) { - IterationState state = new IterationState(this, start, false); - int fontType = state.getMergedAttributes().getFontType(); - int column = 0; - int x = 0; - int spaceSize = EditorUtil.getSpaceWidth(fontType, this); - for (int i = start; i < offset; i++) { - if (i >= state.getEndOffset()) { - state.advance(); - fontType = state.getMergedAttributes().getFontType(); - } - - SoftWrap softWrap = getSoftWrapModel().getSoftWrap(i); - if (softWrap != null) { - column++; // For 'after soft wrap' drawing. - x = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); - } - - char c = text.charAt(i); - if (c == '\t') { - int prevX = x; - x = EditorUtil.nextTabStop(x, this); - column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); - //column += Math.max(1, (x - prevX) / spaceSize); - } - else { - x += EditorUtil.charWidth(c, fontType, this); - column++; - } - } - - return column; - } - - public void putInfo(Map info) { - final VisualPosition visual = getCaretModel().getVisualPosition(); - info.put("caret", visual.getLine() + ":" + visual.getColumn()); - } - - public static boolean isChangeFontSize(MouseWheelEvent e) { - return SystemInfo.isMac - ? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown() - : e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown(); - } - - private class MyScrollPane extends JBScrollPane { - - - private MyScrollPane() { - setViewportBorder(new EmptyBorder(0, 0, 0, 0)); - } - - protected void processMouseWheelEvent(MouseWheelEvent e) { - if (mySettings.isWheelFontChangeEnabled()) { - if (isChangeFontSize(e)) { - setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation()); - return; - } - } - - super.processMouseWheelEvent(e); - } - - @Override - public JScrollBar createVerticalScrollBar() { - return new MyScrollBar(Adjustable.VERTICAL); - } - - @Override - protected void init() { - super.init(); - setCorner(LOWER_LEFT_CORNER, new JPanel() { - @Override - public void paint(Graphics g) { - final Rectangle bounds = getBounds(); - int width = bounds.width; - int height = bounds.height; - - g.setColor(ButtonlessScrollBarUI.TRACK_BACKGROUND); - g.fillRect(0, 0, width, height); - - int shortner = 0; - if (myGutterComponent.isFoldingOutlineShown()) { - shortner = myGutterComponent.getFoldingAreaWidth() / 2; - } - - g.setColor(myGutterComponent.getBackground()); - g.fillRect(0, 0, width - shortner, height); - - g.setColor(ButtonlessScrollBarUI.TRACK_BORDER); - g.drawLine(width - 1 - shortner, 0, width - 1 - shortner, height); - g.drawLine(0, 0, width - 1, 0); - } - }); - } - } - - private class MyHeaderPanel extends JPanel { - private int myOldHeight = 0; - - private MyHeaderPanel() { - super(new BorderLayout()); - } - - public void revalidate() { - myOldHeight = getHeight(); - super.revalidate(); - } - - protected void validateTree() { - int height = myOldHeight; - super.validateTree(); - height -= getHeight(); - - if (height != 0) { - myVerticalScrollBar.setValue(myVerticalScrollBar.getValue() - height); - } - myOldHeight = getHeight(); - } - } - - private class MyTextDrawingCallback implements TextDrawingCallback { - @Override - public void drawChars(Graphics g, char[] data, int start, int end, int x, int y, Color color, FontInfo fontInfo) { - drawCharsCached(g, data, start, end, x, y, fontInfo, color); - } - } -} +/* + * 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.editor.impl; + +import com.intellij.Patches; +import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer; +import com.intellij.codeInsight.hint.EditorFragmentComponent; +import com.intellij.codeInsight.hint.TooltipController; +import com.intellij.codeInsight.hint.TooltipGroup; +import com.intellij.concurrency.JobScheduler; +import com.intellij.ide.*; +import com.intellij.ide.dnd.DnDManager; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.UndoConfirmationPolicy; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.colors.*; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.editor.ex.*; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.HighlighterClient; +import com.intellij.openapi.editor.impl.event.MarkupModelListener; +import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces; +import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType; +import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Queryable; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.GuiUtils; +import com.intellij.ui.LightweightHint; +import com.intellij.ui.components.JBScrollBar; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.Alarm; +import com.intellij.util.IJSwingUtilities; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.text.CharArrayUtil; +import com.intellij.util.ui.ButtonlessScrollBarUI; +import com.intellij.util.ui.EmptyClipboardOwner; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.UiNotifyConnector; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import javax.swing.*; +import javax.swing.Timer; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.ScrollBarUI; +import javax.swing.plaf.basic.BasicScrollBarUI; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetAdapter; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.event.*; +import java.awt.font.TextHitInfo; +import java.awt.im.InputMethodRequests; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.text.CharacterIterator; +import java.util.*; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public final class EditorImpl extends UserDataHolderBase implements EditorEx, HighlighterClient, Queryable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl"); + private static final Key DND_COMMAND_KEY = Key.create("DndCommand"); + public static final Key DO_DOCUMENT_UPDATE_TEST = Key.create("DoDocumentUpdateTest"); + private final DocumentImpl myDocument; + + private final JPanel myPanel; + private final JScrollPane myScrollPane; + private final EditorComponentImpl myEditorComponent; + private final EditorGutterComponentImpl myGutterComponent; + + static { + @SuppressWarnings({"UnusedDeclaration"}) + ComplementaryFontsRegistry registry; // load costly font info + } + + private final CommandProcessor myCommandProcessor; + private final MyScrollBar myVerticalScrollBar; + + private final CopyOnWriteArrayList myMouseListeners = ContainerUtil.createEmptyCOWList(); + private final CopyOnWriteArrayList myMouseMotionListeners; + + private int myCharHeight = -1; + private int myLineHeight = -1; + private int myDescent = -1; + + private boolean myIsInsertMode = true; + + private final CaretCursor myCaretCursor; + private final ScrollingTimer myScrollingTimer = new ScrollingTimer(); + + private final Key MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup"); + + private final DocumentListener myEditorDocumentAdapter; + + private final SettingsImpl mySettings; + + private boolean isReleased = false; + + private MouseEvent myMousePressedEvent = null; + + private int mySavedSelectionStart = -1; + private int mySavedSelectionEnd = -1; + private int myLastColumnNumber = 0; + + private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this); + private MyEditable myEditable; + + private EditorColorsScheme myScheme; + private ArrowPainter myTabPainter; + private final boolean myIsViewer; + private final SelectionModelImpl mySelectionModel; + private final EditorMarkupModelImpl myMarkupModel; + private final FoldingModelImpl myFoldingModel; + private final ScrollingModelImpl myScrollingModel; + private final CaretModelImpl myCaretModel; + private final SoftWrapModelImpl mySoftWrapModel; + + private static final RepaintCursorCommand ourCaretBlinkingCommand; + +// private final BorderEffect myBorderEffect = new BorderEffect(); + + private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE; + private FoldRegion myMouseSelectedRegion = null; + + private static final int MOUSE_SELECTION_STATE_NONE = 0; + private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1; + private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2; + + private final MarkupModelListener myMarkupModelListener; + + private EditorHighlighter myHighlighter; + private final TextDrawingCallback myTextDrawingCallback = new MyTextDrawingCallback(); + + private int myScrollBarOrientation; + private boolean myMousePressedInsideSelection; + private FontMetrics myPlainFontMetrics; + private FontMetrics myBoldFontMetrics; + private FontMetrics myItalicFontMetrics; + private FontMetrics myBoldItalicFontMetrics; + + private static final int CACHED_CHARS_BUFFER_SIZE = 300; + + private final ArrayList myFontCache = new ArrayList(); + private FontInfo myCurrentFontType = null; + + private final EditorSizeContainer mySizeContainer = new EditorSizeContainer(); + + private boolean myUpdateCursor; + private int myCaretUpdateVShift; + private final Project myProject; + private long myMouseSelectionChangeTimestamp; + private int mySavedCaretOffsetForDNDUndoHack; + private final ArrayList myFocusListeners = new ArrayList(); + + private MyInputMethodHandler myInputMethodRequestsHandler; + private InputMethodRequests myInputMethodRequestsSwingWrapper; + private boolean myIsOneLineMode; + private boolean myIsRendererMode; + private VirtualFile myVirtualFile; + private boolean myIsColumnMode = false; + private Color myForcedBackground = null; + private Dimension myPreferredSize; + private Runnable myGutterSizeUpdater = null; + private boolean myGutterNeedsUpdate = false; + private Alarm myAppleRepaintAlarm; + private boolean myEmbeddedIntoDialogWrapper; + private CachedFontContent myLastCache; + private boolean mySpacesHaveSameWidth; + + /** + * There is a possible case that specific font is used for particular text drawing operation (e.g. for 'before' and 'after' + * soft wraps drawings). Hence, even if {@link #mySpacesHaveSameWidth} is true, space size for that specific + * font may be different. So, we define additional flag that should indicate that {@link #myLastCache} should be reset. + */ + private boolean myForceRefreshFont; + private boolean mySoftWrapsChanged; + + private Point myLastBackgroundPosition = null; + private Color myLastBackgroundColor = null; + private int myLastBackgroundWidth; + private static final boolean ourIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); + private final JPanel myHeaderPanel; + + private MouseEvent myInitialMouseEvent; + private boolean myIgnoreMouseEventsConsecutiveToInitial; + + private String myReleasedAt = null; + + private EditorDropHandler myDropHandler; + + private char[] myPrefixText; + private TextAttributes myPrefixAttributes; + private final IndentsModel myIndentsModel; + + static { + ourCaretBlinkingCommand = new RepaintCursorCommand(); + ourCaretBlinkingCommand.start(); + } + + EditorImpl(@NotNull Document document, boolean viewer, Project project) { + myProject = project; + myDocument = (DocumentImpl)document; + myScheme = createBoundColorSchemeDelegate(null); + initTabPainter(); + myIsViewer = viewer; + mySettings = new SettingsImpl(this); + + mySelectionModel = new SelectionModelImpl(this); + myMarkupModel = new EditorMarkupModelImpl(this); + myFoldingModel = new FoldingModelImpl(this); + myCaretModel = new CaretModelImpl(this); + mySoftWrapModel = new SoftWrapModelImpl(this); + mySizeContainer.reset(); + + myCommandProcessor = CommandProcessor.getInstance(); + + myEditorDocumentAdapter = new EditorDocumentAdapter(); + myMouseMotionListeners = ContainerUtil.createEmptyCOWList(); + + myMarkupModelListener = new MarkupModelListener() { + public void afterAdded(@NotNull RangeHighlighterEx highlighter) { + attributesChanged(highlighter); + } + + public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) { + attributesChanged(highlighter); + } + + public void attributesChanged(@NotNull RangeHighlighterEx highlighter) { + int start = highlighter.getAffectedAreaStartOffset(); + int end = highlighter.getAffectedAreaEndOffset(); + int startLine = myDocument.getLineNumber(start); + int endLine = myDocument.getLineNumber(end); + repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount())); + ((EditorMarkupModelImpl)getMarkupModel()).markDirtied(); + ((EditorMarkupModelImpl)getMarkupModel()).repaint(start, end); + GutterIconRenderer renderer = highlighter.getGutterIconRenderer(); + if (renderer != null) { + updateGutterSize(); + } + updateCaretCursor(); + } + }; + + ((MarkupModelEx)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener); + ((MarkupModelEx)getMarkupModel()).addMarkupModelListener(myMarkupModelListener); + + myDocument.addDocumentListener(myFoldingModel); + myDocument.addDocumentListener(myCaretModel); + myDocument.addDocumentListener(mySelectionModel); + myDocument.addDocumentListener(myEditorDocumentAdapter); + myDocument.addDocumentListener(mySoftWrapModel); + + myFoldingModel.addListener(mySoftWrapModel); + + myIndentsModel = new IndentsModelImpl(this); + myCaretModel.addCaretListener(new CaretListener() { + private LightweightHint myCurrentHint = null; + private IndentGuideDescriptor myCurrentCaretGuide = null; + + public void caretPositionChanged(CaretEvent e) { + final IndentGuideDescriptor newGuide = myIndentsModel.getCaretIndentGuide(); + if (!Comparing.equal(myCurrentCaretGuide, newGuide)) { + repaintGuide(newGuide); + repaintGuide(myCurrentCaretGuide); + myCurrentCaretGuide = newGuide; + + if (myCurrentHint != null) { + myCurrentHint.hide(); + myCurrentHint = null; + } + + if (newGuide != null) { + final Rectangle visibleArea = getScrollingModel().getVisibleArea(); + final int line = newGuide.startLine; + if (logicalLineToY(line) < visibleArea.y) { + TextRange textRange = new TextRange(myDocument.getLineStartOffset(line), myDocument.getLineEndOffset(line)); + + myCurrentHint = EditorFragmentComponent.showEditorFragmentHint(EditorImpl.this, textRange, false, false); + } + } + } + } + }); + + myCaretCursor = new CaretCursor(); + + myFoldingModel.flushCaretShift(); + myScrollBarOrientation = VERTICAL_SCROLLBAR_RIGHT; + + mySoftWrapModel.addSoftWrapChangeListener(new SoftWrapChangeListener() { + @Override + public void softWrapAdded(@NotNull SoftWrap softWrap) { + mySoftWrapsChanged = true; + int softWrapLine = myDocument.getLineNumber(softWrap.getStart()); + mySizeContainer.update(softWrapLine, softWrapLine, softWrapLine); + } + + @Override + public void softWrapsRemoved() { + mySoftWrapsChanged = true; + mySizeContainer.reset(); + } + }); + + EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT)); + setHighlighter(highlighter); + + myEditorComponent = new EditorComponentImpl(this); + myScrollPane = new MyScrollPane(); + myPanel = new JPanel(); + + myHeaderPanel = new MyHeaderPanel(); + myVerticalScrollBar = new MyScrollBar(Adjustable.VERTICAL); + myGutterComponent = new EditorGutterComponentImpl(this); + initComponent(); + + myScrollingModel = new ScrollingModelImpl(this); + + myGutterComponent.updateSize(); + Dimension preferredSize = getPreferredSize(); + myEditorComponent.setSize(preferredSize); + + if (Patches.APPLE_BUG_ID_3716835) { + myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() { + public void visibleAreaChanged(VisibleAreaEvent e) { + if (myAppleRepaintAlarm == null) { + myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + } + myAppleRepaintAlarm.cancelAllRequests(); + myAppleRepaintAlarm.addRequest(new Runnable() { + public void run() { + repaint(0, myDocument.getTextLength()); + } + }, 50, ModalityState.stateForComponent(myEditorComponent)); + } + }); + } + + updateCaretCursor(); + + // This hacks context layout problem where editor appears scrolled to the right just after it is created. + if (!ourIsUnitTestMode) { + UiNotifyConnector.doWhenFirstShown(myEditorComponent, new Runnable() { + public void run() { + if (!isDisposed() && !myScrollingModel.isScrollingNow()) { + myScrollingModel.disableAnimation(); + myScrollingModel.scrollHorizontally(0); + myScrollingModel.enableAnimation(); + } + } + }); + } + } + + public EditorColorsScheme createBoundColorSchemeDelegate(@Nullable final EditorColorsScheme customGlobalScheme) { + return new MyColorSchemeDelegate(customGlobalScheme); + } + + private void repaintGuide(IndentGuideDescriptor guide) { + if (guide != null) { + repaintLines(guide.startLine, guide.endLine); + } + } + + public void setPrefixTextAndAttributes(String prefixText, TextAttributes attributes) { + myPrefixText = prefixText == null? null: prefixText.toCharArray(); + myPrefixAttributes = attributes; + } + + public boolean isViewer() { + return myIsViewer || myIsRendererMode; + } + + public boolean isRendererMode() { + return myIsRendererMode; + } + + public void setRendererMode(boolean isRendererMode) { + myIsRendererMode = isRendererMode; + } + + public void setFile(VirtualFile vFile) { + myVirtualFile = vFile; + reinitSettings(); + } + + public VirtualFile getVirtualFile() { + return myVirtualFile; + } + + @Override + public void setSoftWrapAppliancePlace(@NotNull SoftWrapAppliancePlaces place) { + getSoftWrapModel().setPlace(place); + mySettings.setSoftWrapAppliancePlace(place); + } + + @NotNull + public SelectionModel getSelectionModel() { + return mySelectionModel; + } + + @NotNull + public MarkupModel getMarkupModel() { + return myMarkupModel; + } + + @NotNull + public FoldingModelEx getFoldingModel() { + return myFoldingModel; + } + + @NotNull + public CaretModelImpl getCaretModel() { + return myCaretModel; + } + + @NotNull + public ScrollingModel getScrollingModel() { + return myScrollingModel; + } + + @NotNull + public SoftWrapModelImpl getSoftWrapModel() { + return mySoftWrapModel; + } + + @NotNull + public EditorSettings getSettings() { + assertReadAccess(); + return mySettings; + } + + public void reinitSettings() { + assertIsDispatchThread(); + myCharHeight = -1; + myLineHeight = -1; + myDescent = -1; + myPlainFontMetrics = null; + + mySoftWrapModel.reinitSettings(); + myCaretModel.reinitSettings(); + mySelectionModel.reinitSettings(); + mySettings.reinitSettings(); + ourCaretBlinkingCommand.setBlinkCaret(mySettings.isBlinkCaret()); + ourCaretBlinkingCommand.setBlinkPeriod(mySettings.getCaretBlinkPeriod()); + mySizeContainer.reset(); + myFoldingModel.refreshSettings(); + myFoldingModel.rebuild(); + + if (myScheme instanceof MyColorSchemeDelegate) { + ((MyColorSchemeDelegate)myScheme).updateGlobalScheme(); + } + myHighlighter.setColorScheme(myScheme); + + myGutterComponent.reinitSettings(); + myGutterComponent.revalidate(); + + myEditorComponent.repaint(); + + initTabPainter(); + updateCaretCursor(); + + if (myInitialMouseEvent != null) { + myIgnoreMouseEventsConsecutiveToInitial = true; + } + + // There is a possible case that 'use soft wrap' setting value is changed and we need to repaint all affected lines then. + repaintToScreenBottom(getCaretModel().getLogicalPosition().line); + int y = getCaretModel().getVisualLineStart() * getLineHeight(); + myGutterComponent.repaint(0, y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - y); + getCaretModel().moveToOffset(getCaretModel().getOffset()); + } + + private void initTabPainter() { + myTabPainter = new ArrowPainter( + ColorProvider.byColorsScheme(myScheme, EditorColors.WHITESPACES_COLOR), + new Computable() { + @Override + public Integer compute() { + return getCharHeight(); + } + } + ); + } + + public void release() { + assertIsDispatchThread(); + if (isReleased) { + LOG.error("Double release. First released at: =====\n" + myReleasedAt+"\n======"); + } + + myReleasedAt = StringUtil.getThrowableText(new Throwable()); + + isReleased = true; + myDocument.removeDocumentListener(myHighlighter); + myDocument.removeDocumentListener(myEditorDocumentAdapter); + myDocument.removeDocumentListener(myFoldingModel); + myDocument.removeDocumentListener(myCaretModel); + myDocument.removeDocumentListener(mySelectionModel); + myDocument.removeDocumentListener(mySoftWrapModel); + + myFoldingModel.removeListener(mySoftWrapModel); + myFoldingModel.dispose(); + + mySoftWrapModel.release(); + + MarkupModelEx markupModel = (MarkupModelEx)myDocument.getMarkupModel(myProject, false); + if (markupModel instanceof MarkupModelImpl) { + markupModel.removeMarkupModelListener(myMarkupModelListener); + } + + myMarkupModel.dispose(); + + myLineHeight = -1; + myCharHeight = -1; + myDescent = -1; + myPlainFontMetrics = null; + myScrollingModel.dispose(); + myGutterComponent.dispose(); + Disposer.dispose(myCaretModel); + clearCaretThread(); + + myFocusListeners.clear(); + } + + private void clearCaretThread() { + synchronized (ourCaretBlinkingCommand) { + if (ourCaretBlinkingCommand.myEditor == this) { + ourCaretBlinkingCommand.myEditor = null; + } + } + } + + private void initComponent() { +// myStatusBar = new EditorStatusBarImpl(); + + //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS)); + myPanel.setLayout(new BorderLayout()); + + myPanel.add(myHeaderPanel, BorderLayout.NORTH); + + myGutterComponent.setOpaque(true); + + myScrollPane.setVerticalScrollBar(myVerticalScrollBar); + myScrollPane.setViewportView(myEditorComponent); + //myScrollPane.setBorder(null); + myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + + myScrollPane.setRowHeaderView(myGutterComponent); + stopOptimizedScrolling(); + + myEditorComponent.setTransferHandler(new MyTransferHandler()); + myEditorComponent.setAutoscrolls(true); + +/* Default mode till 1.4.0 + * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE); + */ + + if (mayShowToolbar()) { + JLayeredPane layeredPane = new JLayeredPane() { + @Override + public void doLayout() { + final Component[] components = getComponents(); + final Rectangle r = getBounds(); + for (Component c : components) { + if (c instanceof JScrollPane) { + c.setBounds(0, 0, r.width, r.height); + } + else { + final Dimension d = c.getPreferredSize(); + final MyScrollBar scrollBar = getVerticalScrollBar(); + c.setBounds(r.width - d.width - scrollBar.getWidth() - 30, 20, d.width, d.height); + } + } + } + }; + + layeredPane.add(myScrollPane, JLayeredPane.DEFAULT_LAYER); + myPanel.add(layeredPane); + + new ContextMenuImpl(layeredPane, myScrollPane, this); + } + else { + myPanel.add(myScrollPane); + } + + //myPanel.add(myScrollPane); + + myEditorComponent.addKeyListener(new KeyAdapter() { + public void keyTyped(KeyEvent event) { + if (Patches.APPLE_BUG_ID_3337563) return; // Everything is going through InputMethods under MacOS X in JDK releases earlier than 1.4.2_03-117.1 + if (event.isConsumed()) { + return; + } + if (processKeyTyped(event)) { + event.consume(); + } + } + }); + + MyMouseAdapter mouseAdapter = new MyMouseAdapter(); + myEditorComponent.addMouseListener(mouseAdapter); + myGutterComponent.addMouseListener(mouseAdapter); + + MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener(); + myEditorComponent.addMouseMotionListener(mouseMotionListener); + myGutterComponent.addMouseMotionListener(mouseMotionListener); + + myEditorComponent.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + myCaretCursor.activate(); + int caretLine = getCaretModel().getLogicalPosition().line; + repaintLines(caretLine, caretLine); + fireFocusGained(); + if (myGutterNeedsUpdate) { + updateGutterSize(); + } + } + + public void focusLost(FocusEvent e) { + clearCaretThread(); + int caretLine = getCaretModel().getLogicalPosition().line; + repaintLines(caretLine, caretLine); + fireFocusLost(); + } + }); + +// myBorderEffect.reset(); + try { + final DropTarget dropTarget = myEditorComponent.getDropTarget(); + if (dropTarget != null) { // might be null in headless environment + dropTarget.addDropTargetListener(new DropTargetAdapter() { + public void drop(DropTargetDropEvent e) { + } + + public void dragOver(DropTargetDragEvent e) { + Point location = e.getLocation(); + + moveCaretToScreenPos(location.x, location.y); + getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + }); + } + } + catch (TooManyListenersException e) { + LOG.error(e); + } + + myPanel.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + myMarkupModel.repaint(0, myDocument.getTextLength()); + } + }); + } + + private boolean mayShowToolbar() { + return !isEmbeddedIntoDialogWrapper() && !isOneLineMode() && ContextMenuImpl.mayShowToolbar(myDocument); + } + + public void setFontSize(final int fontSize) { + int oldFontSize = myScheme.getEditorFontSize(); + myScheme.setEditorFontSize(fontSize); + myPropertyChangeSupport.firePropertyChange(PROP_FONT_SIZE, oldFontSize, fontSize); + } + + public ActionCallback type(final String text) { + final ActionCallback result = new ActionCallback(); + + Application app = ApplicationManager.getApplication(); + if (!app.isWriteAccessAllowed()) { + result.setRejected(); + } else { + app.runWriteAction(new Runnable() { + public void run() { + for (int i = 0; i < text.length(); i++) { + if (!processKeyTyped(text.charAt(i))) { + result.setRejected(); + return; + } + } + + result.setDone(); + } + }); + } + + return result; + } + + private boolean processKeyTyped(char c) { + // [vova] This is patch for Mac OS X. Under Mac "input methods" + // is handled before our EventQueue consume upcoming KeyEvents. + IdeEventQueue queue = IdeEventQueue.getInstance(); + if (queue.shouldNotTypeInEditor() || ProgressManager.getInstance().hasModalProgressIndicator()) { + return false; + } + ActionManagerEx actionManager = ActionManagerEx.getInstanceEx(); + DataContext dataContext = getDataContext(); + actionManager.fireBeforeEditorTyping(c, dataContext); + EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext); + + return true; + } + + private void fireFocusLost() { + FocusChangeListener[] listeners = getFocusListeners(); + for (FocusChangeListener listener : listeners) { + listener.focusLost(this); + } + } + + private FocusChangeListener[] getFocusListeners() { + return myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]); + } + + private void fireFocusGained() { + FocusChangeListener[] listeners = getFocusListeners(); + for (FocusChangeListener listener : listeners) { + listener.focusGained(this); + } + } + + public void setHighlighter(EditorHighlighter highlighter) { + assertIsDispatchThread(); + final Document document = getDocument(); + if (myHighlighter != null) { + document.removeDocumentListener(myHighlighter); + } + + document.addDocumentListener(highlighter); + highlighter.setEditor(this); + highlighter.setText(document.getCharsSequence()); + myHighlighter = highlighter; + if (document instanceof DocumentImpl) { + ((DocumentImpl)document).rememberEditorHighlighterForCachesOptimization(highlighter); + } + + if (myPanel != null) { + reinitSettings(); + } + } + + public EditorHighlighter getHighlighter() { + assertReadAccess(); + return myHighlighter; + } + + @NotNull + public EditorComponentImpl getContentComponent() { + return myEditorComponent; + } + + public EditorGutterComponentEx getGutterComponentEx() { + return myGutterComponent; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + myPropertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + myPropertyChangeSupport.removePropertyChangeListener(listener); + } + + public void setInsertMode(boolean mode) { + assertIsDispatchThread(); + boolean oldValue = myIsInsertMode; + myIsInsertMode = mode; + myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, mode); + //Repaint the caret line by moving caret to the same place + LogicalPosition caretPosition = getCaretModel().getLogicalPosition(); + getCaretModel().moveToLogicalPosition(caretPosition); + } + + public boolean isInsertMode() { + return myIsInsertMode; + } + + public void setColumnMode(boolean mode) { + assertIsDispatchThread(); + boolean oldValue = myIsColumnMode; + myIsColumnMode = mode; + myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode); + } + + public boolean isColumnMode() { + return myIsColumnMode; + } + + private int yPositionToVisibleLineNumber(int y) { + return y / getLineHeight(); + } + + @NotNull + public VisualPosition xyToVisualPosition(@NotNull Point p) { + int line = yPositionToVisibleLineNumber(p.y); + int px = p.x; + if (line == 0 && myPrefixText != null) { + for (char c : myPrefixText) { + px -= EditorUtil.charWidth(c, myPrefixAttributes.getFontType(), this); + } + } + + LogicalPosition logicalPosition = visualToLogicalPosition(new VisualPosition(line, 0)); + int offset = logicalPositionToOffset(logicalPosition); + int textLength = myDocument.getTextLength(); + + if (offset >= textLength) return new VisualPosition(line, 0); + + // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. + // We mark that we're under such circumstances then. + boolean activeSoftWrapProcessed = true; + if (logicalPosition.softWrapLinesOnCurrentLogicalLine > 0) { + activeSoftWrapProcessed = false; + } + + int column = 0; + int prevX = 0; + CharSequence text = myDocument.getCharsNoThreadCheck(); + char c = ' '; + IterationState state = new IterationState(this, offset, false); + + int fontType = state.getMergedAttributes().getFontType(); + int spaceSize = EditorUtil.getSpaceWidth(fontType, this); + + int x = 0; + int charWidth; + boolean onSoftWrapDrawing = false; + outer: + while (true) { + charWidth = -1; + if (offset >= textLength) { + break; + } + + if (offset >= state.getEndOffset()) { + state.advance(); + fontType = state.getMergedAttributes().getFontType(); + } + + SoftWrap softWrap = mySoftWrapModel.getSoftWrap(offset); + if (softWrap != null) { + if (activeSoftWrapProcessed) { + prevX = x; + charWidth = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); + x += charWidth; + if (x >= px) { + onSoftWrapDrawing = true; + break outer; + } + } + else { + CharSequence softWrapText = softWrap.getText(); + for (int i = 1/*Assuming line feed is located at the first position*/; i < softWrapText.length(); i++) { + c = softWrapText.charAt(i); + prevX = x; + charWidth = charToVisibleWidth(c, fontType, x); + x += charWidth; + if (x >= px) { + break outer; + } + column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); + } + + // Process 'after soft wrap' sign. + prevX = x; + charWidth = mySoftWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); + x += charWidth; + if (x >= px) { + onSoftWrapDrawing = true; + break outer; + } + column++; + activeSoftWrapProcessed = true; + } + } + FoldRegion region = state.getCurrentFold(); + if (region != null) { + char[] placeholder = region.getPlaceholderText().toCharArray(); + for (char aPlaceholder : placeholder) { + c = aPlaceholder; + x += EditorUtil.charWidth(c, fontType, this); + if (x >= px) { + break outer; + } + column++; + } + offset = region.getEndOffset(); + } + else { + prevX = x; + c = text.charAt(offset); + charWidth = charToVisibleWidth(c, fontType, x); + if (charWidth == 0) { + break; + } + x += charWidth; + + if (x >= px) { + break; + } + column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); + + offset++; + } + } + + if (charWidth < 0) { + charWidth = EditorUtil.charWidth(c, fontType, this); + } + + if (x >= px && c == '\t' && !onSoftWrapDrawing) { + if (mySettings.isCaretInsideTabs()) { + column += (px - prevX) / spaceSize; + if ((px - prevX) % spaceSize > spaceSize / 2) column++; + } + else if ((x - px) * 2 < x - prevX) { + column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); + } + } + else { + if (x >= px) { + if ((x - px) * 2 < charWidth) column++; + } + else { + column += (px - x) / spaceSize; + if ((px - x) * 2 >= spaceSize) { + column++; + } + } + } + + return new VisualPosition(line, column); + } + + /** + * Allows to answer how much width requires given char to be represented on a screen. + * + * @param c target character + * @param fontType font type to use for representation of the given character + * @param currentX current 'x' position on a line where given character should be displayed + * @return width required to represent given char with the given settings on a screen; + * '0' if given char is a line break + */ + private int charToVisibleWidth(char c, int fontType, int currentX) { + if (c == '\n') { + return 0; + } + + if (c == '\t') { + return EditorUtil.nextTabStop(currentX, this) - currentX; + } + return EditorUtil.charWidth(c, fontType, this); + } + + @NotNull + public VisualPosition offsetToVisualPosition(int offset) { + return logicalToVisualPosition(offsetToLogicalPosition(offset)); + } + + @NotNull + public LogicalPosition offsetToLogicalPosition(int offset) { + return offsetToLogicalPosition(offset, true); + } + + @NotNull + @Override + public LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware) { + if (softWrapAware) { + return mySoftWrapModel.offsetToLogicalPosition(offset); + } + int line = calcLogicalLineNumber(offset, false); + int column = calcColumnNumber(offset, line, false); + return new LogicalPosition(line, column); + } + + @Override + public boolean isCaretActive() { + synchronized (ourCaretBlinkingCommand) { + return ourCaretBlinkingCommand.myEditor == this; + } + } + + @TestOnly + public void setCaretActive() { + synchronized (ourCaretBlinkingCommand) { + ourCaretBlinkingCommand.myEditor = this; + } + } + + public int offsetToVisualLine(int offset) { + return logicalToVisualPosition(offsetToLogicalPosition(offset)).line; + } + + @NotNull + public LogicalPosition xyToLogicalPosition(@NotNull Point p) { + final Point pp; + if (p.x >= 0 && p.y >= 0) { + pp = p; + } + else { + pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0)); + } + + return visualToLogicalPosition(xyToVisualPosition(pp)); + } + + private int logicalLineToY(int line) { + VisualPosition visible = logicalToVisualPosition(new LogicalPosition(line, 0)); + return visibleLineNumberToYPosition(visible.line); + } + + @NotNull + public Point logicalPositionToXY(@NotNull LogicalPosition pos) { + VisualPosition visible = logicalToVisualPosition(pos); + return visualPositionToXY(visible); + } + + @NotNull + public Point visualPositionToXY(@NotNull VisualPosition visible) { + int y = visibleLineNumberToYPosition(visible.line); + LogicalPosition logical = visualToLogicalPosition(new VisualPosition(visible.line, 0)); + int logLine = logical.line; + + int lineStartOffset = -1; + int reserved = 0; + int column = visible.column; + + if (logical.softWrapLinesOnCurrentLogicalLine > 0) { + int linesToSkip = logical.softWrapLinesOnCurrentLogicalLine; + List softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine); + for (SoftWrap softWrap : softWraps) { + if (myFoldingModel.isOffsetCollapsed(softWrap.getStart()) && myFoldingModel.isOffsetCollapsed(softWrap.getStart() - 1)) { + continue; + } + linesToSkip--; // Assuming here that every soft wrap has exactly one line feed + if (linesToSkip > 0) { + continue; + } + lineStartOffset = softWrap.getStart(); + int widthInColumns = softWrap.getIndentInColumns(); + int widthInPixels = softWrap.getIndentInPixels(); + if (widthInColumns <= column) { + column -= widthInColumns; + reserved = widthInPixels; + } + else { + char[] softWrapChars = softWrap.getChars(); + int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length); + int start = 0; + if (i >= 0) { + start = i + 1; + } + return new Point(EditorUtil.textWidth(this, softWrap.getText(), start, column + 1, Font.PLAIN, 0), y); + } + break; + } + } + + if (logLine < 0) { + lineStartOffset = 0; + } + else if (lineStartOffset < 0) { + if (logLine >= myDocument.getLineCount()) { + lineStartOffset = myDocument.getTextLength(); + } + else { + lineStartOffset = myDocument.getLineStartOffset(logLine); + } + } + + int x = getTabbedTextWidth(lineStartOffset, column, reserved); + return new Point(x, y); + } + + private int getTabbedTextWidth(int startOffset, int length, int xOffset) { + int x = xOffset; + if (startOffset == 0 && myPrefixText != null) { + for (char c : myPrefixText) { + x += EditorUtil.charWidth(c, myPrefixAttributes.getFontType(), this); + } + } + if (length <= 0) return x; + int offset = startOffset; + CharSequence text = myDocument.getCharsNoThreadCheck(); + int textLength = myDocument.getTextLength(); + IterationState state = new IterationState(this, offset, false); + int fontType = state.getMergedAttributes().getFontType(); + int spaceSize = EditorUtil.getSpaceWidth(fontType, this); + + int column = 0; + outer: + while (column < length) { + if (offset >= textLength) break; + + if (offset >= state.getEndOffset()) { + state.advance(); + fontType = state.getMergedAttributes().getFontType(); + } + // We need to consider 'before soft wrap drawing'. + SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offset); + if (softWrap != null && offset > startOffset) { + column++; + x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); + // Assuming that first soft wrap symbol is line feed or all soft wrap symbols before the first line feed are spaces. + break; + } + + FoldRegion region = state.getCurrentFold(); + + if (region != null) { + char[] placeholder = region.getPlaceholderText().toCharArray(); + for (char aPlaceholder : placeholder) { + x += EditorUtil.charWidth(aPlaceholder, fontType, this); + column++; + if (column >= length) break outer; + } + offset = region.getEndOffset(); + } + else { + char c = text.charAt(offset); + if (c == '\n') { + break; + } + if (c == '\t') { + int prevX = x; + x = EditorUtil.nextTabStop(x, this); + int columnDiff = (x - prevX) / spaceSize; + if ((x - prevX) % spaceSize > 0) { + // There is a possible case that tabulation symbol takes more than one visual column to represent and it's shown at + // soft-wrapped line. Soft wrap sign width may be not divisible by space size, hence, part of tabulation symbol represented + // as a separate visual column may take less space than space width. + columnDiff++; + } + column += columnDiff; + } + else { + x += EditorUtil.charWidth(c, fontType, this); + column++; + } + offset++; + } + } + + if (column != length) { + x += EditorUtil.getSpaceWidth(fontType, this) * (length - column); + } + + return x; + } + + public int visibleLineNumberToYPosition(int lineNum) { + if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum); + return lineNum * getLineHeight(); + } + + public void repaint(int startOffset, int endOffset) { + if (!isShowing() || myScrollPane == null || myDocument.isInBulkUpdate()) { + return; + } + + assertIsDispatchThread(); + + if (endOffset > myDocument.getTextLength()) { + endOffset = myDocument.getTextLength(); + } + if (startOffset < endOffset) { + int startLine = myDocument.getLineNumber(startOffset); + int endLine = myDocument.getLineNumber(endOffset); + repaintLines(startLine, endLine); + } + } + + private boolean isShowing() { + return myGutterComponent != null && myGutterComponent.isShowing(); + } + + private void repaintToScreenBottom(int startLine) { + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + int yStartLine = logicalLineToY(startLine); + int yEndLine = visibleArea.y + visibleArea.height; + + myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, yEndLine - yStartLine); + myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine); + } + + /** + * Asks to repaint all logical lines from the given [start; end] range. + * + * @param startLine start logical line to repaint (inclusive) + * @param endLine end logical line to repaint (inclusive) + */ + public void repaintLines(int startLine, int endLine) { + if (!isShowing()) return; + + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + int yStartLine = logicalLineToY(startLine); + int endVisLine; + if (myDocument.getTextLength() <= 0) { + endVisLine = 0; + } + else { + endVisLine = offsetToVisualPosition(myDocument.getLineEndOffset(Math.min(myDocument.getLineCount() - 1, endLine))).line; + } + int height = endVisLine * getLineHeight() - yStartLine + getLineHeight() + WAVE_HEIGHT; + + myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, height); + myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), height); + } + + private void beforeChangedUpdate(DocumentEvent e) { + if (!myDocument.isInBulkUpdate()) { + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + Point pos = visualPositionToXY(getCaretModel().getVisualPosition()); + myCaretUpdateVShift = pos.y - visibleArea.y; + } + mySizeContainer.beforeChange(e); + } + + private void changedUpdate(DocumentEvent e) { + if (myScrollPane == null) return; + + stopOptimizedScrolling(); + mySelectionModel.removeBlockSelection(); + + mySizeContainer.changedUpdate(e); + validateSize(); + + int startLine = calcLogicalLineNumber(e.getOffset()); + int endLine = calcLogicalLineNumber(e.getOffset() + e.getNewLength()); + + boolean painted = false; + if (myDocument.getTextLength() > 0) { + int startDocLine = myDocument.getLineNumber(e.getOffset()); + int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength()); + if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine) { + updateGutterSize(); + } + + if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) { + // Lines removed. Need to repaint till the end of the screen + repaintToScreenBottom(startLine); + painted = true; + } + } + + updateCaretCursor(); + if (!painted) { + repaintLines(startLine, endLine); + } + + if (!myDocument.isInBulkUpdate()) { + Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition()); + int scrollOffset = caretLocation.y - myCaretUpdateVShift; + getScrollingModel().scrollVertically(scrollOffset); + } + } + + private static int countLineFeeds(CharSequence c) { + return StringUtil.countNewLines(c); + } + + private void updateGutterSize() { + if (myGutterSizeUpdater != null) return; + myGutterSizeUpdater = new Runnable() { + public void run() { + if (!isDisposed()) { + if (isShowing()) { + myGutterComponent.updateSize(); + myGutterNeedsUpdate = false; + } + else { + myGutterNeedsUpdate = true; + } + } + myGutterSizeUpdater = null; + } + }; + + SwingUtilities.invokeLater(myGutterSizeUpdater); + } + + void validateSize() { + Dimension dim = getPreferredSize(); + + if (!dim.equals(myPreferredSize) && !myDocument.isInBulkUpdate()) { + myPreferredSize = dim; + + stopOptimizedScrolling(); + int lineNum = Math.max(1, getDocument().getLineCount()); + myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNum + 2)) + 6); + + myEditorComponent.setSize(dim); + myEditorComponent.fireResized(); + + myMarkupModel.repaint(0, myDocument.getTextLength()); + } + } + + void recalculateSizeAndRepaint() { + mySizeContainer.reset(); + validateSize(); + myEditorComponent.repaintEditorComponent(); + } + + @NotNull + public Document getDocument() { + return myDocument; + } + + @NotNull + public JComponent getComponent() { + return myPanel; + } + + public void addEditorMouseListener(@NotNull EditorMouseListener listener) { + myMouseListeners.add(listener); + } + + public void removeEditorMouseListener(@NotNull EditorMouseListener listener) { + boolean success = myMouseListeners.remove(listener); + LOG.assertTrue(success); + } + + public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) { + myMouseMotionListeners.add(listener); + } + + public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) { + boolean success = myMouseMotionListeners.remove(listener); + LOG.assertTrue(success); + } + + public boolean isDisposed() { + return isReleased; + } + + void paint(Graphics g) { + startOptimizedScrolling(); + + if (myUpdateCursor) { + setCursorPosition(); + myUpdateCursor = false; + } + + Rectangle clip = getClipBounds(g); + + if (clip == null) { + return; + } + + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + if (visibleArea == null) { + return; + } + + if (isReleased) { + g.setColor(new Color(128, 255, 128)); + g.fillRect(clip.x, clip.y, clip.width, clip.height); + return; + } + if (myProject != null && myProject.isDisposed()) return; + + paintBackgrounds(g, clip); + paintRectangularSelection(g); + paintRightMargin(g, clip); + paintCustomRenderers((Graphics2D)g, clip); + final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); + paintLineMarkersSeparators(g, clip, docMarkup); + paintLineMarkersSeparators(g, clip, myMarkupModel); + paintText(g, clip); + paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip); + BorderEffect borderEffect = new BorderEffect(this, g); + borderEffect.paintHighlighters(getHighlighter()); + borderEffect.paintHighlighters(docMarkup.getAllHighlighters()); + borderEffect.paintHighlighters(getMarkupModel().getAllHighlighters()); + paintCaretCursor(g); + + paintComposedTextDecoration((Graphics2D)g); + } + + private void paintCustomRenderers(Graphics2D g, Rectangle clip) { + MarkupModel mm = myMarkupModel; + + int clipStartOffset = logicalPositionToOffset(xyToLogicalPosition(new Point(0, clip.y))); + int clipEndOffset = logicalPositionToOffset(xyToLogicalPosition(new Point(0, clip.y + clip.height + getLineHeight()))); + + for (RangeHighlighter highlighter : mm.getAllHighlighters()) { + if (highlighter.isValid()) { + final CustomHighlighterRenderer customRenderer = highlighter.getCustomRenderer(); + if (customRenderer != null && clipStartOffset < highlighter.getEndOffset() && clipEndOffset > highlighter.getStartOffset()) { + customRenderer.paint(this, highlighter, g); + } + } + } + } + + public IndentsModel getIndentsModel() { + return myIndentsModel; + } + + public void setHeaderComponent(JComponent header) { + myHeaderPanel.removeAll(); + if (header != null) { + myHeaderPanel.add(header); + } + + myHeaderPanel.revalidate(); + } + + public boolean hasHeaderComponent() { + return myHeaderPanel.getComponentCount() > 0; + } + + @Nullable + public JComponent getHeaderComponent() { + if (hasHeaderComponent()) { + return (JComponent)myHeaderPanel.getComponent(0); + } + return null; + } + + public void setBackgroundColor(Color color) { + myScrollPane.setBackground(color); + + if (getBackgroundIgnoreForced().equals(color)) { + myForcedBackground = null; + return; + } + myForcedBackground = color; + } + + public Color getForegroundColor() { + return myScheme.getDefaultForeground(); + } + + public Color getBackgroundColor() { + if (myForcedBackground != null) return myForcedBackground; + + return getBackgroundIgnoreForced(); + } + + @Override + public TextDrawingCallback getTextDrawingCallback() { + return myTextDrawingCallback; + } + + private Color getBackgroundColor(final TextAttributes attributes) { + final Color attrColor = attributes.getBackgroundColor(); + return attrColor == myScheme.getDefaultBackground() ? getBackgroundColor() : attrColor; + } + + private Color getBackgroundIgnoreForced() { + Color color = myScheme.getDefaultBackground(); + if (myDocument.isWritable()) { + return color; + } + Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR); + return readOnlyColor != null ? readOnlyColor : color; + } + + private void paintComposedTextDecoration(Graphics2D g) { + if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) { + VisualPosition visStart = + offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength())); + int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1; + Point p1 = visualPositionToXY(visStart); + Point p2 = + logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength()))); + + Stroke saved = g.getStroke(); + BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2}, 0); + g.setStroke(dotted); + UIUtil.drawLine(g, p1.x, y, p2.x, y); + g.setStroke(saved); + } + } + + private static Rectangle getClipBounds(Graphics g) { + return g.getClipBounds(); + } + + private void paintRightMargin(Graphics g, Rectangle clip) { + Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR); + if (!mySettings.isRightMarginShown() || rightMargin == null) { + return; + } + int x = mySettings.getRightMargin(myProject) * EditorUtil.getSpaceWidth(Font.PLAIN, this); + if (x >= clip.x && x < clip.x + clip.width) { + g.setColor(rightMargin); + UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height); + } + } + + private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) { + if (myDocument.getLineCount() == 0) return; + int startLineNumber = yPositionToVisibleLineNumber(clip.y); + int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1; + + final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); + RangeHighlighter[] segmentHighlighters = docMarkup.getAllHighlighters(); + for (RangeHighlighter segmentHighlighter : segmentHighlighters) { + paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber); + } + + segmentHighlighters = getMarkupModel().getAllHighlighters(); + for (RangeHighlighter segmentHighlighter : segmentHighlighters) { + paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber); + } + } + + private void paintSegmentHighlighterAfterEndOfLine(Graphics g, + RangeHighlighterEx segmentHighlighter, + int startLineNumber, + int endLineNumber) { + if (!segmentHighlighter.isValid()) { + return; + } + if (segmentHighlighter.isAfterEndOfLine()) { + int startOffset = segmentHighlighter.getStartOffset(); + int visibleStartLine = offsetToVisualLine(startOffset); + + if (!getFoldingModel().isOffsetCollapsed(startOffset)) { + if (visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) { + int logStartLine = offsetToLogicalPosition(startOffset).line; + LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine)); + Point end = logicalPositionToXY(logPosition); + int charWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this); + int lineHeight = getLineHeight(); + TextAttributes attributes = segmentHighlighter.getTextAttributes(); + if (attributes != null && getBackgroundColor(attributes) != null) { + g.setColor(getBackgroundColor(attributes)); + g.fillRect(end.x, end.y, charWidth, lineHeight); + } + if (attributes != null && attributes.getEffectColor() != null) { + int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1; + g.setColor(attributes.getEffectColor()); + if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) { + drawWave(g, end.x, end.x + charWidth - 1, y); + } + else if (attributes.getEffectType() != EffectType.BOXED) { + UIUtil.drawLine(g, end.x, y, end.x + charWidth - 1, y); + } + } + } + } + } + } + + public int getMaxWidthInRange(int startOffset, int endOffset) { + int width = 0; + int start = offsetToVisualLine(startOffset); + int end = offsetToVisualLine(endOffset); + + for (int i = start; i <= end; i++) { + int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1; + int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x; + + if (lineWidth > width) { + width = lineWidth; + } + } + + return width; + } + + @SuppressWarnings({"StatementWithEmptyBody"}) + private void paintBackgrounds(Graphics g, Rectangle clip) { + Color defaultBackground = getBackgroundColor(); + g.setColor(defaultBackground); + g.fillRect(clip.x, clip.y, clip.width, clip.height); + + int lineHeight = getLineHeight(); + + int visibleLineNumber = clip.y / lineHeight; + + VisualPosition visualPosition = xyToVisualPosition(new Point(0, clip.y)); + LogicalPosition logicalPosition = visualToLogicalPosition(visualPosition); + + Point position = new Point(0, visibleLineNumber * lineHeight); + if (visualPosition.line == 0 && myPrefixText != null) { + position.x = drawBackground(g, myPrefixAttributes.getBackgroundColor(), new String(myPrefixText), position, myPrefixAttributes.getFontType(), + defaultBackground, clip); + } + + if (logicalPosition.line >= myDocument.getLineCount() || logicalPosition.line < 0) { + if (position.x > 0) flushBackground(g, clip); + return; + } + + boolean locateBeforeSoftWrap = !SoftWrapHelper.isCaretAfterSoftWrap(this); + int start = logicalPositionToOffset(logicalPosition); + getSoftWrapModel().registerSoftWrapsIfNecessary(clip, start); + + // There is a possible case that we need to draw background from the start of soft wrap-introduced visual line. Given position + // has valid 'y' coordinate then at it shouldn't be affected by soft wrap that corresponds to the visual line start offset. + // Hence, we store information about soft wrap to be skipped for further processing and adjust 'x' coordinate value if necessary. + TIntHashSet softWrapsToSkip = new TIntHashSet(); + SoftWrap softWrap = getSoftWrapModel().getSoftWrap(start); + if (softWrap != null) { + position.x = softWrap.getIndentInPixels(); + softWrapsToSkip.add(softWrap.getStart()); + } + + // There is a possible case that caret is located at soft-wrapped line. We don't need to paint caret row background + // on a last visual line of that soft-wrapped line then. Below is a holder for the flag that indicates if caret row + // background is already drawn. We don't achieve concurrency stuff here, AtomicBoolean is used just as a data holder. + AtomicBoolean caretRowPainted = new AtomicBoolean(); + + IterationState iterationState = new IterationState(this, start, paintSelection()); + + LineIterator lIterator = createLineIterator(); + lIterator.start(start); + if (lIterator.atEnd()) { + return; + } + + myLastBackgroundPosition = null; + myLastBackgroundColor = null; + + TextAttributes attributes = iterationState.getMergedAttributes(); + Color backColor = getBackgroundColor(attributes); + int fontType = attributes.getFontType(); + CharSequence text = myDocument.getCharsNoThreadCheck(); + int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1); + + while (!iterationState.atEnd() && !lIterator.atEnd()) { + int hEnd = iterationState.getEndOffset(); + int lEnd = lIterator.getEnd(); + + if (hEnd >= lEnd) { + FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start); + if (collapsedFolderAt == null) { + position.x = drawSoftWrapAwareBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType, + defaultBackground, clip, softWrapsToSkip, caretRowPainted); + + if (lIterator.getLineNumber() < lastLineIndex) { + if (backColor != null && !backColor.equals(defaultBackground)) { + g.setColor(backColor); + g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight); + } + } + else { + paintAfterFileEndBackground(iterationState, + g, + position, clip, + lineHeight, defaultBackground, caretRowPainted); + break; + } + + position.x = 0; + if (position.y > clip.y + clip.height) break; + position.y += lineHeight; + start = lEnd; + } + + lIterator.advance(); + } + else { + FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); + if (collapsedFolderAt != null) { + position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(), position, fontType, defaultBackground, clip); + } + else { + if (hEnd > lEnd - lIterator.getSeparatorLength()) { + position.x = drawSoftWrapAwareBackground( + g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType, + defaultBackground, clip, softWrapsToSkip, caretRowPainted + ); + } + else { + position.x = drawSoftWrapAwareBackground( + g, backColor, text, start, hEnd, position, fontType, defaultBackground, clip, softWrapsToSkip, caretRowPainted + ); + } + } + + iterationState.advance(); + attributes = iterationState.getMergedAttributes(); + backColor = getBackgroundColor(attributes); + fontType = attributes.getFontType(); + start = iterationState.getStartOffset(); + } + } + + flushBackground(g, clip); + + if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) { + paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground, caretRowPainted); + } + + // Perform additional activity if soft wrap is added or removed during repainting. + if (mySoftWrapsChanged) { + mySoftWrapsChanged = false; + validateSize(); + + // Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction. + repaintToScreenBottom(xyToLogicalPosition(position).line); + + // Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn + // in accordance with the newly introduced soft wrap(s). + myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y); + + // Ask caret model to update visual caret position. + getCaretModel().moveToOffset(getCaretModel().getOffset(), locateBeforeSoftWrap); + } + } + + private void paintRectangularSelection(Graphics g) { + final SelectionModel model = getSelectionModel(); + if (!model.hasBlockSelection()) return; + final LogicalPosition blockStart = model.getBlockStart(); + final LogicalPosition blockEnd = model.getBlockEnd(); + assert blockStart != null; + assert blockEnd != null; + + final Point start = logicalPositionToXY(blockStart); + final Point end = logicalPositionToXY(blockEnd); + g.setColor(myScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR)); + final int y; + final int height; + if (start.y <= end.y) { + y = start.y; + height = end.y - y + getLineHeight(); + } + else { + y = end.y; + height = start.y - end.y + getLineHeight(); + } + final int x = Math.min(start.x, end.x); + final int width = Math.max(2, Math.abs(end.x - start.x)); + g.fillRect(x, y, width, height); + } + + private void paintAfterFileEndBackground(IterationState iterationState, + Graphics g, + Point position, + Rectangle clip, + int lineHeight, + final Color defaultBackground, + AtomicBoolean caretRowPainted) { + Color backColor = iterationState.getPastFileEndBackground(); + if (backColor == null || backColor.equals(defaultBackground)) { + return; + } + if (caretRowPainted.get() && backColor.equals(getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR))) { + return; + } + g.setColor(backColor); + g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight); + } + + private int drawSoftWrapAwareBackground(Graphics g, Color backColor, CharSequence text, int start, int end, Point position, + int fontType, Color defaultBackground, Rectangle clip, TIntHashSet processSoftWrap, + AtomicBoolean caretRowPainted) + { + int startToUse = start; + List softWraps = getSoftWrapModel().getSoftWrapsForRange(start, end); + for (SoftWrap softWrap : softWraps) { + int softWrapStart = softWrap.getStart(); + if (processSoftWrap.contains(softWrapStart)) { + continue; + } + if (startToUse < softWrapStart) { + position.x = drawBackground(g, backColor, text.subSequence(startToUse, softWrapStart), position, fontType, defaultBackground, clip); + } + drawSoftWrap(g, softWrap, position, fontType, defaultBackground, clip, caretRowPainted); + startToUse = softWrapStart; + } + + if (startToUse < end) { + position.x = drawBackground(g, backColor, text.subSequence(startToUse, end), position, fontType, defaultBackground, clip); + } + return position.x; + } + + private void drawSoftWrap(Graphics g, SoftWrap softWrap, Point position, int fontType, Color defaultBackground, Rectangle clip, + AtomicBoolean caretRowPainted) { + // The main idea is to to do the following: + // *) update given drawing position coordinates in accordance with the current soft wrap; + // *) draw 'active line' background at soft wrap-introduced virtual space if necessary; + + CharSequence softWrapText = softWrap.getText(); + int activeRowY = getCaretModel().getVisualPosition().line * getLineHeight(); + if (position.y == activeRowY) { + // Draw 'active line' background after soft wrap. + Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR); + drawBackground(g, caretRowColor, clip.x + clip.width - position.x, position, defaultBackground, clip); + caretRowPainted.set(true); + } + + int i = CharArrayUtil.lastIndexOf(softWrapText, "\n", softWrapText.length()) + 1; + position.x = getTextSegmentWidth(softWrapText.subSequence(i, softWrapText.length()), 0, fontType, clip); + position.x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); + position.y += getLineHeight(); + + if (position.y == activeRowY) { + // Draw 'active line' background for the soft wrap-introduced virtual space. + Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR); + drawBackground(g, caretRowColor, position.x, new Point(0, activeRowY), defaultBackground, clip); + } + + // The code below draws background for soft wrap-introduced virtual space. It's is considered that we don't want + // to do that for now. Uncomment if that decision is changed. + + //position.x = drawBackground( + // g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED), position, + // defaultBackground, clip + //); + // + //CharSequence softWrapText = softWrap.getText(); + //int start = 0; + //for ( + // int end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n"); + // start < softWrapText.length() && end < softWrapText.length(); + // end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n")) + //{ + // drawBackground(g, backColor, softWrapText.subSequence(start, end), position, fontType, defaultBackground, clip); + // start = end + 1; + // position.x = 0; + // position.y += getLineHeight(); + //} + // + //if (start < softWrapText.length()) { + // position.x = drawBackground( + // g, backColor, softWrapText.subSequence(start, softWrapText.length()), position, fontType, defaultBackground, clip + // ); + //} + // + //position.x = drawBackground( + // g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP), position, + // defaultBackground, clip + //); + } + + private int drawBackground(Graphics g, Color backColor, CharSequence text, Point position, int fontType, Color defaultBackground, + Rectangle clip) + { + int width = getTextSegmentWidth(text, position.x, fontType, clip); + return drawBackground(g, backColor, width, position, defaultBackground, clip); + } + + private int drawBackground(Graphics g, + Color backColor, + int width, + Point position, + Color defaultBackground, + Rectangle clip) { + if (backColor != null && !backColor.equals(defaultBackground) && clip.intersects(position.x, position.y, width, getLineHeight())) { + if (backColor.equals(myLastBackgroundColor) && myLastBackgroundPosition.y == position.y && + myLastBackgroundPosition.x + myLastBackgroundWidth == position.x) { + myLastBackgroundWidth += width; + } + else { + flushBackground(g, clip); + myLastBackgroundColor = backColor; + myLastBackgroundPosition = new Point(position); + myLastBackgroundWidth = width; + } + } + + return position.x + width; + } + + private void flushBackground(Graphics g, final Rectangle clip) { + if (myLastBackgroundColor != null) { + final Point position = myLastBackgroundPosition; + final int w = myLastBackgroundWidth; + final int height = getLineHeight(); + if (clip.intersects(position.x, position.y, w, height)) { + g.setColor(myLastBackgroundColor); + g.fillRect(position.x, position.y, w, height); + } + myLastBackgroundColor = null; + } + } + + private LineIterator createLineIterator() { + return myDocument.createLineIterator(); + } + + private void paintText(Graphics g, Rectangle clip) { + myCurrentFontType = null; + myLastCache = null; + final int plainSpaceWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this); + final int boldSpaceWidth = EditorUtil.getSpaceWidth(Font.BOLD, this); + final int italicSpaceWidth = EditorUtil.getSpaceWidth(Font.ITALIC, this); + final int boldItalicSpaceWidth = EditorUtil.getSpaceWidth(Font.BOLD | Font.ITALIC, this); + mySpacesHaveSameWidth = + plainSpaceWidth == boldSpaceWidth && plainSpaceWidth == italicSpaceWidth && plainSpaceWidth == boldItalicSpaceWidth; + + int lineHeight = getLineHeight(); + + int visibleLineNumber = clip.y / lineHeight; + + // We use AtomicReference here just as a holder for LogicalPosition + // The main idea is that there is a possible case that we need to perform painting starting from soft-wrapped logical line. + // We may want to skip necessary number of visual lines then. Hence, we remember logical position that corresponds to the starting + // visual line in order to use it for further processing. As soon as necessary number of visual lines is skipped, logical + // position is expected to be set to null as an indication that no soft wrap-introduced visual lines should be skipped on + // current painting iteration. + AtomicReference logicalPosition = new AtomicReference(xyToLogicalPosition(new Point(0, clip.y))); + int startLineNumber = logicalPosition.get().line; + + Point position = new Point(0, visibleLineNumber * lineHeight); + if (startLineNumber == 0 && myPrefixText != null) { + position.x = drawStringWithSoftWraps(g, myPrefixText, 0, myPrefixText.length, position, clip, + myPrefixAttributes.getEffectColor(), myPrefixAttributes.getEffectType(), + myPrefixAttributes.getFontType(), myPrefixAttributes.getForegroundColor(), logicalPosition); + } + if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) { + if (position.x > 0) flushCachedChars(g); + return; + } + + int start = myDocument.getLineStartOffset(startLineNumber); + IterationState iterationState = new IterationState(this, start, paintSelection()); + LineIterator lIterator = createLineIterator(); + lIterator.start(start); + if (lIterator.atEnd()) { + return; + } + + TextAttributes attributes = iterationState.getMergedAttributes(); + Color currentColor = attributes.getForegroundColor(); + if (currentColor == null) { + currentColor = getForegroundColor(); + } + Color effectColor = attributes.getEffectColor(); + EffectType effectType = attributes.getEffectType(); + int fontType = attributes.getFontType(); + g.setColor(currentColor); + + final char[] chars = myDocument.getRawChars(); + + while (!iterationState.atEnd() && !lIterator.atEnd()) { + int hEnd = iterationState.getEndOffset(); + int lEnd = lIterator.getEnd(); + if (hEnd >= lEnd) { + FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start); + if (collapsedFolderAt == null) { + drawStringWithSoftWraps(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, + effectType, fontType, currentColor, logicalPosition); + position.x = 0; + if (position.y > clip.y + clip.height) { + break; + } + position.y += lineHeight; + start = lEnd; + } + +// myBorderEffect.eolReached(g, this); + lIterator.advance(); + } + else { + FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); + if (collapsedFolderAt != null) { + SoftWrap softWrap = mySoftWrapModel.getSoftWrap(collapsedFolderAt.getStartOffset()); + if (softWrap != null && logicalPosition.get() != null) { + position.x = drawStringWithSoftWraps( + g, chars, collapsedFolderAt.getStartOffset(), collapsedFolderAt.getStartOffset(), position, clip, effectColor, effectType, + fontType, currentColor, logicalPosition + ); + } + int foldingXStart = position.x; + position.x = drawString( + g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor + ); + //drawStringWithSoftWraps(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, + // fontType, currentColor, logicalPosition); + BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, position.x, getLineHeight(), effectColor, effectType); + + } + else { + if (hEnd > lEnd - lIterator.getSeparatorLength()) { + position.x = drawStringWithSoftWraps(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, + effectColor, effectType, fontType, currentColor, logicalPosition); + } + else { + position.x = drawStringWithSoftWraps(g, chars, start, hEnd, position, clip, effectColor, effectType, fontType, + currentColor, logicalPosition); + } + } + + iterationState.advance(); + attributes = iterationState.getMergedAttributes(); + + currentColor = attributes.getForegroundColor(); + if (currentColor == null) { + currentColor = getForegroundColor(); + } + + effectColor = attributes.getEffectColor(); + effectType = attributes.getEffectType(); + fontType = attributes.getFontType(); + + start = iterationState.getStartOffset(); + } + } + + FoldRegion collapsedFolderAt = iterationState.getCurrentFold(); + if (collapsedFolderAt != null) { + int foldingXStart = position.x; + int foldingXEnd = + drawStringWithSoftWraps(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, + fontType, currentColor, logicalPosition); + BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, foldingXEnd, getLineHeight(), effectColor, effectType); +// myBorderEffect.collapsedFolderReached(g, this); + } + + flushCachedChars(g); + } + + private boolean paintSelection() { + return !isOneLineMode() || IJSwingUtilities.hasFocus(getContentComponent()); + } + + private class CachedFontContent { + final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][]; + final int[] starts = new int[CACHED_CHARS_BUFFER_SIZE]; + final int[] ends = new int[CACHED_CHARS_BUFFER_SIZE]; + final int[] x = new int[CACHED_CHARS_BUFFER_SIZE]; + final int[] y = new int[CACHED_CHARS_BUFFER_SIZE]; + final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE]; + + int myCount = 0; + final FontInfo myFontType; + + private char[] myLastData; + + private CachedFontContent(FontInfo fontInfo) { + myFontType = fontInfo; + } + + private void flushContent(Graphics g) { + if (myCount != 0) { + if (myCurrentFontType != myFontType) { + myCurrentFontType = myFontType; + g.setFont(myFontType.getFont()); + } + Color currentColor = null; + for (int i = 0; i < myCount; i++) { + if (!Comparing.equal(color[i], currentColor)) { + currentColor = color[i]; + g.setColor(currentColor != null ? currentColor : Color.black); + } + + drawChars(g, data[i], starts[i], ends[i], x[i], y[i]); + color[i] = null; + data[i] = null; + } + + myCount = 0; + myLastData = null; + } + } + + private void addContent(Graphics g, char[] _data, int _start, int _end, int _x, int _y, Color _color) { + final int count = myCount; + if (count > 0) { + final int lastCount = count - 1; + final Color lastColor = color[lastCount]; + if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color == lastColor) + && _y == y[lastCount] /* there is a possible case that vertical position is adjusted because of soft wrap */) + { + ends[lastCount] = _end; + if (lastColor == null) color[lastCount] = _color; + return; + } + } + + myLastData = _data; + data[count] = _data; + x[count] = _x; + y[count] = _y; + starts[count] = _start; + ends[count] = _end; + color[count] = _color; + + myCount++; + if (count >= CACHED_CHARS_BUFFER_SIZE - 1) { + flushContent(g); + } + } + } + + private void flushCachedChars(Graphics g) { + for (CachedFontContent cache : myFontCache) { + cache.flushContent(g); + } + myLastCache = null; + } + + private void paintCaretCursor(Graphics g) { + // There is a possible case that visual caret position is changed because of newly added or removed soft wraps. + // We check if that's the case and ask caret model to recalculate visual position if necessary. + + myCaretCursor.paint(g); + } + + private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) { + if (markupModel == null) return; + RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters(); + for (RangeHighlighter lineMarker : lineMarkers) { + paintLineMarkerSeparator(lineMarker, clip, g); + } + } + + private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) { + if (!marker.isValid()) { + return; + } + Color separatorColor = marker.getLineSeparatorColor(); + if (separatorColor != null) { + int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument() + .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset()); + if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) { + return; + } + + int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line); + if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) { + y += getLineHeight(); + } + + if (y < clip.y || y > clip.y + clip.height) return; + + int endShift = clip.x + clip.width; + g.setColor(separatorColor); + + if (mySettings.isRightMarginShown() && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) { + endShift = Math.min(endShift, mySettings.getRightMargin(myProject) * EditorUtil.getSpaceWidth(Font.PLAIN, this)); + } + + UIUtil.drawLine(g, 0, y - 1, endShift, y - 1); + } + } + + private int drawStringWithSoftWraps(Graphics g, + final String text, + Point position, + Rectangle clip, + Color effectColor, + EffectType effectType, + int fontType, + Color fontColor, + AtomicReference startDrawingLogicalPosition) + { + return drawStringWithSoftWraps(g, text.toCharArray(), 0, text.length(), position, clip, effectColor, effectType, + fontType, fontColor, startDrawingLogicalPosition); + } + + private int drawStringWithSoftWraps(Graphics g, + final char[] text, + int start, + int end, + Point position, + Rectangle clip, + Color effectColor, + EffectType effectType, + int fontType, + Color fontColor, + AtomicReference startDrawingLogicalPosition) + { + + int startToUse = start; + + // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. + // We need to skip necessary number of visual lines then. + int softWrapLinesToSkip = 0; + if (startDrawingLogicalPosition.get() != null) { + softWrapLinesToSkip = startDrawingLogicalPosition.get().softWrapLinesOnCurrentLogicalLine; + } + SoftWrap lastSkippedSoftWrap = null; + if (softWrapLinesToSkip > 0) { + List softWraps = getSoftWrapModel().getSoftWrapsForLine(startDrawingLogicalPosition.get().line); + for (SoftWrap softWrap : softWraps) { + softWrapLinesToSkip--; // Assuming that soft wrap has a single line feed all the time + if (softWrapLinesToSkip <= 0) { + lastSkippedSoftWrap = softWrap; + startToUse = softWrap.getStart(); + break; + } + } + + } + startToUse = Math.max(startToUse, start); + + if (startToUse >= end) { + return position.x; + } + startDrawingLogicalPosition.set(null); + + outer: + for (SoftWrap softWrap : getSoftWrapModel().getSoftWrapsForRange(startToUse, end)) { + char[] softWrapChars = softWrap.getChars(); + + if (softWrap.equals(lastSkippedSoftWrap)) { + // If we are here that means that we are located on soft wrap-introduced visual line just after soft wrap. Hence, we need + // to draw soft wrap indent if any and 'after soft wrap' sign. + int i = CharArrayUtil.lastIndexOf(softWrapChars, '\n', 0, softWrapChars.length); + if (i < softWrapChars.length - 1) { + position.x = 0; // Soft wrap starts new visual line + position.x = drawString( + g, softWrapChars, i + 1, softWrapChars.length, position, clip, effectColor, effectType, fontType, fontColor + ); + } + position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight()); + myForceRefreshFont = true; + continue; + } + + // Draw token text before the wrap. + if (softWrap.getStart() > startToUse) { + position.x = drawString( + g, text, startToUse, softWrap.getStart(), position, clip, effectColor, effectType, fontType, fontColor + ); + } + + startToUse = softWrap.getStart(); + + // We don't draw every soft wrap symbol one-by-one but whole visual line. Current variable holds index that points + // to the first soft wrap symbol that is not drawn yet. + int softWrapSegmentStartIndex = 0; + for (int i = 0; i < softWrapChars.length; i++) { + // Delay soft wraps symbols drawing until EOL is found. + if (softWrapChars[i] != '\n') { + continue; + } + + // Draw soft wrap symbols on current visual line if any. + if (i - softWrapSegmentStartIndex > 0) { + drawString( + g, softWrapChars, softWrapSegmentStartIndex, i, position, clip, effectColor, effectType, fontType, fontColor + ); + } + mySoftWrapModel.paint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED, position.x, position.y, getLineHeight()); + myForceRefreshFont = true; + + // Reset 'x' coordinate because of new line start. + position.x = 0; + + // Stop the processing if we drew the whole clip. + if (position.y > clip.y + clip.height) { + break outer; + } + position.y += getLineHeight(); + softWrapSegmentStartIndex = i + 1; + } + + // Draw remaining soft wrap symbols from its last line if any. + if (softWrapSegmentStartIndex < softWrapChars.length - 1) { + position.x += drawString( + g, softWrapChars, softWrapSegmentStartIndex, softWrapChars.length, position, clip, effectColor, effectType, + fontType, fontColor + ); + } + position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight()); + myForceRefreshFont = true; + } + return position.x = drawString(g, text, startToUse, end, position, clip, effectColor, effectType, fontType, fontColor); + } + + private int drawString(Graphics g, + final char[] text, + int start, + int end, + Point position, + Rectangle clip, + Color effectColor, + EffectType effectType, + int fontType, + Color fontColor) { + if (start >= end) return position.x; + + boolean isInClip = getLineHeight() + position.y >= clip.y && position.y <= clip.y + clip.height; + + if (!isInClip) return position.x; + + int y = getLineHeight() - getDescent() + position.y; + int x = position.x; + return drawTabbedString(g, text, start, end, x, y, effectColor, effectType, fontType, fontColor, clip); + } + + private int drawString(Graphics g, + String text, + Point position, + Rectangle clip, + Color effectColor, + EffectType effectType, + int fontType, + Color fontColor) { + boolean isInClip = getLineHeight() + position.y >= clip.y && position.y <= clip.y + clip.height; + + if (!isInClip) return position.x; + + int y = getLineHeight() - getDescent() + position.y; + int x = position.x; + + return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType, fontColor, clip); + } + + private int drawTabbedString(Graphics g, + char[] text, + int start, + int end, + int x, + int y, + Color effectColor, + EffectType effectType, + int fontType, + Color fontColor, + final Rectangle clip) { + int xStart = x; + + for (int i = start; i < end; i++) { + if (text[i] != '\t') continue; + + x = drawTablessString(text, start, i, g, x, y, fontType, fontColor, clip); + + int x1 = EditorUtil.nextTabStop(x, this); + drawTabPlacer(g, y, x, x1); + x = x1; + start = i + 1; + } + + x = drawTablessString(text, start, end, g, x, y, fontType, fontColor, clip); + + if (effectColor != null) { + final Color savedColor = g.getColor(); + +// myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor); + int xEnd = x; + if (xStart < clip.x && xEnd < clip.x || xStart > clip.x + clip.width && xEnd > clip.x + clip.width) { + return x; + } + + if (xEnd > clip.x + clip.width) { + xEnd = clip.x + clip.width; + } + if (xStart < clip.x) { + xStart = clip.x; + } + + if (effectType == EffectType.LINE_UNDERSCORE) { + g.setColor(effectColor); + UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1); + g.setColor(savedColor); + } + else if (effectType == EffectType.BOLD_LINE_UNDERSCORE) { + g.setColor(effectColor); + UIUtil.drawLine(g, xStart, y, xEnd, y); + UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1); + g.setColor(savedColor); + } + else if (effectType == EffectType.STRIKEOUT) { + g.setColor(effectColor); + int y1 = y - getCharHeight() / 2; + UIUtil.drawLine(g, xStart, y1, xEnd, y1); + g.setColor(savedColor); + } + else if (effectType == EffectType.WAVE_UNDERSCORE) { + g.setColor(effectColor); + drawWave(g, xStart, xEnd, y + 1); + g.setColor(savedColor); + } + else if (effectType == EffectType.BOLD_DOTTED_LINE) { + final Color bgColor = getBackgroundColor(); + final int dottedAt = SystemInfo.isMac ? y : y + 1; + UIUtil.drawBoldDottedLine((Graphics2D)g, xStart, xEnd, dottedAt, bgColor, effectColor, false); + } + } + + return x; + } + + private int drawTablessString(final char[] text, + int start, + final int end, + final Graphics g, + int x, + final int y, + final int fontType, + final Color fontColor, + final Rectangle clip) { + int endX = x; + if (start < end) { + FontInfo font = EditorUtil.fontForChar(text[start], fontType, this); + for (int j = start; j < end; j++) { + final char c = text[j]; + FontInfo newFont = EditorUtil.fontForChar(c, fontType, this); + if (font != newFont || endX > clip.x + clip.width) { + if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) { + drawCharsCached(g, text, start, j, x, y, fontType, fontColor); + } + start = j; + x = endX; + font = newFont; + } + if (x < clip.x && endX < clip.x) { + start = j; + x = endX; + font = newFont; + } + else if (x > clip.x + clip.width) { + return endX; + } + endX += font.charWidth(c, myEditorComponent); + } + + if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) { + drawCharsCached(g, text, start, end, x, y, fontType, fontColor); + } + } + + return endX; + } + + private void drawTabPlacer(Graphics g, int y, int start, int stop) { + if (mySettings.isWhitespacesShown()) { + myTabPainter.paint(g, y, start, stop); + } + } + + private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, int fontType, Color color) { + if (!myForceRefreshFont && mySpacesHaveSameWidth && myLastCache != null && spacesOnly(data, start, end)) { + myLastCache.addContent(g, data, start, end, x, y, null); + } + else { + myForceRefreshFont = false; + FontInfo fnt = EditorUtil.fontForChar(data[start], fontType, this); + drawCharsCached(g, data, start, end, x, y, fnt, color); + } + } + + private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, FontInfo fnt, Color color) { + CachedFontContent cache = null; + for (CachedFontContent fontCache : myFontCache) { + if (fontCache.myFontType == fnt) { + cache = fontCache; + break; + } + } + if (cache == null) { + cache = new CachedFontContent(fnt); + myFontCache.add(cache); + } + + myLastCache = cache; + cache.addContent(g, data, start, end, x, y, color); + } + + private static boolean spacesOnly(char[] chars, int start, int end) { + for (int i = start; i < end; i++) { + if (chars[i] != ' ') return false; + } + return true; + } + + private void drawChars(Graphics g, char[] data, int start, int end, int x, int y) { + g.drawChars(data, start, end - start, x, y); + + if (mySettings.isWhitespacesShown()) { + Color oldColor = g.getColor(); + g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR)); + final FontMetrics metrics = g.getFontMetrics(); + int halfSpaceWidth = metrics.charWidth(' ') / 2; + for (int i = start; i < end; i++) { + if (data[i] == ' ') { + g.fillRect(x + halfSpaceWidth, y, 1, 1); + } + x += metrics.charWidth(data[i]); + } + g.setColor(oldColor); + } + } + + private static final int WAVE_HEIGHT = 2; + private static final int WAVE_SEGMENT_LENGTH = 4; + + private static void drawWave(Graphics g, int xStart, int xEnd, int y) { + int startSegment = xStart / WAVE_SEGMENT_LENGTH; + int endSegment = xEnd / WAVE_SEGMENT_LENGTH; + for (int i = startSegment; i < endSegment; i++) { + drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y); + } + + int x = WAVE_SEGMENT_LENGTH * endSegment; + UIUtil.drawLine(g, x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y); + } + + private static void drawWaveSegment(Graphics g, int x, int y) { + UIUtil.drawLine(g, x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y); + UIUtil.drawLine(g, x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT); + } + + private int getTextSegmentWidth(CharSequence text, int xStart, int fontType, Rectangle clip) { + int x = xStart; + + final int textLength = text.length(); + for (int i = 0; i < textLength && xStart < clip.x + clip.width; i++) { + if (text.charAt(i) == '\t') { + x = EditorUtil.nextTabStop(x, this); + } + else { + x += EditorUtil.charWidth(text.charAt(i), fontType, this); + } + if (x > clip.x + clip.width) { + break; + } + } + return x - xStart; + } + + public int getLineHeight() { + if (myLineHeight != -1) return myLineHeight; + + assertReadAccess(); + + FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); + myLineHeight = (int)(fontMetrics.getHeight() * (isOneLineMode() ? 1 : myScheme.getLineSpacing())); + if (myLineHeight == 0) { + myLineHeight = fontMetrics.getHeight(); + if (myLineHeight == 0) { + myLineHeight = 12; + } + } + + return myLineHeight; + } + + int getDescent() { + if (myDescent != -1) { + return myDescent; + } + FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); + myDescent = fontMetrics.getDescent(); + return myDescent; + } + + FontMetrics getFontMetrics(int fontType) { + if (myPlainFontMetrics == null) { + assertIsDispatchThread(); + myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); + myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD)); + myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC)); + myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC)); + } + + if (fontType == Font.PLAIN) return myPlainFontMetrics; + if (fontType == Font.BOLD) return myBoldFontMetrics; + if (fontType == Font.ITALIC) return myItalicFontMetrics; + if (fontType == Font.BOLD + Font.ITALIC) return myBoldItalicFontMetrics; + + LOG.error("Unknown font type: " + fontType); + + return myPlainFontMetrics; + } + + private int getCharHeight() { + if (myCharHeight == -1) { + assertIsDispatchThread(); + FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN)); + myCharHeight = fontMetrics.charWidth('a'); + } + return myCharHeight; + } + + public int getPreferredHeight() { + if (ourIsUnitTestMode && getUserData(DO_DOCUMENT_UPDATE_TEST) == null) { + return 1; + } + + return getHeightWithoutCaret(); + } + + public Dimension getPreferredSize() { + if (ourIsUnitTestMode && getUserData(DO_DOCUMENT_UPDATE_TEST) == null) { + return new Dimension(1, 1); + } + + final Dimension draft = getSizeWithoutCaret(); + final int additionalSpace = mySettings.getAdditionalColumnsCount() * EditorUtil.getSpaceWidth(Font.PLAIN, this); + + if (!myDocument.isInBulkUpdate()) { + int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x; + draft.width = Math.max(caretX, draft.width) + additionalSpace; + } + else { + draft.width += additionalSpace; + } + return draft; + } + + private Dimension getSizeWithoutCaret() { + Dimension size = mySizeContainer.getContentSize(); + if (isOneLineMode()) return new Dimension(size.width, getLineHeight()); + if (mySettings.isAdditionalPageAtBottom()) { + int lineHeight = getLineHeight(); + return new Dimension(size.width, size.height + Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight)); + } + + return getContentSize(); + } + + private int getHeightWithoutCaret() { + if (isOneLineMode()) return getLineHeight(); + int size = mySizeContainer.getContentHeight(); + if (mySettings.isAdditionalPageAtBottom()) { + int lineHeight = getLineHeight(); + return size + Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight); + } + + return size + mySettings.getAdditionalLinesCount() * getLineHeight(); + } + + public Dimension getContentSize() { + Dimension size = mySizeContainer.getContentSize(); + return new Dimension(size.width, size.height + mySettings.getAdditionalLinesCount() * getLineHeight()); + } + + public JScrollPane getScrollPane() { + return myScrollPane; + } + + public void setBorder(Border border) { + myScrollPane.setBorder(border); + } + + public int logicalPositionToOffset(@NotNull LogicalPosition pos) { + assertReadAccess(); + assertIsDispatchThread(); + if (myDocument.getLineCount() == 0) return 0; + + if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line); + if (pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column); + + if (pos.line >= myDocument.getLineCount()) { + return myDocument.getTextLength(); + } + + int start = myDocument.getLineStartOffset(pos.line); + int end = myDocument.getLineEndOffset(pos.line); + + CharSequence text = myDocument.getCharsNoThreadCheck(); + + if (pos.column == 0) return start; + return EditorUtil.calcOffset(this, text, start, end, pos.column, EditorUtil.getTabSize(this)); + } + + public void setLastColumnNumber(int val) { + assertIsDispatchThread(); + myLastColumnNumber = val; + } + + public int getLastColumnNumber() { + assertReadAccess(); + return myLastColumnNumber; + } + + /** + * @return information about total number of lines that can be viewed by user. I.e. this is a number of all document + * lines (considering that single logical document line may be represented on multiple visual lines because of + * soft wraps appliance) minus number of folded lines + */ + int getVisibleLineCount() { + return getVisibleLogicalLinesCount() + getSoftWrapModel().getSoftWrapsIntroducedLinesNumber(); + } + + /** + * @return number of visible logical lines. Generally, that is a total logical lines number minus number of folded lines + */ + private int getVisibleLogicalLinesCount() { + return getDocument().getLineCount() - myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1); + } + + @Override + @NotNull + public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) { + return logicalToVisualPosition(logicalPos, true); + } + + @Override + @NotNull + public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos, boolean softWrapAware) { + assertReadAccess(); + if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) { + return new VisualPosition(logicalPos.line, logicalPos.column); + } + + int offset = logicalPositionToOffset(logicalPos); + + FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset); + if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) { + if (offset < getDocument().getTextLength()) { + offset = outermostCollapsed.getStartOffset(); + LogicalPosition foldStart = offsetToLogicalPosition(offset); + return logicalToVisualPosition(foldStart); + } + else { + offset = outermostCollapsed.getEndOffset() + 3; // WTF? + } + } + + int line = logicalPos.line; + int column = logicalPos.column; + + line -= myFoldingModel.getFoldedLinesCountBefore(offset); + + FoldRegion[] topLevel = myFoldingModel.fetchTopLevel(); + LogicalPosition anchorFoldingPosition = logicalPos; + for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) { + FoldRegion region = topLevel[idx]; + if (region.isValid()) { + if (region.getDocument().getLineNumber(region.getEndOffset()) == anchorFoldingPosition.line && region.getEndOffset() <= offset) { + LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset()); + LogicalPosition foldEnd = offsetToLogicalPosition(region.getEndOffset()); + column += foldStart.column + region.getPlaceholderText().length() - foldEnd.column; + offset = region.getStartOffset(); + anchorFoldingPosition = foldStart; + } + else { + break; + } + } + } + + LOG.assertTrue(line >= 0); + + VisualPosition softWrapUnawarePosition = new VisualPosition(line, Math.max(0, column)); + if (softWrapAware) { + return mySoftWrapModel.adjustVisualPosition(logicalPos, softWrapUnawarePosition); + } + else { + return softWrapUnawarePosition; + } + } + + @Nullable + private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) { + FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel(); + + if (topLevelCollapsed == null) return null; + + int start = 0; + int end = topLevelCollapsed.length - 1; + int i = 0; + + while (start <= end) { + i = (start + end) / 2; + FoldRegion region = topLevelCollapsed[i]; + LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1); + VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd); + if (visFoldEnd.line < visual.line) { + start = i + 1; + } + else { + if (visFoldEnd.line > visual.line) { + end = i - 1; + } + else { + if (visFoldEnd.column < visual.column) { + start = i + 1; + } + else { + if (visFoldEnd.column > visual.column) { + end = i - 1; + } + else { + i--; + break; + } + } + } + } + } + + while (i >= 0 && i < topLevelCollapsed.length) { + if (topLevelCollapsed[i].isValid()) break; + i--; + } + + if (i >= 0 && i < topLevelCollapsed.length) { + FoldRegion region = topLevelCollapsed[i]; + LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1); + VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd); + if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) { + i--; + if (i >= 0) { + return topLevelCollapsed[i]; + } + else { + return null; + } + } + return region; + } + + return null; + } + + @NotNull + public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) { + return visualToLogicalPosition(visiblePos, true); + } + + @NotNull + public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos, boolean softWrapAware) { + if (softWrapAware) { + return mySoftWrapModel.visualToLogicalPosition(visiblePos); + } + assertReadAccess(); + if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column); + + int line = visiblePos.line; + int column = visiblePos.column; + + FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos); + + if (lastCollapsedBefore != null) { + LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset(), false); + VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd, false); + + line = logFoldEnd.line + visiblePos.line - visFoldEnd.line; + if (visFoldEnd.line == visiblePos.line) { + if (visiblePos.column >= visFoldEnd.column) { + column = logFoldEnd.column + visiblePos.column - visFoldEnd.column; + } + else { + return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset(), false); + } + } + } + + if (column < 0) column = 0; + + return new LogicalPosition(line, column); + } + + private int calcLogicalLineNumber(int offset) { + return calcLogicalLineNumber(offset, true); + } + + private int calcLogicalLineNumber(int offset, boolean softWrapAware) { + int textLength = myDocument.getTextLength(); + if (textLength == 0) return 0; + + if (offset > textLength || offset < 0) { + throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength); + } + + int lineIndex = myDocument.getLineNumber(offset); + LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount()); + + if (softWrapAware && getSoftWrapModel().isSoftWrappingEnabled()) { + int column = calcColumnNumber(offset, lineIndex, false); + return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(lineIndex, column), offset).line; + } + else { + return lineIndex; + } + } + + public int calcColumnNumber(int offset, int lineIndex) { + return calcColumnNumber(offset, lineIndex, true); + } + + public int calcColumnNumber(int offset, int lineIndex, boolean softWrapAware) { + if (myDocument.getTextLength() == 0) return 0; + + CharSequence text = myDocument.getCharsSequence(); + int start = myDocument.getLineStartOffset(lineIndex); + if (start == offset) return 0; + int column = EditorUtil.calcColumnNumber(this, text, start, offset, EditorUtil.getTabSize(this)); + + if (softWrapAware) { + int line = calcLogicalLineNumber(offset, false); + return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(line, column), offset).column; + } + else { + return column; + } + } + + private void moveCaretToScreenPos(int x, int y) { + if (x < 0) { + x = 0; + } + + LogicalPosition pos = xyToLogicalPosition(new Point(x, y)); + + int columnNumber = pos.column; + int lineNumber = pos.line; + int softWrapLinesBeforeTargetLogicalLine = pos.softWrapLinesBeforeCurrentLogicalLine; + int softWrapLinesOnTargetLogicalLine = pos.softWrapLinesOnCurrentLogicalLine; + int softWrapColumns = pos.softWrapColumnDiff; + + if (lineNumber < 0) { + lineNumber = 0; + columnNumber = 0; + softWrapLinesBeforeTargetLogicalLine = 0; + softWrapLinesOnTargetLogicalLine = 0; + softWrapColumns = 0; + } + + final int totalLines = myDocument.getLineCount(); + if (totalLines <= 0) { + getCaretModel().moveToOffset(0); + return; + } + + if (lineNumber >= totalLines && totalLines > 0) { + int visibleLineCount = getVisibleLineCount(); + int newY = visibleLineCount > 0 ? visibleLineNumberToYPosition(visibleLineCount - 1) : 0; + if (newY > 0 && newY == y) { + newY = visibleLineNumberToYPosition(getVisibleLogicalLinesCount()); + } + moveCaretToScreenPos(x, newY); + return; + } + + if (!mySettings.isVirtualSpace()) { + int lineEndOffset = myDocument.getLineEndOffset(lineNumber); + int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber); + if (columnNumber > lineEndColumnNumber) { + columnNumber = lineEndColumnNumber; + if (softWrapColumns != 0) { + softWrapColumns -= columnNumber - lineEndColumnNumber; + } + } + } + + if (!mySettings.isCaretInsideTabs()) { + int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber)); + CharSequence text = myDocument.getCharsSequence(); + if (offset >= 0 && offset < myDocument.getTextLength()) { + if (text.charAt(offset) == '\t') { + columnNumber = calcColumnNumber(offset, lineNumber); + } + } + } + LogicalPosition pos1 = new LogicalPosition( + lineNumber, columnNumber, softWrapLinesBeforeTargetLogicalLine, softWrapLinesOnTargetLogicalLine, softWrapColumns, + pos.foldedLines, pos.foldingColumnDiff + ); + getCaretModel().moveToLogicalPosition(pos1); + } + + private boolean checkIgnore(MouseEvent e, boolean isFinalCheck) { + if (!myIgnoreMouseEventsConsecutiveToInitial) { + myInitialMouseEvent = null; + return false; + } + + if (e.getComponent() != myInitialMouseEvent.getComponent() || !e.getPoint().equals(myInitialMouseEvent.getPoint())) { + myIgnoreMouseEventsConsecutiveToInitial = false; + myInitialMouseEvent = null; + return false; + } + + if (isFinalCheck) { + myIgnoreMouseEventsConsecutiveToInitial = false; + myInitialMouseEvent = null; + } + + e.consume(); + + return true; + } + + private void processMouseReleased(MouseEvent e) { + if (checkIgnore(e, true)) return; + + if (e.getSource() == myGutterComponent) { + myGutterComponent.mouseReleased(e); + } + + if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) { + return; + } + +// if (myMousePressedInsideSelection) getSelectionModel().removeSelection(); + final FoldRegion region = getFoldingModel().getFoldingPlaceholderAt(e.getPoint()); + if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) { + getFoldingModel().runBatchFoldingOperation(new Runnable() { + public void run() { + myFoldingModel.flushCaretShift(); + region.setExpanded(true); + } + }); + + // The call below is performed because gutter's height is not updated sometimes, i.e. it sticks to the value that corresponds + // to the situation when fold region is collapsed. That causes bottom of the gutter to not be repainted and that looks really ugly. + getGutterComponentEx().invalidate(); + } + + if (myMousePressedEvent != null && myMousePressedEvent.getClickCount() == 1 && myMousePressedInsideSelection + && !myMousePressedEvent.isShiftDown()) + { + getSelectionModel().removeSelection(); + } + } + + public DataContext getDataContext() { + return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent())); + } + + private DataContext getProjectAwareDataContext(final DataContext original) { + if (PlatformDataKeys.PROJECT.getData(original) == myProject) return original; + + return new DataContext() { + public Object getData(String dataId) { + if (PlatformDataKeys.PROJECT.is(dataId)) { + return myProject; + } + return original.getData(dataId); + } + }; + } + + + public EditorMouseEventArea getMouseEventArea(@NotNull MouseEvent e) { + if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA; + + int x = myGutterComponent.convertX(e.getX()); + + if (x >= myGutterComponent.getLineNumberAreaOffset() && + x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) { + return EditorMouseEventArea.LINE_NUMBERS_AREA; + } + + if (x >= myGutterComponent.getAnnotationsAreaOffset() && + x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) { + return EditorMouseEventArea.ANNOTATIONS_AREA; + } + + if (x >= myGutterComponent.getLineMarkerAreaOffset() && + x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) { + return EditorMouseEventArea.LINE_MARKERS_AREA; + } + + if (x >= myGutterComponent.getFoldingAreaOffset() && + x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) { + return EditorMouseEventArea.FOLDING_OUTLINE_AREA; + } + + return null; + } + + private void requestFocus() { + myEditorComponent.requestFocus(); + } + + private void validateMousePointer(MouseEvent e) { + if (e.getSource() == myGutterComponent) { + FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY()); + myGutterComponent.setActiveFoldRegion(foldingAtCursor); + if (foldingAtCursor != null) { + myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + else { + myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + else { + myGutterComponent.setActiveFoldRegion(null); + if (getSelectionModel().hasSelection() && (e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK)) == 0) { + int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint())); + if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) { + myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return; + } + } + myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + } + } + + private void runMouseDraggedCommand(final MouseEvent e) { + if (myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) { + return; + } + myCommandProcessor.executeCommand(myProject, new Runnable() { + public void run() { + processMouseDragged(e); + } + }, "", MOUSE_DRAGGED_GROUP, UndoConfirmationPolicy.DEFAULT, getDocument()); + } + + private void processMouseDragged(MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e)) { + return; + } + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + + int x = e.getX(); + + if (e.getSource() == myGutterComponent) { + x = 0; + } + + int dx = 0; + if (x < visibleArea.x && visibleArea.x > 0) { + dx = x - visibleArea.x; + } + else { + if (x > visibleArea.x + visibleArea.width) { + dx = x - visibleArea.x - visibleArea.width; + } + } + + int dy = 0; + int y = e.getY(); + if (y < visibleArea.y && visibleArea.y > 0) { + dy = y - visibleArea.y; + } + else { + if (y > visibleArea.y + visibleArea.height) { + dy = y - visibleArea.y - visibleArea.height; + } + } + if (dx == 0 && dy == 0) { + myScrollingTimer.stop(); + + SelectionModel selectionModel = getSelectionModel(); + int oldSelectionStart = selectionModel.getLeadSelectionOffset(); + int oldCaretOffset = getCaretModel().getOffset(); + LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition(); + moveCaretToScreenPos(x, y); + getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + + int newCaretOffset = getCaretModel().getOffset(); + int caretShift = newCaretOffset - mySavedSelectionStart; + + if (myMousePressedEvent != null && getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA && + getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) { + selectionModel.setSelection(oldSelectionStart, newCaretOffset); + } + else { + if (isColumnMode() || e.isAltDown()) { + final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret; + selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition()); + } + else { + if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { + if (caretShift < 0) { + int newSelection = newCaretOffset; + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { + newSelection = mySelectionModel.getWordAtCaretStart(); + } + else { + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { + newSelection = + logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))); + } + } + if (newSelection < 0) newSelection = newCaretOffset; + selectionModel.setSelection(mySavedSelectionEnd, newSelection); + getCaretModel().moveToOffset(newSelection); + } + else { + int newSelection = newCaretOffset; + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { + newSelection = mySelectionModel.getWordAtCaretEnd(); + } + else { + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { + newSelection = + logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))); + } + } + if (newSelection < 0) newSelection = newCaretOffset; + selectionModel.setSelection(mySavedSelectionStart, newSelection); + getCaretModel().moveToOffset(newSelection); + } + return; + } + + if (!myMousePressedInsideSelection) { + selectionModel.setSelection(oldSelectionStart, newCaretOffset); + } + else { + if (caretShift != 0) { + if (myMousePressedEvent != null) { + if (mySettings.isDndEnabled()) { + boolean isCopy = UIUtil.isControlKeyDown(e) || isViewer() || !getDocument().isWritable(); + mySavedCaretOffsetForDNDUndoHack = oldCaretOffset; + getContentComponent().getTransferHandler() + .exportAsDrag(getContentComponent(), e, isCopy ? TransferHandler.COPY : TransferHandler.MOVE); + } + else { + selectionModel.removeSelection(); + } + myMousePressedEvent = null; + } + } + } + } + } + } + else { + myScrollingTimer.start(dx, dy); + } + } + + private static class RepaintCursorCommand implements Runnable { + private long mySleepTime = 500; + private boolean myIsBlinkCaret = true; + private EditorImpl myEditor = null; + private final MyRepaintRunnable myRepaintRunnable; + private ScheduledFuture mySchedulerHandle; + + private RepaintCursorCommand() { + myRepaintRunnable = new MyRepaintRunnable(); + } + + private class MyRepaintRunnable implements Runnable { + public void run() { + if (myEditor != null) { + myEditor.myCaretCursor.repaint(); + } + } + } + + public void start() { + if (mySchedulerHandle != null) { + mySchedulerHandle.cancel(false); + } + mySchedulerHandle = JobScheduler.getScheduler().scheduleAtFixedRate(this, mySleepTime, mySleepTime, TimeUnit.MILLISECONDS); + } + + private void setBlinkPeriod(int blinkPeriod) { + mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10; + start(); + } + + private void setBlinkCaret(boolean value) { + myIsBlinkCaret = value; + } + + public void run() { + if (myEditor != null) { + CaretCursor activeCursor = myEditor.myCaretCursor; + + long time = System.currentTimeMillis(); + time -= activeCursor.myStartTime; + + if (time > mySleepTime) { + boolean toRepaint = true; + if (myIsBlinkCaret) { + activeCursor.myIsShown = !activeCursor.myIsShown; + } + else { + toRepaint = !activeCursor.myIsShown; + activeCursor.myIsShown = true; + } + + if (toRepaint) { + SwingUtilities.invokeLater(myRepaintRunnable); + } + } + } + } + } + + void updateCaretCursor() { + if (!ourIsUnitTestMode && !IJSwingUtilities.hasFocus(getContentComponent())) { + stopOptimizedScrolling(); + } + + myUpdateCursor = true; + } + + private void setCursorPosition() { + VisualPosition caretPosition = getCaretModel().getVisualPosition(); + Point pos1 = visualPositionToXY(caretPosition); + Point pos2 = visualPositionToXY(new VisualPosition(caretPosition.line, caretPosition.column + 1)); + myCaretCursor.setPosition(pos1, pos2.x - pos1.x); + } + + public boolean setCaretVisible(boolean b) { + boolean old = myCaretCursor.isActive(); + if (b) { + myCaretCursor.activate(); + } + else { + myCaretCursor.passivate(); + } + return old; + } + + public boolean setCaretEnabled(boolean enabled) { + boolean old = myCaretCursor.isEnabled(); + myCaretCursor.setEnabled(enabled); + return old; + } + + public void addFocusListener(FocusChangeListener listener) { + myFocusListeners.add(listener); + } + + public Project getProject() { + return myProject; + } + + public boolean isOneLineMode() { + return myIsOneLineMode; + } + + public boolean isEmbeddedIntoDialogWrapper() { + return myEmbeddedIntoDialogWrapper; + } + + public void setEmbeddedIntoDialogWrapper(boolean b) { + assertIsDispatchThread(); + + myEmbeddedIntoDialogWrapper = b; + myScrollPane.setFocusable(!b); + myEditorComponent.setFocusCycleRoot(!b); + myEditorComponent.setFocusable(b); + } + + public void setOneLineMode(boolean isOneLineMode) { + myIsOneLineMode = isOneLineMode; + getScrollPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + reinitSettings(); + } + + public void stopOptimizedScrolling() { + myEditorComponent.setOpaque(false); + } + + private void startOptimizedScrolling() { + myEditorComponent.setOpaque(true); + } + + private class CaretCursor { + private Point myLocation; + private int myWidth; + private boolean myEnabled; + + @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) + private boolean myIsShown = false; + private long myStartTime = 0; + + private CaretCursor() { + myLocation = new Point(0, 0); + setEnabled(true); + } + + public boolean isEnabled() { + return myEnabled; + } + + public void setEnabled(boolean enabled) { + myEnabled = enabled; + } + + private void activate() { + final boolean blink = mySettings.isBlinkCaret(); + final int blinkPeriod = mySettings.getCaretBlinkPeriod(); + synchronized (ourCaretBlinkingCommand) { + ourCaretBlinkingCommand.myEditor = EditorImpl.this; + ourCaretBlinkingCommand.setBlinkCaret(blink); + ourCaretBlinkingCommand.setBlinkPeriod(blinkPeriod); + myIsShown = true; + } + } + + public boolean isActive() { + synchronized (ourCaretBlinkingCommand) { + return myIsShown; + } + } + + private void passivate() { + synchronized (ourCaretBlinkingCommand) { + myIsShown = false; + } + } + + private void setPosition(Point location, int width) { + myStartTime = System.currentTimeMillis(); + myLocation = location; + myWidth = Math.max(width, 2); + myIsShown = true; + repaint(); + } + + private void repaint() { + myEditorComponent.repaintEditorComponent(myLocation.x, myLocation.y, myWidth, getLineHeight()); + } + + private void paint(Graphics g) { + if (!isEnabled() || !myIsShown || !IJSwingUtilities.hasFocus(getContentComponent()) || isRendererMode()) return; + + int x = myLocation.x; + int lineHeight = getLineHeight(); + int y = myLocation.y; + + Rectangle viewRectangle = getScrollingModel().getVisibleArea(); + if (x - viewRectangle.x < 0) { + return; + } + + + g.setColor(myScheme.getColor(EditorColors.CARET_COLOR)); + + if (myIsInsertMode != mySettings.isBlockCursor()) { + for (int i = 0; i < mySettings.getLineCursorWidth(); i++) { + UIUtil.drawLine(g, x + i, y, x + i, y + lineHeight - 1); + } + } + else { + Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR); + if (background == null) background = getBackgroundColor(); + g.setXORMode(background); + + g.fillRect(x, y, myWidth, lineHeight - 1); + + g.setPaintMode(); + } + } + } + + private class ScrollingTimer { + Timer myTimer; + private static final int TIMER_PERIOD = 100; + private static final int CYCLE_SIZE = 20; + private int myXCycles; + private int myYCycles; + private int myDx; + private int myDy; + private int xPassedCycles = 0; + private int yPassedCycles = 0; + + private void start(int dx, int dy) { + myDx = 0; + myDy = 0; + if (dx > 0) { + myXCycles = CYCLE_SIZE / dx + 1; + myDx = 1 + dx / CYCLE_SIZE; + } + else { + if (dx < 0) { + myXCycles = -CYCLE_SIZE / dx + 1; + myDx = -1 + dx / CYCLE_SIZE; + } + } + + if (dy > 0) { + myYCycles = CYCLE_SIZE / dy + 1; + myDy = 1 + dy / CYCLE_SIZE; + } + else { + if (dy < 0) { + myYCycles = -CYCLE_SIZE / dy + 1; + myDy = -1 + dy / CYCLE_SIZE; + } + } + + if (myTimer != null) { + return; + } + + + myTimer = new Timer(TIMER_PERIOD, new ActionListener() { + public void actionPerformed(ActionEvent e) { + myCommandProcessor.executeCommand(myProject, new DocumentRunnable(myDocument, myProject) { + public void run() { + int oldSelectionStart = mySelectionModel.getLeadSelectionOffset(); + VisualPosition caretPosition = getCaretModel().getVisualPosition(); + int columnNumber = caretPosition.column; + xPassedCycles++; + if (xPassedCycles >= myXCycles) { + xPassedCycles = 0; + columnNumber += myDx; + } + + int lineNumber = caretPosition.line; + yPassedCycles++; + if (yPassedCycles >= myYCycles) { + yPassedCycles = 0; + lineNumber += myDy; + } + + lineNumber = Math.max(0, lineNumber); + columnNumber = Math.max(0, columnNumber); + VisualPosition pos = new VisualPosition(lineNumber, columnNumber); + getCaretModel().moveToVisualPosition(pos); + getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + + int newCaretOffset = getCaretModel().getOffset(); + int caretShift = newCaretOffset - mySavedSelectionStart; + + if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { + if (caretShift < 0) { + int newSelection = newCaretOffset; + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { + newSelection = mySelectionModel.getWordAtCaretStart(); + } + else { + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { + newSelection = + logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))); + } + } + if (newSelection < 0) newSelection = newCaretOffset; + mySelectionModel.setSelection(validateOffset(mySavedSelectionEnd), newSelection); + getCaretModel().moveToOffset(newSelection); + } + else { + int newSelection = newCaretOffset; + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { + newSelection = mySelectionModel.getWordAtCaretEnd(); + } + else { + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { + newSelection = logicalPositionToOffset( + visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))); + } + } + if (newSelection < 0) newSelection = newCaretOffset; + mySelectionModel.setSelection(validateOffset(mySavedSelectionStart), newSelection); + getCaretModel().moveToOffset(newSelection); + } + return; + } + + if (mySelectionModel.hasBlockSelection()) { + mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition()); + } + else { + mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset()); + } + } + }, EditorBundle.message("move.cursor.command.name"), DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument()); + } + }); + myTimer.start(); + } + + private void stop() { + if (myTimer != null) { + myTimer.stop(); + myTimer = null; + } + } + + private int validateOffset(int offset) { + if (offset < 0) return 0; + if (offset > myDocument.getTextLength()) return myDocument.getTextLength(); + return offset; + } + } + + private static final Field decrButtonField; + private static final Field incrButtonField; + static { + try { + decrButtonField = BasicScrollBarUI.class.getDeclaredField("decrButton"); + decrButtonField.setAccessible(true); + + incrButtonField = BasicScrollBarUI.class.getDeclaredField("incrButton"); + incrButtonField.setAccessible(true); + } + catch (NoSuchFieldException e) { + throw new IllegalStateException(e); + } + } + + class MyScrollBar extends JBScrollBar { + @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS = "apple.laf.AquaScrollBarUI"; + private ScrollBarUI myPersistentUI; + + private MyScrollBar(int orientation) { + super(orientation); + } + + void setPersistentUI(ScrollBarUI ui) { + myPersistentUI = ui; + setUI(ui); + } + + @Override + public void setUI(ScrollBarUI ui) { + if (myPersistentUI == null) myPersistentUI = ui; + super.setUI(myPersistentUI); + } + + /** + * This is helper method. It returns height of the top (decrease) scroll bar + * button. Please note, that it's possible to return real height only if scroll bar + * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) + * value. + */ + int getDecScrollButtonHeight() { + ScrollBarUI barUI = getUI(); + Insets insets = getInsets(); + if (barUI instanceof BasicScrollBarUI) { + try { + JButton decrButtonValue = (JButton)decrButtonField.get(barUI); + LOG.assertTrue(decrButtonValue != null); + return insets.top + decrButtonValue.getHeight(); + } + catch (Exception exc) { + throw new IllegalStateException(exc); + } + } + else { + return insets.top + 15; + } + } + + /** + * This is helper method. It returns height of the bottom (increase) scroll bar + * button. Please note, that it's possible to return real height only if scroll bar + * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) + * value. + */ + int getIncScrollButtonHeight() { + ScrollBarUI barUI = getUI(); + Insets insets = getInsets(); + if (barUI instanceof BasicScrollBarUI) { + try { + JButton incrButtonValue = (JButton)incrButtonField.get(barUI); + LOG.assertTrue(incrButtonValue != null); + return insets.bottom + incrButtonValue.getHeight(); + } + catch (Exception exc) { + throw new IllegalStateException(exc.getMessage()); + } + } + else if (APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS.equals(barUI.getClass().getName())) { + return insets.bottom + 30; + } + else { + return insets.bottom + 15; + } + } + + public int getUnitIncrement(int direction) { + JViewport vp = myScrollPane.getViewport(); + Rectangle vr = vp.getViewRect(); + return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction); + } + + public int getBlockIncrement(int direction) { + JViewport vp = myScrollPane.getViewport(); + Rectangle vr = vp.getViewRect(); + return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction); + } + } + + private MyEditable getViewer() { + if (myEditable == null) { + myEditable = new MyEditable(); + } + return myEditable; + } + + public CopyProvider getCopyProvider() { + return getViewer(); + } + + public CutProvider getCutProvider() { + return getViewer(); + } + + public PasteProvider getPasteProvider() { + + return getViewer(); + } + + public DeleteProvider getDeleteProvider() { + return getViewer(); + } + + private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider { + public void performCopy(DataContext dataContext) { + executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext); + } + + public boolean isCopyEnabled(DataContext dataContext) { + return true; + } + + public boolean isCopyVisible(DataContext dataContext) { + return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection(); + } + + public void performCut(DataContext dataContext) { + executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext); + } + + public boolean isCutEnabled(DataContext dataContext) { + return !isViewer() && getDocument().isWritable(); + } + + public boolean isCutVisible(DataContext dataContext) { + return getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection(); + } + + public void performPaste(DataContext dataContext) { + executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext); + } + + public boolean isPastePossible(DataContext dataContext) { + // Copy of isPasteEnabled. See interface method javadoc. + return !isViewer() && getDocument().isWritable(); + } + + public boolean isPasteEnabled(DataContext dataContext) { + return !isViewer() && getDocument().isWritable(); + } + + public void deleteElement(DataContext dataContext) { + executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext); + } + + public boolean canDeleteElement(DataContext dataContext) { + return !isViewer() && getDocument().isWritable(); + } + + private void executeAction(String actionId, DataContext dataContext) { + EditorAction action = (EditorAction)ActionManager.getInstance().getAction(actionId); + if (action != null) { + action.actionPerformed(EditorImpl.this, dataContext); + } + } + } + + public void setColorsScheme(@NotNull EditorColorsScheme scheme) { + assertIsDispatchThread(); + myScheme = scheme; + reinitSettings(); + } + + @NotNull + public EditorColorsScheme getColorsScheme() { + assertReadAccess(); + return myScheme; + } + + void assertIsDispatchThread() { + ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(myEditorComponent); + } + private static void assertReadAccess() { + ApplicationManagerEx.getApplicationEx().assertReadAccessAllowed(); + } + + public void setVerticalScrollbarOrientation(int type) { + assertIsDispatchThread(); + int currentHorOffset = myScrollingModel.getHorizontalScrollOffset(); + myScrollBarOrientation = type; + if (type == VERTICAL_SCROLLBAR_LEFT) { + myScrollPane.setLayout(new LeftHandScrollbarLayout()); + } + else { + myScrollPane.setLayout(new ScrollPaneLayout()); + } + myScrollingModel.scrollHorizontally(currentHorOffset); + } + + public void setVerticalScrollbarVisible(boolean b) { + if (b) { + myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + } + else { + myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + } + } + + public void setHorizontalScrollbarVisible(boolean b) { + if (b) { + myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + } + else { + myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + } + } + + int getVerticalScrollbarOrientation() { + return myScrollBarOrientation; + } + + MyScrollBar getVerticalScrollBar() { + return myVerticalScrollBar; + } + + private int getMouseSelectionState() { + return myMouseSelectionState; + } + + private void setMouseSelectionState(int mouseSelectionState) { + myMouseSelectionState = mouseSelectionState; + myMouseSelectionChangeTimestamp = System.currentTimeMillis(); + } + + + void replaceInputMethodText(InputMethodEvent e) { + getInputMethodRequests(); + myInputMethodRequestsHandler.replaceInputMethodText(e); + } + + void inputMethodCaretPositionChanged(InputMethodEvent e) { + getInputMethodRequests(); + myInputMethodRequestsHandler.setInputMethodCaretPosition(e); + } + + InputMethodRequests getInputMethodRequests() { + if (myInputMethodRequestsHandler == null) { + myInputMethodRequestsHandler = new MyInputMethodHandler(); + myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler); + } + return myInputMethodRequestsSwingWrapper; + } + + public boolean processKeyTyped(KeyEvent e) { + if (e.getID() != KeyEvent.KEY_TYPED) return false; + char c = e.getKeyChar(); + if (UIUtil.isReallyTypedEvent(e)) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction + processKeyTyped(c); + return true; + } + else { + return false; + } + } + + void beforeModalityStateChanged() { + myScrollingModel.beforeModalityStateChanged(); + } + + public EditorDropHandler getDropHandler() { + return myDropHandler; + } + + public void setDropHandler(EditorDropHandler dropHandler) { + myDropHandler = dropHandler; + } + + private static class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests { + private final InputMethodRequests myDelegate; + + private MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) { + myDelegate = delegate; + } + + public Rectangle getTextLocation(final TextHitInfo offset) { + if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getTextLocation(offset); + + final Rectangle[] r = new Rectangle[1]; + try { + GuiUtils.invokeAndWait(new Runnable() { + public void run() { + r[0] = myDelegate.getTextLocation(offset); + } + }); + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (InvocationTargetException e) { + LOG.error(e); + } + return r[0]; + } + + public TextHitInfo getLocationOffset(final int x, final int y) { + if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getLocationOffset(x, y); + + final TextHitInfo[] r = new TextHitInfo[1]; + try { + GuiUtils.invokeAndWait(new Runnable() { + public void run() { + r[0] = myDelegate.getLocationOffset(x, y); + } + }); + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (InvocationTargetException e) { + LOG.error(e); + } + return r[0]; + } + + public int getInsertPositionOffset() { + if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getInsertPositionOffset(); + + final int[] r = new int[1]; + try { + GuiUtils.invokeAndWait(new Runnable() { + public void run() { + r[0] = myDelegate.getInsertPositionOffset(); + } + }); + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (InvocationTargetException e) { + LOG.error(e); + } + return r[0]; + } + + public AttributedCharacterIterator getCommittedText(final int beginIndex, + final int endIndex, + final AttributedCharacterIterator.Attribute[] attributes) { + if (ApplicationManager.getApplication().isDispatchThread()) { + return myDelegate.getCommittedText(beginIndex, endIndex, attributes); + } + final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1]; + try { + GuiUtils.invokeAndWait(new Runnable() { + public void run() { + r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes); + } + }); + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (InvocationTargetException e) { + LOG.error(e); + } + return r[0]; + } + + public int getCommittedTextLength() { + if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getCommittedTextLength(); + final int[] r = new int[1]; + try { + GuiUtils.invokeAndWait(new Runnable() { + public void run() { + r[0] = myDelegate.getCommittedTextLength(); + } + }); + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (InvocationTargetException e) { + LOG.error(e); + } + return r[0]; + } + + @Nullable + public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) { + return null; + } + + public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) { + if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getSelectedText(attributes); + + final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1]; + try { + GuiUtils.invokeAndWait(new Runnable() { + public void run() { + r[0] = myDelegate.getSelectedText(attributes); + } + }); + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (InvocationTargetException e) { + LOG.error(e); + } + return r[0]; + } + } + + private class MyInputMethodHandler implements InputMethodRequests { + private String composedText; + private int composedTextStart; + private int composedTextEnd; + + public Rectangle getTextLocation(TextHitInfo offset) { + Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition()); + Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight())); + Point p = getContentComponent().getLocationOnScreen(); + r.translate(p.x, p.y); + + return r; + } + + @Nullable + public TextHitInfo getLocationOffset(int x, int y) { + if (composedText != null) { + Point p = getContentComponent().getLocationOnScreen(); + p.x = x - p.x; + p.y = y - p.y; + int pos = logicalPositionToOffset(xyToLogicalPosition(p)); + if (pos >= composedTextStart && pos <= composedTextEnd) { + return TextHitInfo.leading(pos - composedTextStart); + } + } + return null; + } + + public int getInsertPositionOffset() { + int composedStartIndex = 0; + int composedEndIndex = 0; + if (composedText != null) { + composedStartIndex = composedTextStart; + composedEndIndex = composedTextEnd; + } + + int caretIndex = getCaretModel().getOffset(); + + if (caretIndex < composedStartIndex) { + return caretIndex; + } + else { + if (caretIndex < composedEndIndex) { + return composedStartIndex; + } + else { + return caretIndex - (composedEndIndex - composedStartIndex); + } + } + } + + private String getText(int startIdx, int endIdx) { + CharSequence chars = getDocument().getCharsSequence(); + return chars.subSequence(startIdx, endIdx).toString(); + } + + public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex, AttributedCharacterIterator.Attribute[] attributes) { + int composedStartIndex = 0; + int composedEndIndex = 0; + if (composedText != null) { + composedStartIndex = composedTextStart; + composedEndIndex = composedTextEnd; + } + + String committed; + if (beginIndex < composedStartIndex) { + if (endIndex <= composedStartIndex) { + committed = getText(beginIndex, endIndex - beginIndex); + } + else { + int firstPartLength = composedStartIndex - beginIndex; + committed = getText(beginIndex, firstPartLength) + getText(composedEndIndex, endIndex - beginIndex - firstPartLength); + } + } + else { + committed = getText(beginIndex + composedEndIndex - composedStartIndex, endIndex - beginIndex); + } + + return new AttributedString(committed).getIterator(); + } + + public int getCommittedTextLength() { + int length = getDocument().getTextLength(); + if (composedText != null) { + length -= composedText.length(); + } + return length; + } + + @Nullable + public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) { + return null; + } + + @Nullable + public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) { + String text = getSelectionModel().getSelectedText(); + return text == null ? null : new AttributedString(text).getIterator(); + } + + private void createComposedString(int composedIndex, AttributedCharacterIterator text) { + StringBuffer strBuf = new StringBuffer(); + + // create attributed string with no attributes + for (char c = text.setIndex(composedIndex); c != CharacterIterator.DONE; c = text.next()) { + strBuf.append(c); + } + + composedText = new String(strBuf); + } + + private void setInputMethodCaretPosition(InputMethodEvent e) { + if (composedText != null) { + int dot = composedTextStart; + + TextHitInfo caretPos = e.getCaret(); + if (caretPos != null) { + dot += caretPos.getInsertionIndex(); + } + + getCaretModel().moveToOffset(dot); + getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + } + + private void runUndoTransparent(final Runnable runnable) { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + public void run() { + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(runnable); + } + }, "", getDocument(), UndoConfirmationPolicy.DEFAULT, getDocument()); + } + }); + } + + private void replaceInputMethodText(InputMethodEvent e) { + int commitCount = e.getCommittedCharacterCount(); + AttributedCharacterIterator text = e.getText(); + + // old composed text deletion + final Document doc = getDocument(); + + if (composedText != null) { + if (!isViewer() && doc.isWritable()) { + runUndoTransparent(new Runnable() { + public void run() { + doc.deleteString(Math.max(0, composedTextStart), Math.min(composedTextEnd, doc.getTextLength())); + } + }); + } + composedText = null; + } + + if (text != null) { + text.first(); + + // committed text insertion + if (commitCount > 0) { + //noinspection ForLoopThatDoesntUseLoopVariable + for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) { + if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction + processKeyTyped(c); + } + } + } + + // new composed text insertion + if (!isViewer() && doc.isWritable()) { + int composedTextIndex = text.getIndex(); + if (composedTextIndex < text.getEndIndex()) { + createComposedString(composedTextIndex, text); + + runUndoTransparent(new Runnable() { + public void run() { + EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false); + } + }); + + composedTextStart = getCaretModel().getOffset(); + composedTextEnd = getCaretModel().getOffset() + composedText.length(); + } + } + } + } + } + + private class MyMouseAdapter extends MouseAdapter { + + private boolean mySelectionTweaked; + + public void mousePressed(MouseEvent e) { + requestFocus(); + runMousePressedCommand(e); + } + + public void mouseReleased(MouseEvent e) { + runMouseReleasedCommand(e); + if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() && + Math.abs(e.getX() - myMousePressedEvent.getX()) < EditorUtil.getSpaceWidth(Font.PLAIN, EditorImpl.this) && + Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) { + runMouseClickedCommand(e); + } + myMousePressedEvent = null; + } + + public void mouseEntered(MouseEvent e) { + runMouseEnteredCommand(e); + } + + public void mouseExited(MouseEvent e) { + runMouseExitedCommand(e); + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) { + myGutterComponent.mouseExited(e); + } + + TooltipController.getInstance().cancelTooltip(FOLDING_TOOLTIP_GROUP, e, true); + } + private void runMousePressedCommand(final MouseEvent e) { + mySelectionTweaked = false; + myMousePressedEvent = e; + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + + for (EditorMouseListener mouseListener : myMouseListeners) { + mouseListener.mousePressed(event); + } + + // On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event. + // But this trigger is always consumed by popup handler. In that case we have to + // also move caret. + if (event.isConsumed() && !(event.getMouseEvent().isPopupTrigger() || event.getArea() == EditorMouseEventArea.EDITING_AREA)) { + return; + } + + if (myCommandProcessor != null) { + Runnable runnable = new Runnable() { + public void run() { + processMousePressed(e); + } + }; + myCommandProcessor.executeCommand(myProject, runnable, "", DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument()); + } + else { + processMousePressed(e); + } + } + + private void runMouseClickedCommand(final MouseEvent e) { + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + for (EditorMouseListener listener : myMouseListeners) { + listener.mouseClicked(event); + if (event.isConsumed()) { + e.consume(); + return; + } + } + } + + private void runMouseReleasedCommand(final MouseEvent e) { + if (!mySelectionTweaked) { + tweakSelectionIfNecessary(e); + } + if (e.isConsumed()) { + return; + } + + myScrollingTimer.stop(); + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + for (EditorMouseListener listener : myMouseListeners) { + listener.mouseReleased(event); + if (event.isConsumed()) { + e.consume(); + return; + } + } + + if (myCommandProcessor != null) { + Runnable runnable = new Runnable() { + public void run() { + processMouseReleased(e); + } + }; + myCommandProcessor.executeCommand(myProject, runnable, "", DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT, getDocument()); + } + else { + processMouseReleased(e); + } + } + + private void runMouseEnteredCommand(MouseEvent e) { + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + for (EditorMouseListener listener : myMouseListeners) { + listener.mouseEntered(event); + if (event.isConsumed()) { + e.consume(); + return; + } + } + } + + private void runMouseExitedCommand(MouseEvent e) { + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + for (EditorMouseListener listener : myMouseListeners) { + listener.mouseExited(event); + if (event.isConsumed()) { + e.consume(); + return; + } + } + } + + private void processMousePressed(MouseEvent e) { + myInitialMouseEvent = e; + + if (myMouseSelectionState != MOUSE_SELECTION_STATE_NONE && System.currentTimeMillis() - myMouseSelectionChangeTimestamp > 1000) { + setMouseSelectionState(MOUSE_SELECTION_STATE_NONE); + } + + int x = e.getX(); + int y = e.getY(); + + if (x < 0) x = 0; + if (y < 0) y = 0; + + final EditorMouseEventArea eventArea = getMouseEventArea(e); + if (eventArea == EditorMouseEventArea.FOLDING_OUTLINE_AREA) { + final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y); + if (range != null) { + final boolean expansion = !range.isExpanded(); + + int scrollShift = y - getScrollingModel().getVerticalScrollOffset(); + Runnable processor = new Runnable() { + public void run() { + myFoldingModel.flushCaretShift(); + range.setExpanded(expansion); + } + }; + getFoldingModel().runBatchFoldingOperation(processor); + y = myGutterComponent.getHeadCenterY(range); + getScrollingModel().scrollVertically(y - scrollShift); + return; + } + } + + if (e.getSource() == myGutterComponent) { + if (eventArea == EditorMouseEventArea.LINE_MARKERS_AREA || eventArea == EditorMouseEventArea.ANNOTATIONS_AREA || eventArea == EditorMouseEventArea.LINE_NUMBERS_AREA) { + if (tweakSelectionIfNecessary(e)) { + mySelectionTweaked = true; + } + else { + myGutterComponent.mousePressed(e); + } + if (e.isConsumed()) return; + } + x = 0; + } + + int oldSelectionStart = mySelectionModel.getLeadSelectionOffset(); + moveCaretToScreenPos(x, y); + + if (e.isPopupTrigger()) return; + + requestFocus(); + + int caretOffset = getCaretModel().getOffset(); + + myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y)); + myMousePressedInsideSelection = mySelectionModel.hasSelection() && caretOffset >= mySelectionModel.getSelectionStart() && + caretOffset <= mySelectionModel.getSelectionEnd(); + + if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) { + int[] starts = mySelectionModel.getBlockSelectionStarts(); + int[] ends = mySelectionModel.getBlockSelectionEnds(); + for (int i = 0; i < starts.length; i++) { + if (caretOffset >= starts[i] && caretOffset < ends[i]) { + myMousePressedInsideSelection = true; + break; + } + } + } + + if (getMouseEventArea(e) == EditorMouseEventArea.LINE_NUMBERS_AREA && e.getClickCount() == 1) { + mySelectionModel.selectLineAtCaret(); + setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED); + mySavedSelectionStart = mySelectionModel.getSelectionStart(); + mySavedSelectionEnd = mySelectionModel.getSelectionEnd(); + return; + } + + if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) { + if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { + if (caretOffset < mySavedSelectionStart) { + mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset); + } + else { + mySelectionModel.setSelection(mySavedSelectionStart, caretOffset); + } + } + else { + mySelectionModel.setSelection(oldSelectionStart, caretOffset); + } + } + else { + if (!myMousePressedInsideSelection && (getSelectionModel().hasSelection() || getSelectionModel().hasBlockSelection())) { + setMouseSelectionState(MOUSE_SELECTION_STATE_NONE); + mySelectionModel.setSelection(caretOffset, caretOffset); + } + else { + if (!e.isPopupTrigger()) { + switch (e.getClickCount()) { + case 2: + mySelectionModel.selectWordAtCaret(mySettings.isMouseClickSelectionHonorsCamelWords()); + setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED); + mySavedSelectionStart = mySelectionModel.getSelectionStart(); + mySavedSelectionEnd = mySelectionModel.getSelectionEnd(); + getCaretModel().moveToOffset(mySavedSelectionEnd); + break; + + case 3: + mySelectionModel.selectLineAtCaret(); + setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED); + mySavedSelectionStart = mySelectionModel.getSelectionStart(); + mySavedSelectionEnd = mySelectionModel.getSelectionEnd(); + break; + } + } + } + } + } + } + + /** + * Allows to answer if given event should tweak editor selection. + * + * @param e event for occurred mouse action + * @return true if action that produces given event will trigger editor selection change; false otherwise + */ + private boolean tweakSelectionEvent(@NotNull MouseEvent e) { + return getSelectionModel().hasSelection() && e.getButton() == MouseEvent.BUTTON1 && e.isShiftDown() + && e.getComponent() == getGutterComponentEx(); + } + + /** + * Checks if editor selection should be changed because of click at the given point at gutter and proceeds if necessary. + *

+ * The main idea is that selection can be changed during left mouse clicks with hold Shift button and we want + * to distinguish that situation from 'close line numbers gutter' action (activated by 'Shift+click' + * on gutter area). + * + * @param e event for mouse click on gutter area + * @return true if editor's selection is changed because of the click; false otherwise + */ + private boolean tweakSelectionIfNecessary(@NotNull MouseEvent e) { + if (!tweakSelectionEvent(e)) { + return false; + } + + int startSelectionOffset = getSelectionModel().getSelectionStart(); + int startVisLine = offsetToVisualLine(startSelectionOffset); + + int endSelectionOffset = getSelectionModel().getSelectionEnd(); + int endVisLine = offsetToVisualLine(endSelectionOffset - 1); + + int clickVisLine = xyToVisualPosition(e.getPoint()).line; + + if (clickVisLine < startVisLine) { + // Expand selection at backward direction. + int startOffset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(clickVisLine, 0))); + getSelectionModel().setSelection(startOffset, endSelectionOffset); + getCaretModel().moveToOffset(startOffset); + } + else if (clickVisLine > endVisLine) { + // Expand selection at forward direction. + int endLineOffset = EditorUtil.getVisualLineEndOffset(this, clickVisLine); + getSelectionModel().setSelection(getSelectionModel().getSelectionStart(), endLineOffset); + getCaretModel().moveToOffset(endLineOffset, true); + } + else if (startVisLine == endVisLine) { + // Remove selection + getSelectionModel().removeSelection(); + } + else { + // Reduce selection in backward direction. + if (getSelectionModel().getLeadSelectionOffset() == endSelectionOffset) { + if (clickVisLine == startVisLine) { + clickVisLine++; + } + int startOffset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(clickVisLine, 0))); + getSelectionModel().setSelection(startOffset, endSelectionOffset); + getCaretModel().moveToOffset(startOffset); + } + else { + // Reduce selection is forward direction. + if (clickVisLine == endVisLine) { + clickVisLine--; + } + int endLineOffset = EditorUtil.getVisualLineEndOffset(this, clickVisLine); + getSelectionModel().setSelection(startSelectionOffset, endLineOffset); + getCaretModel().moveToOffset(endLineOffset); + } + } + e.consume(); + return true; + } + + private static final TooltipGroup FOLDING_TOOLTIP_GROUP = new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10); + + private class MyMouseMotionListener implements MouseMotionListener { + public void mouseDragged(MouseEvent e) { + validateMousePointer(e); + runMouseDraggedCommand(e); + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) { + myGutterComponent.mouseDragged(e); + } + + for (EditorMouseMotionListener listener : myMouseMotionListeners) { + listener.mouseDragged(event); + } + } + + public void mouseMoved(MouseEvent e) { + validateMousePointer(e); + EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + if (e.getSource() == myGutterComponent) { + myGutterComponent.mouseMoved(e); + } + + if (event.getArea() == EditorMouseEventArea.EDITING_AREA) { + FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint()); + TooltipController controller = TooltipController.getInstance(); + if (fold != null) { + DocumentFragment range = createDocumentFragment(fold); + final Point p = + SwingUtilities.convertPoint((Component)e.getSource(), e.getPoint(), getComponent().getRootPane().getLayeredPane()); + controller.showTooltip(EditorImpl.this, p, new DocumentFragmentTooltipRenderer(range), false, FOLDING_TOOLTIP_GROUP); + } + else { + controller.cancelTooltip(FOLDING_TOOLTIP_GROUP, e, true); + } + } + + for (EditorMouseMotionListener listener : myMouseMotionListeners) { + listener.mouseMoved(event); + } + } + + private DocumentFragment createDocumentFragment(FoldRegion fold) { + final FoldingGroup group = fold.getGroup(); + final int foldStart = fold.getStartOffset(); + if (group != null) { + final int endOffset = myFoldingModel.getEndOffset(group); + if (offsetToVisualLine(endOffset) == offsetToVisualLine(foldStart)) { + return new DocumentFragment(myDocument, foldStart, endOffset); + } + } + + final int oldEnd = fold.getEndOffset(); + return new DocumentFragment(myDocument, foldStart, oldEnd); + } + } + + private class MyColorSchemeDelegate implements EditorColorsScheme { + private final HashMap myOwnAttributes = new HashMap(); + private final HashMap myOwnColors = new HashMap(); + private final EditorColorsScheme myCustomGlobalScheme; + private Map myFontsMap = null; + private int myFontSize = -1; + private String myFaceName = null; + private EditorColorsScheme myGlobalScheme; + + private MyColorSchemeDelegate(@Nullable final EditorColorsScheme globalScheme) { + myCustomGlobalScheme = globalScheme; + updateGlobalScheme(); + } + + private EditorColorsScheme getGlobal() { + return myGlobalScheme; + } + + public String getName() { + return getGlobal().getName(); + } + + + protected void initFonts() { + String editorFontName = getEditorFontName(); + int editorFontSize = getEditorFontSize(); + + myFontsMap = new EnumMap(EditorFontType.class); + + Font plainFont = new Font(editorFontName, Font.PLAIN, editorFontSize); + Font boldFont = new Font(editorFontName, Font.BOLD, editorFontSize); + Font italicFont = new Font(editorFontName, Font.ITALIC, editorFontSize); + Font boldItalicFont = new Font(editorFontName, Font.BOLD + Font.ITALIC, editorFontSize); + + myFontsMap.put(EditorFontType.PLAIN, plainFont); + myFontsMap.put(EditorFontType.BOLD, boldFont); + myFontsMap.put(EditorFontType.ITALIC, italicFont); + myFontsMap.put(EditorFontType.BOLD_ITALIC, boldItalicFont); + + reinitSettings(); + } + + public void setName(String name) { + getGlobal().setName(name); + } + + public TextAttributes getAttributes(TextAttributesKey key) { + if (myOwnAttributes.containsKey(key)) return myOwnAttributes.get(key); + return getGlobal().getAttributes(key); + } + + public void setAttributes(TextAttributesKey key, TextAttributes attributes) { + myOwnAttributes.put(key, attributes); + } + + public Color getDefaultBackground() { + return getGlobal().getDefaultBackground(); + } + + public Color getDefaultForeground() { + return getGlobal().getDefaultForeground(); + } + + public Color getColor(ColorKey key) { + if (myOwnColors.containsKey(key)) return myOwnColors.get(key); + return getGlobal().getColor(key); + } + + public void setColor(ColorKey key, Color color) { + myOwnColors.put(key, color); + + // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit + // settings in this case. + myCaretModel.reinitSettings(); + mySelectionModel.reinitSettings(); + } + + public int getEditorFontSize() { + if (myFontSize == -1) { + return getGlobal().getEditorFontSize(); + } + return myFontSize; + } + + public void setEditorFontSize(int fontSize) { + if (fontSize < 8) fontSize = 8; + if (fontSize > 20) fontSize = 20; + myFontSize = fontSize; + initFonts(); + } + + public String getEditorFontName() { + if (myFaceName == null) { + return getGlobal().getEditorFontName(); + } + return myFaceName; + } + + public void setEditorFontName(String fontName) { + myFaceName = fontName; + initFonts(); + } + + public Font getFont(EditorFontType key) { + if (myFontsMap != null) { + Font font = myFontsMap.get(key); + if (font != null) return font; + } + return getGlobal().getFont(key); + } + + public void setFont(EditorFontType key, Font font) { + if (myFontsMap == null) { + initFonts(); + } + myFontsMap.put(key, font); + reinitSettings(); + } + + public float getLineSpacing() { + return getGlobal().getLineSpacing(); + } + + public void setLineSpacing(float lineSpacing) { + getGlobal().setLineSpacing(lineSpacing); + } + + @Nullable + public Object clone() { + return null; + } + + public void readExternal(Element element) throws InvalidDataException { + } + + public void writeExternal(Element element) throws WriteExternalException { + } + + public void updateGlobalScheme() { + myGlobalScheme = myCustomGlobalScheme != null ? myCustomGlobalScheme + : EditorColorsManager.getInstance().getGlobalScheme(); + } + } + + private static class MyTransferHandler extends TransferHandler { + private RangeMarker myDraggedRange = null; + + private static Editor getEditor(JComponent comp) { + EditorComponentImpl editorComponent = (EditorComponentImpl)comp; + return editorComponent.getEditor(); + } + + public boolean importData(final JComponent comp, final Transferable t) { + final EditorImpl editor = (EditorImpl)getEditor(comp); + + final EditorDropHandler dropHandler = editor.getDropHandler(); + if (dropHandler != null && dropHandler.canHandleDrop(t.getTransferDataFlavors())) { + dropHandler.handleDrop(t, editor.getProject()); + return true; + } + + final int caretOffset = editor.getCaretModel().getOffset(); + if (myDraggedRange != null && myDraggedRange.getStartOffset() <= caretOffset && caretOffset < myDraggedRange.getEndOffset()) { + return false; + } + + if (myDraggedRange != null) { + editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack); + } + + CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + editor.getSelectionModel().removeSelection(); + final int offset; + if (myDraggedRange != null) { + editor.getCaretModel().moveToOffset(caretOffset); + offset = caretOffset; + } + else { + offset = editor.getCaretModel().getOffset(); + } + if (editor.getDocument().getRangeGuard(offset, offset) != null) return; + + EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + + Transferable backup = null; + try { + backup = clipboard.getContents(this); + clipboard.setContents(t, EmptyClipboardOwner.INSTANCE); + } + catch (Exception e) { + LOG.info("Error communicating with system clipboard", e); + } + + editor.putUserData(LAST_PASTED_REGION, null); + pasteHandler.execute(editor, editor.getDataContext()); + try { + if (backup != null) { + clipboard.setContents(backup, EmptyClipboardOwner.INSTANCE); + } + } + catch (IllegalStateException e) { + LOG.info(e); + } + + TextRange range = editor.getUserData(LAST_PASTED_REGION); + if (range != null) { + editor.getCaretModel().moveToOffset(range.getStartOffset()); + editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()); + } + } + catch (Exception exception) { + LOG.error(exception); + } + } + }); + } + }, EditorBundle.message("paste.command.name"), DND_COMMAND_KEY, UndoConfirmationPolicy.DEFAULT, editor.getDocument()); + + return true; + } + + public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { + Editor editor = getEditor(comp); + final EditorDropHandler dropHandler = ((EditorImpl)editor).getDropHandler(); + if (dropHandler != null && dropHandler.canHandleDrop(transferFlavors)) { + return true; + } + if (editor.isViewer()) return false; + + int offset = editor.getCaretModel().getOffset(); + if (editor.getDocument().getRangeGuard(offset, offset) != null) return false; + + for (DataFlavor transferFlavor : transferFlavors) { + if (transferFlavor.equals(DataFlavor.stringFlavor)) return true; + } + + return false; + } + + @Nullable + protected Transferable createTransferable(JComponent c) { + Editor editor = getEditor(c); + String s = editor.getSelectionModel().getSelectedText(); + if (s == null) return null; + int selectionStart = editor.getSelectionModel().getSelectionStart(); + int selectionEnd = editor.getSelectionModel().getSelectionEnd(); + myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd); + + return new StringSelection(s); + } + + public int getSourceActions(JComponent c) { + return COPY_OR_MOVE; + } + + protected void exportDone(final JComponent source, Transferable data, int action) { + if (data == null) return; + + final Component last = DnDManager.getInstance().getLastDropHandler(); + + if (last != null && !(last instanceof EditorComponentImpl)) return; + + final Editor editor = getEditor(source); + if (action == MOVE && !editor.isViewer()) { + if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), editor.getProject())) { + return; + } + CommandProcessor.getInstance().executeCommand(((EditorImpl)editor).myProject, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + Document doc = editor.getDocument(); + doc.startGuardedBlockChecking(); + try { + doc.deleteString(myDraggedRange.getStartOffset(), myDraggedRange.getEndOffset()); + } + catch (ReadOnlyFragmentModificationException e) { + EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e); + } + finally { + doc.stopGuardedBlockChecking(); + } + } + }); + } + }, EditorBundle.message("move.selection.command.name"), DND_COMMAND_KEY, UndoConfirmationPolicy.DEFAULT, editor.getDocument()); + } + + myDraggedRange = null; + } + } + + class EditorDocumentAdapter implements PrioritizedDocumentListener { + public void beforeDocumentChange(DocumentEvent e) { + beforeChangedUpdate(e); + } + + public void documentChanged(DocumentEvent e) { + changedUpdate(e); + } + + public int getPriority() { + return EditorDocumentPriorities.EDITOR_DOCUMENT_ADAPTER; + } + } + + @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) + private class EditorSizeContainer { + + /** Holds logical line widths in pixels. */ + private TIntArrayList myLineWidths; + + /** Holds value that indicates if line widths recalculation should be performed. */ + private volatile boolean myIsDirty; + + /** Holds number of the last logical line affected by the last document change. */ + private int myOldEndLine; + + private Dimension mySize; + + private final Object lock = new Object(); + private int myMaxWidth = -1; + + public synchronized void reset() { + int lineCount = getDocument().getLineCount(); + myLineWidths = new TIntArrayList(lineCount + 300); + int[] values = new int[lineCount]; + Arrays.fill(values, -1); + myLineWidths.add(values); + myIsDirty = true; + } + + @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"}) + public synchronized void beforeChange(DocumentEvent e) { + if (myDocument.isInBulkUpdate()) { + myMaxWidth = mySize != null ? mySize.width : -1; + } + + myOldEndLine = offsetToLogicalPosition(e.getOffset() + e.getOldLength()).line; + } + + // Commented as nobody uses this method. + //private int getVisualPositionLine(int offset) { + // // Do round up of offset to the nearest line start (valid since we need only line) + // // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation + // + // int line = calcLogicalLineNumber(offset); + // return logicalToVisualLine(line); + //} + + public synchronized void update(int startLine, int newEndLine, int oldEndLine) { + final int lineWidthSize = myLineWidths.size(); + if (lineWidthSize == 0 || myDocument.getTextLength() <= 0) { + reset(); + } + else { + final int min = Math.min(oldEndLine, newEndLine); + final boolean toAddNewLines = min >= lineWidthSize; + + if (toAddNewLines) { + final int[] delta = new int[min - lineWidthSize + 1]; + myLineWidths.insert(lineWidthSize, delta); + } + + for (int i = startLine; i <= min; i++) myLineWidths.set(i, -1); + if (newEndLine > oldEndLine) { + int[] delta = new int[newEndLine - oldEndLine]; + Arrays.fill(delta, -1); + myLineWidths.insert(oldEndLine + 1, delta); + } + else if (oldEndLine > newEndLine && !toAddNewLines && newEndLine + 1 < lineWidthSize) { + myLineWidths.remove(newEndLine + 1, Math.min(oldEndLine, lineWidthSize) - newEndLine - 1); + } + myIsDirty = true; + } + } + + public synchronized void changedUpdate(DocumentEvent e) { + int startLine = e.getOldLength() == 0 ? myOldEndLine : offsetToLogicalPosition(e.getOffset()).line; + int newEndLine = e.getNewLength() == 0 ? startLine : offsetToLogicalPosition(e.getOffset() + e.getNewLength()).line; + int oldEndLine = myOldEndLine; + + update(startLine, newEndLine, oldEndLine); + } + + @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext", "AssignmentToForLoopParameter"}) + private void validateSizes() { + if (!myIsDirty) return; + + synchronized (lock) { + if (!myIsDirty) return; + int lineCount = Math.min(myLineWidths.size(), myDocument.getLineCount()); + + if (myMaxWidth != -1 && myDocument.isInBulkUpdate()) { + mySize = new Dimension(myMaxWidth, getLineHeight() * lineCount); + myIsDirty = false; + return; + } + + final CharSequence text = myDocument.getCharsNoThreadCheck(); + int end = myDocument.getTextLength(); + int x = 0; + final int fontSize = myScheme.getEditorFontSize(); + final String fontName = myScheme.getEditorFontName(); + + List softWraps = getSoftWrapModel().getRegisteredSoftWraps(); + int softWrapsIndex = -1; + + for (int line = 0; line < lineCount; line++) { + if (myLineWidths.getQuick(line) != -1) continue; + x = 0; + int offset = myDocument.getLineStartOffset(line); + + if (offset >= myDocument.getTextLength()) { + myLineWidths.set(line, 0); + break; + } + + if (softWrapsIndex < 0) { + softWrapsIndex = getSoftWrapModel().getSoftWrapIndex(offset); + if (softWrapsIndex < 0) { + softWrapsIndex = -softWrapsIndex - 1; + } + } + + IterationState state = new IterationState(EditorImpl.this, offset, false); + int fontType = state.getMergedAttributes().getFontType(); + + int maxPreviousSoftWrappedWidth = -1; + + while (offset < end && line < lineCount) { + char c = text.charAt(offset); + if (offset >= state.getEndOffset()) { + state.advance(); + fontType = state.getMergedAttributes().getFontType(); + } + + while (softWrapsIndex < softWraps.size() && line < lineCount) { + SoftWrap softWrap = softWraps.get(softWrapsIndex); + if (softWrap.getStart() > offset) { + break; + } + softWrapsIndex++; + if (softWrap.getStart() == offset) { + maxPreviousSoftWrappedWidth = Math.max(maxPreviousSoftWrappedWidth, x); + x = softWrap.getIndentInPixels(); + } + } + if (line + 1 >= lineCount) { + myLineWidths.set(line, Math.max(x, maxPreviousSoftWrappedWidth)); + break; + } + + FoldRegion collapsed = state.getCurrentFold(); + if (collapsed != null) { + String placeholder = collapsed.getPlaceholderText(); + for (int i = 0; i < placeholder.length(); i++) { + x += EditorUtil.charWidth(placeholder.charAt(i), fontType, EditorImpl.this); + } + offset = collapsed.getEndOffset(); + line = myDocument.getLineNumber(offset); + } + else { + if (c == '\t') { + x = EditorUtil.nextTabStop(x, EditorImpl.this); + offset++; + } + else { + if (c == '\n') { + myLineWidths.set(line, Math.max(x, maxPreviousSoftWrappedWidth)); + if (line + 1 >= lineCount || myLineWidths.getQuick(line + 1) != -1) break; + offset++; + x = 0; + //noinspection AssignmentToForLoopParameter + line++; + } + else { + x += ComplementaryFontsRegistry.getFontAbleToDisplay(c, fontSize, fontType, fontName).charWidth(c, myEditorComponent); + offset++; + } + } + } + } + } + + if (lineCount > 0) { + myLineWidths.set(lineCount - 1, + x); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's + } + + // There is a following possible situation: + // 1. Big document is opened at editor; + // 2. Soft wraps are calculated for the current visible area; + // 2. The user scrolled down; + // 3. The user significantly reduced visible area width (say, reduced it twice); + // 4. Soft wraps are calculated for the current visible area; + // We need to consider only the widths for the logical lines that are completely shown at the current visible area then. + // I.e. we shouldn't use widths of the lines that are not shown for max width calculation because previous widths are calculated + // for another visible area width. + int startToUse = 0; + int endToUse = Math.min(lineCount, myLineWidths.size()); + if (endToUse > 0 && getSoftWrapModel().isSoftWrappingEnabled()) { + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + startToUse = xyToLogicalPosition(visibleArea.getLocation()).line + 1; + endToUse = Math.min(endToUse, xyToLogicalPosition(new Point(0, visibleArea.y + visibleArea.height)).line); + } + int maxWidth = 0; + for (int i = startToUse; i < endToUse; i++) { + maxWidth = Math.max(maxWidth, myLineWidths.getQuick(i)); + } + + mySize = new Dimension(maxWidth, getLineHeight() * getVisibleLineCount()); + + myIsDirty = false; + } + } + + private Dimension getContentSize() { + validateSizes(); + return mySize; + } + + private int getContentHeight() { + return getVisibleLineCount() * getLineHeight(); + } + } + + @NotNull + public EditorGutter getGutter() { + return getGutterComponentEx(); + } + + public int calcColumnNumber(CharSequence text, int start, int offset, int tabSize) { + IterationState state = new IterationState(this, start, false); + int fontType = state.getMergedAttributes().getFontType(); + int column = 0; + int x = 0; + int spaceSize = EditorUtil.getSpaceWidth(fontType, this); + for (int i = start; i < offset; i++) { + if (i >= state.getEndOffset()) { + state.advance(); + fontType = state.getMergedAttributes().getFontType(); + } + + SoftWrap softWrap = getSoftWrapModel().getSoftWrap(i); + if (softWrap != null) { + column++; // For 'after soft wrap' drawing. + x = getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP); + } + + char c = text.charAt(i); + if (c == '\t') { + int prevX = x; + x = EditorUtil.nextTabStop(x, this); + column += EditorUtil.columnsNumber(c, x, prevX, spaceSize); + //column += Math.max(1, (x - prevX) / spaceSize); + } + else { + x += EditorUtil.charWidth(c, fontType, this); + column++; + } + } + + return column; + } + + public void putInfo(Map info) { + final VisualPosition visual = getCaretModel().getVisualPosition(); + info.put("caret", visual.getLine() + ":" + visual.getColumn()); + } + + public static boolean isChangeFontSize(MouseWheelEvent e) { + return SystemInfo.isMac + ? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown() + : e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown(); + } + + private class MyScrollPane extends JBScrollPane { + + + private MyScrollPane() { + setViewportBorder(new EmptyBorder(0, 0, 0, 0)); + } + + protected void processMouseWheelEvent(MouseWheelEvent e) { + if (mySettings.isWheelFontChangeEnabled()) { + if (isChangeFontSize(e)) { + setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation()); + return; + } + } + + super.processMouseWheelEvent(e); + } + + @Override + public JScrollBar createVerticalScrollBar() { + return new MyScrollBar(Adjustable.VERTICAL); + } + + @Override + protected void init() { + super.init(); + setCorner(LOWER_LEFT_CORNER, new JPanel() { + @Override + public void paint(Graphics g) { + final Rectangle bounds = getBounds(); + int width = bounds.width; + int height = bounds.height; + + g.setColor(ButtonlessScrollBarUI.TRACK_BACKGROUND); + g.fillRect(0, 0, width, height); + + int shortner = 0; + if (myGutterComponent.isFoldingOutlineShown()) { + shortner = myGutterComponent.getFoldingAreaWidth() / 2; + } + + g.setColor(myGutterComponent.getBackground()); + g.fillRect(0, 0, width - shortner, height); + + g.setColor(ButtonlessScrollBarUI.TRACK_BORDER); + g.drawLine(width - 1 - shortner, 0, width - 1 - shortner, height); + g.drawLine(0, 0, width - 1, 0); + } + }); + } + } + + private class MyHeaderPanel extends JPanel { + private int myOldHeight = 0; + + private MyHeaderPanel() { + super(new BorderLayout()); + } + + public void revalidate() { + myOldHeight = getHeight(); + super.revalidate(); + } + + protected void validateTree() { + int height = myOldHeight; + super.validateTree(); + height -= getHeight(); + + if (height != 0) { + myVerticalScrollBar.setValue(myVerticalScrollBar.getValue() - height); + } + myOldHeight = getHeight(); + } + } + + private class MyTextDrawingCallback implements TextDrawingCallback { + @Override + public void drawChars(Graphics g, char[] data, int start, int end, int x, int y, Color color, FontInfo fontInfo) { + drawCharsCached(g, data, start, end, x, y, fontInfo, color); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java index 8884727daa90..ea0d171231b7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java @@ -1,654 +1,654 @@ -/* - * 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.editor.impl.softwrap.mapping; - -import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.ex.FoldingModelEx; -import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper; -import com.intellij.openapi.editor.impl.softwrap.*; -import com.intellij.openapi.util.Pair; -import gnu.trove.TIntObjectProcedure; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.List; - -/** - * {@link SoftWrapDataMapper} implementation that is implemented using the following principles: - *

- * 
    - *
  • it caches document dimension for starts of all visual lines;
  • - *
  • - * every time document position mapping should be performed this mapper chooses target visual line to process - * and calculates target position from its start (note that we can optimize that in order to choose calculation - * direction - 'from start' or 'from end'); - *
  • - *
  • - * information about target visual lines is updated incrementally on document changes, i.e. we're trying to reduce - * calculations number as much as possible; - *
  • - *
- *
- *

- * Not thread-safe. - * - * @author Denis Zhdanov - * @since Aug 31, 2010 10:24:47 AM - */ -public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAwareDocumentParsingListener { - - /** Caches information for the document visual line starts sorted in ascending order. */ - private final List myCache = new ArrayList(); - private final CacheEntry mySearchKey = new CacheEntry(0, null, null, null); - private final List myNotAffectedByUpdateTailCacheEntries = new ArrayList(); - private final CacheState myBeforeChangeState = new CacheState(); - private final CacheState myAfterChangeState = new CacheState(); - - private final OffsetToLogicalCalculationStrategy myOffsetToLogicalStrategy; - private final VisualToLogicalCalculationStrategy myVisualToLogicalStrategy; - - private final EditorEx myEditor; - private final SoftWrapsStorage myStorage; - private final EditorTextRepresentationHelper myRepresentationHelper; - - public CachingSoftWrapDataMapper(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, - @NotNull EditorTextRepresentationHelper representationHelper) - { - myEditor = editor; - myStorage = storage; - myRepresentationHelper = representationHelper; - - myOffsetToLogicalStrategy = new OffsetToLogicalCalculationStrategy(editor, storage, representationHelper); - myVisualToLogicalStrategy = new VisualToLogicalCalculationStrategy(editor, storage, representationHelper); - } - - @NotNull - @Override - public LogicalPosition visualToLogical(@NotNull VisualPosition visual) throws IllegalStateException { - if (myCache.isEmpty()) { - return new LogicalPosition(visual.line, visual.column, 0, 0, 0, 0, 0); - } - - // There is a possible case that we're asked to map visual position that lay beyond the document content (e.g. there is a - // 'virtual space' below the document text and it's perfectly possible to map visual position that points into it). - // We consider that there are no soft wraps, tabulations and fold regions between the last cached visual line and target line then. - if (!myCache.isEmpty()) { - CacheEntry cacheEntry = myCache.get(myCache.size() - 1); - if (visual.line > cacheEntry.visualLine) { - int lineDiff = visual.line - cacheEntry.visualLine; - return new LogicalPosition( - cacheEntry.endLogicalLine + lineDiff, visual.column, cacheEntry.endSoftWrapLinesBefore + cacheEntry.endSoftWrapLinesCurrent, - 0, 0, cacheEntry.endFoldedLines, 0 - ); - } - } - - myVisualToLogicalStrategy.init(visual, myCache); - return calculate(myVisualToLogicalStrategy); - } - - @NotNull - @Override - public LogicalPosition offsetToLogicalPosition(int offset) { - // There is a possible case that this method is called during the first refresh (the cache is empty). So, we delegate it to - // soft wraps-unaware code. - if (myCache.isEmpty()) { - return myEditor.offsetToLogicalPosition(offset, false); - } - myOffsetToLogicalStrategy.init(offset, myCache); - return calculate(myOffsetToLogicalStrategy); - } - - @Override - public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition softWrapUnawareVisual) - throws IllegalStateException - { - // We can't use standard strategy-based approach with logical -> visual mapping because folding processing quite often - // temporarily disables folding. So, there is an inconsistency between cached data (folding aware) and current folding - // state. So, we use direct soft wraps adjustment instead of normal calculation. - - - if (logical.visualPositionAware) { - // We don't need to recalculate logical position adjustments because given object already has them. - return logical.toVisualPosition(); - } - - List softWraps = myStorage.getSoftWraps(); - - // Check if there are registered soft wraps before the target logical position. - int maxOffset = myEditor.logicalPositionToOffset(logical); - int endIndex = myStorage.getSoftWrapIndex(maxOffset); - if (endIndex < 0) { - endIndex = -endIndex - 2; // We subtract '2' instead of '1' here in order to point to offset of the first soft wrap that - // is located before the given logical position. - } - - // Return eagerly if no soft wraps are registered before the target offset. - if (endIndex < 0 || endIndex >= softWraps.size()) { - return softWrapUnawareVisual; - } - - int lineDiff = 0; - int column = -1; - - int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0)); - for (int i = endIndex; i >= 0; i--) { - SoftWrap softWrap = softWraps.get(i); - if (softWrap == null) { - assert false; - continue; - } - - // Count soft wrap column offset only if it's located at the same line as the target offset. - if (column < 0 && softWrap.getStart() >= targetLogicalLineStartOffset) { - column = softWrap.getIndentInColumns() + myRepresentationHelper.toVisualColumnSymbolsNumber( - myEditor.getDocument().getCharsSequence(), softWrap.getStart(), maxOffset, softWrap.getIndentInPixels() - ); - - // Count lines introduced by the current soft wrap. We assume that every soft wrap has a single line feed. - lineDiff++; - } - else { - // We assume that every soft wrap has a single line feed. - lineDiff += i + 1; - break; - } - } - - int columnToUse = column >= 0 ? column : softWrapUnawareVisual.column; - return new VisualPosition(softWrapUnawareVisual.line + lineDiff, columnToUse); - } - - public void release() { - myCache.clear(); - } - - private T calculate(@NotNull MappingStrategy strategy) throws IllegalStateException { - T eagerMatch = strategy.eagerMatch(); - if (eagerMatch != null) { - return eagerMatch; - } - - ProcessingContext context = strategy.buildInitialContext(); - - // Folding model doesn't return information about fold regions if their 'enabled' state is set to 'false'. Unfortunately, - // such situation occurs not only on manual folding disabling but on internal processing as well. E.g. 'enabled' is set - // to 'false' during fold preview representation. So, we set it to 'true' in order to retrieve fold regions and restore - // to previous state after that. - FoldingModelEx foldingModel = myEditor.getFoldingModel(); - boolean foldingState = foldingModel.isFoldingEnabled(); - foldingModel.setFoldingEnabled(true); - CompositeDataProvider provider; - try { - provider = new CompositeDataProvider( - new SoftWrapsDataProvider(myStorage), new FoldingDataProvider(myEditor.getFoldingModel().fetchTopLevel()), - getTabulationDataProvider(context.visualLine) - ); - } - finally { - foldingModel.setFoldingEnabled(foldingState); - } - provider.advance(context.offset); - - while (provider.hasData()) { - Pair data = provider.getData(); - T result = null; - int sortingKey = provider.getSortingKey(); - - // There is a possible case that, say, fold region is soft wrapped. We don't want to perform unnecessary then. - if (context.offset <= sortingKey) { - result = strategy.advance(context, sortingKey); - if (result != null) { - return result; - } - } - - switch (data.first) { - case SOFT_WRAP: result = strategy.processSoftWrap(context, (SoftWrap)data.second); break; - case COLLAPSED_FOLDING: result = strategy.processFoldRegion(context, (FoldRegion)data.second); break; - case TABULATION: result = strategy.processTabulation(context, (TabData)data.second); break; - } - if (result != null) { - return result; - } - provider.next(); - } - return strategy.build(context); - } - - private TabulationDataProvider getTabulationDataProvider(int visualLine) throws IllegalStateException { - mySearchKey.visualLine = visualLine; - int i = Collections.binarySearch(myCache, mySearchKey); - List tabs; - if (i >= 0) { - tabs = myCache.get(i).getTabData(); - } - else { - assert false; - tabs = Collections.emptyList(); - } - return new TabulationDataProvider(tabs); - } - - @Override - public void onProcessedSymbol(@NotNull ProcessingContext context) { - switch (context.symbol) { - case '\n': onLineFeed(context); break; - case '\t': onTabulation(context); break; - default: onNonLineFeedSymbol(context); break; - } - - int documentLength = myEditor.getDocument().getTextLength(); - if (documentLength > 0 && context.offset >= documentLength - 1 && context.symbol != '\n') { - // Update information about end position of the last visual line. - CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); - cacheEntry.endLogicalColumn += context.symbolWidthInColumns; - cacheEntry.endVisualColumn += context.symbolWidthInColumns; - cacheEntry.endOffset++; - } - } - - @Override - public void onCollapsedFoldRegion(@NotNull FoldRegion foldRegion, int x, int visualLine) { - CacheEntry cacheEntry = getCacheEntryForVisualLine(visualLine); - cacheEntry.store(foldRegion, x); - } - - @Override - public void beforeSoftWrap(@NotNull ProcessingContext context) { - CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); - cacheEntry.setLineEndPosition(context); - } - - @Override - public void afterSoftWrapLineFeed(@NotNull ProcessingContext context) { - CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); - cacheEntry.setLineStartPosition(context, context.visualLine); - } - - @Override - public void revertToOffset(final int offset, int visualLine) { - CacheEntry cacheEntry = getCacheEntryForVisualLine(visualLine); - - // Remove information about cached tabulation symbol data. - int tabIndex = 0; - for (; tabIndex < cacheEntry.getTabData().size(); tabIndex++) { - if (cacheEntry.getTabData().get(tabIndex).offset >= offset) { - break; - } - } - if (tabIndex < cacheEntry.getTabData().size() - 1) { - cacheEntry.getTabData().subList(tabIndex, cacheEntry.getTabData().size()).clear(); - } - - // Remove information about cached single line fold regions. - cacheEntry.getFoldingData().retainEntries(new TIntObjectProcedure() { - @Override - public boolean execute(int foldStartOffset, FoldingData data) { - return foldStartOffset < offset; - } - }); - } - - /** - * Updates inner cache for the context that points to line feed symbol - * - * @param context processing context that points to line feed symbol - */ - private void onLineFeed(@NotNull ProcessingContext context) { - CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); - cacheEntry.setLineEndPosition(context); - - cacheEntry = getCacheEntryForVisualLine(context.visualLine + 1); - - ProcessingContext newLineContext = context.clone(); - newLineContext.onNewLine(); - newLineContext.offset++; - cacheEntry.setLineStartPosition(newLineContext, context.visualLine + 1); - cacheEntry.setLineEndPosition(newLineContext); - } - - /** - * Updates inner cache for the context that points to tabulation symbol. - * - * @param context processing context that points to tabulation symbol - */ - private void onTabulation(@NotNull ProcessingContext context) { - onNonLineFeedSymbol(context); - CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); - cacheEntry.storeTabData(context); - } - - /** - * Process given context in assumption that it points to non-line feed symbol. - * - * @param context context that is assumed to point to non-line feed symbol - */ - private void onNonLineFeedSymbol(@NotNull ProcessingContext context) { - CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); - Document document = myEditor.getDocument(); - if (context.offset >= document.getTextLength() - 1) { - cacheEntry.setLineEndPosition(context); - return; - } - - if (context.offset < 0 || (context.offset > 0 && context.offset < document.getTextLength() - && document.getCharsSequence().charAt(context.offset - 1) != '\n')) - { - return; - } - - // Process first symbol on a new line. - cacheEntry.getTabData().clear(); - cacheEntry.getFoldingData().clear(); - cacheEntry.setLineStartPosition(context.clone(), context.visualLine); - } - - private CacheEntry getCacheEntryForVisualLine(int visualLine) { - // Blind guess, worth to perform in assumption that this method is called on document parsing most of the time. - if (!myCache.isEmpty()) { - CacheEntry lastEntry = myCache.get(myCache.size() - 1); - if (lastEntry.visualLine == visualLine) { - if (lastEntry.locked) { - myCache.set(myCache.size() - 1, lastEntry = lastEntry.clone()); - } - return lastEntry; - } - else if (lastEntry.visualLine < visualLine) { - CacheEntry result = new CacheEntry(visualLine, myEditor, myRepresentationHelper, myCache); - myCache.add(result); - return result; - } - } - - mySearchKey.visualLine = visualLine; - - int start = 0; - int end = myCache.size() - 1; - int cacheEntryIndex = -1; - - // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch(). - while (start <= end) { - int i = (end + start) >>> 1; - CacheEntry cacheEntry = myCache.get(i); - if (cacheEntry.visualLine < visualLine) { - start = i + 1; - continue; - } - if (cacheEntry.visualLine > visualLine) { - end = i - 1; - continue; - } - - cacheEntryIndex = i; - break; - } - - CacheEntry result; - if (cacheEntryIndex < 0) { - cacheEntryIndex = start; - myCache.add(cacheEntryIndex, result = new CacheEntry(visualLine, myEditor, myRepresentationHelper, myCache)); - } - else if (myBeforeChangeState.valid && cacheEntryIndex > myBeforeChangeState.endCacheEntryIndex && myCache.get(cacheEntryIndex).locked) { - myCache.set(cacheEntryIndex, result = myCache.get(cacheEntryIndex).clone()); - } - else { - result = myCache.get(cacheEntryIndex); - } - return result; - } - - @Override - public void onRecalculationStart(int startOffset, int endOffset) { - myBeforeChangeState.updateByDocumentOffsets(startOffset, endOffset); - if (!myBeforeChangeState.valid) { - return; - } - - //System.out.println("Before recalculation (" + startOffset + "-" + endOffset + ")"); - //dumpCache(); - - myNotAffectedByUpdateTailCacheEntries.clear(); - myNotAffectedByUpdateTailCacheEntries.addAll(myCache.subList(myBeforeChangeState.endCacheEntryIndex + 1, myCache.size())); - - // There is a possible case that the change is non-line feed symbol insertion to the document's end and its previous symbol - // is line feed. E.g.: - // line1 - // line2 - // - // We stored cache entry that corresponds to offset just before the previous last symbol (line feed). However, last symbol is - // not line feed now, hence, we need to remove corresponding virtual cache entry. - int textLength = myEditor.getDocument().getTextLength(); - if (endOffset >= textLength && !myNotAffectedByUpdateTailCacheEntries.isEmpty() - && myNotAffectedByUpdateTailCacheEntries.get(myNotAffectedByUpdateTailCacheEntries.size() - 1).startOffset >= textLength) - { - myNotAffectedByUpdateTailCacheEntries.remove(myNotAffectedByUpdateTailCacheEntries.size() - 1); - } - myCache.subList(myBeforeChangeState.startCacheEntryIndex + 1, myCache.size()).clear(); - for (CacheEntry entry : myNotAffectedByUpdateTailCacheEntries) { - entry.locked = true; - } - myCache.get(myBeforeChangeState.startCacheEntryIndex).collapse(); - - //System.out.println("Dropped all cache records starting from index " + (myBeforeChangeState.startCacheEntryIndex + 1)); - } - - @Override - public void onRecalculationEnd(int startOffset, int endOffset) { - if (!myBeforeChangeState.valid) { - return; - } - int endIndex = Math.max(0, myCache.size() - 1/*because of zero-based indexing*/); - if (endIndex > 0 && endOffset < myEditor.getDocument().getTextLength() - 1) { - endIndex--; // We assume that non-last document line ends with line feed symbol. - } - myAfterChangeState.updateByCacheIndices(myBeforeChangeState.startCacheEntryIndex, endIndex); - myCache.subList(myAfterChangeState.endCacheEntryIndex + 1, myCache.size()).clear(); - myCache.addAll(myNotAffectedByUpdateTailCacheEntries); - for (CacheEntry entry : myNotAffectedByUpdateTailCacheEntries) { - entry.locked = false; - } - - //System.out.println("Before applying state change:"); - //dumpCache(); - - applyStateChange(); - myNotAffectedByUpdateTailCacheEntries.clear(); - - //System.out.println("After Applying state change"); - //dumpCache(); - - myBeforeChangeState.valid = false; - } - - @SuppressWarnings({"UseOfSystemOutOrSystemErr", "UnusedDeclaration", "CallToPrintStackTrace"}) - private void dumpCache() { - Document document = myEditor.getDocument(); - CharSequence text = document.getCharsSequence(); - System.out.println("--------------------------------------------------"); - System.out.println("|"); - System.out.println("|"); - System.out.println(text); - System.out.println("- - - - - - - - - - - - - - - - - -"); - System.out.println("text length: " + text.length() + ", soft wraps: " + myStorage.getSoftWraps()); - for (int i = 0; i < myCache.size(); i++) { - CacheEntry entry = myCache.get(i); - if (text.length() <= 0 && i <= 0) { - continue; - } - try { - System.out.printf("line %d. %d-%d: '%s'%n", i, entry.startOffset, entry.endOffset, - text.subSequence(Math.min(text.length() - 1, entry.startOffset) ,Math.min(entry.endOffset, text.length() - 1))); - } - catch (Throwable e) { - e.printStackTrace(); - } - if (i > 0 && myCache.size() > 1 && entry.startOffset < text.length()) { - CacheEntry previous = myCache.get(i - 1); - if (previous.endOffset < entry.startOffset - 1 - || (previous.endOffset == entry.startOffset && myEditor.getSoftWrapModel().getSoftWrap(entry.startOffset) == null) - || previous.endOffset > entry.startOffset) - { - assert false; - } - } - } - if (!myCache.isEmpty() && myCache.get(myCache.size() - 1).endOffset < text.length() - 1) { - System.out.printf("Incomplete re-parsing detected! Document length is %d but last processed offset is %s%n", text.length(), - myCache.get(myCache.size() - 1).endOffset); - } - - - for (CacheEntry cacheEntry : myCache) { - if (cacheEntry.startOffset >= document.getTextLength()) { - continue; - } - if (cacheEntry.startOffset > 0) { - if (text.charAt(cacheEntry.startOffset - 1) != '\n' && myStorage.getSoftWrap(cacheEntry.startOffset) == null) { - assert false; - } - } - if (cacheEntry.endOffset < document.getTextLength() - 1) { - if (text.charAt(cacheEntry.endOffset) != '\n' && myStorage.getSoftWrap(cacheEntry.endOffset) == null) { - assert false; - } - } - } - - if (!myCache.isEmpty() && document.getTextLength() > 0) { - if (myCache.get(myCache.size() - 1).endOffset < myEditor.getDocument().getTextLength() - 1) { - assert false; - } - } - } - - /** - * Is assumed to be called for updating {@link #myCache document dimensions cache} entries that lay after document position identified - * by {@link #myAfterChangeState} in order to apply to them diff between {@link #myBeforeChangeState} and {@link #myAfterChangeState}. - *

- * I.e. the general idea of incremental cache update is the following: - *

- *

-   * 
    - *
  1. We are notified on document region update;
  2. - *
  3. We remember significant information about target region;
  4. - *
  5. Region data recalculation is performed;
  6. - *
  7. Diff between current region state and remembered one is applied to the cache;
  8. - *
-
- */ - private void applyStateChange() { - if (myNotAffectedByUpdateTailCacheEntries.isEmpty()) { - return; - } - - int visualLinesDiff = myAfterChangeState.visualLines - myBeforeChangeState.visualLines; - int logicalLinesDiff = myAfterChangeState.logicalLines - myBeforeChangeState.logicalLines; - int softWrappedLinesDiff = myAfterChangeState.softWrapLines - myBeforeChangeState.softWrapLines; - int foldedLinesDiff = myAfterChangeState.foldedLines - myBeforeChangeState.foldedLines; - int offsetsDiff = (myAfterChangeState.endOffset - myAfterChangeState.startOffset) - - (myBeforeChangeState.endOffset - myBeforeChangeState.startOffset); - - for (CacheEntry cacheEntry : myNotAffectedByUpdateTailCacheEntries) { - cacheEntry.visualLine += visualLinesDiff; - cacheEntry.startLogicalLine += logicalLinesDiff; - cacheEntry.endLogicalLine += logicalLinesDiff; - cacheEntry.advance(offsetsDiff); - cacheEntry.startSoftWrapLinesBefore += softWrappedLinesDiff; - cacheEntry.endSoftWrapLinesBefore += softWrappedLinesDiff; - cacheEntry.startFoldedLines += foldedLinesDiff; - cacheEntry.endFoldedLines += foldedLinesDiff; - } - - int offset = myNotAffectedByUpdateTailCacheEntries.get(0).startOffset + 1/* in order to exclude soft wrap from previous line if any*/; - if (offsetsDiff == 0 || offset >= myEditor.getDocument().getTextLength()) { - return; - } - - int softWrapIndex = myStorage.getSoftWrapIndex(offset); - if (softWrapIndex < 0) { - softWrapIndex = -softWrapIndex - 1; - } - - List softWraps = myStorage.getSoftWraps(); - for (int i = softWrapIndex; i < softWraps.size(); i++) { - softWraps.get(i).advance(offsetsDiff); - } - } - - @Override - public String toString() { - return myCache.toString(); - } - - private class CacheState { - - public boolean valid = true; - - public int visualLines; - public int logicalLines; - public int softWrapLines; - public int foldedLines; - public int startOffset; - public int endOffset; - public int startCacheEntryIndex; - public int endCacheEntryIndex; - - public void updateByDocumentOffsets(int startOffset, int endOffset) { - this.startOffset = startOffset; - this.endOffset = endOffset; - Document document = myEditor.getDocument(); - startCacheEntryIndex = MappingUtil.getCacheEntryIndexForOffset(startOffset, document, myCache); - - // There is a possible case that particular document tail fragment is removed (this is the case for cyclic buffer for example). - // We want to drop corresponding cache entries then. - endCacheEntryIndex = MappingUtil.getCacheEntryIndexForOffset(Math.min(document.getTextLength(), endOffset), document, myCache); - - if (startCacheEntryIndex < 0 || endCacheEntryIndex < 0) { - valid = false; - // This is correct situation for, say, initial cache update. - return; - } - updateByCacheIndices(startCacheEntryIndex, endCacheEntryIndex); - - } - - public void updateByCacheIndices(int startIndex, int endIndex) { - reset(); - startOffset = myCache.get(startIndex).startOffset; - endOffset = myCache.get(endIndex).endOffset; - startCacheEntryIndex = startIndex; - endCacheEntryIndex = endIndex; - visualLines = endIndex - startIndex + 1; - - CacheEntry startEntry = myCache.get(startIndex); - CacheEntry endEntry = myCache.get(endIndex); - logicalLines = endEntry.endLogicalLine - startEntry.startLogicalLine + 1; - foldedLines = endEntry.endFoldedLines - startEntry.startFoldedLines; - softWrapLines = endEntry.endSoftWrapLinesBefore + endEntry.endSoftWrapLinesCurrent - startEntry.startSoftWrapLinesBefore - - startEntry.startSoftWrapLinesCurrent; - } - - public void reset() { - logicalLines = 0; - softWrapLines = 0; - foldedLines = 0; - endCacheEntryIndex = 0; - valid = true; - } - } +/* + * 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.editor.impl.softwrap.mapping; + +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.ex.FoldingModelEx; +import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper; +import com.intellij.openapi.editor.impl.softwrap.*; +import com.intellij.openapi.util.Pair; +import gnu.trove.TIntObjectProcedure; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.List; + +/** + * {@link SoftWrapDataMapper} implementation that is implemented using the following principles: + *
+ * 
    + *
  • it caches document dimension for starts of all visual lines;
  • + *
  • + * every time document position mapping should be performed this mapper chooses target visual line to process + * and calculates target position from its start (note that we can optimize that in order to choose calculation + * direction - 'from start' or 'from end'); + *
  • + *
  • + * information about target visual lines is updated incrementally on document changes, i.e. we're trying to reduce + * calculations number as much as possible; + *
  • + *
+ *
+ *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since Aug 31, 2010 10:24:47 AM + */ +public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAwareDocumentParsingListener { + + /** Caches information for the document visual line starts sorted in ascending order. */ + private final List myCache = new ArrayList(); + private final CacheEntry mySearchKey = new CacheEntry(0, null, null, null); + private final List myNotAffectedByUpdateTailCacheEntries = new ArrayList(); + private final CacheState myBeforeChangeState = new CacheState(); + private final CacheState myAfterChangeState = new CacheState(); + + private final OffsetToLogicalCalculationStrategy myOffsetToLogicalStrategy; + private final VisualToLogicalCalculationStrategy myVisualToLogicalStrategy; + + private final EditorEx myEditor; + private final SoftWrapsStorage myStorage; + private final EditorTextRepresentationHelper myRepresentationHelper; + + public CachingSoftWrapDataMapper(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, + @NotNull EditorTextRepresentationHelper representationHelper) + { + myEditor = editor; + myStorage = storage; + myRepresentationHelper = representationHelper; + + myOffsetToLogicalStrategy = new OffsetToLogicalCalculationStrategy(editor, storage, representationHelper); + myVisualToLogicalStrategy = new VisualToLogicalCalculationStrategy(editor, storage, representationHelper); + } + + @NotNull + @Override + public LogicalPosition visualToLogical(@NotNull VisualPosition visual) throws IllegalStateException { + if (myCache.isEmpty()) { + return new LogicalPosition(visual.line, visual.column, 0, 0, 0, 0, 0); + } + + // There is a possible case that we're asked to map visual position that lay beyond the document content (e.g. there is a + // 'virtual space' below the document text and it's perfectly possible to map visual position that points into it). + // We consider that there are no soft wraps, tabulations and fold regions between the last cached visual line and target line then. + if (!myCache.isEmpty()) { + CacheEntry cacheEntry = myCache.get(myCache.size() - 1); + if (visual.line > cacheEntry.visualLine) { + int lineDiff = visual.line - cacheEntry.visualLine; + return new LogicalPosition( + cacheEntry.endLogicalLine + lineDiff, visual.column, cacheEntry.endSoftWrapLinesBefore + cacheEntry.endSoftWrapLinesCurrent, + 0, 0, cacheEntry.endFoldedLines, 0 + ); + } + } + + myVisualToLogicalStrategy.init(visual, myCache); + return calculate(myVisualToLogicalStrategy); + } + + @NotNull + @Override + public LogicalPosition offsetToLogicalPosition(int offset) { + // There is a possible case that this method is called during the first refresh (the cache is empty). So, we delegate it to + // soft wraps-unaware code. + if (myCache.isEmpty()) { + return myEditor.offsetToLogicalPosition(offset, false); + } + myOffsetToLogicalStrategy.init(offset, myCache); + return calculate(myOffsetToLogicalStrategy); + } + + @Override + public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition softWrapUnawareVisual) + throws IllegalStateException + { + // We can't use standard strategy-based approach with logical -> visual mapping because folding processing quite often + // temporarily disables folding. So, there is an inconsistency between cached data (folding aware) and current folding + // state. So, we use direct soft wraps adjustment instead of normal calculation. + + + if (logical.visualPositionAware) { + // We don't need to recalculate logical position adjustments because given object already has them. + return logical.toVisualPosition(); + } + + List softWraps = myStorage.getSoftWraps(); + + // Check if there are registered soft wraps before the target logical position. + int maxOffset = myEditor.logicalPositionToOffset(logical); + int endIndex = myStorage.getSoftWrapIndex(maxOffset); + if (endIndex < 0) { + endIndex = -endIndex - 2; // We subtract '2' instead of '1' here in order to point to offset of the first soft wrap that + // is located before the given logical position. + } + + // Return eagerly if no soft wraps are registered before the target offset. + if (endIndex < 0 || endIndex >= softWraps.size()) { + return softWrapUnawareVisual; + } + + int lineDiff = 0; + int column = -1; + + int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0)); + for (int i = endIndex; i >= 0; i--) { + SoftWrap softWrap = softWraps.get(i); + if (softWrap == null) { + assert false; + continue; + } + + // Count soft wrap column offset only if it's located at the same line as the target offset. + if (column < 0 && softWrap.getStart() >= targetLogicalLineStartOffset) { + column = softWrap.getIndentInColumns() + myRepresentationHelper.toVisualColumnSymbolsNumber( + myEditor.getDocument().getCharsSequence(), softWrap.getStart(), maxOffset, softWrap.getIndentInPixels() + ); + + // Count lines introduced by the current soft wrap. We assume that every soft wrap has a single line feed. + lineDiff++; + } + else { + // We assume that every soft wrap has a single line feed. + lineDiff += i + 1; + break; + } + } + + int columnToUse = column >= 0 ? column : softWrapUnawareVisual.column; + return new VisualPosition(softWrapUnawareVisual.line + lineDiff, columnToUse); + } + + public void release() { + myCache.clear(); + } + + private T calculate(@NotNull MappingStrategy strategy) throws IllegalStateException { + T eagerMatch = strategy.eagerMatch(); + if (eagerMatch != null) { + return eagerMatch; + } + + ProcessingContext context = strategy.buildInitialContext(); + + // Folding model doesn't return information about fold regions if their 'enabled' state is set to 'false'. Unfortunately, + // such situation occurs not only on manual folding disabling but on internal processing as well. E.g. 'enabled' is set + // to 'false' during fold preview representation. So, we set it to 'true' in order to retrieve fold regions and restore + // to previous state after that. + FoldingModelEx foldingModel = myEditor.getFoldingModel(); + boolean foldingState = foldingModel.isFoldingEnabled(); + foldingModel.setFoldingEnabled(true); + CompositeDataProvider provider; + try { + provider = new CompositeDataProvider( + new SoftWrapsDataProvider(myStorage), new FoldingDataProvider(myEditor.getFoldingModel().fetchTopLevel()), + getTabulationDataProvider(context.visualLine) + ); + } + finally { + foldingModel.setFoldingEnabled(foldingState); + } + provider.advance(context.offset); + + while (provider.hasData()) { + Pair data = provider.getData(); + T result = null; + int sortingKey = provider.getSortingKey(); + + // There is a possible case that, say, fold region is soft wrapped. We don't want to perform unnecessary then. + if (context.offset <= sortingKey) { + result = strategy.advance(context, sortingKey); + if (result != null) { + return result; + } + } + + switch (data.first) { + case SOFT_WRAP: result = strategy.processSoftWrap(context, (SoftWrap)data.second); break; + case COLLAPSED_FOLDING: result = strategy.processFoldRegion(context, (FoldRegion)data.second); break; + case TABULATION: result = strategy.processTabulation(context, (TabData)data.second); break; + } + if (result != null) { + return result; + } + provider.next(); + } + return strategy.build(context); + } + + private TabulationDataProvider getTabulationDataProvider(int visualLine) throws IllegalStateException { + mySearchKey.visualLine = visualLine; + int i = Collections.binarySearch(myCache, mySearchKey); + List tabs; + if (i >= 0) { + tabs = myCache.get(i).getTabData(); + } + else { + assert false; + tabs = Collections.emptyList(); + } + return new TabulationDataProvider(tabs); + } + + @Override + public void onProcessedSymbol(@NotNull ProcessingContext context) { + switch (context.symbol) { + case '\n': onLineFeed(context); break; + case '\t': onTabulation(context); break; + default: onNonLineFeedSymbol(context); break; + } + + int documentLength = myEditor.getDocument().getTextLength(); + if (documentLength > 0 && context.offset >= documentLength - 1 && context.symbol != '\n') { + // Update information about end position of the last visual line. + CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); + cacheEntry.endLogicalColumn += context.symbolWidthInColumns; + cacheEntry.endVisualColumn += context.symbolWidthInColumns; + cacheEntry.endOffset++; + } + } + + @Override + public void onCollapsedFoldRegion(@NotNull FoldRegion foldRegion, int x, int visualLine) { + CacheEntry cacheEntry = getCacheEntryForVisualLine(visualLine); + cacheEntry.store(foldRegion, x); + } + + @Override + public void beforeSoftWrap(@NotNull ProcessingContext context) { + CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); + cacheEntry.setLineEndPosition(context); + } + + @Override + public void afterSoftWrapLineFeed(@NotNull ProcessingContext context) { + CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); + cacheEntry.setLineStartPosition(context, context.visualLine); + } + + @Override + public void revertToOffset(final int offset, int visualLine) { + CacheEntry cacheEntry = getCacheEntryForVisualLine(visualLine); + + // Remove information about cached tabulation symbol data. + int tabIndex = 0; + for (; tabIndex < cacheEntry.getTabData().size(); tabIndex++) { + if (cacheEntry.getTabData().get(tabIndex).offset >= offset) { + break; + } + } + if (tabIndex < cacheEntry.getTabData().size() - 1) { + cacheEntry.getTabData().subList(tabIndex, cacheEntry.getTabData().size()).clear(); + } + + // Remove information about cached single line fold regions. + cacheEntry.getFoldingData().retainEntries(new TIntObjectProcedure() { + @Override + public boolean execute(int foldStartOffset, FoldingData data) { + return foldStartOffset < offset; + } + }); + } + + /** + * Updates inner cache for the context that points to line feed symbol + * + * @param context processing context that points to line feed symbol + */ + private void onLineFeed(@NotNull ProcessingContext context) { + CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); + cacheEntry.setLineEndPosition(context); + + cacheEntry = getCacheEntryForVisualLine(context.visualLine + 1); + + ProcessingContext newLineContext = context.clone(); + newLineContext.onNewLine(); + newLineContext.offset++; + cacheEntry.setLineStartPosition(newLineContext, context.visualLine + 1); + cacheEntry.setLineEndPosition(newLineContext); + } + + /** + * Updates inner cache for the context that points to tabulation symbol. + * + * @param context processing context that points to tabulation symbol + */ + private void onTabulation(@NotNull ProcessingContext context) { + onNonLineFeedSymbol(context); + CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); + cacheEntry.storeTabData(context); + } + + /** + * Process given context in assumption that it points to non-line feed symbol. + * + * @param context context that is assumed to point to non-line feed symbol + */ + private void onNonLineFeedSymbol(@NotNull ProcessingContext context) { + CacheEntry cacheEntry = getCacheEntryForVisualLine(context.visualLine); + Document document = myEditor.getDocument(); + if (context.offset >= document.getTextLength() - 1) { + cacheEntry.setLineEndPosition(context); + return; + } + + if (context.offset < 0 || (context.offset > 0 && context.offset < document.getTextLength() + && document.getCharsSequence().charAt(context.offset - 1) != '\n')) + { + return; + } + + // Process first symbol on a new line. + cacheEntry.getTabData().clear(); + cacheEntry.getFoldingData().clear(); + cacheEntry.setLineStartPosition(context.clone(), context.visualLine); + } + + private CacheEntry getCacheEntryForVisualLine(int visualLine) { + // Blind guess, worth to perform in assumption that this method is called on document parsing most of the time. + if (!myCache.isEmpty()) { + CacheEntry lastEntry = myCache.get(myCache.size() - 1); + if (lastEntry.visualLine == visualLine) { + if (lastEntry.locked) { + myCache.set(myCache.size() - 1, lastEntry = lastEntry.clone()); + } + return lastEntry; + } + else if (lastEntry.visualLine < visualLine) { + CacheEntry result = new CacheEntry(visualLine, myEditor, myRepresentationHelper, myCache); + myCache.add(result); + return result; + } + } + + mySearchKey.visualLine = visualLine; + + int start = 0; + int end = myCache.size() - 1; + int cacheEntryIndex = -1; + + // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch(). + while (start <= end) { + int i = (end + start) >>> 1; + CacheEntry cacheEntry = myCache.get(i); + if (cacheEntry.visualLine < visualLine) { + start = i + 1; + continue; + } + if (cacheEntry.visualLine > visualLine) { + end = i - 1; + continue; + } + + cacheEntryIndex = i; + break; + } + + CacheEntry result; + if (cacheEntryIndex < 0) { + cacheEntryIndex = start; + myCache.add(cacheEntryIndex, result = new CacheEntry(visualLine, myEditor, myRepresentationHelper, myCache)); + } + else if (myBeforeChangeState.valid && cacheEntryIndex > myBeforeChangeState.endCacheEntryIndex && myCache.get(cacheEntryIndex).locked) { + myCache.set(cacheEntryIndex, result = myCache.get(cacheEntryIndex).clone()); + } + else { + result = myCache.get(cacheEntryIndex); + } + return result; + } + + @Override + public void onRecalculationStart(int startOffset, int endOffset) { + myBeforeChangeState.updateByDocumentOffsets(startOffset, endOffset); + if (!myBeforeChangeState.valid) { + return; + } + + //System.out.println("Before recalculation (" + startOffset + "-" + endOffset + ")"); + //dumpCache(); + + myNotAffectedByUpdateTailCacheEntries.clear(); + myNotAffectedByUpdateTailCacheEntries.addAll(myCache.subList(myBeforeChangeState.endCacheEntryIndex + 1, myCache.size())); + + // There is a possible case that the change is non-line feed symbol insertion to the document's end and its previous symbol + // is line feed. E.g.: + // line1 + // line2 + // + // We stored cache entry that corresponds to offset just before the previous last symbol (line feed). However, last symbol is + // not line feed now, hence, we need to remove corresponding virtual cache entry. + int textLength = myEditor.getDocument().getTextLength(); + if (endOffset >= textLength && !myNotAffectedByUpdateTailCacheEntries.isEmpty() + && myNotAffectedByUpdateTailCacheEntries.get(myNotAffectedByUpdateTailCacheEntries.size() - 1).startOffset >= textLength) + { + myNotAffectedByUpdateTailCacheEntries.remove(myNotAffectedByUpdateTailCacheEntries.size() - 1); + } + myCache.subList(myBeforeChangeState.startCacheEntryIndex + 1, myCache.size()).clear(); + for (CacheEntry entry : myNotAffectedByUpdateTailCacheEntries) { + entry.locked = true; + } + myCache.get(myBeforeChangeState.startCacheEntryIndex).collapse(); + + //System.out.println("Dropped all cache records starting from index " + (myBeforeChangeState.startCacheEntryIndex + 1)); + } + + @Override + public void onRecalculationEnd(int startOffset, int endOffset) { + if (!myBeforeChangeState.valid) { + return; + } + int endIndex = Math.max(0, myCache.size() - 1/*because of zero-based indexing*/); + if (endIndex > 0 && endOffset < myEditor.getDocument().getTextLength() - 1) { + endIndex--; // We assume that non-last document line ends with line feed symbol. + } + myAfterChangeState.updateByCacheIndices(myBeforeChangeState.startCacheEntryIndex, endIndex); + myCache.subList(myAfterChangeState.endCacheEntryIndex + 1, myCache.size()).clear(); + myCache.addAll(myNotAffectedByUpdateTailCacheEntries); + for (CacheEntry entry : myNotAffectedByUpdateTailCacheEntries) { + entry.locked = false; + } + + //System.out.println("Before applying state change:"); + //dumpCache(); + + applyStateChange(); + myNotAffectedByUpdateTailCacheEntries.clear(); + + //System.out.println("After Applying state change"); + //dumpCache(); + + myBeforeChangeState.valid = false; + } + + @SuppressWarnings({"UseOfSystemOutOrSystemErr", "UnusedDeclaration", "CallToPrintStackTrace"}) + private void dumpCache() { + Document document = myEditor.getDocument(); + CharSequence text = document.getCharsSequence(); + System.out.println("--------------------------------------------------"); + System.out.println("|"); + System.out.println("|"); + System.out.println(text); + System.out.println("- - - - - - - - - - - - - - - - - -"); + System.out.println("text length: " + text.length() + ", soft wraps: " + myStorage.getSoftWraps()); + for (int i = 0; i < myCache.size(); i++) { + CacheEntry entry = myCache.get(i); + if (text.length() <= 0 && i <= 0) { + continue; + } + try { + System.out.printf("line %d. %d-%d: '%s'%n", i, entry.startOffset, entry.endOffset, + text.subSequence(Math.min(text.length() - 1, entry.startOffset) ,Math.min(entry.endOffset, text.length() - 1))); + } + catch (Throwable e) { + e.printStackTrace(); + } + if (i > 0 && myCache.size() > 1 && entry.startOffset < text.length()) { + CacheEntry previous = myCache.get(i - 1); + if (previous.endOffset < entry.startOffset - 1 + || (previous.endOffset == entry.startOffset && myEditor.getSoftWrapModel().getSoftWrap(entry.startOffset) == null) + || previous.endOffset > entry.startOffset) + { + assert false; + } + } + } + if (!myCache.isEmpty() && myCache.get(myCache.size() - 1).endOffset < text.length() - 1) { + System.out.printf("Incomplete re-parsing detected! Document length is %d but last processed offset is %s%n", text.length(), + myCache.get(myCache.size() - 1).endOffset); + } + + + for (CacheEntry cacheEntry : myCache) { + if (cacheEntry.startOffset >= document.getTextLength()) { + continue; + } + if (cacheEntry.startOffset > 0) { + if (text.charAt(cacheEntry.startOffset - 1) != '\n' && myStorage.getSoftWrap(cacheEntry.startOffset) == null) { + assert false; + } + } + if (cacheEntry.endOffset < document.getTextLength() - 1) { + if (text.charAt(cacheEntry.endOffset) != '\n' && myStorage.getSoftWrap(cacheEntry.endOffset) == null) { + assert false; + } + } + } + + if (!myCache.isEmpty() && document.getTextLength() > 0) { + if (myCache.get(myCache.size() - 1).endOffset < myEditor.getDocument().getTextLength() - 1) { + assert false; + } + } + } + + /** + * Is assumed to be called for updating {@link #myCache document dimensions cache} entries that lay after document position identified + * by {@link #myAfterChangeState} in order to apply to them diff between {@link #myBeforeChangeState} and {@link #myAfterChangeState}. + *

+ * I.e. the general idea of incremental cache update is the following: + *

+ *

+   * 
    + *
  1. We are notified on document region update;
  2. + *
  3. We remember significant information about target region;
  4. + *
  5. Region data recalculation is performed;
  6. + *
  7. Diff between current region state and remembered one is applied to the cache;
  8. + *
+
+ */ + private void applyStateChange() { + if (myNotAffectedByUpdateTailCacheEntries.isEmpty()) { + return; + } + + int visualLinesDiff = myAfterChangeState.visualLines - myBeforeChangeState.visualLines; + int logicalLinesDiff = myAfterChangeState.logicalLines - myBeforeChangeState.logicalLines; + int softWrappedLinesDiff = myAfterChangeState.softWrapLines - myBeforeChangeState.softWrapLines; + int foldedLinesDiff = myAfterChangeState.foldedLines - myBeforeChangeState.foldedLines; + int offsetsDiff = (myAfterChangeState.endOffset - myAfterChangeState.startOffset) + - (myBeforeChangeState.endOffset - myBeforeChangeState.startOffset); + + for (CacheEntry cacheEntry : myNotAffectedByUpdateTailCacheEntries) { + cacheEntry.visualLine += visualLinesDiff; + cacheEntry.startLogicalLine += logicalLinesDiff; + cacheEntry.endLogicalLine += logicalLinesDiff; + cacheEntry.advance(offsetsDiff); + cacheEntry.startSoftWrapLinesBefore += softWrappedLinesDiff; + cacheEntry.endSoftWrapLinesBefore += softWrappedLinesDiff; + cacheEntry.startFoldedLines += foldedLinesDiff; + cacheEntry.endFoldedLines += foldedLinesDiff; + } + + int offset = myNotAffectedByUpdateTailCacheEntries.get(0).startOffset + 1/* in order to exclude soft wrap from previous line if any*/; + if (offsetsDiff == 0 || offset >= myEditor.getDocument().getTextLength()) { + return; + } + + int softWrapIndex = myStorage.getSoftWrapIndex(offset); + if (softWrapIndex < 0) { + softWrapIndex = -softWrapIndex - 1; + } + + List softWraps = myStorage.getSoftWraps(); + for (int i = softWrapIndex; i < softWraps.size(); i++) { + softWraps.get(i).advance(offsetsDiff); + } + } + + @Override + public String toString() { + return myCache.toString(); + } + + private class CacheState { + + public boolean valid = true; + + public int visualLines; + public int logicalLines; + public int softWrapLines; + public int foldedLines; + public int startOffset; + public int endOffset; + public int startCacheEntryIndex; + public int endCacheEntryIndex; + + public void updateByDocumentOffsets(int startOffset, int endOffset) { + this.startOffset = startOffset; + this.endOffset = endOffset; + Document document = myEditor.getDocument(); + startCacheEntryIndex = MappingUtil.getCacheEntryIndexForOffset(startOffset, document, myCache); + + // There is a possible case that particular document tail fragment is removed (this is the case for cyclic buffer for example). + // We want to drop corresponding cache entries then. + endCacheEntryIndex = MappingUtil.getCacheEntryIndexForOffset(Math.min(document.getTextLength(), endOffset), document, myCache); + + if (startCacheEntryIndex < 0 || endCacheEntryIndex < 0) { + valid = false; + // This is correct situation for, say, initial cache update. + return; + } + updateByCacheIndices(startCacheEntryIndex, endCacheEntryIndex); + + } + + public void updateByCacheIndices(int startIndex, int endIndex) { + reset(); + startOffset = myCache.get(startIndex).startOffset; + endOffset = myCache.get(endIndex).endOffset; + startCacheEntryIndex = startIndex; + endCacheEntryIndex = endIndex; + visualLines = endIndex - startIndex + 1; + + CacheEntry startEntry = myCache.get(startIndex); + CacheEntry endEntry = myCache.get(endIndex); + logicalLines = endEntry.endLogicalLine - startEntry.startLogicalLine + 1; + foldedLines = endEntry.endFoldedLines - startEntry.startFoldedLines; + softWrapLines = endEntry.endSoftWrapLinesBefore + endEntry.endSoftWrapLinesCurrent - startEntry.startSoftWrapLinesBefore + - startEntry.startSoftWrapLinesCurrent; + } + + public void reset() { + logicalLines = 0; + softWrapLines = 0; + foldedLines = 0; + endCacheEntryIndex = 0; + valid = true; + } + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/MappingUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/MappingUtil.java index 611b22d3cd2d..2b38aeddfdbe 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/MappingUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/MappingUtil.java @@ -1,84 +1,84 @@ -/* - * 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.editor.impl.softwrap.mapping; - -import com.intellij.openapi.editor.Document; - -import java.util.List; - -/** - * Holds utility methods for document dimensions mapping. - * - * @author Denis Zhdanov - * @since Sep 9, 2010 9:38:42 AM - */ -public class MappingUtil { - - private MappingUtil() { - } - - /** - * Maps given offset to the index of target {@link CacheEntry} that holds information about visual line with that offset. - * - * @param offset target offset - * @param document document which dimensions are being mapped - * @param cache parsed cache entries list that hold information about given document positions and are sorted by visual lines - * in ascending order - * @return index of target {@link CacheEntry} that holds information about visual line with given offset if possible; - * -1 if it's not possible to perform such mapping - */ - public static int getCacheEntryIndexForOffset(int offset, Document document, List cache) { - if (offset >= document.getTextLength() && (cache.isEmpty() || cache.get(cache.size() - 1).endOffset < offset)) { - if (cache.isEmpty()) { - return -1; - } - else { - return cache.size() - 1; - } - } - - int start = 0; - int end = cache.size() - 1; - - // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch(). - while (start <= end) { - int i = (end + start) >>> 1; - CacheEntry cacheEntry = cache.get(i); - if (cacheEntry.endOffset < offset) { - start = i + 1; - continue; - } - if (cacheEntry.startOffset > offset) { - end = i - 1; - continue; - } - - // There is a possible case that currently found cache entry corresponds to soft-wrapped line and soft wrap occurred - // at target offset. We need to return cache entry for the next visual line then (because document offset shared with - // soft wrap offset is assumed to point to 'after soft wrap' position). - if (offset == cacheEntry.endOffset && i < cache.size() - 1) { - CacheEntry nextLineCacheEntry = cache.get(i + 1); - if (nextLineCacheEntry.startOffset == offset) { - return i + 1; - } - } - return i; - } - - return -1; - } - -} +/* + * 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.editor.impl.softwrap.mapping; + +import com.intellij.openapi.editor.Document; + +import java.util.List; + +/** + * Holds utility methods for document dimensions mapping. + * + * @author Denis Zhdanov + * @since Sep 9, 2010 9:38:42 AM + */ +public class MappingUtil { + + private MappingUtil() { + } + + /** + * Maps given offset to the index of target {@link CacheEntry} that holds information about visual line with that offset. + * + * @param offset target offset + * @param document document which dimensions are being mapped + * @param cache parsed cache entries list that hold information about given document positions and are sorted by visual lines + * in ascending order + * @return index of target {@link CacheEntry} that holds information about visual line with given offset if possible; + * -1 if it's not possible to perform such mapping + */ + public static int getCacheEntryIndexForOffset(int offset, Document document, List cache) { + if (offset >= document.getTextLength() && (cache.isEmpty() || cache.get(cache.size() - 1).endOffset < offset)) { + if (cache.isEmpty()) { + return -1; + } + else { + return cache.size() - 1; + } + } + + int start = 0; + int end = cache.size() - 1; + + // We inline binary search here because profiling indicates that it becomes bottleneck to use Collections.binarySearch(). + while (start <= end) { + int i = (end + start) >>> 1; + CacheEntry cacheEntry = cache.get(i); + if (cacheEntry.endOffset < offset) { + start = i + 1; + continue; + } + if (cacheEntry.startOffset > offset) { + end = i - 1; + continue; + } + + // There is a possible case that currently found cache entry corresponds to soft-wrapped line and soft wrap occurred + // at target offset. We need to return cache entry for the next visual line then (because document offset shared with + // soft wrap offset is assumed to point to 'after soft wrap' position). + if (offset == cacheEntry.endOffset && i < cache.size() - 1) { + CacheEntry nextLineCacheEntry = cache.get(i + 1); + if (nextLineCacheEntry.startOffset == offset) { + return i + 1; + } + } + return i; + } + + return -1; + } + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorComposite.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorComposite.java index 73046aa0d0d9..7742fa9564c4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorComposite.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorComposite.java @@ -1,439 +1,439 @@ -/* - * 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.fileEditor.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -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.*; -import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.FocusWatcher; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.ui.PrevNextActionsDescriptor; -import com.intellij.ui.SideBorder; -import com.intellij.ui.TabbedPaneWrapper; -import com.intellij.ui.tabs.UiDecorator; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.*; -import java.util.HashMap; -import java.util.Map; - -/** - * This class hides internal structure of UI component which represent - * set of opened editors. For example, one myEditor is represented by its - * component, more then one myEditor is wrapped into tabbed pane. - * - * @author Vladimir Kondratyev - */ -public abstract class EditorComposite implements Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorComposite"); - - /** - * File for which composite is created - */ - private final VirtualFile myFile; - /** - * Whether the composite is pinned or not - */ - private boolean myPinned; - /** - * Editors which are opened in the composite - */ - protected final FileEditor[] myEditors; - /** - * This is initial timestamp of the file. It uses to implement - * "close non modified editors first" feature. - */ - private final long myInitialFileTimeStamp; - protected final TabbedPaneWrapper myTabbedPaneWrapper; - private final MyComponent myComponent; - private final FocusWatcher myFocusWatcher; - /** - * Currently selected myEditor - */ - private FileEditor mySelectedEditor; - private final FileEditorManagerEx myFileEditorManager; - private final long myInitialFileModificationStamp; - private final Map myTopComponents = new HashMap(); - private final Map myBottomComponents = new HashMap(); - - /** - * @param file file for which composite is being constructed - * - * @param editors edittors that should be placed into the composite - * - * @exception java.lang.IllegalArgumentException if editors - * is null - * - * @exception java.lang.IllegalArgumentException if providers - * is null - * - * @exception java.lang.IllegalArgumentException if myEditor - * arrays is empty - */ - EditorComposite( - @NotNull final VirtualFile file, - @NotNull final FileEditor[] editors, - @NotNull final FileEditorManagerEx fileEditorManager - ){ - myFile = file; - myEditors = editors; - myFileEditorManager = fileEditorManager; - myInitialFileTimeStamp = myFile.getTimeStamp(); - myInitialFileModificationStamp = myFile.getModificationStamp(); - - Disposer.register(fileEditorManager.getProject(), this); - - if(editors.length > 1){ - PrevNextActionsDescriptor descriptor = new PrevNextActionsDescriptor(IdeActions.ACTION_NEXT_EDITOR_TAB, IdeActions.ACTION_PREVIOUS_EDITOR_TAB); - final TabbedPaneWrapper.AsJBTabs wrapper = new TabbedPaneWrapper.AsJBTabs(fileEditorManager.getProject(), SwingConstants.BOTTOM, descriptor, this); - wrapper.getTabs().getPresentation().setPaintBorder(0, 0, 0, 0).setTabSidePaintBorder(1).setGhostsAlwaysVisible(true).setUiDecorator(new UiDecorator() { - @NotNull - public UiDecoration getDecoration() { - return new UiDecoration(null, new Insets(0, 8, 0, 8)); - } - }); - wrapper.getTabs().getComponent().setBorder(new EmptyBorder(0, 0, 1, 0)); - - myTabbedPaneWrapper=wrapper; - myComponent=new MyComponent(wrapper.getComponent()){ - public boolean requestFocusInWindow() { - return wrapper.getComponent().requestFocusInWindow(); - } - - public void requestFocus() { - wrapper.getComponent().requestFocus(); - } - - public boolean requestDefaultFocus() { - return wrapper.getComponent().requestDefaultFocus(); - } - }; - for (FileEditor editor : editors) { - wrapper.addTab(editor.getName(), createEditorComponent(editor)); - } - myTabbedPaneWrapper.addChangeListener(new MyChangeListener()); - } - else if(editors.length==1){ - myTabbedPaneWrapper=null; - myComponent = new MyComponent(createEditorComponent(editors[0])){ - public void requestFocus() { - JComponent component = editors[0].getPreferredFocusedComponent(); - if (component != null) { - component.requestFocus(); - } - } - - public boolean requestFocusInWindow() { - JComponent component = editors[0].getPreferredFocusedComponent(); - if (component != null) { - return component.requestFocusInWindow(); - } - - return false; - } - - public boolean requestDefaultFocus() { - JComponent component = editors[0].getPreferredFocusedComponent(); - if (component != null) { - return component.requestDefaultFocus(); - } - return false; - } - }; - } - else{ - throw new IllegalArgumentException("editors array cannot be empty"); - } - - mySelectedEditor = editors[0]; - myFocusWatcher = new FocusWatcher(); - myFocusWatcher.install(myComponent); - - myFileEditorManager.addFileEditorManagerListener(new FileEditorManagerAdapter() { - @Override - public void selectionChanged(final FileEditorManagerEvent event) { - final VirtualFile oldFile = event.getOldFile(); - final VirtualFile newFile = event.getNewFile(); - if (oldFile == newFile && getFile() == newFile) { - final FileEditor oldEditor = event.getOldEditor(); - if (oldEditor != null) oldEditor.deselectNotify(); - final FileEditor newEditor = event.getNewEditor(); - if (newEditor != null) newEditor.selectNotify(); - } - } - }, this); - } - - private JComponent createEditorComponent(final FileEditor editor) { - JPanel component = new JPanel(new BorderLayout()); - JComponent comp = editor.getComponent(); - if (!FileEditorManagerImpl.isDumbAware(editor)) { - comp = DumbService.getInstance(myFileEditorManager.getProject()).wrapGently(comp, editor); - } - - component.add(comp, BorderLayout.CENTER); - - JPanel topPanel = new TopBottomPanel(); - myTopComponents.put(editor, topPanel); - component.add(topPanel, BorderLayout.NORTH); - - final JPanel bottomPanel = new TopBottomPanel(); - myBottomComponents.put(editor, bottomPanel); - component.add(bottomPanel, BorderLayout.SOUTH); - - return component; - } - - /** - * @return whether myEditor composite is pinned - */ - public boolean isPinned(){ - return myPinned; - } - - /** - * Sets new "pinned" state - */ - void setPinned(final boolean pinned){ - myPinned = pinned; - } - - private void fireSelectedEditorChanged(final FileEditor oldSelectedEditor, final FileEditor newSelectedEditor){ - if (!myFileEditorManager.isInsideChange() && !Comparing.equal(oldSelectedEditor, newSelectedEditor)) { - final FileEditorManagerEvent event = new FileEditorManagerEvent(myFileEditorManager, myFile, oldSelectedEditor, myFile, newSelectedEditor); - final FileEditorManagerListener publisher = myFileEditorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); - IdeFocusManager.getInstance(myFileEditorManager.getProject()).doWhenFocusSettlesDown(new Runnable() { - @Override - public void run() { - publisher.selectionChanged(event); - } - }); - } - } - - - /** - * @return preferred focused component inside myEditor composite. Composite uses FocusWatcher to - * track focus movement inside the myEditor. - */ - public JComponent getPreferredFocusedComponent(){ - final Component component = myFocusWatcher.getFocusedComponent(); - if(!(component instanceof JComponent) || !component.isShowing() || !component.isEnabled() || !component.isFocusable()){ - return getSelectedEditor().getPreferredFocusedComponent(); - } - return (JComponent)component; - } - - /** - * @return file for which composite was created. The method always - * returns not null valus. - */ - public VirtualFile getFile() { - return myFile; - } - - public FileEditorManager getFileEditorManager() { - return myFileEditorManager; - } - - /** - * @return initial time stamp of the file (on moment of creation of - * the composite) - */ - public long getInitialFileTimeStamp() { - return myInitialFileTimeStamp; - } - - /** - * @return initial modifcation stamp of the file (on moment of creation of - * the composite) - */ - public long getInitialFileModificationStamp() { - return myInitialFileModificationStamp; - } - - /** - * @return editors which are opened in the composite. Do not modify - * this array. - */ - public FileEditor[] getEditors() { - return myEditors; - } - - public void addTopComponent(FileEditor editor, JComponent component) { - manageTopOrBottomComponent(editor, component, true, false); - } - - public void removeTopComponent(FileEditor editor, JComponent component) { - manageTopOrBottomComponent(editor, component, true, true); - } - - public void addBottomComponent(FileEditor editor, JComponent component) { - manageTopOrBottomComponent(editor, component, false, false); - } - - public void removeBottomComponent(FileEditor editor, JComponent component) { - manageTopOrBottomComponent(editor, component, false, true); - } - - private void manageTopOrBottomComponent(FileEditor editor, JComponent component, boolean top, boolean remove) { - final JComponent container = top ? myTopComponents.get(editor) : myBottomComponents.get(editor); - assert container != null; - - if (remove) { - container.remove(component.getParent()); - } else { - container.add(new TopBottomComponentWrapper(component, top)); - } - container.revalidate(); - } - - /** - * @return currently selected myEditor. The method never returns null. - */ - @NotNull FileEditor getSelectedEditor(){ - return getSelectedEditorWithProvider ().getFirst (); - } - - public boolean isDisposed() { - return myTabbedPaneWrapper != null && myTabbedPaneWrapper.isDisposed(); - } - - /** - * @return currently selected myEditor with its provider. The method never returns null. - */ - public abstract Pair getSelectedEditorWithProvider(); - - void setSelectedEditor(final int index){ - if(myEditors.length == 1){ - // nothing to do - LOG.assertTrue(myTabbedPaneWrapper == null); - } - else{ - LOG.assertTrue(myTabbedPaneWrapper != null); - myTabbedPaneWrapper.setSelectedIndex(index); - } - } - - /** - * @return component which represents set of file editors in the UI - */ - public JComponent getComponent() { - return myComponent; - } - - /** - * @return true if the composite contains at least one - * modified myEditor - */ - public boolean isModified(){ - for(int i=myEditors.length-1;i>=0;i--){ - if(myEditors[i].isModified()){ - return true; - } - } - return false; - } - - /** - * Handles changes of selected myEditor - */ - private final class MyChangeListener implements ChangeListener{ - public void stateChanged(ChangeEvent e) { - FileEditor oldSelectedEditor = mySelectedEditor; - LOG.assertTrue(oldSelectedEditor != null); - int selectedIndex = myTabbedPaneWrapper.getSelectedIndex(); - LOG.assertTrue(selectedIndex != -1); - mySelectedEditor = myEditors[selectedIndex]; - fireSelectedEditorChanged(oldSelectedEditor, mySelectedEditor); - } - } - - private abstract class MyComponent extends JPanel implements DataProvider{ - public MyComponent(JComponent realComponent){ - super(new BorderLayout()); - add(realComponent, BorderLayout.CENTER); - } - - public final Object getData(String dataId){ - if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { - return getSelectedEditor(); - } - else if(PlatformDataKeys.VIRTUAL_FILE.is(dataId)){ - return myFile.isValid() ? myFile : null; - } - else if(PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)){ - return myFile.isValid() ? new VirtualFile[] {myFile} : null; - } - else{ - JComponent component = getPreferredFocusedComponent(); - if(component instanceof DataProvider && component != this){ - return ((DataProvider)component).getData(dataId); - } - else{ - return null; - } - } - } - } - - public void dispose() { - } - - private class TopBottomPanel extends JPanel { - private TopBottomPanel() { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - } - - @Override - public Color getBackground() { - Color color = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND); - return color == null ? Color.gray : color; - } - } - - private class TopBottomComponentWrapper extends JPanel { - public TopBottomComponentWrapper(JComponent component, boolean top) { - super(new BorderLayout()); - setOpaque(false); - - setBorder(new SideBorder(null, top ? SideBorder.BOTTOM : SideBorder.TOP, true) { - @Override - public Color getLineColor() { - Color result = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.TEARLINE_COLOR); - return result == null ? Color.black : result; - } - }); - - add(component); - } - } -} +/* + * 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.fileEditor.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +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.*; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.FocusWatcher; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.ui.PrevNextActionsDescriptor; +import com.intellij.ui.SideBorder; +import com.intellij.ui.TabbedPaneWrapper; +import com.intellij.ui.tabs.UiDecorator; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +/** + * This class hides internal structure of UI component which represent + * set of opened editors. For example, one myEditor is represented by its + * component, more then one myEditor is wrapped into tabbed pane. + * + * @author Vladimir Kondratyev + */ +public abstract class EditorComposite implements Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorComposite"); + + /** + * File for which composite is created + */ + private final VirtualFile myFile; + /** + * Whether the composite is pinned or not + */ + private boolean myPinned; + /** + * Editors which are opened in the composite + */ + protected final FileEditor[] myEditors; + /** + * This is initial timestamp of the file. It uses to implement + * "close non modified editors first" feature. + */ + private final long myInitialFileTimeStamp; + protected final TabbedPaneWrapper myTabbedPaneWrapper; + private final MyComponent myComponent; + private final FocusWatcher myFocusWatcher; + /** + * Currently selected myEditor + */ + private FileEditor mySelectedEditor; + private final FileEditorManagerEx myFileEditorManager; + private final long myInitialFileModificationStamp; + private final Map myTopComponents = new HashMap(); + private final Map myBottomComponents = new HashMap(); + + /** + * @param file file for which composite is being constructed + * + * @param editors edittors that should be placed into the composite + * + * @exception java.lang.IllegalArgumentException if editors + * is null + * + * @exception java.lang.IllegalArgumentException if providers + * is null + * + * @exception java.lang.IllegalArgumentException if myEditor + * arrays is empty + */ + EditorComposite( + @NotNull final VirtualFile file, + @NotNull final FileEditor[] editors, + @NotNull final FileEditorManagerEx fileEditorManager + ){ + myFile = file; + myEditors = editors; + myFileEditorManager = fileEditorManager; + myInitialFileTimeStamp = myFile.getTimeStamp(); + myInitialFileModificationStamp = myFile.getModificationStamp(); + + Disposer.register(fileEditorManager.getProject(), this); + + if(editors.length > 1){ + PrevNextActionsDescriptor descriptor = new PrevNextActionsDescriptor(IdeActions.ACTION_NEXT_EDITOR_TAB, IdeActions.ACTION_PREVIOUS_EDITOR_TAB); + final TabbedPaneWrapper.AsJBTabs wrapper = new TabbedPaneWrapper.AsJBTabs(fileEditorManager.getProject(), SwingConstants.BOTTOM, descriptor, this); + wrapper.getTabs().getPresentation().setPaintBorder(0, 0, 0, 0).setTabSidePaintBorder(1).setGhostsAlwaysVisible(true).setUiDecorator(new UiDecorator() { + @NotNull + public UiDecoration getDecoration() { + return new UiDecoration(null, new Insets(0, 8, 0, 8)); + } + }); + wrapper.getTabs().getComponent().setBorder(new EmptyBorder(0, 0, 1, 0)); + + myTabbedPaneWrapper=wrapper; + myComponent=new MyComponent(wrapper.getComponent()){ + public boolean requestFocusInWindow() { + return wrapper.getComponent().requestFocusInWindow(); + } + + public void requestFocus() { + wrapper.getComponent().requestFocus(); + } + + public boolean requestDefaultFocus() { + return wrapper.getComponent().requestDefaultFocus(); + } + }; + for (FileEditor editor : editors) { + wrapper.addTab(editor.getName(), createEditorComponent(editor)); + } + myTabbedPaneWrapper.addChangeListener(new MyChangeListener()); + } + else if(editors.length==1){ + myTabbedPaneWrapper=null; + myComponent = new MyComponent(createEditorComponent(editors[0])){ + public void requestFocus() { + JComponent component = editors[0].getPreferredFocusedComponent(); + if (component != null) { + component.requestFocus(); + } + } + + public boolean requestFocusInWindow() { + JComponent component = editors[0].getPreferredFocusedComponent(); + if (component != null) { + return component.requestFocusInWindow(); + } + + return false; + } + + public boolean requestDefaultFocus() { + JComponent component = editors[0].getPreferredFocusedComponent(); + if (component != null) { + return component.requestDefaultFocus(); + } + return false; + } + }; + } + else{ + throw new IllegalArgumentException("editors array cannot be empty"); + } + + mySelectedEditor = editors[0]; + myFocusWatcher = new FocusWatcher(); + myFocusWatcher.install(myComponent); + + myFileEditorManager.addFileEditorManagerListener(new FileEditorManagerAdapter() { + @Override + public void selectionChanged(final FileEditorManagerEvent event) { + final VirtualFile oldFile = event.getOldFile(); + final VirtualFile newFile = event.getNewFile(); + if (oldFile == newFile && getFile() == newFile) { + final FileEditor oldEditor = event.getOldEditor(); + if (oldEditor != null) oldEditor.deselectNotify(); + final FileEditor newEditor = event.getNewEditor(); + if (newEditor != null) newEditor.selectNotify(); + } + } + }, this); + } + + private JComponent createEditorComponent(final FileEditor editor) { + JPanel component = new JPanel(new BorderLayout()); + JComponent comp = editor.getComponent(); + if (!FileEditorManagerImpl.isDumbAware(editor)) { + comp = DumbService.getInstance(myFileEditorManager.getProject()).wrapGently(comp, editor); + } + + component.add(comp, BorderLayout.CENTER); + + JPanel topPanel = new TopBottomPanel(); + myTopComponents.put(editor, topPanel); + component.add(topPanel, BorderLayout.NORTH); + + final JPanel bottomPanel = new TopBottomPanel(); + myBottomComponents.put(editor, bottomPanel); + component.add(bottomPanel, BorderLayout.SOUTH); + + return component; + } + + /** + * @return whether myEditor composite is pinned + */ + public boolean isPinned(){ + return myPinned; + } + + /** + * Sets new "pinned" state + */ + void setPinned(final boolean pinned){ + myPinned = pinned; + } + + private void fireSelectedEditorChanged(final FileEditor oldSelectedEditor, final FileEditor newSelectedEditor){ + if (!myFileEditorManager.isInsideChange() && !Comparing.equal(oldSelectedEditor, newSelectedEditor)) { + final FileEditorManagerEvent event = new FileEditorManagerEvent(myFileEditorManager, myFile, oldSelectedEditor, myFile, newSelectedEditor); + final FileEditorManagerListener publisher = myFileEditorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); + IdeFocusManager.getInstance(myFileEditorManager.getProject()).doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + publisher.selectionChanged(event); + } + }); + } + } + + + /** + * @return preferred focused component inside myEditor composite. Composite uses FocusWatcher to + * track focus movement inside the myEditor. + */ + public JComponent getPreferredFocusedComponent(){ + final Component component = myFocusWatcher.getFocusedComponent(); + if(!(component instanceof JComponent) || !component.isShowing() || !component.isEnabled() || !component.isFocusable()){ + return getSelectedEditor().getPreferredFocusedComponent(); + } + return (JComponent)component; + } + + /** + * @return file for which composite was created. The method always + * returns not null valus. + */ + public VirtualFile getFile() { + return myFile; + } + + public FileEditorManager getFileEditorManager() { + return myFileEditorManager; + } + + /** + * @return initial time stamp of the file (on moment of creation of + * the composite) + */ + public long getInitialFileTimeStamp() { + return myInitialFileTimeStamp; + } + + /** + * @return initial modifcation stamp of the file (on moment of creation of + * the composite) + */ + public long getInitialFileModificationStamp() { + return myInitialFileModificationStamp; + } + + /** + * @return editors which are opened in the composite. Do not modify + * this array. + */ + public FileEditor[] getEditors() { + return myEditors; + } + + public void addTopComponent(FileEditor editor, JComponent component) { + manageTopOrBottomComponent(editor, component, true, false); + } + + public void removeTopComponent(FileEditor editor, JComponent component) { + manageTopOrBottomComponent(editor, component, true, true); + } + + public void addBottomComponent(FileEditor editor, JComponent component) { + manageTopOrBottomComponent(editor, component, false, false); + } + + public void removeBottomComponent(FileEditor editor, JComponent component) { + manageTopOrBottomComponent(editor, component, false, true); + } + + private void manageTopOrBottomComponent(FileEditor editor, JComponent component, boolean top, boolean remove) { + final JComponent container = top ? myTopComponents.get(editor) : myBottomComponents.get(editor); + assert container != null; + + if (remove) { + container.remove(component.getParent()); + } else { + container.add(new TopBottomComponentWrapper(component, top)); + } + container.revalidate(); + } + + /** + * @return currently selected myEditor. The method never returns null. + */ + @NotNull FileEditor getSelectedEditor(){ + return getSelectedEditorWithProvider ().getFirst (); + } + + public boolean isDisposed() { + return myTabbedPaneWrapper != null && myTabbedPaneWrapper.isDisposed(); + } + + /** + * @return currently selected myEditor with its provider. The method never returns null. + */ + public abstract Pair getSelectedEditorWithProvider(); + + void setSelectedEditor(final int index){ + if(myEditors.length == 1){ + // nothing to do + LOG.assertTrue(myTabbedPaneWrapper == null); + } + else{ + LOG.assertTrue(myTabbedPaneWrapper != null); + myTabbedPaneWrapper.setSelectedIndex(index); + } + } + + /** + * @return component which represents set of file editors in the UI + */ + public JComponent getComponent() { + return myComponent; + } + + /** + * @return true if the composite contains at least one + * modified myEditor + */ + public boolean isModified(){ + for(int i=myEditors.length-1;i>=0;i--){ + if(myEditors[i].isModified()){ + return true; + } + } + return false; + } + + /** + * Handles changes of selected myEditor + */ + private final class MyChangeListener implements ChangeListener{ + public void stateChanged(ChangeEvent e) { + FileEditor oldSelectedEditor = mySelectedEditor; + LOG.assertTrue(oldSelectedEditor != null); + int selectedIndex = myTabbedPaneWrapper.getSelectedIndex(); + LOG.assertTrue(selectedIndex != -1); + mySelectedEditor = myEditors[selectedIndex]; + fireSelectedEditorChanged(oldSelectedEditor, mySelectedEditor); + } + } + + private abstract class MyComponent extends JPanel implements DataProvider{ + public MyComponent(JComponent realComponent){ + super(new BorderLayout()); + add(realComponent, BorderLayout.CENTER); + } + + public final Object getData(String dataId){ + if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { + return getSelectedEditor(); + } + else if(PlatformDataKeys.VIRTUAL_FILE.is(dataId)){ + return myFile.isValid() ? myFile : null; + } + else if(PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)){ + return myFile.isValid() ? new VirtualFile[] {myFile} : null; + } + else{ + JComponent component = getPreferredFocusedComponent(); + if(component instanceof DataProvider && component != this){ + return ((DataProvider)component).getData(dataId); + } + else{ + return null; + } + } + } + } + + public void dispose() { + } + + private class TopBottomPanel extends JPanel { + private TopBottomPanel() { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + } + + @Override + public Color getBackground() { + Color color = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND); + return color == null ? Color.gray : color; + } + } + + private class TopBottomComponentWrapper extends JPanel { + public TopBottomComponentWrapper(JComponent component, boolean top) { + super(new BorderLayout()); + setOpaque(false); + + setBorder(new SideBorder(null, top ? SideBorder.BOTTOM : SideBorder.TOP, true) { + @Override + public Color getLineColor() { + Color result = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.TEARLINE_COLOR); + return result == null ? Color.black : result; + } + }); + + add(component); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java index 24ed3315c947..5f3f5b97bd87 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java @@ -1,895 +1,895 @@ -/* - * 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.fileEditor.impl; - -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.actionSystem.DataKey; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.fileTypes.FileTypes; -import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.ui.LayeredIcon; -import com.intellij.util.IconUtil; -import com.intellij.util.ui.EmptyIcon; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Author: msk - */ -public class EditorWindow { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorWindow"); - - public static final DataKey DATA_KEY = DataKey.create("editorWindow"); - - protected JPanel myPanel; - private EditorTabbedContainer myTabbedPane; - private final EditorsSplitters myOwner; - private static final Icon MODIFIED_ICON = IconLoader.getIcon("/general/modified.png"); - private static final Icon GAP_ICON = new EmptyIcon(MODIFIED_ICON.getIconWidth(), MODIFIED_ICON.getIconHeight()); - - private boolean myIsDisposed = false; - private static final Icon PIN_ICON = IconLoader.getIcon("/nodes/tabPin.png"); - public static final Key INITIAL_INDEX_KEY = Key.create("initial editor index"); - - protected EditorWindow(final EditorsSplitters owner) { - myOwner = owner; - myPanel = new JPanel(new BorderLayout()); - myPanel.setOpaque(false); - - myTabbedPane = null; - - final int tabPlacement = UISettings.getInstance().EDITOR_TAB_PLACEMENT; - if (tabPlacement != UISettings.TABS_NONE) { - createTabs(tabPlacement); - } - - // Tab layout policy - if (UISettings.getInstance().SCROLL_TAB_LAYOUT_IN_EDITOR) { - setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - } else { - setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); - } - - getWindows().add(this); - if (myOwner.getCurrentWindow() == null) { - myOwner.setCurrentWindow(this, false); - } - } - - private void createTabs(int tabPlacement) { - LOG.assertTrue (myTabbedPane == null); - myTabbedPane = new EditorTabbedContainer(this, getManager().getProject(), getManager().getDockManager(), tabPlacement); - myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); - } - - private Set getWindows() { - return myOwner.myWindows; - } - - private void dispose() { - try { - disposeTabs(); - getWindows ().remove(this); - } - finally { - myIsDisposed = true; - } - } - - public boolean isDisposed() { - return myIsDisposed; - } - - private void disposeTabs() { - if (myTabbedPane != null) { - Disposer.dispose(myTabbedPane); - myTabbedPane = null; - } - myPanel.removeAll(); - myPanel.revalidate(); - } - - public void closeFile(final VirtualFile file) { - closeFile (file, true); - } - - public void closeFile(final VirtualFile file, final boolean unsplit) { - closeFile(file, unsplit, true); - } - - public void closeFile(final VirtualFile file, final boolean unsplit, final boolean transferFocus) { - final FileEditorManagerImpl editorManager = getManager(); - editorManager.runChange(new Runnable() { - public void run() { - try { - final List editors = myOwner.findEditorComposites(file); - if (editors.isEmpty()) return; - final EditorWithProviderComposite editor = findFileComposite(file); - - final FileEditorManagerListener.Before beforePublisher = - editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER); - - beforePublisher.beforeFileClosed(editorManager, file); - - if (myTabbedPane != null) { - final int componentIndex = findComponentIndex(editor.getComponent()); - if (componentIndex >= 0) { // editor could close itself on decomposition - final int indexToSelect = calcIndexToSelect(file, componentIndex); - myTabbedPane.removeTabAt(componentIndex, indexToSelect, transferFocus).doWhenDone(new Runnable() { - public void run() { - editorManager.disposeComposite(editor); - } - }); - } - } - else { - myPanel.removeAll (); - editorManager.disposeComposite(editor); - } - - if (unsplit && getTabCount() == 0) { - unsplit (true); - } - myPanel.revalidate (); - if (myTabbedPane == null) { - // in tabless mode - myPanel.repaint(); - } - } - finally { - final FileEditorManagerListener afterPublisher = - editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); - - IdeFocusManager.getInstance(editorManager.getProject()).doWhenFocusSettlesDown(new Runnable() { - @Override - public void run() { - afterPublisher.fileClosed(editorManager, file); - } - }); - - myOwner.afterFileClosed(file); - } - } - }); - } - - private int calcIndexToSelect(VirtualFile fileBeingClosed, final int fileIndex) { - final int currentlySelectedIndex = myTabbedPane.getSelectedIndex(); - if (currentlySelectedIndex != fileIndex) { - // if the file being closed is not currently selected, keep the currently selected file open - return currentlySelectedIndex; - } - if (UISettings.getInstance().ACTIVATE_MRU_EDITOR_ON_CLOSE) { - // try to open last visited file - final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); - for (int idx = histFiles.length - 1; idx >= 0; idx--) { - final VirtualFile histFile = histFiles[idx]; - if (histFile.equals(fileBeingClosed)) { - continue; - } - final EditorWithProviderComposite editor = findFileComposite(histFile); - if (editor == null) { - continue; // ???? - } - final int histFileIndex = findComponentIndex(editor.getComponent()); - if (histFileIndex >= 0) { - // if the file being closed is located before the hist file, then after closing the index of the histFile will be shifted by -1 - return histFileIndex; - } - } - } - // by default select previous neighbour - if (fileIndex > 0) { - return fileIndex - 1; - } - // do nothing - return -1; - } - - public FileEditorManagerImpl getManager() { return myOwner.getManager(); } - - public int getTabCount() { - if (myTabbedPane != null) { - return myTabbedPane.getTabCount(); - } - return myPanel.getComponentCount(); - } - - public void setForegroundAt(final int index, final Color color) { - if (myTabbedPane != null) { - myTabbedPane.setForegroundAt(index, color); - } - } - - public void setWaveColor(final int index, @Nullable final Color color) { - if (myTabbedPane != null) { - myTabbedPane.setWaveColor(index, color); - } - } - - private void setIconAt(final int index, final Icon icon) { - if (myTabbedPane != null) { - myTabbedPane.setIconAt(index, icon); - } - } - - private void setTitleAt(final int index, final String text) { - if (myTabbedPane != null) { - myTabbedPane.setTitleAt(index, text); - } - } - - private void setBackgroundColorAt(final int index, final Color color) { - if (myTabbedPane != null) { - myTabbedPane.setBackgroundColorAt(index, color); - } - } - - private void setToolTipTextAt(final int index, final String text) { - if (myTabbedPane != null) { - myTabbedPane.setToolTipTextAt(index, text); - } - } - - - public void setTabLayoutPolicy(final int policy) { - if (myTabbedPane != null) { - myTabbedPane.setTabLayoutPolicy(policy); - } - } - - public void setTabsPlacement(final int tabPlacement) { - if (tabPlacement != UISettings.TABS_NONE) { - if (myTabbedPane == null) { - final EditorWithProviderComposite editor = getSelectedEditor(); - myPanel.removeAll(); - createTabs(tabPlacement); - setEditor (editor, true); - } - else { - myTabbedPane.setTabPlacement(tabPlacement); - } - } - else if (myTabbedPane != null) { - final boolean focusEditor = ToolWindowManager.getInstance(getManager().getProject()).isEditorComponentActive(); - final VirtualFile currentFile = getSelectedFile(); - final VirtualFile[] files = getFiles(); - for (VirtualFile file : files) { - closeFile(file, false); - } - disposeTabs(); - if (currentFile != null) { - getManager().openFileImpl2(this, currentFile, focusEditor && myOwner.getCurrentWindow() == this); - } - else { - myPanel.repaint(); - } - } - } - - public void setAsCurrentWindow(final boolean requestFocus) { - myOwner.setCurrentWindow(this, requestFocus); - } - - public void updateFileBackgroundColor(final VirtualFile file) { - final int index = findEditorIndex(findFileComposite(file)); - if (index != -1) { - final Color color = EditorTabbedContainer.calcTabColor(getManager().getProject(), file); - setBackgroundColorAt(index, color); - } - } - - public EditorsSplitters getOwner() { - return myOwner; - } - - public boolean isEmptyVisible() { - return myTabbedPane != null ? myTabbedPane.isEmptyVisible() : getFiles().length == 0; - } - - protected static class TComp extends JPanel implements DataProvider{ - final EditorWithProviderComposite myEditor; - - TComp(final EditorWithProviderComposite editor) { - super(new BorderLayout()); - myEditor = editor; - add(editor.getComponent(), BorderLayout.CENTER); - } - - public Object getData(String dataId) { - if (PlatformDataKeys.VIRTUAL_FILE.is(dataId)){ - final VirtualFile virtualFile = myEditor.getFile(); - return virtualFile.isValid() ? virtualFile : null; - } - else if (PlatformDataKeys.PROJECT.is(dataId)) { - return myEditor.getFileEditorManager().getProject(); - } - return null; - } - } - - protected static class TCompForTablessMode extends TComp{ - private final EditorWindow myWindow; - - TCompForTablessMode(final EditorWindow window, final EditorWithProviderComposite editor) { - super(editor); - myWindow = window; - } - - public Object getData(String dataId) { - if (EditorWindow.DATA_KEY.is(dataId)){ - // this is essintial for ability to close opened file - return myWindow; - } - return super.getData(dataId); - } - } - - private void checkConsistency() { - LOG.assertTrue(getWindows().contains(this), "EditorWindow not in collection"); - } - - public EditorWithProviderComposite getSelectedEditor() { - final TComp comp; - if (myTabbedPane != null) { - comp = (TComp)myTabbedPane.getSelectedComponent(); - } - else if (myPanel.getComponentCount() != 0) { - final Component component = myPanel.getComponent(0); - comp = component instanceof TComp ? (TComp)component : null; - } - else { - return null; - } - - if (comp != null) { - return comp.myEditor; - } - return null; - } - - public EditorWithProviderComposite[] getEditors() { - final int tabCount = getTabCount(); - final EditorWithProviderComposite[] res = new EditorWithProviderComposite[tabCount]; - for (int i = 0; i != tabCount; ++i) { - res[i] = getEditorAt(i); - } - return res; - } - - public VirtualFile[] getFiles() { - final int tabCount = getTabCount(); - final VirtualFile[] res = new VirtualFile[tabCount]; - for (int i = 0; i != tabCount; ++i) { - res[i] = getEditorAt(i).getFile(); - } - return res; - } - - public void setSelectedEditor(final EditorComposite editor, final boolean focusEditor) { - if (myTabbedPane == null) { - return; - } - if (editor != null) { - final int index = findFileIndex(editor.getFile()); - if (index != -1) { - myTabbedPane.setSelectedIndex(index, focusEditor); - } - } - } - - public void setEditor(final EditorWithProviderComposite editor, final boolean focusEditor) { - if (editor != null) { - if (myTabbedPane == null) { - myPanel.removeAll (); - myPanel.add (new TCompForTablessMode(this, editor), BorderLayout.CENTER); - myPanel.revalidate (); - return; - } - - final int index = findEditorIndex(editor); - if (index != -1) { - setSelectedEditor(editor, focusEditor); - } - else { - Integer initialIndex = editor.getFile().getUserData(INITIAL_INDEX_KEY); - final int indexToInsert = initialIndex == null ? myTabbedPane.getSelectedIndex() + 1 : initialIndex; - final VirtualFile file = editor.getFile(); - final Icon template = IconLoader.getIcon("/fileTypes/text.png"); - myTabbedPane.insertTab(file, new EmptyIcon(template.getIconWidth(), template.getIconHeight()), new TComp(editor), null, indexToInsert); - trimToSize(UISettings.getInstance().EDITOR_TAB_LIMIT, file, false); - setSelectedEditor(editor, focusEditor); - myOwner.updateFileIcon(file); - myOwner.updateFileColor(file); - } - myOwner.setCurrentWindow(this, false); - } - myPanel.revalidate(); - } - - private boolean splitAvailable() { - return getTabCount() >= 1; - } - - @Nullable - public EditorWindow split(final int orientation, boolean forceSplit, @Nullable VirtualFile virtualFile, boolean focusNew) { - checkConsistency(); - final FileEditorManagerImpl fileEditorManager = myOwner.getManager(); - if (splitAvailable()) { - if (!forceSplit && inSplitter()) { - final EditorWindow[] siblings = findSiblings(); - final EditorWindow target = siblings[0]; - if (virtualFile != null) { - fileEditorManager.openFileImpl3(target, virtualFile, focusNew, null, true); - } - return target; - } - final JPanel panel = myPanel; - final int tabCount = getTabCount(); - if (tabCount != 0) { - final EditorWithProviderComposite firstEC = getEditorAt(0); - myPanel = new JPanel(new BorderLayout()); - final Splitter splitter = new Splitter(orientation == JSplitPane.VERTICAL_SPLIT, 0.5f, 0.1f, 0.9f); - final EditorWindow res = new EditorWindow(myOwner); - if (myTabbedPane != null) { - final EditorWithProviderComposite selectedEditor = getSelectedEditor(); - panel.remove(myTabbedPane.getComponent()); - panel.add(splitter, BorderLayout.CENTER); - splitter.setFirstComponent(myPanel); - myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); - splitter.setSecondComponent(res.myPanel); - /* - for (int i = 0; i != tabCount; ++i) { - final EditorWithProviderComposite eC = getEditorAt(i); - final VirtualFile file = eC.getFile(); - fileEditorManager.openFileImpl3(res, file, false, null); - res.setFilePinned (file, isFilePinned (file)); - } - */ - // open only selected file in the new splitter instead of opening all tabs - final VirtualFile file = selectedEditor.getFile(); - final VirtualFile nextFile = virtualFile == null ? file : virtualFile; - fileEditorManager.openFileImpl3(res, nextFile, false, null, true); - res.setFilePinned (nextFile, isFilePinned (file)); - if (!focusNew) { - res.setSelectedEditor(selectedEditor, true); - selectedEditor.getComponent().requestFocus(); - } - panel.revalidate(); - } - else { - panel.removeAll(); - panel.add(splitter, BorderLayout.CENTER); - splitter.setFirstComponent(myPanel); - splitter.setSecondComponent(res.myPanel); - panel.revalidate(); - final VirtualFile firstFile = firstEC.getFile(); - final VirtualFile nextFile = virtualFile == null ? firstFile : virtualFile; - fileEditorManager.openFileImpl3(this, firstFile, !focusNew, null, true); - fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true); - } - return res; - } - } - return null; - } - - - public EditorWindow[] findSiblings() { - checkConsistency(); - final ArrayList res = new ArrayList(); - if (myPanel.getParent() instanceof Splitter) { - final Splitter splitter = (Splitter)myPanel.getParent(); - for (final EditorWindow win : getWindows()) { - if (win != this && SwingUtilities.isDescendingFrom(win.myPanel, splitter)) { - res.add(win); - } - } - } - return res.toArray(new EditorWindow[res.size()]); - } - - public void changeOrientation() { - checkConsistency(); - final Container parent = myPanel.getParent(); - if (parent instanceof Splitter) { - final Splitter splitter = (Splitter)parent; - splitter.setOrientation(!splitter.getOrientation()); - } - } - - protected void updateFileIcon(VirtualFile file) { - final int index = findEditorIndex(findFileComposite(file)); - LOG.assertTrue(index != -1); - setIconAt(index, getFileIcon(file)); - } - - protected void updateFileName(VirtualFile file) { - final int index = findEditorIndex(findFileComposite(file)); - if (index != -1) { - setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file)); - setToolTipTextAt(index, getManager().getFileTooltipText(file)); - } - } - - /** - * @return icon which represents file's type and modification status - */ - private Icon getFileIcon(@NotNull final VirtualFile file) { - if (!file.isValid()) { - Icon fakeIcon = FileTypes.UNKNOWN.getIcon(); - assert fakeIcon != null : "Can't find the icon for unknown file type"; - return fakeIcon; - } - - final Icon baseIcon = IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, getManager().getProject()); - - int count = 1; - - final Icon pinIcon; - final EditorComposite composite = findFileComposite(file); - if (composite != null && composite.isPinned()) { - count++; - pinIcon = PIN_ICON; - } - else { - pinIcon = null; - } - - final Icon modifiedIcon; - if (UISettings.getInstance().MARK_MODIFIED_TABS_WITH_ASTERISK) { - modifiedIcon = composite != null && composite.isModified() ? MODIFIED_ICON : GAP_ICON; - count++; - } - else { - modifiedIcon = null; - } - - if (count == 1) return baseIcon; - - int i = 0; - final LayeredIcon result = new LayeredIcon(count); - result.setIcon(baseIcon, i++); - if (pinIcon != null) result.setIcon(pinIcon, i++); - if (modifiedIcon != null) result.setIcon(modifiedIcon, i++); - - return result; - } - - public void unsplit(boolean setCurrent) { - checkConsistency(); - final Container splitter = myPanel.getParent(); - - if (!(splitter instanceof Splitter)) return; - - EditorWithProviderComposite editorToSelect = getSelectedEditor(); - final EditorWindow[] siblings = findSiblings(); - final JPanel parent = (JPanel)splitter.getParent(); - - for (EditorWindow eachSibling : siblings) { - // selected editors will be added first - final EditorWithProviderComposite selected = eachSibling.getSelectedEditor(); - if (editorToSelect == null) { - editorToSelect = selected; - } - } - - for (final EditorWindow sibling : siblings) { - final EditorWithProviderComposite[] siblingEditors = sibling.getEditors(); - for (final EditorWithProviderComposite siblingEditor : siblingEditors) { - if (editorToSelect == null) { - editorToSelect = siblingEditor; - } - processSiblingEditor(siblingEditor); - } - LOG.assertTrue(sibling != this); - sibling.dispose(); - } - parent.remove(splitter); - if (myTabbedPane != null) { - parent.add(myTabbedPane.getComponent(), BorderLayout.CENTER); - } - else { - if (myPanel.getComponentCount() > 0) { - parent.add(myPanel.getComponent(0), BorderLayout.CENTER); - } - } - parent.revalidate(); - myPanel = parent; - if (editorToSelect != null) { - setSelectedEditor(editorToSelect, true); - } - if (setCurrent) { - myOwner.setCurrentWindow(this, false); - } - } - - private void processSiblingEditor(final EditorWithProviderComposite siblingEditor) { - if (myTabbedPane != null && getTabCount() < UISettings.getInstance().EDITOR_TAB_LIMIT && findFileComposite(siblingEditor.getFile()) == null) { - setEditor(siblingEditor, true); - } - else if (myTabbedPane == null && getTabCount() == 0) { // tabless mode and no file opened - setEditor(siblingEditor, true); - } - else { - getManager().disposeComposite(siblingEditor); - } - } - - public void unsplitAll() { - checkConsistency(); - while (inSplitter()) { - unsplit(true); - } - } - - public boolean inSplitter() { - checkConsistency(); - return myPanel.getParent() instanceof Splitter; - } - - public VirtualFile getSelectedFile() { - checkConsistency(); - final EditorWithProviderComposite editor = getSelectedEditor(); - return editor == null ? null : editor.getFile(); - } - - public EditorWithProviderComposite findFileComposite(final VirtualFile file) { - for (int i = 0; i != getTabCount(); ++i) { - final EditorWithProviderComposite editor = getEditorAt(i); - if (editor.getFile ().equals (file)) { - return editor; - } - } - return null; - } - - - public int findComponentIndex(final Component component) { - for (int i = 0; i != getTabCount(); ++i) { - final EditorWithProviderComposite editor = getEditorAt(i); - if (editor.getComponent ().equals (component)) { - return i; - } - } - return -1; - } - - public int findEditorIndex(final EditorComposite editorToFind) { - for (int i = 0; i != getTabCount(); ++i) { - final EditorWithProviderComposite editor = getEditorAt(i); - if (editor.equals (editorToFind)) { - return i; - } - } - return -1; - } - - public int findFileIndex(final VirtualFile fileToFind) { - for (int i = 0; i != getTabCount(); ++i) { - final VirtualFile file = getFileAt(i); - if (file.equals (fileToFind)) { - return i; - } - } - return -1; - } - - private EditorWithProviderComposite getEditorAt(final int i) { - final TComp comp; - if (myTabbedPane != null) { - comp = (TComp)myTabbedPane.getComponentAt(i); - } - else { - LOG.assertTrue(i <= 1); - comp = (TComp)myPanel.getComponent(i); - } - return comp.myEditor; - } - - public boolean isFileOpen(final VirtualFile file) { - return findFileComposite(file) != null; - } - - public boolean isFilePinned(final VirtualFile file) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if(!isFileOpen(file)){ - throw new IllegalArgumentException("file is not open: " + file.getPath()); - } - final EditorComposite editorComposite = findFileComposite(file); - return editorComposite.isPinned(); - } - - public void setFilePinned(final VirtualFile file, final boolean pinned) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if(!isFileOpen(file)){ - throw new IllegalArgumentException("file is not open: " + file.getPath()); - } - final EditorComposite editorComposite = findFileComposite(file); - editorComposite.setPinned(pinned); - updateFileIcon(file); - } - - void trimToSize(final int limit, final VirtualFile fileToIgnore, boolean transferFocus) { - if (myTabbedPane == null) { - return; - } - final boolean closeNonModifiedFilesFirst = UISettings.getInstance().CLOSE_NON_MODIFIED_FILES_FIRST; - final EditorComposite selectedComposite = getSelectedEditor(); - try { - doTrimSize(limit, fileToIgnore, closeNonModifiedFilesFirst, transferFocus); - } - finally { - setSelectedEditor(selectedComposite, false); - } - } - - private void doTrimSize(int limit, VirtualFile fileToIgnore, boolean closeNonModifiedFilesFirst, boolean transferFocus) { - while_label: - while (myTabbedPane.getTabCount() > limit && myTabbedPane.getTabCount() > 0) { - // If all tabs are pinned then do nothings. Othrwise we will get infinitive loop - boolean allTabsArePinned = true; - for (int i = myTabbedPane.getTabCount() - 1; i >= 0; i--) { - final VirtualFile file = getFileAt(i); - if (fileCanBeClosed(file, fileToIgnore)) { - allTabsArePinned = false; - break; - } - } - if (allTabsArePinned) { - return; - } - - // Try to close non-modified files first (is specified in option) - if (closeNonModifiedFilesFirst) { - // Search in history - final VirtualFile[] allFiles = getFiles(); - final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); - - // first, we search for files not in history - for (int i = 0; i != allFiles.length; ++ i) { - final VirtualFile file = allFiles[i]; - if (fileCanBeClosed(file, fileToIgnore)) { - boolean found = false; - for (int j = 0; j != histFiles.length; j++) { - if (histFiles[j] == file) { - found = true; - break; - } - } - if (!found) { - defaultCloseFile(file, transferFocus); - continue while_label; - } - } - } - - for (final VirtualFile file : histFiles) { - if (!fileCanBeClosed(file, fileToIgnore)) { - continue; - } - - final EditorComposite composite = findFileComposite(file); - //LOG.assertTrue(composite != null); - if (composite != null && composite.getInitialFileTimeStamp() == file.getTimeStamp()) { - // we found non modified file - defaultCloseFile(file, transferFocus); - continue while_label; - } - } - - // Search in tabbed pane - final VirtualFile selectedFile = getSelectedFile(); - for (int i = 0; i < myTabbedPane.getTabCount(); i++) { - final VirtualFile file = getFileAt(i); - final EditorComposite composite = getEditorAt(i); - if (!fileCanBeClosed(file, fileToIgnore)) { - continue; - } - if (!selectedFile.equals(file)) { - if (composite.getInitialFileTimeStamp() == file.getTimeStamp()) { - // we found non modified file - defaultCloseFile(file, transferFocus); - continue while_label; - } - } - } - } - - // It's non enough to close non-modified files only. Try all other files. - // Search in history from less frequently used. - { - final VirtualFile[] allFiles = getFiles(); - final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); - - // first, we search for files not in history - for (int i = 0; i != allFiles.length; ++ i) { - final VirtualFile file = allFiles[i]; - if (fileCanBeClosed(file, fileToIgnore)) { - boolean found = false; - for (int j = 0; j != histFiles.length; j++) { - if (histFiles[j] == file) { - found = true; - break; - } - } - if (!found) { - defaultCloseFile(file, transferFocus); - continue while_label; - } - } - } - - - for (final VirtualFile file : histFiles) { - if (fileCanBeClosed(file, fileToIgnore)) { - defaultCloseFile(file, transferFocus); - continue while_label; - } - } - } - - // Close first opened file in tabbed pane that isn't a selected one - { - final VirtualFile selectedFile = getSelectedFile(); - for (int i = 0; i < myTabbedPane.getTabCount(); i++) { - final VirtualFile file = getFileAt(i); - if (!fileCanBeClosed(file, fileToIgnore)) { - continue; - } - if (!selectedFile.equals(file)) { - defaultCloseFile(file, transferFocus); - continue while_label; - } - else if (i == myTabbedPane.getTabCount() - 1) { - // if file is selected one and it's last file that we have no choice as close it - defaultCloseFile(file, transferFocus); - continue while_label; - } - } - } - } - } - - private void defaultCloseFile(VirtualFile file, boolean transferFocus) { - closeFile(file, true, transferFocus); - } - - private boolean fileCanBeClosed(final VirtualFile file, final VirtualFile fileToIgnore) { - return isFileOpen (file) && !file.equals(fileToIgnore) && !isFilePinned(file); - } - - protected VirtualFile getFileAt(int i) { - return getEditorAt(i).getFile(); - } -} +/* + * 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.fileEditor.impl; + +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.LayeredIcon; +import com.intellij.util.IconUtil; +import com.intellij.util.ui.EmptyIcon; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Author: msk + */ +public class EditorWindow { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorWindow"); + + public static final DataKey DATA_KEY = DataKey.create("editorWindow"); + + protected JPanel myPanel; + private EditorTabbedContainer myTabbedPane; + private final EditorsSplitters myOwner; + private static final Icon MODIFIED_ICON = IconLoader.getIcon("/general/modified.png"); + private static final Icon GAP_ICON = new EmptyIcon(MODIFIED_ICON.getIconWidth(), MODIFIED_ICON.getIconHeight()); + + private boolean myIsDisposed = false; + private static final Icon PIN_ICON = IconLoader.getIcon("/nodes/tabPin.png"); + public static final Key INITIAL_INDEX_KEY = Key.create("initial editor index"); + + protected EditorWindow(final EditorsSplitters owner) { + myOwner = owner; + myPanel = new JPanel(new BorderLayout()); + myPanel.setOpaque(false); + + myTabbedPane = null; + + final int tabPlacement = UISettings.getInstance().EDITOR_TAB_PLACEMENT; + if (tabPlacement != UISettings.TABS_NONE) { + createTabs(tabPlacement); + } + + // Tab layout policy + if (UISettings.getInstance().SCROLL_TAB_LAYOUT_IN_EDITOR) { + setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + } else { + setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); + } + + getWindows().add(this); + if (myOwner.getCurrentWindow() == null) { + myOwner.setCurrentWindow(this, false); + } + } + + private void createTabs(int tabPlacement) { + LOG.assertTrue (myTabbedPane == null); + myTabbedPane = new EditorTabbedContainer(this, getManager().getProject(), getManager().getDockManager(), tabPlacement); + myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); + } + + private Set getWindows() { + return myOwner.myWindows; + } + + private void dispose() { + try { + disposeTabs(); + getWindows ().remove(this); + } + finally { + myIsDisposed = true; + } + } + + public boolean isDisposed() { + return myIsDisposed; + } + + private void disposeTabs() { + if (myTabbedPane != null) { + Disposer.dispose(myTabbedPane); + myTabbedPane = null; + } + myPanel.removeAll(); + myPanel.revalidate(); + } + + public void closeFile(final VirtualFile file) { + closeFile (file, true); + } + + public void closeFile(final VirtualFile file, final boolean unsplit) { + closeFile(file, unsplit, true); + } + + public void closeFile(final VirtualFile file, final boolean unsplit, final boolean transferFocus) { + final FileEditorManagerImpl editorManager = getManager(); + editorManager.runChange(new Runnable() { + public void run() { + try { + final List editors = myOwner.findEditorComposites(file); + if (editors.isEmpty()) return; + final EditorWithProviderComposite editor = findFileComposite(file); + + final FileEditorManagerListener.Before beforePublisher = + editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER); + + beforePublisher.beforeFileClosed(editorManager, file); + + if (myTabbedPane != null) { + final int componentIndex = findComponentIndex(editor.getComponent()); + if (componentIndex >= 0) { // editor could close itself on decomposition + final int indexToSelect = calcIndexToSelect(file, componentIndex); + myTabbedPane.removeTabAt(componentIndex, indexToSelect, transferFocus).doWhenDone(new Runnable() { + public void run() { + editorManager.disposeComposite(editor); + } + }); + } + } + else { + myPanel.removeAll (); + editorManager.disposeComposite(editor); + } + + if (unsplit && getTabCount() == 0) { + unsplit (true); + } + myPanel.revalidate (); + if (myTabbedPane == null) { + // in tabless mode + myPanel.repaint(); + } + } + finally { + final FileEditorManagerListener afterPublisher = + editorManager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); + + IdeFocusManager.getInstance(editorManager.getProject()).doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + afterPublisher.fileClosed(editorManager, file); + } + }); + + myOwner.afterFileClosed(file); + } + } + }); + } + + private int calcIndexToSelect(VirtualFile fileBeingClosed, final int fileIndex) { + final int currentlySelectedIndex = myTabbedPane.getSelectedIndex(); + if (currentlySelectedIndex != fileIndex) { + // if the file being closed is not currently selected, keep the currently selected file open + return currentlySelectedIndex; + } + if (UISettings.getInstance().ACTIVATE_MRU_EDITOR_ON_CLOSE) { + // try to open last visited file + final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); + for (int idx = histFiles.length - 1; idx >= 0; idx--) { + final VirtualFile histFile = histFiles[idx]; + if (histFile.equals(fileBeingClosed)) { + continue; + } + final EditorWithProviderComposite editor = findFileComposite(histFile); + if (editor == null) { + continue; // ???? + } + final int histFileIndex = findComponentIndex(editor.getComponent()); + if (histFileIndex >= 0) { + // if the file being closed is located before the hist file, then after closing the index of the histFile will be shifted by -1 + return histFileIndex; + } + } + } + // by default select previous neighbour + if (fileIndex > 0) { + return fileIndex - 1; + } + // do nothing + return -1; + } + + public FileEditorManagerImpl getManager() { return myOwner.getManager(); } + + public int getTabCount() { + if (myTabbedPane != null) { + return myTabbedPane.getTabCount(); + } + return myPanel.getComponentCount(); + } + + public void setForegroundAt(final int index, final Color color) { + if (myTabbedPane != null) { + myTabbedPane.setForegroundAt(index, color); + } + } + + public void setWaveColor(final int index, @Nullable final Color color) { + if (myTabbedPane != null) { + myTabbedPane.setWaveColor(index, color); + } + } + + private void setIconAt(final int index, final Icon icon) { + if (myTabbedPane != null) { + myTabbedPane.setIconAt(index, icon); + } + } + + private void setTitleAt(final int index, final String text) { + if (myTabbedPane != null) { + myTabbedPane.setTitleAt(index, text); + } + } + + private void setBackgroundColorAt(final int index, final Color color) { + if (myTabbedPane != null) { + myTabbedPane.setBackgroundColorAt(index, color); + } + } + + private void setToolTipTextAt(final int index, final String text) { + if (myTabbedPane != null) { + myTabbedPane.setToolTipTextAt(index, text); + } + } + + + public void setTabLayoutPolicy(final int policy) { + if (myTabbedPane != null) { + myTabbedPane.setTabLayoutPolicy(policy); + } + } + + public void setTabsPlacement(final int tabPlacement) { + if (tabPlacement != UISettings.TABS_NONE) { + if (myTabbedPane == null) { + final EditorWithProviderComposite editor = getSelectedEditor(); + myPanel.removeAll(); + createTabs(tabPlacement); + setEditor (editor, true); + } + else { + myTabbedPane.setTabPlacement(tabPlacement); + } + } + else if (myTabbedPane != null) { + final boolean focusEditor = ToolWindowManager.getInstance(getManager().getProject()).isEditorComponentActive(); + final VirtualFile currentFile = getSelectedFile(); + final VirtualFile[] files = getFiles(); + for (VirtualFile file : files) { + closeFile(file, false); + } + disposeTabs(); + if (currentFile != null) { + getManager().openFileImpl2(this, currentFile, focusEditor && myOwner.getCurrentWindow() == this); + } + else { + myPanel.repaint(); + } + } + } + + public void setAsCurrentWindow(final boolean requestFocus) { + myOwner.setCurrentWindow(this, requestFocus); + } + + public void updateFileBackgroundColor(final VirtualFile file) { + final int index = findEditorIndex(findFileComposite(file)); + if (index != -1) { + final Color color = EditorTabbedContainer.calcTabColor(getManager().getProject(), file); + setBackgroundColorAt(index, color); + } + } + + public EditorsSplitters getOwner() { + return myOwner; + } + + public boolean isEmptyVisible() { + return myTabbedPane != null ? myTabbedPane.isEmptyVisible() : getFiles().length == 0; + } + + protected static class TComp extends JPanel implements DataProvider{ + final EditorWithProviderComposite myEditor; + + TComp(final EditorWithProviderComposite editor) { + super(new BorderLayout()); + myEditor = editor; + add(editor.getComponent(), BorderLayout.CENTER); + } + + public Object getData(String dataId) { + if (PlatformDataKeys.VIRTUAL_FILE.is(dataId)){ + final VirtualFile virtualFile = myEditor.getFile(); + return virtualFile.isValid() ? virtualFile : null; + } + else if (PlatformDataKeys.PROJECT.is(dataId)) { + return myEditor.getFileEditorManager().getProject(); + } + return null; + } + } + + protected static class TCompForTablessMode extends TComp{ + private final EditorWindow myWindow; + + TCompForTablessMode(final EditorWindow window, final EditorWithProviderComposite editor) { + super(editor); + myWindow = window; + } + + public Object getData(String dataId) { + if (EditorWindow.DATA_KEY.is(dataId)){ + // this is essintial for ability to close opened file + return myWindow; + } + return super.getData(dataId); + } + } + + private void checkConsistency() { + LOG.assertTrue(getWindows().contains(this), "EditorWindow not in collection"); + } + + public EditorWithProviderComposite getSelectedEditor() { + final TComp comp; + if (myTabbedPane != null) { + comp = (TComp)myTabbedPane.getSelectedComponent(); + } + else if (myPanel.getComponentCount() != 0) { + final Component component = myPanel.getComponent(0); + comp = component instanceof TComp ? (TComp)component : null; + } + else { + return null; + } + + if (comp != null) { + return comp.myEditor; + } + return null; + } + + public EditorWithProviderComposite[] getEditors() { + final int tabCount = getTabCount(); + final EditorWithProviderComposite[] res = new EditorWithProviderComposite[tabCount]; + for (int i = 0; i != tabCount; ++i) { + res[i] = getEditorAt(i); + } + return res; + } + + public VirtualFile[] getFiles() { + final int tabCount = getTabCount(); + final VirtualFile[] res = new VirtualFile[tabCount]; + for (int i = 0; i != tabCount; ++i) { + res[i] = getEditorAt(i).getFile(); + } + return res; + } + + public void setSelectedEditor(final EditorComposite editor, final boolean focusEditor) { + if (myTabbedPane == null) { + return; + } + if (editor != null) { + final int index = findFileIndex(editor.getFile()); + if (index != -1) { + myTabbedPane.setSelectedIndex(index, focusEditor); + } + } + } + + public void setEditor(final EditorWithProviderComposite editor, final boolean focusEditor) { + if (editor != null) { + if (myTabbedPane == null) { + myPanel.removeAll (); + myPanel.add (new TCompForTablessMode(this, editor), BorderLayout.CENTER); + myPanel.revalidate (); + return; + } + + final int index = findEditorIndex(editor); + if (index != -1) { + setSelectedEditor(editor, focusEditor); + } + else { + Integer initialIndex = editor.getFile().getUserData(INITIAL_INDEX_KEY); + final int indexToInsert = initialIndex == null ? myTabbedPane.getSelectedIndex() + 1 : initialIndex; + final VirtualFile file = editor.getFile(); + final Icon template = IconLoader.getIcon("/fileTypes/text.png"); + myTabbedPane.insertTab(file, new EmptyIcon(template.getIconWidth(), template.getIconHeight()), new TComp(editor), null, indexToInsert); + trimToSize(UISettings.getInstance().EDITOR_TAB_LIMIT, file, false); + setSelectedEditor(editor, focusEditor); + myOwner.updateFileIcon(file); + myOwner.updateFileColor(file); + } + myOwner.setCurrentWindow(this, false); + } + myPanel.revalidate(); + } + + private boolean splitAvailable() { + return getTabCount() >= 1; + } + + @Nullable + public EditorWindow split(final int orientation, boolean forceSplit, @Nullable VirtualFile virtualFile, boolean focusNew) { + checkConsistency(); + final FileEditorManagerImpl fileEditorManager = myOwner.getManager(); + if (splitAvailable()) { + if (!forceSplit && inSplitter()) { + final EditorWindow[] siblings = findSiblings(); + final EditorWindow target = siblings[0]; + if (virtualFile != null) { + fileEditorManager.openFileImpl3(target, virtualFile, focusNew, null, true); + } + return target; + } + final JPanel panel = myPanel; + final int tabCount = getTabCount(); + if (tabCount != 0) { + final EditorWithProviderComposite firstEC = getEditorAt(0); + myPanel = new JPanel(new BorderLayout()); + final Splitter splitter = new Splitter(orientation == JSplitPane.VERTICAL_SPLIT, 0.5f, 0.1f, 0.9f); + final EditorWindow res = new EditorWindow(myOwner); + if (myTabbedPane != null) { + final EditorWithProviderComposite selectedEditor = getSelectedEditor(); + panel.remove(myTabbedPane.getComponent()); + panel.add(splitter, BorderLayout.CENTER); + splitter.setFirstComponent(myPanel); + myPanel.add(myTabbedPane.getComponent(), BorderLayout.CENTER); + splitter.setSecondComponent(res.myPanel); + /* + for (int i = 0; i != tabCount; ++i) { + final EditorWithProviderComposite eC = getEditorAt(i); + final VirtualFile file = eC.getFile(); + fileEditorManager.openFileImpl3(res, file, false, null); + res.setFilePinned (file, isFilePinned (file)); + } + */ + // open only selected file in the new splitter instead of opening all tabs + final VirtualFile file = selectedEditor.getFile(); + final VirtualFile nextFile = virtualFile == null ? file : virtualFile; + fileEditorManager.openFileImpl3(res, nextFile, false, null, true); + res.setFilePinned (nextFile, isFilePinned (file)); + if (!focusNew) { + res.setSelectedEditor(selectedEditor, true); + selectedEditor.getComponent().requestFocus(); + } + panel.revalidate(); + } + else { + panel.removeAll(); + panel.add(splitter, BorderLayout.CENTER); + splitter.setFirstComponent(myPanel); + splitter.setSecondComponent(res.myPanel); + panel.revalidate(); + final VirtualFile firstFile = firstEC.getFile(); + final VirtualFile nextFile = virtualFile == null ? firstFile : virtualFile; + fileEditorManager.openFileImpl3(this, firstFile, !focusNew, null, true); + fileEditorManager.openFileImpl3(res, nextFile, focusNew, null, true); + } + return res; + } + } + return null; + } + + + public EditorWindow[] findSiblings() { + checkConsistency(); + final ArrayList res = new ArrayList(); + if (myPanel.getParent() instanceof Splitter) { + final Splitter splitter = (Splitter)myPanel.getParent(); + for (final EditorWindow win : getWindows()) { + if (win != this && SwingUtilities.isDescendingFrom(win.myPanel, splitter)) { + res.add(win); + } + } + } + return res.toArray(new EditorWindow[res.size()]); + } + + public void changeOrientation() { + checkConsistency(); + final Container parent = myPanel.getParent(); + if (parent instanceof Splitter) { + final Splitter splitter = (Splitter)parent; + splitter.setOrientation(!splitter.getOrientation()); + } + } + + protected void updateFileIcon(VirtualFile file) { + final int index = findEditorIndex(findFileComposite(file)); + LOG.assertTrue(index != -1); + setIconAt(index, getFileIcon(file)); + } + + protected void updateFileName(VirtualFile file) { + final int index = findEditorIndex(findFileComposite(file)); + if (index != -1) { + setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file)); + setToolTipTextAt(index, getManager().getFileTooltipText(file)); + } + } + + /** + * @return icon which represents file's type and modification status + */ + private Icon getFileIcon(@NotNull final VirtualFile file) { + if (!file.isValid()) { + Icon fakeIcon = FileTypes.UNKNOWN.getIcon(); + assert fakeIcon != null : "Can't find the icon for unknown file type"; + return fakeIcon; + } + + final Icon baseIcon = IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, getManager().getProject()); + + int count = 1; + + final Icon pinIcon; + final EditorComposite composite = findFileComposite(file); + if (composite != null && composite.isPinned()) { + count++; + pinIcon = PIN_ICON; + } + else { + pinIcon = null; + } + + final Icon modifiedIcon; + if (UISettings.getInstance().MARK_MODIFIED_TABS_WITH_ASTERISK) { + modifiedIcon = composite != null && composite.isModified() ? MODIFIED_ICON : GAP_ICON; + count++; + } + else { + modifiedIcon = null; + } + + if (count == 1) return baseIcon; + + int i = 0; + final LayeredIcon result = new LayeredIcon(count); + result.setIcon(baseIcon, i++); + if (pinIcon != null) result.setIcon(pinIcon, i++); + if (modifiedIcon != null) result.setIcon(modifiedIcon, i++); + + return result; + } + + public void unsplit(boolean setCurrent) { + checkConsistency(); + final Container splitter = myPanel.getParent(); + + if (!(splitter instanceof Splitter)) return; + + EditorWithProviderComposite editorToSelect = getSelectedEditor(); + final EditorWindow[] siblings = findSiblings(); + final JPanel parent = (JPanel)splitter.getParent(); + + for (EditorWindow eachSibling : siblings) { + // selected editors will be added first + final EditorWithProviderComposite selected = eachSibling.getSelectedEditor(); + if (editorToSelect == null) { + editorToSelect = selected; + } + } + + for (final EditorWindow sibling : siblings) { + final EditorWithProviderComposite[] siblingEditors = sibling.getEditors(); + for (final EditorWithProviderComposite siblingEditor : siblingEditors) { + if (editorToSelect == null) { + editorToSelect = siblingEditor; + } + processSiblingEditor(siblingEditor); + } + LOG.assertTrue(sibling != this); + sibling.dispose(); + } + parent.remove(splitter); + if (myTabbedPane != null) { + parent.add(myTabbedPane.getComponent(), BorderLayout.CENTER); + } + else { + if (myPanel.getComponentCount() > 0) { + parent.add(myPanel.getComponent(0), BorderLayout.CENTER); + } + } + parent.revalidate(); + myPanel = parent; + if (editorToSelect != null) { + setSelectedEditor(editorToSelect, true); + } + if (setCurrent) { + myOwner.setCurrentWindow(this, false); + } + } + + private void processSiblingEditor(final EditorWithProviderComposite siblingEditor) { + if (myTabbedPane != null && getTabCount() < UISettings.getInstance().EDITOR_TAB_LIMIT && findFileComposite(siblingEditor.getFile()) == null) { + setEditor(siblingEditor, true); + } + else if (myTabbedPane == null && getTabCount() == 0) { // tabless mode and no file opened + setEditor(siblingEditor, true); + } + else { + getManager().disposeComposite(siblingEditor); + } + } + + public void unsplitAll() { + checkConsistency(); + while (inSplitter()) { + unsplit(true); + } + } + + public boolean inSplitter() { + checkConsistency(); + return myPanel.getParent() instanceof Splitter; + } + + public VirtualFile getSelectedFile() { + checkConsistency(); + final EditorWithProviderComposite editor = getSelectedEditor(); + return editor == null ? null : editor.getFile(); + } + + public EditorWithProviderComposite findFileComposite(final VirtualFile file) { + for (int i = 0; i != getTabCount(); ++i) { + final EditorWithProviderComposite editor = getEditorAt(i); + if (editor.getFile ().equals (file)) { + return editor; + } + } + return null; + } + + + public int findComponentIndex(final Component component) { + for (int i = 0; i != getTabCount(); ++i) { + final EditorWithProviderComposite editor = getEditorAt(i); + if (editor.getComponent ().equals (component)) { + return i; + } + } + return -1; + } + + public int findEditorIndex(final EditorComposite editorToFind) { + for (int i = 0; i != getTabCount(); ++i) { + final EditorWithProviderComposite editor = getEditorAt(i); + if (editor.equals (editorToFind)) { + return i; + } + } + return -1; + } + + public int findFileIndex(final VirtualFile fileToFind) { + for (int i = 0; i != getTabCount(); ++i) { + final VirtualFile file = getFileAt(i); + if (file.equals (fileToFind)) { + return i; + } + } + return -1; + } + + private EditorWithProviderComposite getEditorAt(final int i) { + final TComp comp; + if (myTabbedPane != null) { + comp = (TComp)myTabbedPane.getComponentAt(i); + } + else { + LOG.assertTrue(i <= 1); + comp = (TComp)myPanel.getComponent(i); + } + return comp.myEditor; + } + + public boolean isFileOpen(final VirtualFile file) { + return findFileComposite(file) != null; + } + + public boolean isFilePinned(final VirtualFile file) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if(!isFileOpen(file)){ + throw new IllegalArgumentException("file is not open: " + file.getPath()); + } + final EditorComposite editorComposite = findFileComposite(file); + return editorComposite.isPinned(); + } + + public void setFilePinned(final VirtualFile file, final boolean pinned) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if(!isFileOpen(file)){ + throw new IllegalArgumentException("file is not open: " + file.getPath()); + } + final EditorComposite editorComposite = findFileComposite(file); + editorComposite.setPinned(pinned); + updateFileIcon(file); + } + + void trimToSize(final int limit, final VirtualFile fileToIgnore, boolean transferFocus) { + if (myTabbedPane == null) { + return; + } + final boolean closeNonModifiedFilesFirst = UISettings.getInstance().CLOSE_NON_MODIFIED_FILES_FIRST; + final EditorComposite selectedComposite = getSelectedEditor(); + try { + doTrimSize(limit, fileToIgnore, closeNonModifiedFilesFirst, transferFocus); + } + finally { + setSelectedEditor(selectedComposite, false); + } + } + + private void doTrimSize(int limit, VirtualFile fileToIgnore, boolean closeNonModifiedFilesFirst, boolean transferFocus) { + while_label: + while (myTabbedPane.getTabCount() > limit && myTabbedPane.getTabCount() > 0) { + // If all tabs are pinned then do nothings. Othrwise we will get infinitive loop + boolean allTabsArePinned = true; + for (int i = myTabbedPane.getTabCount() - 1; i >= 0; i--) { + final VirtualFile file = getFileAt(i); + if (fileCanBeClosed(file, fileToIgnore)) { + allTabsArePinned = false; + break; + } + } + if (allTabsArePinned) { + return; + } + + // Try to close non-modified files first (is specified in option) + if (closeNonModifiedFilesFirst) { + // Search in history + final VirtualFile[] allFiles = getFiles(); + final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); + + // first, we search for files not in history + for (int i = 0; i != allFiles.length; ++ i) { + final VirtualFile file = allFiles[i]; + if (fileCanBeClosed(file, fileToIgnore)) { + boolean found = false; + for (int j = 0; j != histFiles.length; j++) { + if (histFiles[j] == file) { + found = true; + break; + } + } + if (!found) { + defaultCloseFile(file, transferFocus); + continue while_label; + } + } + } + + for (final VirtualFile file : histFiles) { + if (!fileCanBeClosed(file, fileToIgnore)) { + continue; + } + + final EditorComposite composite = findFileComposite(file); + //LOG.assertTrue(composite != null); + if (composite != null && composite.getInitialFileTimeStamp() == file.getTimeStamp()) { + // we found non modified file + defaultCloseFile(file, transferFocus); + continue while_label; + } + } + + // Search in tabbed pane + final VirtualFile selectedFile = getSelectedFile(); + for (int i = 0; i < myTabbedPane.getTabCount(); i++) { + final VirtualFile file = getFileAt(i); + final EditorComposite composite = getEditorAt(i); + if (!fileCanBeClosed(file, fileToIgnore)) { + continue; + } + if (!selectedFile.equals(file)) { + if (composite.getInitialFileTimeStamp() == file.getTimeStamp()) { + // we found non modified file + defaultCloseFile(file, transferFocus); + continue while_label; + } + } + } + } + + // It's non enough to close non-modified files only. Try all other files. + // Search in history from less frequently used. + { + final VirtualFile[] allFiles = getFiles(); + final VirtualFile[] histFiles = EditorHistoryManager.getInstance(getManager ().getProject()).getFiles(); + + // first, we search for files not in history + for (int i = 0; i != allFiles.length; ++ i) { + final VirtualFile file = allFiles[i]; + if (fileCanBeClosed(file, fileToIgnore)) { + boolean found = false; + for (int j = 0; j != histFiles.length; j++) { + if (histFiles[j] == file) { + found = true; + break; + } + } + if (!found) { + defaultCloseFile(file, transferFocus); + continue while_label; + } + } + } + + + for (final VirtualFile file : histFiles) { + if (fileCanBeClosed(file, fileToIgnore)) { + defaultCloseFile(file, transferFocus); + continue while_label; + } + } + } + + // Close first opened file in tabbed pane that isn't a selected one + { + final VirtualFile selectedFile = getSelectedFile(); + for (int i = 0; i < myTabbedPane.getTabCount(); i++) { + final VirtualFile file = getFileAt(i); + if (!fileCanBeClosed(file, fileToIgnore)) { + continue; + } + if (!selectedFile.equals(file)) { + defaultCloseFile(file, transferFocus); + continue while_label; + } + else if (i == myTabbedPane.getTabCount() - 1) { + // if file is selected one and it's last file that we have no choice as close it + defaultCloseFile(file, transferFocus); + continue while_label; + } + } + } + } + } + + private void defaultCloseFile(VirtualFile file, boolean transferFocus) { + closeFile(file, true, transferFocus); + } + + private boolean fileCanBeClosed(final VirtualFile file, final VirtualFile fileToIgnore) { + return isFileOpen (file) && !file.equals(fileToIgnore) && !isFilePinned(file); + } + + protected VirtualFile getFileAt(int i) { + return getEditorAt(i).getFile(); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java index 7a199a0e41a1..0d03a6a7ad0c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java @@ -1,737 +1,737 @@ -/* - * 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.fileEditor.impl; - -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorProvider; -import com.intellij.openapi.fileEditor.FileEditorState; -import com.intellij.openapi.fileEditor.FileEditorStateLevel; -import com.intellij.openapi.fileEditor.impl.text.FileDropHandler; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.FocusWatcher; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.openapi.wm.impl.FrameTitleBuilder; -import com.intellij.openapi.wm.impl.IdeFrameImpl; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.docking.DockManager; -import com.intellij.ui.tabs.JBTabs; -import com.intellij.ui.tabs.impl.JBTabsImpl; -import com.intellij.util.Alarm; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.ArrayListSet; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.UIUtil; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.io.File; -import java.util.*; -import java.util.List; - - -/** - * Author: msk - */ -public class EditorsSplitters extends JPanel { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorsSplitters"); - private EditorWindow myCurrentWindow; - private VirtualFile myCurrentFile; - private final FileEditorManagerImpl myManager; - private Element mySplittersElement; // temporarily used during initialization - private int myInsideChange = 0; - private final MyFocusWatcher myFocusWatcher; - private EditorWithProviderComposite myCurrentSelectedEditor; - private final Alarm myIconUpdaterAlarm = new Alarm(); - private DockManager myDockManager; - - public EditorsSplitters(final FileEditorManagerImpl manager, DockManager dockManager, boolean createOwnDockableContainer) { - super(new BorderLayout()); - setOpaque(true); - setBackground(Color.GRAY); - myManager = manager; - myDockManager = dockManager; - myFocusWatcher = new MyFocusWatcher(); - setFocusTraversalPolicy(new MyFocusTraversalPolicy()); - setTransferHandler(new MyTransferHandler()); - clear(); - - if (createOwnDockableContainer) { - DockableEditorTabbedContainer dockable = new DockableEditorTabbedContainer(myManager.getProject(), dockManager, this, false); - Disposer.register(manager.getProject(), dockable); - myDockManager.register(dockable); - } - } - - public FileEditorManagerImpl getManager() { - return myManager; - } - - public void clear() { - removeAll(); - myWindows.clear(); - setCurrentWindow(null); - myCurrentFile = null; - repaint (); // revalidate doesn't repaint correctly after "Close All" - } - - public void startListeningFocus() { - myFocusWatcher.install(this); - } - - private void stopListeningFocus() { - myFocusWatcher.deinstall(this); - } - - public void dispose() { - myIconUpdaterAlarm.cancelAllRequests(); - stopListeningFocus(); - } - - @Nullable - public VirtualFile getCurrentFile() { - if (myCurrentWindow != null) { - return myCurrentWindow.getSelectedFile(); - } - return null; - } - - - public void writeExternal(final Element element) { - if (getComponentCount() != 0) { - final Component comp = getComponent(0); - LOG.assertTrue(comp instanceof JPanel); - final JPanel panel = (JPanel)comp; - if (panel.getComponentCount() != 0) { - final Element res = writePanel(panel); - element.addContent(res); - } - } - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private Element writePanel(final JPanel panel) { - final Component comp = panel.getComponent(0); - if (comp instanceof Splitter) { - final Splitter splitter = (Splitter)comp; - final Element res = new Element("splitter"); - res.setAttribute("split-orientation", splitter.getOrientation() ? "vertical" : "horizontal"); - res.setAttribute("split-proportion", Float.toString(splitter.getProportion())); - final Element first = new Element("split-first"); - first.addContent(writePanel((JPanel)splitter.getFirstComponent())); - final Element second = new Element("split-second"); - second.addContent(writePanel((JPanel)splitter.getSecondComponent())); - res.addContent(first); - res.addContent(second); - return res; - } - else if (comp instanceof JBTabs) { - final Element res = new Element("leaf"); - final EditorWindow window = findWindowWith(comp); - writeWindow(res, window); - return res; - } - else if (comp instanceof EditorWindow.TCompForTablessMode) { - final EditorWithProviderComposite composite = ((EditorWindow.TCompForTablessMode)comp).myEditor; - final Element res = new Element("leaf"); - writeComposite(res, composite.getFile(), composite, false, composite); - return res; - } - else { - LOG.error(comp != null ? comp.getClass().getName() : null); - return null; - } - } - - private void writeWindow(final Element res, final EditorWindow window) { - if (window != null) { - final EditorWithProviderComposite[] composites = window.getEditors(); - for (int i = 0; i < composites.length; i++) { - final VirtualFile file = window.getFileAt(i); - final boolean isPinned = window.isFilePinned(file); - final EditorWithProviderComposite composite = composites[i]; - final EditorWithProviderComposite selectedEditor = window.getSelectedEditor(); - - writeComposite(res, file, composite, isPinned, selectedEditor); - } - } - } - - private void writeComposite(final Element res, final VirtualFile file, final EditorWithProviderComposite composite, - final boolean pinned, - final EditorWithProviderComposite selectedEditor) { - final FileEditor[] editors = composite.getEditors(); - final Element fileElement = new Element("file"); - fileElement.setAttribute("leaf-file-name", file.getName()); // TODO: all files - final FileEditorState[] states = new FileEditorState[editors.length]; - for (int j = 0; j < states.length; j++) { - states[j] = editors[j].getState(FileEditorStateLevel.FULL); - LOG.assertTrue(states[j] != null); - } - final int selectedProviderIndex = ArrayUtil.find(editors, composite.getSelectedEditor()); - LOG.assertTrue(selectedProviderIndex != -1); - final FileEditorProvider[] providers = composite.getProviders(); - final HistoryEntry entry = new HistoryEntry(file, providers, states, providers[selectedProviderIndex]); // TODO - entry.writeExternal(fileElement, getManager().getProject()); - fileElement.setAttribute("pinned", Boolean.toString(pinned)); - fileElement.setAttribute("current", Boolean.toString(composite.equals (getManager ().getLastSelected ()))); - fileElement.setAttribute("current-in-tab", Boolean.toString(composite.equals (selectedEditor))); - res.addContent(fileElement); - } - - public void openFiles() { - if (mySplittersElement != null) { - final JPanel comp = readExternalPanel(mySplittersElement, getTopPanel()); - if (comp != null) { - removeAll(); - add(comp, BorderLayout.CENTER); - final EditorComposite lastSelected = getManager().getLastSelected(); - if(lastSelected != null) { - getManager().openFileImpl3(myCurrentWindow, lastSelected.getFile(), true, null, true); - //lastSelected.getComponent().requestFocus(); - //ToolWindowManager.getInstance(getManager().myProject).activateEditorComponent(); - } - mySplittersElement = null; - } - // clear empty splitters - for (EditorWindow window : getWindows()) { - if (window.getEditors().length == 0) { - for (EditorWindow sibling : window.findSiblings()) { - sibling.unsplit(false); - } - } - } - } - } - - public void readExternal(final Element element) { - mySplittersElement = element; - } - - @Nullable - @SuppressWarnings({"HardCodedStringLiteral"}) - public JPanel readExternalPanel(final Element element, @Nullable JPanel panel) { - final Element splitterElement = element.getChild("splitter"); - if (splitterElement != null) { - LOG.info("splitter"); - final boolean orientation = "vertical".equals(splitterElement.getAttributeValue("split-orientation")); - final float proportion = Float.valueOf(splitterElement.getAttributeValue("split-proportion")).floatValue(); - final Element first = splitterElement.getChild("split-first"); - final Element second = splitterElement.getChild("split-second"); - - Splitter splitter; - if (panel == null) { - panel = new JPanel(new BorderLayout()); - splitter = new Splitter(orientation, proportion, 0.1f, 0.9f); - panel.add(splitter, BorderLayout.CENTER); - splitter.setFirstComponent(readExternalPanel(first, null)); - splitter.setSecondComponent(readExternalPanel(second, null)); - } else if (panel.getComponent(0) instanceof Splitter) { - splitter = (Splitter)panel.getComponent(0); - readExternalPanel(first, (JPanel)splitter.getFirstComponent()); - readExternalPanel(second, (JPanel)splitter.getSecondComponent()); - } else { - readExternalPanel(first, panel); - readExternalPanel(second, panel); - } - return panel; - } - final Element leaf = element.getChild("leaf"); - if (leaf != null) { - EditorWindow window; - if (panel == null) { - window = new EditorWindow(this); - } else { - window = findWindowWith(panel); - } - //noinspection unchecked - final List children = leaf.getChildren("file"); - VirtualFile currentFile = null; - for (final Element file : children) { - try { - final HistoryEntry entry = new HistoryEntry(getManager().getProject(), file.getChild(HistoryEntry.TAG)); - boolean isCurrent = Boolean.valueOf(file.getAttributeValue("current")).booleanValue(); - getManager().openFileImpl3(window, entry.myFile, false, entry, isCurrent); - if (getManager().isFileOpen(entry.myFile)) { - window.setFilePinned(entry.myFile, Boolean.valueOf(file.getAttributeValue("pinned")).booleanValue()); - if (Boolean.valueOf(file.getAttributeValue("current-in-tab")).booleanValue()) { - currentFile = entry.myFile; - } - if (isCurrent) { - setCurrentWindow(window, false); - } - } - } - catch (InvalidDataException e) { - // OK - } - } - if (currentFile != null) { - final EditorComposite editor = window.findFileComposite(currentFile); - if (editor != null) { - window.setSelectedEditor(editor, true); - } - } - return window.myPanel; - } - return null; - } - - @NotNull public VirtualFile[] getOpenFiles() { - final ArrayListSet files = new ArrayListSet(); - for (final EditorWindow myWindow : myWindows) { - final EditorWithProviderComposite[] editors = myWindow.getEditors(); - for (final EditorWithProviderComposite editor : editors) { - files.add(editor.getFile()); - } - } - return VfsUtil.toVirtualFileArray(files); - } - - @NotNull public VirtualFile[] getSelectedFiles() { - final ArrayListSet files = new ArrayListSet(); - for (final EditorWindow window : myWindows) { - final VirtualFile file = window.getSelectedFile(); - if (file != null) { - files.add(file); - } - } - final VirtualFile[] virtualFiles = VfsUtil.toVirtualFileArray(files); - final VirtualFile currentFile = getCurrentFile(); - if (currentFile != null) { - for (int i = 0; i != virtualFiles.length; ++i) { - if (virtualFiles[i] == currentFile) { - virtualFiles[i] = virtualFiles[0]; - virtualFiles[0] = currentFile; - break; - } - } - } - return virtualFiles; - } - - @NotNull public FileEditor[] getSelectedEditors() { - final List editors = new ArrayList(); - final EditorWindow currentWindow = getCurrentWindow(); - if (currentWindow != null) { - final EditorWithProviderComposite composite = currentWindow.getSelectedEditor(); - if (composite != null) { - editors.add (composite.getSelectedEditor()); - } - } - - for (final EditorWindow window : myWindows) { - if (!window.equals(currentWindow)) { - final EditorWithProviderComposite composite = window.getSelectedEditor(); - if (composite != null) { - editors.add(composite.getSelectedEditor()); - } - } - } - return editors.toArray(new FileEditor[editors.size()]); - } - - public void updateFileIcon(@NotNull final VirtualFile file) { - updateFileIconLater(file); - } - - private void updateFileIconImmediately(final VirtualFile file) { - final Collection windows = findWindows(file); - for (EditorWindow window : windows) { - window.updateFileIcon(file); - } - } - - private final Set myFilesToUpdateIconsFor = new HashSet(); - - private void updateFileIconLater(VirtualFile file) { - myFilesToUpdateIconsFor.add(file); - myIconUpdaterAlarm.cancelAllRequests(); - myIconUpdaterAlarm.addRequest(new Runnable() { - public void run() { - if (myManager.getProject().isDisposed()) return; - for (VirtualFile file : myFilesToUpdateIconsFor) { - updateFileIconImmediately(file); - } - myFilesToUpdateIconsFor.clear(); - } - }, 200, ModalityState.stateForComponent(this)); - } - - public void updateFileColor(@NotNull final VirtualFile file) { - final Collection windows = findWindows(file); - for (final EditorWindow window : windows) { - final int index = window.findEditorIndex(window.findFileComposite(file)); - LOG.assertTrue(index != -1); - window.setForegroundAt(index, getManager().getFileColor(file)); - window.setWaveColor(index, getManager().isProblem(file) ? Color.red : null); - } - } - - public void trimToSize(final int editor_tab_limit) { - for (final EditorWindow window : myWindows) { - window.trimToSize(editor_tab_limit, null, true); - } - } - - public void setTabsPlacement(final int tabPlacement) { - final EditorWindow[] windows = getWindows(); - for (int i = 0; i != windows.length; ++ i) { - windows[i].setTabsPlacement(tabPlacement); - } - } - - public void setTabLayoutPolicy(int scrollTabLayout) { - final EditorWindow[] windows = getWindows(); - for (int i = 0; i != windows.length; ++ i) { - windows[i].setTabLayoutPolicy(scrollTabLayout); - } - } - - public void updateFileName(final VirtualFile updatedFile) { - final EditorWindow[] windows = getWindows(); - for (int i = 0; i != windows.length; ++ i) { - windows [i].updateFileName(updatedFile); - } - - Project project = myManager.getProject(); - - final IdeFrame frame = getFrame(project); - - VirtualFile file = getCurrentFile(); - - File ioFile = file == null ? null : new File(file.getPresentableUrl()); - String fileTitle = file == null ? null : FrameTitleBuilder.getInstance().getFileTitle(project, file); - frame.setFileTitle(fileTitle, ioFile); - } - - protected IdeFrame getFrame(Project project) { - final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx(); - final IdeFrame frame = windowManagerEx.getFrame(project); - LOG.assertTrue(frame != null); - return frame; - } - - public boolean isInsideChange() { - return myInsideChange > 0; - } - - private void setCurrentWindow(final EditorWindow currentWindow) { - myCurrentWindow = currentWindow; - } - - public void updateFileBackgroundColor(final VirtualFile file) { - final EditorWindow[] windows = getWindows(); - for (int i = 0; i != windows.length; ++ i) { - windows [i].updateFileBackgroundColor(file); - } - } - - public int getSplitCount() { - if (getComponentCount() > 0) { - JPanel panel = (JPanel) getComponent(0); - return getSplitCount(panel); - } - return 0; - } - - private static int getSplitCount(JComponent component) { - if (component.getComponentCount() > 0) { - final JComponent firstChild = (JComponent)component.getComponent(0); - if (firstChild instanceof Splitter) { - final Splitter splitter = (Splitter)firstChild; - return getSplitCount(splitter.getFirstComponent()) + getSplitCount(splitter.getSecondComponent()); - } - return 1; - } - return 0; - } - - protected void afterFileClosed(VirtualFile file) { - } - - protected void afterFileOpen(VirtualFile file) { - } - - public JBTabs getTabsAt(RelativePoint point) { - Point thisPoint = point.getPoint(this); - Component c = SwingUtilities.getDeepestComponentAt(this, thisPoint.x, thisPoint.y); - while (c != null) { - if (c instanceof JBTabs) { - return (JBTabs)c; - } - c = c.getParent(); - } - - return null; - } - - public boolean isEmptyVisible() { - EditorWindow[] windows = getWindows(); - for (EditorWindow each : windows) { - if (!each.isEmptyVisible()) { - return false; - } - } - return true; - } - - private final class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy { - public final Component getDefaultComponentImpl(final Container focusCycleRoot) { - if (myCurrentWindow != null) { - final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor(); - if (selectedEditor != null) { - return IdeFocusTraversalPolicy.getPreferredFocusedComponent(selectedEditor.getComponent(), this); - } - } - return IdeFocusTraversalPolicy.getPreferredFocusedComponent(EditorsSplitters.this, this); - } - } - - @Nullable - public JPanel getTopPanel() { - return getComponentCount() > 0 ? (JPanel)getComponent(0) : null; - } - - public EditorWindow getCurrentWindow() { - return myCurrentWindow; - } - - public EditorWindow getOrCreateCurrentWindow(final VirtualFile file) { - final List windows = findWindows(file); - if (getCurrentWindow() == null) { - final Iterator iterator = myWindows.iterator(); - if (!windows.isEmpty()) { - setCurrentWindow(windows.get(0), false); - } - else if (iterator.hasNext()) { - setCurrentWindow(iterator.next(), false); - } - else { - createCurrentWindow(); - } - } - else if (!windows.isEmpty()) { - if (!windows.contains(getCurrentWindow())) { - setCurrentWindow(windows.get(0), false); - } - } - return getCurrentWindow(); - } - - public void createCurrentWindow() { - LOG.assertTrue(myCurrentWindow == null); - setCurrentWindow(new EditorWindow(this)); - add(myCurrentWindow.myPanel, BorderLayout.CENTER); - } - - /** - * sets the window passed as a current ('focused') window among all splitters. All file openings will be done inside this - * current window - * @param window a window to be set as current - * @param requestFocus whether to request focus to the editor currently selected in this window - */ - public void setCurrentWindow(final EditorWindow window, final boolean requestFocus) { - final EditorWithProviderComposite oldEditor = myCurrentSelectedEditor; - final EditorWithProviderComposite newEditor = window != null? window.getSelectedEditor() : null; - - Runnable fireRunnable = new Runnable() { - public void run() { - getManager().fireSelectionChanged(oldEditor, newEditor); - } - }; - - setCurrentWindow(window); - myCurrentSelectedEditor = newEditor; - - getManager().updateFileName(window == null ? null : window.getSelectedFile()); - - if (window != null) { - final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor(); - if (selectedEditor != null) { - boolean alreadyFocused = false; - final JComponent comp = selectedEditor.getComponent(); - final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (owner != null && comp != null) { - alreadyFocused = owner == comp || SwingUtilities.isDescendingFrom(owner, comp); - } - - if (requestFocus && !alreadyFocused) { - IdeFocusManager.getInstance(myManager.getProject()).requestFocus(comp, requestFocus).doWhenDone(fireRunnable); - } else { - fireRunnable.run(); - } - } - } else { - fireRunnable.run(); - } - } - - //--------------------------------------------------------- - - public EditorWithProviderComposite[] getEditorsComposites() { - final ArrayList res = new ArrayList(); - - for (final EditorWindow myWindow : myWindows) { - final EditorWithProviderComposite[] editors = myWindow.getEditors(); - ContainerUtil.addAll(res, editors); - } - return res.toArray(new EditorWithProviderComposite[res.size()]); - } - - //--------------------------------------------------------- - - final Set myWindows = new ArrayListSet(); - - @NotNull - public List findEditorComposites(final VirtualFile file) { - final ArrayList res = new ArrayList(); - for (final EditorWindow window : myWindows) { - final EditorWithProviderComposite fileComposite = window.findFileComposite(file); - if (fileComposite != null) { - res.add(fileComposite); - } - } - return res; - } - - @NotNull - public List findWindows(final VirtualFile file) { - final ArrayList res = new ArrayList(); - for (final EditorWindow window : myWindows) { - if (window.findFileComposite(file) != null) { - res.add(window); - } - } - return res; - } - - @NotNull public EditorWindow [] getWindows() { - return myWindows.toArray(new EditorWindow [myWindows.size()]); - } - - @NotNull public EditorWindow[] getOrderedWindows() { - final ArrayList res = new ArrayList(); - - // Collector for windows in tree ordering: - class Inner{ - final void collect(final JPanel panel){ - final Component comp = panel.getComponent(0); - if (comp instanceof Splitter) { - final Splitter splitter = (Splitter)comp; - collect((JPanel)splitter.getFirstComponent()); - collect((JPanel)splitter.getSecondComponent()); - } - else if (comp instanceof JPanel || comp instanceof JBTabs) { - final EditorWindow window = findWindowWith(comp); - if (window != null) { - res.add(window); - } - } - } - } - - // get root component and traverse splitters tree: - if (getComponentCount() != 0) { - final Component comp = getComponent(0); - LOG.assertTrue(comp instanceof JPanel); - final JPanel panel = (JPanel)comp; - if (panel.getComponentCount() != 0) { - new Inner().collect (panel); - } - } - - LOG.assertTrue(res.size() == myWindows.size()); - return res.toArray(new EditorWindow [res.size()]); - } - - private EditorWindow findWindowWith(final Component component) { - if (component != null) { - for (final EditorWindow window : myWindows) { - if (SwingUtilities.isDescendingFrom(component, window.myPanel)) { - return window; - } - } - } - return null; - } - - private final class MyFocusWatcher extends FocusWatcher { - protected void focusedComponentChanged(final Component component, final AWTEvent cause) { - EditorWindow newWindow = null; - VirtualFile newFile = null; - - if (component != null) { - newWindow = findWindowWith(component); - if (newWindow != null) { - newFile = newWindow.getSelectedFile(); - } - } - - boolean changed = !Comparing.equal(newWindow, myCurrentWindow) || !Comparing.equal(newFile, myCurrentFile); - - myCurrentFile = newFile; - setCurrentWindow(newWindow); - - if (changed) { - setCurrentWindow(newWindow, false); - } - } - } - - public void runChange(Runnable change) { - myInsideChange++; - try { - change.run(); - } - finally { - myInsideChange--; - } - } - - private final class MyTransferHandler extends TransferHandler { - private final FileDropHandler myFileDropHandler = new FileDropHandler(); - - @Override - public boolean importData(JComponent comp, Transferable t) { - if (myFileDropHandler.canHandleDrop(t.getTransferDataFlavors())) { - myFileDropHandler.handleDrop(t, myManager.getProject()); - return true; - } - return false; - } - - @Override - public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { - return myFileDropHandler.canHandleDrop(transferFlavors); - } - } -} +/* + * 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.fileEditor.impl; + +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorProvider; +import com.intellij.openapi.fileEditor.FileEditorState; +import com.intellij.openapi.fileEditor.FileEditorStateLevel; +import com.intellij.openapi.fileEditor.impl.text.FileDropHandler; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.FocusWatcher; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.openapi.wm.impl.FrameTitleBuilder; +import com.intellij.openapi.wm.impl.IdeFrameImpl; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.docking.DockManager; +import com.intellij.ui.tabs.JBTabs; +import com.intellij.ui.tabs.impl.JBTabsImpl; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ArrayListSet; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.io.File; +import java.util.*; +import java.util.List; + + +/** + * Author: msk + */ +public class EditorsSplitters extends JPanel { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorsSplitters"); + private EditorWindow myCurrentWindow; + private VirtualFile myCurrentFile; + private final FileEditorManagerImpl myManager; + private Element mySplittersElement; // temporarily used during initialization + private int myInsideChange = 0; + private final MyFocusWatcher myFocusWatcher; + private EditorWithProviderComposite myCurrentSelectedEditor; + private final Alarm myIconUpdaterAlarm = new Alarm(); + private DockManager myDockManager; + + public EditorsSplitters(final FileEditorManagerImpl manager, DockManager dockManager, boolean createOwnDockableContainer) { + super(new BorderLayout()); + setOpaque(true); + setBackground(Color.GRAY); + myManager = manager; + myDockManager = dockManager; + myFocusWatcher = new MyFocusWatcher(); + setFocusTraversalPolicy(new MyFocusTraversalPolicy()); + setTransferHandler(new MyTransferHandler()); + clear(); + + if (createOwnDockableContainer) { + DockableEditorTabbedContainer dockable = new DockableEditorTabbedContainer(myManager.getProject(), dockManager, this, false); + Disposer.register(manager.getProject(), dockable); + myDockManager.register(dockable); + } + } + + public FileEditorManagerImpl getManager() { + return myManager; + } + + public void clear() { + removeAll(); + myWindows.clear(); + setCurrentWindow(null); + myCurrentFile = null; + repaint (); // revalidate doesn't repaint correctly after "Close All" + } + + public void startListeningFocus() { + myFocusWatcher.install(this); + } + + private void stopListeningFocus() { + myFocusWatcher.deinstall(this); + } + + public void dispose() { + myIconUpdaterAlarm.cancelAllRequests(); + stopListeningFocus(); + } + + @Nullable + public VirtualFile getCurrentFile() { + if (myCurrentWindow != null) { + return myCurrentWindow.getSelectedFile(); + } + return null; + } + + + public void writeExternal(final Element element) { + if (getComponentCount() != 0) { + final Component comp = getComponent(0); + LOG.assertTrue(comp instanceof JPanel); + final JPanel panel = (JPanel)comp; + if (panel.getComponentCount() != 0) { + final Element res = writePanel(panel); + element.addContent(res); + } + } + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private Element writePanel(final JPanel panel) { + final Component comp = panel.getComponent(0); + if (comp instanceof Splitter) { + final Splitter splitter = (Splitter)comp; + final Element res = new Element("splitter"); + res.setAttribute("split-orientation", splitter.getOrientation() ? "vertical" : "horizontal"); + res.setAttribute("split-proportion", Float.toString(splitter.getProportion())); + final Element first = new Element("split-first"); + first.addContent(writePanel((JPanel)splitter.getFirstComponent())); + final Element second = new Element("split-second"); + second.addContent(writePanel((JPanel)splitter.getSecondComponent())); + res.addContent(first); + res.addContent(second); + return res; + } + else if (comp instanceof JBTabs) { + final Element res = new Element("leaf"); + final EditorWindow window = findWindowWith(comp); + writeWindow(res, window); + return res; + } + else if (comp instanceof EditorWindow.TCompForTablessMode) { + final EditorWithProviderComposite composite = ((EditorWindow.TCompForTablessMode)comp).myEditor; + final Element res = new Element("leaf"); + writeComposite(res, composite.getFile(), composite, false, composite); + return res; + } + else { + LOG.error(comp != null ? comp.getClass().getName() : null); + return null; + } + } + + private void writeWindow(final Element res, final EditorWindow window) { + if (window != null) { + final EditorWithProviderComposite[] composites = window.getEditors(); + for (int i = 0; i < composites.length; i++) { + final VirtualFile file = window.getFileAt(i); + final boolean isPinned = window.isFilePinned(file); + final EditorWithProviderComposite composite = composites[i]; + final EditorWithProviderComposite selectedEditor = window.getSelectedEditor(); + + writeComposite(res, file, composite, isPinned, selectedEditor); + } + } + } + + private void writeComposite(final Element res, final VirtualFile file, final EditorWithProviderComposite composite, + final boolean pinned, + final EditorWithProviderComposite selectedEditor) { + final FileEditor[] editors = composite.getEditors(); + final Element fileElement = new Element("file"); + fileElement.setAttribute("leaf-file-name", file.getName()); // TODO: all files + final FileEditorState[] states = new FileEditorState[editors.length]; + for (int j = 0; j < states.length; j++) { + states[j] = editors[j].getState(FileEditorStateLevel.FULL); + LOG.assertTrue(states[j] != null); + } + final int selectedProviderIndex = ArrayUtil.find(editors, composite.getSelectedEditor()); + LOG.assertTrue(selectedProviderIndex != -1); + final FileEditorProvider[] providers = composite.getProviders(); + final HistoryEntry entry = new HistoryEntry(file, providers, states, providers[selectedProviderIndex]); // TODO + entry.writeExternal(fileElement, getManager().getProject()); + fileElement.setAttribute("pinned", Boolean.toString(pinned)); + fileElement.setAttribute("current", Boolean.toString(composite.equals (getManager ().getLastSelected ()))); + fileElement.setAttribute("current-in-tab", Boolean.toString(composite.equals (selectedEditor))); + res.addContent(fileElement); + } + + public void openFiles() { + if (mySplittersElement != null) { + final JPanel comp = readExternalPanel(mySplittersElement, getTopPanel()); + if (comp != null) { + removeAll(); + add(comp, BorderLayout.CENTER); + final EditorComposite lastSelected = getManager().getLastSelected(); + if(lastSelected != null) { + getManager().openFileImpl3(myCurrentWindow, lastSelected.getFile(), true, null, true); + //lastSelected.getComponent().requestFocus(); + //ToolWindowManager.getInstance(getManager().myProject).activateEditorComponent(); + } + mySplittersElement = null; + } + // clear empty splitters + for (EditorWindow window : getWindows()) { + if (window.getEditors().length == 0) { + for (EditorWindow sibling : window.findSiblings()) { + sibling.unsplit(false); + } + } + } + } + } + + public void readExternal(final Element element) { + mySplittersElement = element; + } + + @Nullable + @SuppressWarnings({"HardCodedStringLiteral"}) + public JPanel readExternalPanel(final Element element, @Nullable JPanel panel) { + final Element splitterElement = element.getChild("splitter"); + if (splitterElement != null) { + LOG.info("splitter"); + final boolean orientation = "vertical".equals(splitterElement.getAttributeValue("split-orientation")); + final float proportion = Float.valueOf(splitterElement.getAttributeValue("split-proportion")).floatValue(); + final Element first = splitterElement.getChild("split-first"); + final Element second = splitterElement.getChild("split-second"); + + Splitter splitter; + if (panel == null) { + panel = new JPanel(new BorderLayout()); + splitter = new Splitter(orientation, proportion, 0.1f, 0.9f); + panel.add(splitter, BorderLayout.CENTER); + splitter.setFirstComponent(readExternalPanel(first, null)); + splitter.setSecondComponent(readExternalPanel(second, null)); + } else if (panel.getComponent(0) instanceof Splitter) { + splitter = (Splitter)panel.getComponent(0); + readExternalPanel(first, (JPanel)splitter.getFirstComponent()); + readExternalPanel(second, (JPanel)splitter.getSecondComponent()); + } else { + readExternalPanel(first, panel); + readExternalPanel(second, panel); + } + return panel; + } + final Element leaf = element.getChild("leaf"); + if (leaf != null) { + EditorWindow window; + if (panel == null) { + window = new EditorWindow(this); + } else { + window = findWindowWith(panel); + } + //noinspection unchecked + final List children = leaf.getChildren("file"); + VirtualFile currentFile = null; + for (final Element file : children) { + try { + final HistoryEntry entry = new HistoryEntry(getManager().getProject(), file.getChild(HistoryEntry.TAG)); + boolean isCurrent = Boolean.valueOf(file.getAttributeValue("current")).booleanValue(); + getManager().openFileImpl3(window, entry.myFile, false, entry, isCurrent); + if (getManager().isFileOpen(entry.myFile)) { + window.setFilePinned(entry.myFile, Boolean.valueOf(file.getAttributeValue("pinned")).booleanValue()); + if (Boolean.valueOf(file.getAttributeValue("current-in-tab")).booleanValue()) { + currentFile = entry.myFile; + } + if (isCurrent) { + setCurrentWindow(window, false); + } + } + } + catch (InvalidDataException e) { + // OK + } + } + if (currentFile != null) { + final EditorComposite editor = window.findFileComposite(currentFile); + if (editor != null) { + window.setSelectedEditor(editor, true); + } + } + return window.myPanel; + } + return null; + } + + @NotNull public VirtualFile[] getOpenFiles() { + final ArrayListSet files = new ArrayListSet(); + for (final EditorWindow myWindow : myWindows) { + final EditorWithProviderComposite[] editors = myWindow.getEditors(); + for (final EditorWithProviderComposite editor : editors) { + files.add(editor.getFile()); + } + } + return VfsUtil.toVirtualFileArray(files); + } + + @NotNull public VirtualFile[] getSelectedFiles() { + final ArrayListSet files = new ArrayListSet(); + for (final EditorWindow window : myWindows) { + final VirtualFile file = window.getSelectedFile(); + if (file != null) { + files.add(file); + } + } + final VirtualFile[] virtualFiles = VfsUtil.toVirtualFileArray(files); + final VirtualFile currentFile = getCurrentFile(); + if (currentFile != null) { + for (int i = 0; i != virtualFiles.length; ++i) { + if (virtualFiles[i] == currentFile) { + virtualFiles[i] = virtualFiles[0]; + virtualFiles[0] = currentFile; + break; + } + } + } + return virtualFiles; + } + + @NotNull public FileEditor[] getSelectedEditors() { + final List editors = new ArrayList(); + final EditorWindow currentWindow = getCurrentWindow(); + if (currentWindow != null) { + final EditorWithProviderComposite composite = currentWindow.getSelectedEditor(); + if (composite != null) { + editors.add (composite.getSelectedEditor()); + } + } + + for (final EditorWindow window : myWindows) { + if (!window.equals(currentWindow)) { + final EditorWithProviderComposite composite = window.getSelectedEditor(); + if (composite != null) { + editors.add(composite.getSelectedEditor()); + } + } + } + return editors.toArray(new FileEditor[editors.size()]); + } + + public void updateFileIcon(@NotNull final VirtualFile file) { + updateFileIconLater(file); + } + + private void updateFileIconImmediately(final VirtualFile file) { + final Collection windows = findWindows(file); + for (EditorWindow window : windows) { + window.updateFileIcon(file); + } + } + + private final Set myFilesToUpdateIconsFor = new HashSet(); + + private void updateFileIconLater(VirtualFile file) { + myFilesToUpdateIconsFor.add(file); + myIconUpdaterAlarm.cancelAllRequests(); + myIconUpdaterAlarm.addRequest(new Runnable() { + public void run() { + if (myManager.getProject().isDisposed()) return; + for (VirtualFile file : myFilesToUpdateIconsFor) { + updateFileIconImmediately(file); + } + myFilesToUpdateIconsFor.clear(); + } + }, 200, ModalityState.stateForComponent(this)); + } + + public void updateFileColor(@NotNull final VirtualFile file) { + final Collection windows = findWindows(file); + for (final EditorWindow window : windows) { + final int index = window.findEditorIndex(window.findFileComposite(file)); + LOG.assertTrue(index != -1); + window.setForegroundAt(index, getManager().getFileColor(file)); + window.setWaveColor(index, getManager().isProblem(file) ? Color.red : null); + } + } + + public void trimToSize(final int editor_tab_limit) { + for (final EditorWindow window : myWindows) { + window.trimToSize(editor_tab_limit, null, true); + } + } + + public void setTabsPlacement(final int tabPlacement) { + final EditorWindow[] windows = getWindows(); + for (int i = 0; i != windows.length; ++ i) { + windows[i].setTabsPlacement(tabPlacement); + } + } + + public void setTabLayoutPolicy(int scrollTabLayout) { + final EditorWindow[] windows = getWindows(); + for (int i = 0; i != windows.length; ++ i) { + windows[i].setTabLayoutPolicy(scrollTabLayout); + } + } + + public void updateFileName(final VirtualFile updatedFile) { + final EditorWindow[] windows = getWindows(); + for (int i = 0; i != windows.length; ++ i) { + windows [i].updateFileName(updatedFile); + } + + Project project = myManager.getProject(); + + final IdeFrame frame = getFrame(project); + + VirtualFile file = getCurrentFile(); + + File ioFile = file == null ? null : new File(file.getPresentableUrl()); + String fileTitle = file == null ? null : FrameTitleBuilder.getInstance().getFileTitle(project, file); + frame.setFileTitle(fileTitle, ioFile); + } + + protected IdeFrame getFrame(Project project) { + final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx(); + final IdeFrame frame = windowManagerEx.getFrame(project); + LOG.assertTrue(frame != null); + return frame; + } + + public boolean isInsideChange() { + return myInsideChange > 0; + } + + private void setCurrentWindow(final EditorWindow currentWindow) { + myCurrentWindow = currentWindow; + } + + public void updateFileBackgroundColor(final VirtualFile file) { + final EditorWindow[] windows = getWindows(); + for (int i = 0; i != windows.length; ++ i) { + windows [i].updateFileBackgroundColor(file); + } + } + + public int getSplitCount() { + if (getComponentCount() > 0) { + JPanel panel = (JPanel) getComponent(0); + return getSplitCount(panel); + } + return 0; + } + + private static int getSplitCount(JComponent component) { + if (component.getComponentCount() > 0) { + final JComponent firstChild = (JComponent)component.getComponent(0); + if (firstChild instanceof Splitter) { + final Splitter splitter = (Splitter)firstChild; + return getSplitCount(splitter.getFirstComponent()) + getSplitCount(splitter.getSecondComponent()); + } + return 1; + } + return 0; + } + + protected void afterFileClosed(VirtualFile file) { + } + + protected void afterFileOpen(VirtualFile file) { + } + + public JBTabs getTabsAt(RelativePoint point) { + Point thisPoint = point.getPoint(this); + Component c = SwingUtilities.getDeepestComponentAt(this, thisPoint.x, thisPoint.y); + while (c != null) { + if (c instanceof JBTabs) { + return (JBTabs)c; + } + c = c.getParent(); + } + + return null; + } + + public boolean isEmptyVisible() { + EditorWindow[] windows = getWindows(); + for (EditorWindow each : windows) { + if (!each.isEmptyVisible()) { + return false; + } + } + return true; + } + + private final class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy { + public final Component getDefaultComponentImpl(final Container focusCycleRoot) { + if (myCurrentWindow != null) { + final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor(); + if (selectedEditor != null) { + return IdeFocusTraversalPolicy.getPreferredFocusedComponent(selectedEditor.getComponent(), this); + } + } + return IdeFocusTraversalPolicy.getPreferredFocusedComponent(EditorsSplitters.this, this); + } + } + + @Nullable + public JPanel getTopPanel() { + return getComponentCount() > 0 ? (JPanel)getComponent(0) : null; + } + + public EditorWindow getCurrentWindow() { + return myCurrentWindow; + } + + public EditorWindow getOrCreateCurrentWindow(final VirtualFile file) { + final List windows = findWindows(file); + if (getCurrentWindow() == null) { + final Iterator iterator = myWindows.iterator(); + if (!windows.isEmpty()) { + setCurrentWindow(windows.get(0), false); + } + else if (iterator.hasNext()) { + setCurrentWindow(iterator.next(), false); + } + else { + createCurrentWindow(); + } + } + else if (!windows.isEmpty()) { + if (!windows.contains(getCurrentWindow())) { + setCurrentWindow(windows.get(0), false); + } + } + return getCurrentWindow(); + } + + public void createCurrentWindow() { + LOG.assertTrue(myCurrentWindow == null); + setCurrentWindow(new EditorWindow(this)); + add(myCurrentWindow.myPanel, BorderLayout.CENTER); + } + + /** + * sets the window passed as a current ('focused') window among all splitters. All file openings will be done inside this + * current window + * @param window a window to be set as current + * @param requestFocus whether to request focus to the editor currently selected in this window + */ + public void setCurrentWindow(final EditorWindow window, final boolean requestFocus) { + final EditorWithProviderComposite oldEditor = myCurrentSelectedEditor; + final EditorWithProviderComposite newEditor = window != null? window.getSelectedEditor() : null; + + Runnable fireRunnable = new Runnable() { + public void run() { + getManager().fireSelectionChanged(oldEditor, newEditor); + } + }; + + setCurrentWindow(window); + myCurrentSelectedEditor = newEditor; + + getManager().updateFileName(window == null ? null : window.getSelectedFile()); + + if (window != null) { + final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor(); + if (selectedEditor != null) { + boolean alreadyFocused = false; + final JComponent comp = selectedEditor.getComponent(); + final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (owner != null && comp != null) { + alreadyFocused = owner == comp || SwingUtilities.isDescendingFrom(owner, comp); + } + + if (requestFocus && !alreadyFocused) { + IdeFocusManager.getInstance(myManager.getProject()).requestFocus(comp, requestFocus).doWhenDone(fireRunnable); + } else { + fireRunnable.run(); + } + } + } else { + fireRunnable.run(); + } + } + + //--------------------------------------------------------- + + public EditorWithProviderComposite[] getEditorsComposites() { + final ArrayList res = new ArrayList(); + + for (final EditorWindow myWindow : myWindows) { + final EditorWithProviderComposite[] editors = myWindow.getEditors(); + ContainerUtil.addAll(res, editors); + } + return res.toArray(new EditorWithProviderComposite[res.size()]); + } + + //--------------------------------------------------------- + + final Set myWindows = new ArrayListSet(); + + @NotNull + public List findEditorComposites(final VirtualFile file) { + final ArrayList res = new ArrayList(); + for (final EditorWindow window : myWindows) { + final EditorWithProviderComposite fileComposite = window.findFileComposite(file); + if (fileComposite != null) { + res.add(fileComposite); + } + } + return res; + } + + @NotNull + public List findWindows(final VirtualFile file) { + final ArrayList res = new ArrayList(); + for (final EditorWindow window : myWindows) { + if (window.findFileComposite(file) != null) { + res.add(window); + } + } + return res; + } + + @NotNull public EditorWindow [] getWindows() { + return myWindows.toArray(new EditorWindow [myWindows.size()]); + } + + @NotNull public EditorWindow[] getOrderedWindows() { + final ArrayList res = new ArrayList(); + + // Collector for windows in tree ordering: + class Inner{ + final void collect(final JPanel panel){ + final Component comp = panel.getComponent(0); + if (comp instanceof Splitter) { + final Splitter splitter = (Splitter)comp; + collect((JPanel)splitter.getFirstComponent()); + collect((JPanel)splitter.getSecondComponent()); + } + else if (comp instanceof JPanel || comp instanceof JBTabs) { + final EditorWindow window = findWindowWith(comp); + if (window != null) { + res.add(window); + } + } + } + } + + // get root component and traverse splitters tree: + if (getComponentCount() != 0) { + final Component comp = getComponent(0); + LOG.assertTrue(comp instanceof JPanel); + final JPanel panel = (JPanel)comp; + if (panel.getComponentCount() != 0) { + new Inner().collect (panel); + } + } + + LOG.assertTrue(res.size() == myWindows.size()); + return res.toArray(new EditorWindow [res.size()]); + } + + private EditorWindow findWindowWith(final Component component) { + if (component != null) { + for (final EditorWindow window : myWindows) { + if (SwingUtilities.isDescendingFrom(component, window.myPanel)) { + return window; + } + } + } + return null; + } + + private final class MyFocusWatcher extends FocusWatcher { + protected void focusedComponentChanged(final Component component, final AWTEvent cause) { + EditorWindow newWindow = null; + VirtualFile newFile = null; + + if (component != null) { + newWindow = findWindowWith(component); + if (newWindow != null) { + newFile = newWindow.getSelectedFile(); + } + } + + boolean changed = !Comparing.equal(newWindow, myCurrentWindow) || !Comparing.equal(newFile, myCurrentFile); + + myCurrentFile = newFile; + setCurrentWindow(newWindow); + + if (changed) { + setCurrentWindow(newWindow, false); + } + } + } + + public void runChange(Runnable change) { + myInsideChange++; + try { + change.run(); + } + finally { + myInsideChange--; + } + } + + private final class MyTransferHandler extends TransferHandler { + private final FileDropHandler myFileDropHandler = new FileDropHandler(); + + @Override + public boolean importData(JComponent comp, Transferable t) { + if (myFileDropHandler.canHandleDrop(t.getTransferDataFlavors())) { + myFileDropHandler.handleDrop(t, myManager.getProject()); + return true; + } + return false; + } + + @Override + public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { + return myFileDropHandler.canHandleDrop(transferFlavors); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java index bfd4ffe41a59..3e9a1e2d872d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java @@ -1,1524 +1,1524 @@ -/* - * 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.fileEditor.impl; - -import com.intellij.AppTopics; -import com.intellij.ProjectTopics; -import com.intellij.ide.IdeBundle; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.ide.ui.UISettings; -import com.intellij.ide.ui.UISettingsListener; -import com.intellij.injected.editor.VirtualFileWindow; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.application.impl.LaterInvocator; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.*; -import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; -import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager; -import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; -import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl; -import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; -import com.intellij.openapi.fileTypes.FileTypeEvent; -import com.intellij.openapi.fileTypes.FileTypeListener; -import com.intellij.openapi.project.DumbAwareRunnable; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.impl.ProjectImpl; -import com.intellij.openapi.roots.ModuleRootEvent; -import com.intellij.openapi.roots.ModuleRootListener; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.FileStatusListener; -import com.intellij.openapi.vcs.FileStatusManager; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.openapi.wm.ex.StatusBarEx; -import com.intellij.ui.docking.DockContainer; -import com.intellij.ui.docking.DockManager; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.messages.impl.MessageListenerList; -import com.intellij.util.ui.update.MergingUpdateQueue; -import com.intellij.util.ui.update.Update; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import java.awt.*; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.*; -import java.util.List; - -/** - * @author Anton Katilin - * @author Eugene Belyaev - * @author Vladimir Kondratyev - */ -public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl"); - private static final Key WATCH_REQUEST_KEY = Key.create("WATCH_REQUEST_KEY"); - private static final Key DUMB_AWARE = Key.create("DUMB_AWARE"); - - private static final FileEditor[] EMPTY_EDITOR_ARRAY = {}; - private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {}; - - private volatile JPanel myPanels; - private EditorsSplitters mySplitters; - private final Project myProject; - - private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null); - - /** - * Removes invalid myEditor and updates "modified" status. - */ - private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener(); - private DockManager myDockManager; - private DockableEditorContainerFactory myContentFactory; - - public FileEditorManagerImpl(final Project project, DockManager dockManager) { -/* ApplicationManager.getApplication().assertIsDispatchThread(); */ - myProject = project; - myDockManager = dockManager; - myListenerList = new MessageListenerList(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER); - } - - private void initDockableContentFactory() { - if (myContentFactory != null) return; - - myContentFactory = new DockableEditorContainerFactory(myProject, this, myDockManager); - myDockManager.register(DockableEditorContainerFactory.TYPE, myContentFactory); - Disposer.register(myProject, myContentFactory); - } - - public static boolean isDumbAware(FileEditor editor) { - return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE)); - } - - //------------------------------------------------------------------------------- - - public JComponent getComponent() { - initUI(); - return myPanels; - } - - public EditorsSplitters getMainSplitters() { - initUI(); - - return mySplitters; - } - - public Set getAllSplitters() { - HashSet all = new HashSet(); - all.add(getMainSplitters()); - Set dockContainers = DockManager.getInstance(myProject).getContainers(); - for (Iterator iterator = dockContainers.iterator(); iterator.hasNext();) { - DockContainer each = iterator.next(); - if (each instanceof DockableEditorTabbedContainer) { - all.add(((DockableEditorTabbedContainer)each).getSplitters()); - } - } - - return Collections.unmodifiableSet(all); - } - - private AsyncResult getActiveSplitters(boolean syncUsage) { - final boolean async = Registry.is("ide.windowSystem.asyncSplitters") && !syncUsage; - - final AsyncResult result = new AsyncResult(); - final IdeFocusManager fm = IdeFocusManager.getInstance(myProject); - Runnable run = new Runnable() { - @Override - public void run() { - if (myProject.isDisposed()) { - result.setRejected(); - return; - } - - Component focusOwner = async ? fm.getFocusOwner() : KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - DockContainer container = DockManager.getInstance(myProject).getContainerFor(focusOwner); - if (container instanceof DockableEditorTabbedContainer) { - result.setDone(((DockableEditorTabbedContainer)container).getSplitters()); - } else { - result.setDone(getMainSplitters()); - } - } - }; - - if (async) { - fm.doWhenFocusSettlesDown(run); - } else { - run.run(); - } - - return result; - } - - private final Object myInitLock = new Object(); - private void initUI() { - if (myPanels == null) { - synchronized (myInitLock) { - if (myPanels == null) { - myPanels = new JPanel(new BorderLayout()); - myPanels.setBorder(new MyBorder()); - mySplitters = new EditorsSplitters(this, myDockManager, true); - myPanels.add(mySplitters, BorderLayout.CENTER); - } - } - } - } - - public DockManager getDockManager() { - return myDockManager; - } - - private class MyBorder implements Border { - @Override - public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { - } - - @Override - public Insets getBorderInsets(Component c) { - boolean filesOpen = mySplitters != null && mySplitters.getOpenFiles().length > 0; - return new Insets(filesOpen ? 1 : 0, 0, 0, 0); - } - - @Override - public boolean isBorderOpaque() { - return false; - } - } - - public JComponent getPreferredFocusedComponent() { - assertReadAccess(); - final EditorWindow window = getSplitters().getCurrentWindow(); - if (window != null) { - final EditorWithProviderComposite editor = window.getSelectedEditor(); - if (editor != null) { - return editor.getPreferredFocusedComponent(); - } - } - return null; - } - - //------------------------------------------------------- - - /** - * @return color of the file which corresponds to the - * file's status - */ - public Color getFileColor(@NotNull final VirtualFile file) { - final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); - Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : Color.BLACK; - if (statusColor == null) statusColor = Color.BLACK; - return statusColor; - } - - public boolean isProblem(@NotNull final VirtualFile file) { - return false; - } - - public String getFileTooltipText(VirtualFile file) { - return file.getPresentableUrl(); - } - - public void updateFilePresentation(VirtualFile file) { - if (!isFileOpen(file)) return; - - updateFileColor(file); - updateFileIcon(file); - updateFileName(file); - updateFileBackgroundColor(file); - } - - /** - * Updates tab color for the specified file. The file - * should be opened in the myEditor, otherwise the method throws an assertion. - */ - private void updateFileColor(final VirtualFile file) { - Set all = getAllSplitters(); - for (EditorsSplitters each : all) { - each.updateFileColor(file); - } - } - - private void updateFileBackgroundColor(final VirtualFile file) { - Set all = getAllSplitters(); - for (EditorsSplitters each : all) { - each.updateFileBackgroundColor(file); - } - } - - /** - * Updates tab icon for the specified file. The file - * should be opened in the myEditor, otherwise the method throws an assertion. - */ - protected void updateFileIcon(final VirtualFile file) { - Set all = getAllSplitters(); - for (EditorsSplitters each : all) { - each.updateFileIcon(file); - } - } - - /** - * Updates tab title and tab tool tip for the specified file - */ - void updateFileName(@Nullable final VirtualFile file) { - // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab - // only the last event makes sense to handle - myQueue.queue(new Update("UpdateFileName "+(file==null?"":file.getPath())) { - public boolean isExpired() { - return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid()); - } - - public void run() { - Set all = getAllSplitters(); - for (EditorsSplitters each : all) { - each.updateFileName(file); - } - } - }); - } - - //------------------------------------------------------- - - - public VirtualFile getFile(@NotNull final FileEditor editor) { - final EditorComposite editorComposite = getEditorComposite(editor); - if (editorComposite != null) { - return editorComposite.getFile(); - } - return null; - } - - public void unsplitWindow() { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - if (currentWindow != null) { - currentWindow.unsplit(true); - } - } - - public void unsplitAllWindow() { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - if (currentWindow != null) { - currentWindow.unsplitAll(); - } - } - - @Override - public int getWindowSplitCount() { - return getSplitters().getSplitCount(); - } - - @NotNull - public EditorWindow[] getWindows() { - ArrayList windows = new ArrayList(); - Set all = getAllSplitters(); - for (EditorsSplitters each : all) { - EditorWindow[] eachList = each.getWindows(); - windows.addAll(Arrays.asList(eachList)); - } - - return windows.toArray(new EditorWindow[windows.size()]); - } - - public EditorWindow getNextWindow(@NotNull final EditorWindow window) { - final EditorWindow[] windows = getSplitters().getOrderedWindows(); - for (int i = 0; i != windows.length; ++i) { - if (windows[i].equals(window)) { - return windows[(i + 1) % windows.length]; - } - } - LOG.error("Not window found"); - return null; - } - - public EditorWindow getPrevWindow(@NotNull final EditorWindow window) { - final EditorWindow[] windows = getSplitters().getOrderedWindows(); - for (int i = 0; i != windows.length; ++i) { - if (windows[i].equals(window)) { - return windows[(i + windows.length - 1) % windows.length]; - } - } - LOG.error("Not window found"); - return null; - } - - public void createSplitter(final int orientation, @Nullable final EditorWindow window) { - // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current' - if (window != null) { - window.split(orientation, true, null, false); - } - // otherwise we'll split the current window, if any - else { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - if (currentWindow != null) { - currentWindow.split(orientation, true, null, false); - } - } - } - - public void changeSplitterOrientation() { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - if (currentWindow != null) { - currentWindow.changeOrientation(); - } - } - - - public void flipTabs() { - /* - if (myTabs == null) { - myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT); - remove (mySplitters); - add (myTabs, BorderLayout.CENTER); - initTabs (); - } else { - remove (myTabs); - add (mySplitters, BorderLayout.CENTER); - myTabs.dispose (); - myTabs = null; - } - */ - myPanels.revalidate(); - } - - public boolean tabsMode() { - return false; - } - - private void setTabsMode(final boolean mode) { - if (tabsMode() != mode) { - flipTabs(); - } - //LOG.assertTrue (tabsMode () == mode); - } - - - public boolean isInSplitter() { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - return currentWindow != null && currentWindow.inSplitter(); - } - - public boolean hasOpenedFile() { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - return currentWindow != null && currentWindow.getSelectedEditor() != null; - } - - public VirtualFile getCurrentFile() { - return getSplitters().getCurrentFile(); - } - - public AsyncResult getActiveWindow() { - final AsyncResult result = new AsyncResult(); - getActiveSplitters(false).doWhenDone(new AsyncResult.Handler() { - @Override - public void run(EditorsSplitters editorsSplitters) { - result.setDone(editorsSplitters.getCurrentWindow()); - } - }); - - return result; - } - - public EditorWindow getCurrentWindow() { - return getActiveWindow().getResult(); - } - - public void setCurrentWindow(final EditorWindow window) { - getSplitters().setCurrentWindow(window, true); - } - - public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) { - assertDispatchThread(); - - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - if (window.isFileOpen(file)) { - window.closeFile(file); - final List windows = window.getOwner().findWindows(file); - if (windows.isEmpty()) { // no more windows containing this file left - final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY); - if (request != null) { - LocalFileSystem.getInstance().removeWatchedRoot(request); - } - } - } - } - }, IdeBundle.message("command.close.active.editor"), null); - } - - //============================= EditorManager methods ================================ - - public void closeFile(@NotNull final VirtualFile file) { - closeFile(file, true); - } - - public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus) { - assertDispatchThread(); - - final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY); - if (request != null) { - LocalFileSystem.getInstance().removeWatchedRoot(request); - } - - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - closeFileImpl(file, moveFocus); - } - }, "", null); - } - - - private VirtualFile findNextFile(final VirtualFile file) { - final EditorWindow [] windows = getWindows(); // TODO: use current file as base - for (int i = 0; i != windows.length; ++ i) { - final VirtualFile[] files = windows[i].getFiles(); - for (final VirtualFile fileAt : files) { - if (fileAt != file) { - return fileAt; - } - } - } - return null; - } - - private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus) { - assertDispatchThread(); - getSplitters().runChange(new Runnable() { - public void run() { - final List windows = getSplitters().findWindows(file); - if (!windows.isEmpty()) { - final VirtualFile nextFile = findNextFile(file); - for (final EditorWindow window : windows) { - LOG.assertTrue(window.getSelectedEditor() != null); - window.closeFile(file, false, moveFocus); - if (window.getTabCount() == 0 && nextFile != null) { - EditorWithProviderComposite newComposite = newEditorComposite(nextFile); - window.setEditor(newComposite, moveFocus); // newComposite can be null - } - } - // cleanup windows with no tabs - for (final EditorWindow window : windows) { - if (window.isDisposed()) { - // call to window.unsplit() which might make its sibling disposed - continue; - } - if (window.getTabCount() == 0) { - window.unsplit(false); - } - } - } - } - }); - } - -//-------------------------------------- Open File ---------------------------------------- - - @NotNull public Pair openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor) { - if (!file.isValid()) { - throw new IllegalArgumentException("file is not valid: " + file); - } - assertDispatchThread(); - return openFileImpl2(getSplitters().getOrCreateCurrentWindow(file), file, focusEditor); - } - - @NotNull public Pair openFileImpl2(@NotNull final EditorWindow window, - @NotNull final VirtualFile file, - final boolean focusEditor) { - final Ref> result = new Ref>(); - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - result.set(openFileImpl3(window, file, focusEditor, null, true)); - } - }, "", null); - return result.get(); - } - - /** - * @param file to be opened. Unlike openFile method, file can be - * invalid. For example, all file were invalidate and they are being - * removed one by one. If we have removed one invalid file, then another - * invalid file become selected. That's why we do not require that - * passed file is valid. - * @param entry map between FileEditorProvider and FileEditorState. If this parameter - * @param current - */ - @NotNull Pair openFileImpl3(@NotNull final EditorWindow window, - @NotNull final VirtualFile file, - final boolean focusEditor, - final HistoryEntry entry, - boolean current) { - // Open file - FileEditor[] editors; - FileEditorProvider[] providers; - final EditorWithProviderComposite newSelectedComposite; - boolean newEditorCreated = false; - - final boolean open = window.isFileOpen(file); - if (open) { - // File is already opened. In this case we have to just select existing EditorComposite - newSelectedComposite = window.findFileComposite(file); - LOG.assertTrue(newSelectedComposite != null); - - editors = newSelectedComposite.getEditors(); - providers = newSelectedComposite.getProviders(); - } - else { - // File is not opened yet. In this case we have to create editors - // and select the created EditorComposite. - final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); - providers = editorProviderManager.getProviders(myProject, file); - if (DumbService.getInstance(myProject).isDumb()) { - final List dumbAware = ContainerUtil.findAll(providers, new Condition() { - public boolean value(FileEditorProvider fileEditorProvider) { - return DumbService.isDumbAware(fileEditorProvider); - } - }); - providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]); - } - - if (providers.length == 0) { - return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); - } - newEditorCreated = true; - - getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(this, file); - - editors = new FileEditor[providers.length]; - for (int i = 0; i < providers.length; i++) { - try { - final FileEditorProvider provider = providers[i]; - LOG.assertTrue(provider != null); - LOG.assertTrue(provider.accept(myProject, file)); - final FileEditor editor = provider.createEditor(myProject, file); - LOG.assertTrue(editor != null); - LOG.assertTrue(editor.isValid()); - editors[i] = editor; - // Register PropertyChangeListener into editor - editor.addPropertyChangeListener(myEditorPropertyChangeListener); - editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider)); - - if (current && editor instanceof TextEditorImpl) { - ((TextEditorImpl)editor).initFolding(); - } - } - catch (Exception e) { - LOG.error(e); - } - catch (AssertionError e) { - LOG.error(e); - } - } - - // Now we have to create EditorComposite and insert it into the TabbedEditorComponent. - // After that we have to select opened editor. - newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this); - } - - window.setEditor(newSelectedComposite, focusEditor); - - final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject); - for (int i = 0; i < editors.length; i++) { - final FileEditor editor = editors[i]; - if (editor instanceof TextEditor) { - // hack!!! - // This code prevents "jumping" on next repaint. - ((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling(); - } - - final FileEditorProvider provider = providers[i];//getProvider(editor); - - // Restore editor state - FileEditorState state = null; - if (entry != null) { - state = entry.getState(provider); - } - if (state == null && !open) { - // We have to try to get state from the history only in case - // if editor is not opened. Otherwise history enty might have a state - // out of sync with the current editor state. - state = editorHistoryManager.getState(file, provider); - } - if (state != null) { - editor.setState(state); - } - } - - // Restore selected editor - final FileEditorProvider selectedProvider = editorHistoryManager.getSelectedProvider(file); - if (selectedProvider != null) { - final FileEditor[] _editors = newSelectedComposite.getEditors(); - final FileEditorProvider[] _providers = newSelectedComposite.getProviders(); - for (int i = _editors.length - 1; i >= 0; i--) { - final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]); - if (provider.equals(selectedProvider)) { - newSelectedComposite.setSelectedEditor(i); - break; - } - } - } - - // Notify editors about selection changes - window.getOwner().setCurrentWindow(window, false); - window.getOwner().afterFileOpen(file); - - newSelectedComposite.getSelectedEditor().selectNotify(); - - if (newEditorCreated) { - IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { - @Override - public void run() { - getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER).fileOpened(FileEditorManagerImpl.this, file); - } - }); - - //Add request to watch this editor's virtual file - final VirtualFile parentDir = file.getParent(); - if (parentDir != null) { - final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false); - file.putUserData(WATCH_REQUEST_KEY, request); - } - } - - //[jeka] this is a hack to support back-forward navigation - // previously here was incorrect call to fireSelectionChanged() with a side-effect - ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged(); - - // Transfer focus into editor - if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) { - if (focusEditor) { - //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer); - window.setAsCurrentWindow(false); - ToolWindowManager.getInstance(myProject).activateEditorComponent(); - } - } - - // Update frame and tab title - updateFileName(file); - - // Make back/forward work - IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); - - return Pair.create(editors, providers); - } - - private void setSelectedEditor(VirtualFile file, String fileEditorProviderId) { - EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); - if (composite == null) { - final List composites = getEditorComposites(file); - - if (composites.isEmpty()) return; - composite = composites.get(0); - } - - final FileEditorProvider[] editorProviders = composite.getProviders(); - final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond(); - - for (int i = 0; i < editorProviders.length; i++) { - if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) { - composite.setSelectedEditor(i); - composite.getSelectedEditor().selectNotify(); - } - } - } - - - private EditorWithProviderComposite newEditorComposite(final VirtualFile file) { - if (file == null) { - return null; - } - - final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); - final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file); - final FileEditor[] editors = new FileEditor[providers.length]; - for (int i = 0; i < providers.length; i++) { - final FileEditorProvider provider = providers[i]; - LOG.assertTrue(provider != null); - LOG.assertTrue(provider.accept(myProject, file)); - final FileEditor editor = provider.createEditor(myProject, file); - editors[i] = editor; - LOG.assertTrue(editor.isValid()); - editor.addPropertyChangeListener(myEditorPropertyChangeListener); - } - - final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this); - final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject); - for (int i = 0; i < editors.length; i++) { - final FileEditor editor = editors[i]; - if (editor instanceof TextEditor) { - // hack!!! - // This code prevents "jumping" on next repaint. - //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling(); - } - - final FileEditorProvider provider = providers[i]; - -// Restore myEditor state - FileEditorState state = editorHistoryManager.getState(file, provider); - if (state != null) { - editor.setState(state); - } - } - return newComposite; - } - - @NotNull - public List openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) { - assertDispatchThread(); - if (descriptor.getFile() instanceof VirtualFileWindow) { - VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile(); - int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset()); - OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset); - return openEditor(realDescriptor, focusEditor); - } - - final List result = new ArrayList(); - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - VirtualFile file = descriptor.getFile(); - final FileEditor[] editors = openFile(file, focusEditor); - ContainerUtil.addAll(result, editors); - - boolean navigated = false; - for (final FileEditor editor : editors) { - if (editor instanceof NavigatableFileEditor && - getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor - navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor); - if (navigated) break; - } - } - - if (!navigated) { - for (final FileEditor editor : editors) { - if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors - if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) { - break; - } - } - } - } - } - }, "", null); - - return result; - } - - private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) { - if (editor.canNavigateTo(descriptor)) { - setSelectedEditor(editor); - editor.navigateTo(descriptor); - return true; - } - - return false; - } - - private void setSelectedEditor(final FileEditor editor) { - final EditorWithProviderComposite composite = getEditorComposite(editor); - if (composite == null) return; - - final FileEditor[] editors = composite.getEditors(); - for (int i = 0; i < editors.length; i++) { - final FileEditor each = editors[i]; - if (editor == each) { - composite.setSelectedEditor(i); - composite.getSelectedEditor().selectNotify(); - break; - } - } - } - - @NotNull - public Project getProject() { - return myProject; - } - - @Nullable - public Editor openTextEditor(final OpenFileDescriptor descriptor, final boolean focusEditor) { - final Collection fileEditors = openEditor(descriptor, focusEditor); - for (FileEditor fileEditor : fileEditors) { - if (fileEditor instanceof TextEditor) { - setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId()); - Editor editor = ((TextEditor)fileEditor).getEditor(); - return getOpenedEditor(editor, focusEditor); - } - } - - return null; - } - - protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) { - return editor; - } - - public Editor getSelectedTextEditor() { - assertReadAccess(); - - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - if (currentWindow != null) { - final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor(); - if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) { - return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor(); - } - } - - return null; - } - - - public boolean isFileOpen(@NotNull final VirtualFile file) { - return getEditors(file).length != 0; - } - - @NotNull - public VirtualFile[] getOpenFiles() { - return getSplitters().getOpenFiles(); - } - - @NotNull - public VirtualFile[] getSelectedFiles() { - return getSplitters().getSelectedFiles(); - } - - @NotNull - public FileEditor[] getSelectedEditors() { - return getSplitters().getSelectedEditors(); - } - - public EditorsSplitters getSplitters() { - EditorsSplitters active = getActiveSplitters(true).getResult(); - return active == null ? getMainSplitters() : active; - } - - public FileEditor getSelectedEditor(@NotNull final VirtualFile file) { - final Pair selectedEditorWithProvider = getSelectedEditorWithProvider(file); - return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst(); - } - - - public Pair getSelectedEditorWithProvider(@NotNull VirtualFile file) { - if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); - final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); - if (composite != null) { - return composite.getSelectedEditorWithProvider(); - } - - final List composites = getEditorComposites(file); - return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider(); - } - - @NotNull - public Pair getEditorsWithProviders(@NotNull final VirtualFile file) { - assertReadAccess(); - - final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); - if (composite != null) { - return Pair.create(composite.getEditors(), composite.getProviders()); - } - - final List composites = getEditorComposites(file); - if (!composites.isEmpty()) { - return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders()); - } - else { - return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); - } - } - - @NotNull - public FileEditor[] getEditors(@NotNull VirtualFile file) { - assertReadAccess(); - if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); - - final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); - if (composite != null) { - return composite.getEditors(); - } - - final List composites = getEditorComposites(file); - if (!composites.isEmpty()) { - return composites.get(0).getEditors(); - } - else { - return EMPTY_EDITOR_ARRAY; - } - } - - @NotNull - @Override - public FileEditor[] getAllEditors(@NotNull VirtualFile file) { - List editorComposites = getEditorComposites(file); - List editors = new ArrayList(); - for (EditorWithProviderComposite composite : editorComposites) { - ContainerUtil.addAll(editors, composite.getEditors()); - } - return editors.toArray(new FileEditor[editors.size()]); - } - - @Nullable - private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) { - final EditorWindow editorWindow = getSplitters().getCurrentWindow(); - if (editorWindow != null) { - return editorWindow.findFileComposite(virtualFile); - } - return null; - } - - @NotNull - public List getEditorComposites(final VirtualFile file) { - ArrayList result = new ArrayList(); - Set all = getAllSplitters(); - for (EditorsSplitters each : all) { - result.addAll(each.findEditorComposites(file)); - } - return result; - } - - @NotNull - public FileEditor[] getAllEditors() { - assertReadAccess(); - final ArrayList result = new ArrayList(); - final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites(); - for (EditorWithProviderComposite editorsComposite : editorsComposites) { - final FileEditor[] editors = editorsComposite.getEditors(); - ContainerUtil.addAll(result, editors); - } - return result.toArray(new FileEditor[result.size()]); - } - - public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) { - addTopComponent(editor, annotationComponent); - } - - public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) { - removeTopComponent(editor, annotationComponent); - } - - public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { - final EditorComposite composite = getEditorComposite(editor); - if (composite != null) { - composite.addTopComponent(editor, component); - } - } - - public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { - final EditorComposite composite = getEditorComposite(editor); - if (composite != null) { - composite.removeTopComponent(editor, component); - } - } - - public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { - final EditorComposite composite = getEditorComposite(editor); - if (composite != null) { - composite.addBottomComponent(editor, component); - } - } - - public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { - final EditorComposite composite = getEditorComposite(editor); - if (composite != null) { - composite.removeBottomComponent(editor, component); - } - } - - private final MessageListenerList myListenerList; - - public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) { - myListenerList.add(listener); - } - - public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, final Disposable parentDisposable) { - myListenerList.add(listener, parentDisposable); - } - - public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) { - myListenerList.remove(listener); - } - -// ProjectComponent methods - - public void projectOpened() { - //myFocusWatcher.install(myWindows.getComponent ()); - getMainSplitters().startListeningFocus(); - - MessageBusConnection connection = myProject.getMessageBus().connect(myProject); - - final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); - if (fileStatusManager != null) { - /** - * Updates tabs colors - */ - final MyFileStatusListener myFileStatusListener = new MyFileStatusListener(); - fileStatusManager.addFileStatusListener(myFileStatusListener, myProject); - } - connection.subscribe(AppTopics.FILE_TYPES, new MyFileTypeListener()); - connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener()); - - /** - * Updates tabs names - */ - final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener(); - VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject); - /** - * Extends/cuts number of opened tabs. Also updates location of tabs. - */ - final MyUISettingsListener myUISettingsListener = new MyUISettingsListener(); - UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject); - - StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { - public void run() { - ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() { - public void run() { - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE); - getMainSplitters().openFiles(); - initDockableContentFactory(); - - LaterInvocator.invokeLater(new Runnable() { - public void run() { - long currentTime = System.nanoTime(); - Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME); - if (startTime != null) { - LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms"); - PluginManager.dumpPluginClassStatistics(); - } - } - }); -// group 1 - } - }, "", null); - } - }); - } - }); - } - - public void projectClosed() { - //myFocusWatcher.deinstall(myWindows.getComponent ()); - getMainSplitters().dispose(); - -// Dispose created editors. We do not use use closeEditor method because -// it fires event and changes history. - closeAllFiles(); - } - -// BaseCompomemnt methods - - @NotNull - public String getComponentName() { - return "FileEditorManager"; - } - - public void initComponent() { - - } - - public void disposeComponent() { /* really do nothing */ } - -//JDOMExternalizable methods - - public void writeExternal(final Element element) { - getMainSplitters().writeExternal(element); - } - - public void readExternal(final Element element) { - getMainSplitters().readExternal(element); - } - - private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) { - final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites(); - for (int i = editorsComposites.length - 1; i >= 0; i--) { - final EditorWithProviderComposite composite = editorsComposites[i]; - final FileEditor[] editors = composite.getEditors(); - for (int j = editors.length - 1; j >= 0; j--) { - final FileEditor _editor = editors[j]; - LOG.assertTrue(_editor != null); - if (editor.equals(_editor)) { - return composite; - } - } - } - return null; - } - -//======================= Misc ===================== - - private static void assertDispatchThread() { - ApplicationManager.getApplication().assertIsDispatchThread(); - } - private static void assertReadAccess() { - ApplicationManager.getApplication().assertReadAccessAllowed(); - } - - public void fireSelectionChanged(final EditorComposite oldSelectedComposite, final EditorComposite newSelectedComposite) { - final VirtualFile oldSelectedFile = oldSelectedComposite != null ? oldSelectedComposite.getFile() : null; - final VirtualFile newSelectedFile = newSelectedComposite != null ? newSelectedComposite.getFile() : null; - - final FileEditor oldSelectedEditor = oldSelectedComposite != null && !oldSelectedComposite.isDisposed() ? oldSelectedComposite.getSelectedEditor() : null; - final FileEditor newSelectedEditor = newSelectedComposite != null && !newSelectedComposite.isDisposed() ? newSelectedComposite.getSelectedEditor() : null; - - final boolean filesEqual = oldSelectedFile == null ? newSelectedFile == null : oldSelectedFile.equals(newSelectedFile); - final boolean editorsEqual = oldSelectedEditor == null ? newSelectedEditor == null : oldSelectedEditor.equals(newSelectedEditor); - if (!filesEqual || !editorsEqual) { - final FileEditorManagerEvent event = - new FileEditorManagerEvent(this, oldSelectedFile, oldSelectedEditor, newSelectedFile, newSelectedEditor); - final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); - IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { - @Override - public void run() { - publisher.selectionChanged(event); - } - }); - } - } - - public boolean isChanged(@NotNull final EditorComposite editor) { - final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); - if (fileStatusManager != null) { - if (!fileStatusManager.getStatus(editor.getFile()).equals(FileStatus.NOT_CHANGED)) { - return true; - } - } - return false; - } - - public void disposeComposite(EditorWithProviderComposite editor) { - if (getAllEditors().length == 0) { - setCurrentWindow(null); - } - - if (editor.equals(getLastSelected())) { - editor.getSelectedEditor().deselectNotify(); - getSplitters().setCurrentWindow(null, false); - } - - final FileEditor[] editors = editor.getEditors(); - final FileEditorProvider[] providers = editor.getProviders(); - - final FileEditor selectedEditor = editor.getSelectedEditor(); - for (int i = editors.length - 1; i >= 0; i--) { - final FileEditor editor1 = editors[i]; - final FileEditorProvider provider = providers[i]; - if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event) - if (selectedEditor.equals(editor1)) { - editor1.deselectNotify(); - } - } - editor1.removePropertyChangeListener(myEditorPropertyChangeListener); - provider.disposeEditor(editor1); - } - - Disposer.dispose(editor); - } - - EditorComposite getLastSelected() { - final EditorWindow currentWindow = getSplitters().getCurrentWindow(); - if (currentWindow != null) { - return currentWindow.getSelectedEditor(); - } - return null; - } - - public void runChange(Runnable runnable) { - getSplitters().runChange(runnable); - } - - //================== Listeners ===================== - - /** - * Closes deleted files. Closes file which are in the deleted directories. - */ - private final class MyVirtualFileListener extends VirtualFileAdapter { - public void beforeFileDeletion(VirtualFileEvent e) { - assertDispatchThread(); - final VirtualFile file = e.getFile(); - final VirtualFile[] openFiles = getOpenFiles(); - for (int i = openFiles.length - 1; i >= 0; i--) { - if (VfsUtil.isAncestor(file, openFiles[i], false)) { - closeFile(openFiles[i]); - } - } - } - - public void propertyChanged(VirtualFilePropertyEvent e) { - if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) { - assertDispatchThread(); - final VirtualFile file = e.getFile(); - if (isFileOpen(file)) { - updateFileName(file); - updateFileIcon(file); // file type can change after renaming - updateFileBackgroundColor(file); - } - } - else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) { - // TODO: message bus? - updateIconAndStatusbar(e); - } - } - - private void updateIconAndStatusbar(final VirtualFilePropertyEvent e) { - assertDispatchThread(); - final VirtualFile file = e.getFile(); - if (isFileOpen(file)) { - updateFileIcon(file); - if (file.equals(getSelectedFiles()[0])) { // update "write" status - final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject); - assert statusBar != null; - statusBar.updateWidgets(); - } - } - } - - public void fileMoved(VirtualFileMoveEvent e) { - final VirtualFile file = e.getFile(); - final VirtualFile[] openFiles = getOpenFiles(); - for (final VirtualFile openFile : openFiles) { - if (VfsUtil.isAncestor(file, openFile, false)) { - updateFileName(openFile); - updateFileBackgroundColor(openFile); - } - } - } - } - -/* -private final class MyVirtualFileListener extends VirtualFileAdapter { - public void beforeFileDeletion(final VirtualFileEvent e) { - assertDispatchThread(); - final VirtualFile file = e.getFile(); - final VirtualFile[] openFiles = getOpenFiles(); - for (int i = openFiles.length - 1; i >= 0; i--) { - if (VfsUtil.isAncestor(file, openFiles[i], false)) { - closeFile(openFiles[i]); - } - } - } - - public void propertyChanged(final VirtualFilePropertyEvent e) { - if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) { - assertDispatchThread(); - final VirtualFile file = e.getFile(); - if (isFileOpen(file)) { - if (file.equals(getSelectedFiles()[0])) { // update "write" status - final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject); - LOG.assertTrue(statusBar != null); - statusBar.setWriteStatus(!file.isWritable()); - } - } - } - } - - //public void fileMoved(final VirtualFileMoveEvent e){ } -} -*/ - - public boolean isInsideChange() { - return getSplitters().isInsideChange(); - } - - private final class MyEditorPropertyChangeListener implements PropertyChangeListener { - public void propertyChange(final PropertyChangeEvent e) { - assertDispatchThread(); - - final String propertyName = e.getPropertyName(); - if (FileEditor.PROP_MODIFIED.equals(propertyName)) { - final FileEditor editor = (FileEditor)e.getSource(); - final EditorComposite composite = getEditorComposite(editor); - if (composite != null) { - updateFileIcon(composite.getFile()); - } - } - else if (FileEditor.PROP_VALID.equals(propertyName)) { - final boolean valid = ((Boolean)e.getNewValue()).booleanValue(); - if (!valid) { - final FileEditor editor = (FileEditor)e.getSource(); - LOG.assertTrue(editor != null); - final EditorComposite composite = getEditorComposite(editor); - if (composite != null) { - closeFile(composite.getFile()); - } - } - } - - } - } - - - - /** - * Gets events from VCS and updates color of myEditor tabs - */ - private final class MyFileStatusListener implements FileStatusListener { - public void fileStatusesChanged() { // update color of all open files - assertDispatchThread(); - LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()"); - final VirtualFile[] openFiles = getOpenFiles(); - for (int i = openFiles.length - 1; i >= 0; i--) { - final VirtualFile file = openFiles[i]; - LOG.assertTrue(file != null); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (LOG.isDebugEnabled()) { - LOG.debug("updating file status in tab for " + file.getPath()); - } - updateFileStatus(file); - } - }, ModalityState.NON_MODAL, myProject.getDisposed()); - } - } - - public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary) - assertDispatchThread(); - if (isFileOpen(file)) { - updateFileStatus(file); - } - } - - private void updateFileStatus(final VirtualFile file) { - updateFileColor(file); - updateFileIcon(file); - } - } - - /** - * Gets events from FileTypeManager and updates icons on tabs - */ - private final class MyFileTypeListener implements FileTypeListener { - public void beforeFileTypesChanged(FileTypeEvent event) { - } - - public void fileTypesChanged(final FileTypeEvent event) { - assertDispatchThread(); - final VirtualFile[] openFiles = getOpenFiles(); - for (int i = openFiles.length - 1; i >= 0; i--) { - final VirtualFile file = openFiles[i]; - LOG.assertTrue(file != null); - updateFileIcon(file); - } - } - } - - private class MyRootsListener implements ModuleRootListener { - public void beforeRootsChange(ModuleRootEvent event) { - } - - public void rootsChanged(ModuleRootEvent event) { - EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME); - - for (EditorWindow eachWindow : getWindows()) { - VirtualFile selected = eachWindow.getSelectedFile(); - VirtualFile[] files = eachWindow.getFiles(); - for (int i = 0; i < files.length - 1 + 1; i++) { - VirtualFile eachFile = files[i]; - if (!eachFile.isValid()) continue; - - VirtualFile newFile = null; - for (EditorFileSwapper each : swappers) { - newFile = each.getFileToSwapTo(myProject, eachFile); - } - if (newFile == null) continue; - - // already open - if (eachWindow.findFileIndex(newFile) != -1) continue; - - closeFile(eachFile, eachWindow); - try { - newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i); - openFile(newFile, eachFile == selected); - } - finally { - newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null); - } - } - } - } - } - - /** - * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT - * and EDITOR_TAB_LIMIT, etc values. - */ - private final class MyUISettingsListener implements UISettingsListener { - public void uiSettingsChanged(final UISettings source) { - assertDispatchThread(); - setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE); - - for (EditorsSplitters each : getAllSplitters()) { - each.setTabsPlacement(source.EDITOR_TAB_PLACEMENT); - each.trimToSize(source.EDITOR_TAB_LIMIT); - - // Tab layout policy - if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) { - each.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - } - else { - each.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); - } - } - - // "Mark modified files with asterisk" - final VirtualFile[] openFiles = getOpenFiles(); - for (int i = openFiles.length - 1; i >= 0; i--) { - final VirtualFile file = openFiles[i]; - updateFileIcon(file); - updateFileName(file); - updateFileBackgroundColor(file); - } - } - } - - public void closeAllFiles() { - final VirtualFile[] openFiles = getSplitters().getOpenFiles(); - for (VirtualFile openFile : openFiles) { - closeFile(openFile); - } - } - - @NotNull - public VirtualFile[] getSiblings(VirtualFile file) { - return getOpenFiles(); - } - - protected void queueUpdateFile(final VirtualFile file) { - myQueue.queue(new Update(file) { - public void run() { - if (isFileOpen(file)) { - updateFileIcon(file); - updateFileColor(file); - updateFileBackgroundColor(file); - } - - } - }); - } -} +/* + * 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.fileEditor.impl; + +import com.intellij.AppTopics; +import com.intellij.ProjectTopics; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.ui.UISettings; +import com.intellij.ide.ui.UISettingsListener; +import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.application.impl.LaterInvocator; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager; +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; +import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl; +import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; +import com.intellij.openapi.fileTypes.FileTypeEvent; +import com.intellij.openapi.fileTypes.FileTypeListener; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.impl.ProjectImpl; +import com.intellij.openapi.roots.ModuleRootEvent; +import com.intellij.openapi.roots.ModuleRootListener; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusListener; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.ex.StatusBarEx; +import com.intellij.ui.docking.DockContainer; +import com.intellij.ui.docking.DockManager; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.messages.impl.MessageListenerList; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.*; +import java.util.List; + +/** + * @author Anton Katilin + * @author Eugene Belyaev + * @author Vladimir Kondratyev + */ +public class FileEditorManagerImpl extends FileEditorManagerEx implements ProjectComponent, JDOMExternalizable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl"); + private static final Key WATCH_REQUEST_KEY = Key.create("WATCH_REQUEST_KEY"); + private static final Key DUMB_AWARE = Key.create("DUMB_AWARE"); + + private static final FileEditor[] EMPTY_EDITOR_ARRAY = {}; + private static final FileEditorProvider[] EMPTY_PROVIDER_ARRAY = {}; + + private volatile JPanel myPanels; + private EditorsSplitters mySplitters; + private final Project myProject; + + private final MergingUpdateQueue myQueue = new MergingUpdateQueue("FileEditorManagerUpdateQueue", 50, true, null); + + /** + * Removes invalid myEditor and updates "modified" status. + */ + private final MyEditorPropertyChangeListener myEditorPropertyChangeListener = new MyEditorPropertyChangeListener(); + private DockManager myDockManager; + private DockableEditorContainerFactory myContentFactory; + + public FileEditorManagerImpl(final Project project, DockManager dockManager) { +/* ApplicationManager.getApplication().assertIsDispatchThread(); */ + myProject = project; + myDockManager = dockManager; + myListenerList = new MessageListenerList(myProject.getMessageBus(), FileEditorManagerListener.FILE_EDITOR_MANAGER); + } + + private void initDockableContentFactory() { + if (myContentFactory != null) return; + + myContentFactory = new DockableEditorContainerFactory(myProject, this, myDockManager); + myDockManager.register(DockableEditorContainerFactory.TYPE, myContentFactory); + Disposer.register(myProject, myContentFactory); + } + + public static boolean isDumbAware(FileEditor editor) { + return Boolean.TRUE.equals(editor.getUserData(DUMB_AWARE)); + } + + //------------------------------------------------------------------------------- + + public JComponent getComponent() { + initUI(); + return myPanels; + } + + public EditorsSplitters getMainSplitters() { + initUI(); + + return mySplitters; + } + + public Set getAllSplitters() { + HashSet all = new HashSet(); + all.add(getMainSplitters()); + Set dockContainers = DockManager.getInstance(myProject).getContainers(); + for (Iterator iterator = dockContainers.iterator(); iterator.hasNext();) { + DockContainer each = iterator.next(); + if (each instanceof DockableEditorTabbedContainer) { + all.add(((DockableEditorTabbedContainer)each).getSplitters()); + } + } + + return Collections.unmodifiableSet(all); + } + + private AsyncResult getActiveSplitters(boolean syncUsage) { + final boolean async = Registry.is("ide.windowSystem.asyncSplitters") && !syncUsage; + + final AsyncResult result = new AsyncResult(); + final IdeFocusManager fm = IdeFocusManager.getInstance(myProject); + Runnable run = new Runnable() { + @Override + public void run() { + if (myProject.isDisposed()) { + result.setRejected(); + return; + } + + Component focusOwner = async ? fm.getFocusOwner() : KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + DockContainer container = DockManager.getInstance(myProject).getContainerFor(focusOwner); + if (container instanceof DockableEditorTabbedContainer) { + result.setDone(((DockableEditorTabbedContainer)container).getSplitters()); + } else { + result.setDone(getMainSplitters()); + } + } + }; + + if (async) { + fm.doWhenFocusSettlesDown(run); + } else { + run.run(); + } + + return result; + } + + private final Object myInitLock = new Object(); + private void initUI() { + if (myPanels == null) { + synchronized (myInitLock) { + if (myPanels == null) { + myPanels = new JPanel(new BorderLayout()); + myPanels.setBorder(new MyBorder()); + mySplitters = new EditorsSplitters(this, myDockManager, true); + myPanels.add(mySplitters, BorderLayout.CENTER); + } + } + } + } + + public DockManager getDockManager() { + return myDockManager; + } + + private class MyBorder implements Border { + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + } + + @Override + public Insets getBorderInsets(Component c) { + boolean filesOpen = mySplitters != null && mySplitters.getOpenFiles().length > 0; + return new Insets(filesOpen ? 1 : 0, 0, 0, 0); + } + + @Override + public boolean isBorderOpaque() { + return false; + } + } + + public JComponent getPreferredFocusedComponent() { + assertReadAccess(); + final EditorWindow window = getSplitters().getCurrentWindow(); + if (window != null) { + final EditorWithProviderComposite editor = window.getSelectedEditor(); + if (editor != null) { + return editor.getPreferredFocusedComponent(); + } + } + return null; + } + + //------------------------------------------------------- + + /** + * @return color of the file which corresponds to the + * file's status + */ + public Color getFileColor(@NotNull final VirtualFile file) { + final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); + Color statusColor = fileStatusManager != null ? fileStatusManager.getStatus(file).getColor() : Color.BLACK; + if (statusColor == null) statusColor = Color.BLACK; + return statusColor; + } + + public boolean isProblem(@NotNull final VirtualFile file) { + return false; + } + + public String getFileTooltipText(VirtualFile file) { + return file.getPresentableUrl(); + } + + public void updateFilePresentation(VirtualFile file) { + if (!isFileOpen(file)) return; + + updateFileColor(file); + updateFileIcon(file); + updateFileName(file); + updateFileBackgroundColor(file); + } + + /** + * Updates tab color for the specified file. The file + * should be opened in the myEditor, otherwise the method throws an assertion. + */ + private void updateFileColor(final VirtualFile file) { + Set all = getAllSplitters(); + for (EditorsSplitters each : all) { + each.updateFileColor(file); + } + } + + private void updateFileBackgroundColor(final VirtualFile file) { + Set all = getAllSplitters(); + for (EditorsSplitters each : all) { + each.updateFileBackgroundColor(file); + } + } + + /** + * Updates tab icon for the specified file. The file + * should be opened in the myEditor, otherwise the method throws an assertion. + */ + protected void updateFileIcon(final VirtualFile file) { + Set all = getAllSplitters(); + for (EditorsSplitters each : all) { + each.updateFileIcon(file); + } + } + + /** + * Updates tab title and tab tool tip for the specified file + */ + void updateFileName(@Nullable final VirtualFile file) { + // Queue here is to prevent title flickering when tab is being closed and two events arriving: with component==null and component==next focused tab + // only the last event makes sense to handle + myQueue.queue(new Update("UpdateFileName "+(file==null?"":file.getPath())) { + public boolean isExpired() { + return myProject.isDisposed() || !myProject.isOpen() || (file == null ? super.isExpired() : !file.isValid()); + } + + public void run() { + Set all = getAllSplitters(); + for (EditorsSplitters each : all) { + each.updateFileName(file); + } + } + }); + } + + //------------------------------------------------------- + + + public VirtualFile getFile(@NotNull final FileEditor editor) { + final EditorComposite editorComposite = getEditorComposite(editor); + if (editorComposite != null) { + return editorComposite.getFile(); + } + return null; + } + + public void unsplitWindow() { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + if (currentWindow != null) { + currentWindow.unsplit(true); + } + } + + public void unsplitAllWindow() { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + if (currentWindow != null) { + currentWindow.unsplitAll(); + } + } + + @Override + public int getWindowSplitCount() { + return getSplitters().getSplitCount(); + } + + @NotNull + public EditorWindow[] getWindows() { + ArrayList windows = new ArrayList(); + Set all = getAllSplitters(); + for (EditorsSplitters each : all) { + EditorWindow[] eachList = each.getWindows(); + windows.addAll(Arrays.asList(eachList)); + } + + return windows.toArray(new EditorWindow[windows.size()]); + } + + public EditorWindow getNextWindow(@NotNull final EditorWindow window) { + final EditorWindow[] windows = getSplitters().getOrderedWindows(); + for (int i = 0; i != windows.length; ++i) { + if (windows[i].equals(window)) { + return windows[(i + 1) % windows.length]; + } + } + LOG.error("Not window found"); + return null; + } + + public EditorWindow getPrevWindow(@NotNull final EditorWindow window) { + final EditorWindow[] windows = getSplitters().getOrderedWindows(); + for (int i = 0; i != windows.length; ++i) { + if (windows[i].equals(window)) { + return windows[(i + windows.length - 1) % windows.length]; + } + } + LOG.error("Not window found"); + return null; + } + + public void createSplitter(final int orientation, @Nullable final EditorWindow window) { + // window was available from action event, for example when invoked from the tab menu of an editor that is not the 'current' + if (window != null) { + window.split(orientation, true, null, false); + } + // otherwise we'll split the current window, if any + else { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + if (currentWindow != null) { + currentWindow.split(orientation, true, null, false); + } + } + } + + public void changeSplitterOrientation() { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + if (currentWindow != null) { + currentWindow.changeOrientation(); + } + } + + + public void flipTabs() { + /* + if (myTabs == null) { + myTabs = new EditorTabs (this, UISettings.getInstance().EDITOR_TAB_PLACEMENT); + remove (mySplitters); + add (myTabs, BorderLayout.CENTER); + initTabs (); + } else { + remove (myTabs); + add (mySplitters, BorderLayout.CENTER); + myTabs.dispose (); + myTabs = null; + } + */ + myPanels.revalidate(); + } + + public boolean tabsMode() { + return false; + } + + private void setTabsMode(final boolean mode) { + if (tabsMode() != mode) { + flipTabs(); + } + //LOG.assertTrue (tabsMode () == mode); + } + + + public boolean isInSplitter() { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + return currentWindow != null && currentWindow.inSplitter(); + } + + public boolean hasOpenedFile() { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + return currentWindow != null && currentWindow.getSelectedEditor() != null; + } + + public VirtualFile getCurrentFile() { + return getSplitters().getCurrentFile(); + } + + public AsyncResult getActiveWindow() { + final AsyncResult result = new AsyncResult(); + getActiveSplitters(false).doWhenDone(new AsyncResult.Handler() { + @Override + public void run(EditorsSplitters editorsSplitters) { + result.setDone(editorsSplitters.getCurrentWindow()); + } + }); + + return result; + } + + public EditorWindow getCurrentWindow() { + return getActiveWindow().getResult(); + } + + public void setCurrentWindow(final EditorWindow window) { + getSplitters().setCurrentWindow(window, true); + } + + public void closeFile(@NotNull final VirtualFile file, @NotNull final EditorWindow window) { + assertDispatchThread(); + + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + if (window.isFileOpen(file)) { + window.closeFile(file); + final List windows = window.getOwner().findWindows(file); + if (windows.isEmpty()) { // no more windows containing this file left + final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY); + if (request != null) { + LocalFileSystem.getInstance().removeWatchedRoot(request); + } + } + } + } + }, IdeBundle.message("command.close.active.editor"), null); + } + + //============================= EditorManager methods ================================ + + public void closeFile(@NotNull final VirtualFile file) { + closeFile(file, true); + } + + public void closeFile(@NotNull final VirtualFile file, final boolean moveFocus) { + assertDispatchThread(); + + final LocalFileSystem.WatchRequest request = file.getUserData(WATCH_REQUEST_KEY); + if (request != null) { + LocalFileSystem.getInstance().removeWatchedRoot(request); + } + + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + closeFileImpl(file, moveFocus); + } + }, "", null); + } + + + private VirtualFile findNextFile(final VirtualFile file) { + final EditorWindow [] windows = getWindows(); // TODO: use current file as base + for (int i = 0; i != windows.length; ++ i) { + final VirtualFile[] files = windows[i].getFiles(); + for (final VirtualFile fileAt : files) { + if (fileAt != file) { + return fileAt; + } + } + } + return null; + } + + private void closeFileImpl(@NotNull final VirtualFile file, final boolean moveFocus) { + assertDispatchThread(); + getSplitters().runChange(new Runnable() { + public void run() { + final List windows = getSplitters().findWindows(file); + if (!windows.isEmpty()) { + final VirtualFile nextFile = findNextFile(file); + for (final EditorWindow window : windows) { + LOG.assertTrue(window.getSelectedEditor() != null); + window.closeFile(file, false, moveFocus); + if (window.getTabCount() == 0 && nextFile != null) { + EditorWithProviderComposite newComposite = newEditorComposite(nextFile); + window.setEditor(newComposite, moveFocus); // newComposite can be null + } + } + // cleanup windows with no tabs + for (final EditorWindow window : windows) { + if (window.isDisposed()) { + // call to window.unsplit() which might make its sibling disposed + continue; + } + if (window.getTabCount() == 0) { + window.unsplit(false); + } + } + } + } + }); + } + +//-------------------------------------- Open File ---------------------------------------- + + @NotNull public Pair openFileWithProviders(@NotNull final VirtualFile file, final boolean focusEditor) { + if (!file.isValid()) { + throw new IllegalArgumentException("file is not valid: " + file); + } + assertDispatchThread(); + return openFileImpl2(getSplitters().getOrCreateCurrentWindow(file), file, focusEditor); + } + + @NotNull public Pair openFileImpl2(@NotNull final EditorWindow window, + @NotNull final VirtualFile file, + final boolean focusEditor) { + final Ref> result = new Ref>(); + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + result.set(openFileImpl3(window, file, focusEditor, null, true)); + } + }, "", null); + return result.get(); + } + + /** + * @param file to be opened. Unlike openFile method, file can be + * invalid. For example, all file were invalidate and they are being + * removed one by one. If we have removed one invalid file, then another + * invalid file become selected. That's why we do not require that + * passed file is valid. + * @param entry map between FileEditorProvider and FileEditorState. If this parameter + * @param current + */ + @NotNull Pair openFileImpl3(@NotNull final EditorWindow window, + @NotNull final VirtualFile file, + final boolean focusEditor, + final HistoryEntry entry, + boolean current) { + // Open file + FileEditor[] editors; + FileEditorProvider[] providers; + final EditorWithProviderComposite newSelectedComposite; + boolean newEditorCreated = false; + + final boolean open = window.isFileOpen(file); + if (open) { + // File is already opened. In this case we have to just select existing EditorComposite + newSelectedComposite = window.findFileComposite(file); + LOG.assertTrue(newSelectedComposite != null); + + editors = newSelectedComposite.getEditors(); + providers = newSelectedComposite.getProviders(); + } + else { + // File is not opened yet. In this case we have to create editors + // and select the created EditorComposite. + final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); + providers = editorProviderManager.getProviders(myProject, file); + if (DumbService.getInstance(myProject).isDumb()) { + final List dumbAware = ContainerUtil.findAll(providers, new Condition() { + public boolean value(FileEditorProvider fileEditorProvider) { + return DumbService.isDumbAware(fileEditorProvider); + } + }); + providers = dumbAware.toArray(new FileEditorProvider[dumbAware.size()]); + } + + if (providers.length == 0) { + return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); + } + newEditorCreated = true; + + getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER).beforeFileOpened(this, file); + + editors = new FileEditor[providers.length]; + for (int i = 0; i < providers.length; i++) { + try { + final FileEditorProvider provider = providers[i]; + LOG.assertTrue(provider != null); + LOG.assertTrue(provider.accept(myProject, file)); + final FileEditor editor = provider.createEditor(myProject, file); + LOG.assertTrue(editor != null); + LOG.assertTrue(editor.isValid()); + editors[i] = editor; + // Register PropertyChangeListener into editor + editor.addPropertyChangeListener(myEditorPropertyChangeListener); + editor.putUserData(DUMB_AWARE, DumbService.isDumbAware(provider)); + + if (current && editor instanceof TextEditorImpl) { + ((TextEditorImpl)editor).initFolding(); + } + } + catch (Exception e) { + LOG.error(e); + } + catch (AssertionError e) { + LOG.error(e); + } + } + + // Now we have to create EditorComposite and insert it into the TabbedEditorComponent. + // After that we have to select opened editor. + newSelectedComposite = new EditorWithProviderComposite(file, editors, providers, this); + } + + window.setEditor(newSelectedComposite, focusEditor); + + final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject); + for (int i = 0; i < editors.length; i++) { + final FileEditor editor = editors[i]; + if (editor instanceof TextEditor) { + // hack!!! + // This code prevents "jumping" on next repaint. + ((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling(); + } + + final FileEditorProvider provider = providers[i];//getProvider(editor); + + // Restore editor state + FileEditorState state = null; + if (entry != null) { + state = entry.getState(provider); + } + if (state == null && !open) { + // We have to try to get state from the history only in case + // if editor is not opened. Otherwise history enty might have a state + // out of sync with the current editor state. + state = editorHistoryManager.getState(file, provider); + } + if (state != null) { + editor.setState(state); + } + } + + // Restore selected editor + final FileEditorProvider selectedProvider = editorHistoryManager.getSelectedProvider(file); + if (selectedProvider != null) { + final FileEditor[] _editors = newSelectedComposite.getEditors(); + final FileEditorProvider[] _providers = newSelectedComposite.getProviders(); + for (int i = _editors.length - 1; i >= 0; i--) { + final FileEditorProvider provider = _providers[i];//getProvider(_editors[i]); + if (provider.equals(selectedProvider)) { + newSelectedComposite.setSelectedEditor(i); + break; + } + } + } + + // Notify editors about selection changes + window.getOwner().setCurrentWindow(window, false); + window.getOwner().afterFileOpen(file); + + newSelectedComposite.getSelectedEditor().selectNotify(); + + if (newEditorCreated) { + IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER).fileOpened(FileEditorManagerImpl.this, file); + } + }); + + //Add request to watch this editor's virtual file + final VirtualFile parentDir = file.getParent(); + if (parentDir != null) { + final LocalFileSystem.WatchRequest request = LocalFileSystem.getInstance().addRootToWatch(parentDir.getPath(), false); + file.putUserData(WATCH_REQUEST_KEY, request); + } + } + + //[jeka] this is a hack to support back-forward navigation + // previously here was incorrect call to fireSelectionChanged() with a side-effect + ((IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(myProject)).onSelectionChanged(); + + // Transfer focus into editor + if (!ApplicationManagerEx.getApplicationEx().isUnitTestMode()) { + if (focusEditor) { + //myFirstIsActive = myTabbedContainer1.equals(tabbedContainer); + window.setAsCurrentWindow(false); + ToolWindowManager.getInstance(myProject).activateEditorComponent(); + } + } + + // Update frame and tab title + updateFileName(file); + + // Make back/forward work + IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); + + return Pair.create(editors, providers); + } + + private void setSelectedEditor(VirtualFile file, String fileEditorProviderId) { + EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); + if (composite == null) { + final List composites = getEditorComposites(file); + + if (composites.isEmpty()) return; + composite = composites.get(0); + } + + final FileEditorProvider[] editorProviders = composite.getProviders(); + final FileEditorProvider selectedProvider = composite.getSelectedEditorWithProvider().getSecond(); + + for (int i = 0; i < editorProviders.length; i++) { + if (editorProviders[i].getEditorTypeId().equals(fileEditorProviderId) && !selectedProvider.equals(editorProviders[i])) { + composite.setSelectedEditor(i); + composite.getSelectedEditor().selectNotify(); + } + } + } + + + private EditorWithProviderComposite newEditorComposite(final VirtualFile file) { + if (file == null) { + return null; + } + + final FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance(); + final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file); + final FileEditor[] editors = new FileEditor[providers.length]; + for (int i = 0; i < providers.length; i++) { + final FileEditorProvider provider = providers[i]; + LOG.assertTrue(provider != null); + LOG.assertTrue(provider.accept(myProject, file)); + final FileEditor editor = provider.createEditor(myProject, file); + editors[i] = editor; + LOG.assertTrue(editor.isValid()); + editor.addPropertyChangeListener(myEditorPropertyChangeListener); + } + + final EditorWithProviderComposite newComposite = new EditorWithProviderComposite(file, editors, providers, this); + final EditorHistoryManager editorHistoryManager = EditorHistoryManager.getInstance(myProject); + for (int i = 0; i < editors.length; i++) { + final FileEditor editor = editors[i]; + if (editor instanceof TextEditor) { + // hack!!! + // This code prevents "jumping" on next repaint. + //((EditorEx)((TextEditor)editor).getEditor()).stopOptimizedScrolling(); + } + + final FileEditorProvider provider = providers[i]; + +// Restore myEditor state + FileEditorState state = editorHistoryManager.getState(file, provider); + if (state != null) { + editor.setState(state); + } + } + return newComposite; + } + + @NotNull + public List openEditor(@NotNull final OpenFileDescriptor descriptor, final boolean focusEditor) { + assertDispatchThread(); + if (descriptor.getFile() instanceof VirtualFileWindow) { + VirtualFileWindow delegate = (VirtualFileWindow)descriptor.getFile(); + int hostOffset = delegate.getDocumentWindow().injectedToHost(descriptor.getOffset()); + OpenFileDescriptor realDescriptor = new OpenFileDescriptor(descriptor.getProject(), delegate.getDelegate(), hostOffset); + return openEditor(realDescriptor, focusEditor); + } + + final List result = new ArrayList(); + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + VirtualFile file = descriptor.getFile(); + final FileEditor[] editors = openFile(file, focusEditor); + ContainerUtil.addAll(result, editors); + + boolean navigated = false; + for (final FileEditor editor : editors) { + if (editor instanceof NavigatableFileEditor && + getSelectedEditor(descriptor.getFile()) == editor) { // try to navigate opened editor + navigated = navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor); + if (navigated) break; + } + } + + if (!navigated) { + for (final FileEditor editor : editors) { + if (editor instanceof NavigatableFileEditor && getSelectedEditor(descriptor.getFile()) != editor) { // try other editors + if (navigateAndSelectEditor((NavigatableFileEditor)editor, descriptor)) { + break; + } + } + } + } + } + }, "", null); + + return result; + } + + private boolean navigateAndSelectEditor(final NavigatableFileEditor editor, final OpenFileDescriptor descriptor) { + if (editor.canNavigateTo(descriptor)) { + setSelectedEditor(editor); + editor.navigateTo(descriptor); + return true; + } + + return false; + } + + private void setSelectedEditor(final FileEditor editor) { + final EditorWithProviderComposite composite = getEditorComposite(editor); + if (composite == null) return; + + final FileEditor[] editors = composite.getEditors(); + for (int i = 0; i < editors.length; i++) { + final FileEditor each = editors[i]; + if (editor == each) { + composite.setSelectedEditor(i); + composite.getSelectedEditor().selectNotify(); + break; + } + } + } + + @NotNull + public Project getProject() { + return myProject; + } + + @Nullable + public Editor openTextEditor(final OpenFileDescriptor descriptor, final boolean focusEditor) { + final Collection fileEditors = openEditor(descriptor, focusEditor); + for (FileEditor fileEditor : fileEditors) { + if (fileEditor instanceof TextEditor) { + setSelectedEditor(descriptor.getFile(), TextEditorProvider.getInstance().getEditorTypeId()); + Editor editor = ((TextEditor)fileEditor).getEditor(); + return getOpenedEditor(editor, focusEditor); + } + } + + return null; + } + + protected Editor getOpenedEditor(final Editor editor, final boolean focusEditor) { + return editor; + } + + public Editor getSelectedTextEditor() { + assertReadAccess(); + + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + if (currentWindow != null) { + final EditorWithProviderComposite selectedEditor = currentWindow.getSelectedEditor(); + if (selectedEditor != null && selectedEditor.getSelectedEditor() instanceof TextEditor) { + return ((TextEditor)selectedEditor.getSelectedEditor()).getEditor(); + } + } + + return null; + } + + + public boolean isFileOpen(@NotNull final VirtualFile file) { + return getEditors(file).length != 0; + } + + @NotNull + public VirtualFile[] getOpenFiles() { + return getSplitters().getOpenFiles(); + } + + @NotNull + public VirtualFile[] getSelectedFiles() { + return getSplitters().getSelectedFiles(); + } + + @NotNull + public FileEditor[] getSelectedEditors() { + return getSplitters().getSelectedEditors(); + } + + public EditorsSplitters getSplitters() { + EditorsSplitters active = getActiveSplitters(true).getResult(); + return active == null ? getMainSplitters() : active; + } + + public FileEditor getSelectedEditor(@NotNull final VirtualFile file) { + final Pair selectedEditorWithProvider = getSelectedEditorWithProvider(file); + return selectedEditorWithProvider == null ? null : selectedEditorWithProvider.getFirst(); + } + + + public Pair getSelectedEditorWithProvider(@NotNull VirtualFile file) { + if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); + final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); + if (composite != null) { + return composite.getSelectedEditorWithProvider(); + } + + final List composites = getEditorComposites(file); + return composites.isEmpty() ? null : composites.get(0).getSelectedEditorWithProvider(); + } + + @NotNull + public Pair getEditorsWithProviders(@NotNull final VirtualFile file) { + assertReadAccess(); + + final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); + if (composite != null) { + return Pair.create(composite.getEditors(), composite.getProviders()); + } + + final List composites = getEditorComposites(file); + if (!composites.isEmpty()) { + return Pair.create(composites.get(0).getEditors(), composites.get(0).getProviders()); + } + else { + return Pair.create(EMPTY_EDITOR_ARRAY, EMPTY_PROVIDER_ARRAY); + } + } + + @NotNull + public FileEditor[] getEditors(@NotNull VirtualFile file) { + assertReadAccess(); + if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate(); + + final EditorWithProviderComposite composite = getCurrentEditorWithProviderComposite(file); + if (composite != null) { + return composite.getEditors(); + } + + final List composites = getEditorComposites(file); + if (!composites.isEmpty()) { + return composites.get(0).getEditors(); + } + else { + return EMPTY_EDITOR_ARRAY; + } + } + + @NotNull + @Override + public FileEditor[] getAllEditors(@NotNull VirtualFile file) { + List editorComposites = getEditorComposites(file); + List editors = new ArrayList(); + for (EditorWithProviderComposite composite : editorComposites) { + ContainerUtil.addAll(editors, composite.getEditors()); + } + return editors.toArray(new FileEditor[editors.size()]); + } + + @Nullable + private EditorWithProviderComposite getCurrentEditorWithProviderComposite(@NotNull final VirtualFile virtualFile) { + final EditorWindow editorWindow = getSplitters().getCurrentWindow(); + if (editorWindow != null) { + return editorWindow.findFileComposite(virtualFile); + } + return null; + } + + @NotNull + public List getEditorComposites(final VirtualFile file) { + ArrayList result = new ArrayList(); + Set all = getAllSplitters(); + for (EditorsSplitters each : all) { + result.addAll(each.findEditorComposites(file)); + } + return result; + } + + @NotNull + public FileEditor[] getAllEditors() { + assertReadAccess(); + final ArrayList result = new ArrayList(); + final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites(); + for (EditorWithProviderComposite editorsComposite : editorsComposites) { + final FileEditor[] editors = editorsComposite.getEditors(); + ContainerUtil.addAll(result, editors); + } + return result.toArray(new FileEditor[result.size()]); + } + + public void showEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) { + addTopComponent(editor, annotationComponent); + } + + public void removeEditorAnnotation(@NotNull FileEditor editor, @NotNull JComponent annotationComponent) { + removeTopComponent(editor, annotationComponent); + } + + public void addTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { + final EditorComposite composite = getEditorComposite(editor); + if (composite != null) { + composite.addTopComponent(editor, component); + } + } + + public void removeTopComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { + final EditorComposite composite = getEditorComposite(editor); + if (composite != null) { + composite.removeTopComponent(editor, component); + } + } + + public void addBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { + final EditorComposite composite = getEditorComposite(editor); + if (composite != null) { + composite.addBottomComponent(editor, component); + } + } + + public void removeBottomComponent(@NotNull final FileEditor editor, @NotNull final JComponent component) { + final EditorComposite composite = getEditorComposite(editor); + if (composite != null) { + composite.removeBottomComponent(editor, component); + } + } + + private final MessageListenerList myListenerList; + + public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) { + myListenerList.add(listener); + } + + public void addFileEditorManagerListener(@NotNull final FileEditorManagerListener listener, final Disposable parentDisposable) { + myListenerList.add(listener, parentDisposable); + } + + public void removeFileEditorManagerListener(@NotNull final FileEditorManagerListener listener) { + myListenerList.remove(listener); + } + +// ProjectComponent methods + + public void projectOpened() { + //myFocusWatcher.install(myWindows.getComponent ()); + getMainSplitters().startListeningFocus(); + + MessageBusConnection connection = myProject.getMessageBus().connect(myProject); + + final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); + if (fileStatusManager != null) { + /** + * Updates tabs colors + */ + final MyFileStatusListener myFileStatusListener = new MyFileStatusListener(); + fileStatusManager.addFileStatusListener(myFileStatusListener, myProject); + } + connection.subscribe(AppTopics.FILE_TYPES, new MyFileTypeListener()); + connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootsListener()); + + /** + * Updates tabs names + */ + final MyVirtualFileListener myVirtualFileListener = new MyVirtualFileListener(); + VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileListener, myProject); + /** + * Extends/cuts number of opened tabs. Also updates location of tabs. + */ + final MyUISettingsListener myUISettingsListener = new MyUISettingsListener(); + UISettings.getInstance().addUISettingsListener(myUISettingsListener, myProject); + + StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { + public void run() { + ToolWindowManager.getInstance(myProject).invokeLater(new Runnable() { + public void run() { + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + setTabsMode(UISettings.getInstance().EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE); + getMainSplitters().openFiles(); + initDockableContentFactory(); + + LaterInvocator.invokeLater(new Runnable() { + public void run() { + long currentTime = System.nanoTime(); + Long startTime = myProject.getUserData(ProjectImpl.CREATION_TIME); + if (startTime != null) { + LOG.info("Project opening took " + (currentTime - startTime.longValue()) / 1000000 + " ms"); + PluginManager.dumpPluginClassStatistics(); + } + } + }); +// group 1 + } + }, "", null); + } + }); + } + }); + } + + public void projectClosed() { + //myFocusWatcher.deinstall(myWindows.getComponent ()); + getMainSplitters().dispose(); + +// Dispose created editors. We do not use use closeEditor method because +// it fires event and changes history. + closeAllFiles(); + } + +// BaseCompomemnt methods + + @NotNull + public String getComponentName() { + return "FileEditorManager"; + } + + public void initComponent() { + + } + + public void disposeComponent() { /* really do nothing */ } + +//JDOMExternalizable methods + + public void writeExternal(final Element element) { + getMainSplitters().writeExternal(element); + } + + public void readExternal(final Element element) { + getMainSplitters().readExternal(element); + } + + private EditorWithProviderComposite getEditorComposite(@NotNull final FileEditor editor) { + final EditorWithProviderComposite[] editorsComposites = getSplitters().getEditorsComposites(); + for (int i = editorsComposites.length - 1; i >= 0; i--) { + final EditorWithProviderComposite composite = editorsComposites[i]; + final FileEditor[] editors = composite.getEditors(); + for (int j = editors.length - 1; j >= 0; j--) { + final FileEditor _editor = editors[j]; + LOG.assertTrue(_editor != null); + if (editor.equals(_editor)) { + return composite; + } + } + } + return null; + } + +//======================= Misc ===================== + + private static void assertDispatchThread() { + ApplicationManager.getApplication().assertIsDispatchThread(); + } + private static void assertReadAccess() { + ApplicationManager.getApplication().assertReadAccessAllowed(); + } + + public void fireSelectionChanged(final EditorComposite oldSelectedComposite, final EditorComposite newSelectedComposite) { + final VirtualFile oldSelectedFile = oldSelectedComposite != null ? oldSelectedComposite.getFile() : null; + final VirtualFile newSelectedFile = newSelectedComposite != null ? newSelectedComposite.getFile() : null; + + final FileEditor oldSelectedEditor = oldSelectedComposite != null && !oldSelectedComposite.isDisposed() ? oldSelectedComposite.getSelectedEditor() : null; + final FileEditor newSelectedEditor = newSelectedComposite != null && !newSelectedComposite.isDisposed() ? newSelectedComposite.getSelectedEditor() : null; + + final boolean filesEqual = oldSelectedFile == null ? newSelectedFile == null : oldSelectedFile.equals(newSelectedFile); + final boolean editorsEqual = oldSelectedEditor == null ? newSelectedEditor == null : oldSelectedEditor.equals(newSelectedEditor); + if (!filesEqual || !editorsEqual) { + final FileEditorManagerEvent event = + new FileEditorManagerEvent(this, oldSelectedFile, oldSelectedEditor, newSelectedFile, newSelectedEditor); + final FileEditorManagerListener publisher = getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); + IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + publisher.selectionChanged(event); + } + }); + } + } + + public boolean isChanged(@NotNull final EditorComposite editor) { + final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject); + if (fileStatusManager != null) { + if (!fileStatusManager.getStatus(editor.getFile()).equals(FileStatus.NOT_CHANGED)) { + return true; + } + } + return false; + } + + public void disposeComposite(EditorWithProviderComposite editor) { + if (getAllEditors().length == 0) { + setCurrentWindow(null); + } + + if (editor.equals(getLastSelected())) { + editor.getSelectedEditor().deselectNotify(); + getSplitters().setCurrentWindow(null, false); + } + + final FileEditor[] editors = editor.getEditors(); + final FileEditorProvider[] providers = editor.getProviders(); + + final FileEditor selectedEditor = editor.getSelectedEditor(); + for (int i = editors.length - 1; i >= 0; i--) { + final FileEditor editor1 = editors[i]; + final FileEditorProvider provider = providers[i]; + if (!editor.equals(selectedEditor)) { // we already notified the myEditor (when fire event) + if (selectedEditor.equals(editor1)) { + editor1.deselectNotify(); + } + } + editor1.removePropertyChangeListener(myEditorPropertyChangeListener); + provider.disposeEditor(editor1); + } + + Disposer.dispose(editor); + } + + EditorComposite getLastSelected() { + final EditorWindow currentWindow = getSplitters().getCurrentWindow(); + if (currentWindow != null) { + return currentWindow.getSelectedEditor(); + } + return null; + } + + public void runChange(Runnable runnable) { + getSplitters().runChange(runnable); + } + + //================== Listeners ===================== + + /** + * Closes deleted files. Closes file which are in the deleted directories. + */ + private final class MyVirtualFileListener extends VirtualFileAdapter { + public void beforeFileDeletion(VirtualFileEvent e) { + assertDispatchThread(); + final VirtualFile file = e.getFile(); + final VirtualFile[] openFiles = getOpenFiles(); + for (int i = openFiles.length - 1; i >= 0; i--) { + if (VfsUtil.isAncestor(file, openFiles[i], false)) { + closeFile(openFiles[i]); + } + } + } + + public void propertyChanged(VirtualFilePropertyEvent e) { + if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) { + assertDispatchThread(); + final VirtualFile file = e.getFile(); + if (isFileOpen(file)) { + updateFileName(file); + updateFileIcon(file); // file type can change after renaming + updateFileBackgroundColor(file); + } + } + else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName()) || VirtualFile.PROP_ENCODING.equals(e.getPropertyName())) { + // TODO: message bus? + updateIconAndStatusbar(e); + } + } + + private void updateIconAndStatusbar(final VirtualFilePropertyEvent e) { + assertDispatchThread(); + final VirtualFile file = e.getFile(); + if (isFileOpen(file)) { + updateFileIcon(file); + if (file.equals(getSelectedFiles()[0])) { // update "write" status + final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject); + assert statusBar != null; + statusBar.updateWidgets(); + } + } + } + + public void fileMoved(VirtualFileMoveEvent e) { + final VirtualFile file = e.getFile(); + final VirtualFile[] openFiles = getOpenFiles(); + for (final VirtualFile openFile : openFiles) { + if (VfsUtil.isAncestor(file, openFile, false)) { + updateFileName(openFile); + updateFileBackgroundColor(openFile); + } + } + } + } + +/* +private final class MyVirtualFileListener extends VirtualFileAdapter { + public void beforeFileDeletion(final VirtualFileEvent e) { + assertDispatchThread(); + final VirtualFile file = e.getFile(); + final VirtualFile[] openFiles = getOpenFiles(); + for (int i = openFiles.length - 1; i >= 0; i--) { + if (VfsUtil.isAncestor(file, openFiles[i], false)) { + closeFile(openFiles[i]); + } + } + } + + public void propertyChanged(final VirtualFilePropertyEvent e) { + if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) { + assertDispatchThread(); + final VirtualFile file = e.getFile(); + if (isFileOpen(file)) { + if (file.equals(getSelectedFiles()[0])) { // update "write" status + final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(myProject); + LOG.assertTrue(statusBar != null); + statusBar.setWriteStatus(!file.isWritable()); + } + } + } + } + + //public void fileMoved(final VirtualFileMoveEvent e){ } +} +*/ + + public boolean isInsideChange() { + return getSplitters().isInsideChange(); + } + + private final class MyEditorPropertyChangeListener implements PropertyChangeListener { + public void propertyChange(final PropertyChangeEvent e) { + assertDispatchThread(); + + final String propertyName = e.getPropertyName(); + if (FileEditor.PROP_MODIFIED.equals(propertyName)) { + final FileEditor editor = (FileEditor)e.getSource(); + final EditorComposite composite = getEditorComposite(editor); + if (composite != null) { + updateFileIcon(composite.getFile()); + } + } + else if (FileEditor.PROP_VALID.equals(propertyName)) { + final boolean valid = ((Boolean)e.getNewValue()).booleanValue(); + if (!valid) { + final FileEditor editor = (FileEditor)e.getSource(); + LOG.assertTrue(editor != null); + final EditorComposite composite = getEditorComposite(editor); + if (composite != null) { + closeFile(composite.getFile()); + } + } + } + + } + } + + + + /** + * Gets events from VCS and updates color of myEditor tabs + */ + private final class MyFileStatusListener implements FileStatusListener { + public void fileStatusesChanged() { // update color of all open files + assertDispatchThread(); + LOG.debug("FileEditorManagerImpl.MyFileStatusListener.fileStatusesChanged()"); + final VirtualFile[] openFiles = getOpenFiles(); + for (int i = openFiles.length - 1; i >= 0; i--) { + final VirtualFile file = openFiles[i]; + LOG.assertTrue(file != null); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (LOG.isDebugEnabled()) { + LOG.debug("updating file status in tab for " + file.getPath()); + } + updateFileStatus(file); + } + }, ModalityState.NON_MODAL, myProject.getDisposed()); + } + } + + public void fileStatusChanged(@NotNull final VirtualFile file) { // update color of the file (if necessary) + assertDispatchThread(); + if (isFileOpen(file)) { + updateFileStatus(file); + } + } + + private void updateFileStatus(final VirtualFile file) { + updateFileColor(file); + updateFileIcon(file); + } + } + + /** + * Gets events from FileTypeManager and updates icons on tabs + */ + private final class MyFileTypeListener implements FileTypeListener { + public void beforeFileTypesChanged(FileTypeEvent event) { + } + + public void fileTypesChanged(final FileTypeEvent event) { + assertDispatchThread(); + final VirtualFile[] openFiles = getOpenFiles(); + for (int i = openFiles.length - 1; i >= 0; i--) { + final VirtualFile file = openFiles[i]; + LOG.assertTrue(file != null); + updateFileIcon(file); + } + } + } + + private class MyRootsListener implements ModuleRootListener { + public void beforeRootsChange(ModuleRootEvent event) { + } + + public void rootsChanged(ModuleRootEvent event) { + EditorFileSwapper[] swappers = Extensions.getExtensions(EditorFileSwapper.EP_NAME); + + for (EditorWindow eachWindow : getWindows()) { + VirtualFile selected = eachWindow.getSelectedFile(); + VirtualFile[] files = eachWindow.getFiles(); + for (int i = 0; i < files.length - 1 + 1; i++) { + VirtualFile eachFile = files[i]; + if (!eachFile.isValid()) continue; + + VirtualFile newFile = null; + for (EditorFileSwapper each : swappers) { + newFile = each.getFileToSwapTo(myProject, eachFile); + } + if (newFile == null) continue; + + // already open + if (eachWindow.findFileIndex(newFile) != -1) continue; + + closeFile(eachFile, eachWindow); + try { + newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, i); + openFile(newFile, eachFile == selected); + } + finally { + newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null); + } + } + } + } + } + + /** + * Gets notifications from UISetting component to track changes of RECENT_FILES_LIMIT + * and EDITOR_TAB_LIMIT, etc values. + */ + private final class MyUISettingsListener implements UISettingsListener { + public void uiSettingsChanged(final UISettings source) { + assertDispatchThread(); + setTabsMode(source.EDITOR_TAB_PLACEMENT != UISettings.TABS_NONE); + + for (EditorsSplitters each : getAllSplitters()) { + each.setTabsPlacement(source.EDITOR_TAB_PLACEMENT); + each.trimToSize(source.EDITOR_TAB_LIMIT); + + // Tab layout policy + if (source.SCROLL_TAB_LAYOUT_IN_EDITOR) { + each.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + } + else { + each.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); + } + } + + // "Mark modified files with asterisk" + final VirtualFile[] openFiles = getOpenFiles(); + for (int i = openFiles.length - 1; i >= 0; i--) { + final VirtualFile file = openFiles[i]; + updateFileIcon(file); + updateFileName(file); + updateFileBackgroundColor(file); + } + } + } + + public void closeAllFiles() { + final VirtualFile[] openFiles = getSplitters().getOpenFiles(); + for (VirtualFile openFile : openFiles) { + closeFile(openFile); + } + } + + @NotNull + public VirtualFile[] getSiblings(VirtualFile file) { + return getOpenFiles(); + } + + protected void queueUpdateFile(final VirtualFile file) { + myQueue.queue(new Update(file) { + public void run() { + if (isFileOpen(file)) { + updateFileIcon(file); + updateFileColor(file); + updateFileBackgroundColor(file); + } + + } + }); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/MouseShortcutDialog.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/MouseShortcutDialog.java index e757bcc9a2f4..bc572d63d572 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/MouseShortcutDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/MouseShortcutDialog.java @@ -1,286 +1,286 @@ -/* - * 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.keymap.impl.ui; - -import com.intellij.openapi.actionSystem.MouseShortcut; -import com.intellij.openapi.actionSystem.Shortcut; -import com.intellij.openapi.keymap.KeyMapBundle; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.IconLoader; -import com.intellij.ui.IdeBorderFactory; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; - -/** - * @author Vladimir Kondratyev - */ -class MouseShortcutDialog extends DialogWrapper{ - private final Keymap myKeymap; - private final String myActionId; - private final Group myMainGroup; - - private final JRadioButton myRbSingleClick; - private final JRadioButton myRbDoubleClick; - private final JLabel myLblPreview; - private final MyClickPad myClickPad; - private final JTextArea myTarConflicts; - - private int myButton; - private int myModifiers; - - /** - * @param shortcut dialog will be initialized with this shortcut. It can be null - * if dialog is used to create new mouse shortcut. - */ - public MouseShortcutDialog( - JComponent parentComponent, - MouseShortcut shortcut, - @NotNull Keymap keymap, - @NotNull String actiondId, - @NotNull Group mainGroup - ){ - super(parentComponent,true); - setTitle(KeyMapBundle.message("mouse.shortcut.dialog.title")); - - myKeymap=keymap; - myActionId=actiondId; - myMainGroup=mainGroup; - - myRbSingleClick=new JRadioButton(KeyMapBundle.message("mouse.shortcut.dialog.single.click.radio")); - myRbDoubleClick=new JRadioButton(KeyMapBundle.message("mouse.shortcut.dialog.double.click.radio")); - ButtonGroup buttonGroup=new ButtonGroup(); - buttonGroup.add(myRbSingleClick); - buttonGroup.add(myRbDoubleClick); - - myLblPreview=new JLabel(" "); - - myClickPad=new MyClickPad(); - - myTarConflicts=new JTextArea(); - myTarConflicts.setFocusable(false); - myTarConflicts.setEditable(false); - myTarConflicts.setBackground(UIUtil.getPanelBackground()); - myTarConflicts.setLineWrap(true); - myTarConflicts.setWrapStyleWord(true); - - if(shortcut!=null){ - if(shortcut.getClickCount()==1){ - myRbSingleClick.setSelected(true); - }else{ - myRbDoubleClick.setSelected(true); - } - myButton=shortcut.getButton(); - myModifiers=shortcut.getModifiers(); - }else{ - myRbSingleClick.setSelected(true); - myButton=-1; - myModifiers=-1; - } - - updatePreviewAndConflicts(); - - init(); - } - - /** - * @return created/edited shortcut. Returns null if shortcut is invalid. - */ - public MouseShortcut getMouseShortcut(){ - if(myButton!=-1 && myModifiers!=-1){ - return new MouseShortcut(myButton,myModifiers,myRbSingleClick.isSelected()?1:2); - }else{ - return null; - } - } - - @Override - protected String getHelpId() { - return "preferences.mouse.shortcut"; - } - - protected JComponent createCenterPanel(){ - JPanel panel=new JPanel(new GridBagLayout()); - - // Single/Double click - - JPanel clickCountPanel=new JPanel(new GridBagLayout()); - clickCountPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.click.count.border"))); - panel.add( - clickCountPanel, - new GridBagConstraints(0,0,1,1,1,0,GridBagConstraints.CENTER,GridBagConstraints.HORIZONTAL,new Insets(0,0,4,0),0,0) - ); - clickCountPanel.add( - myRbSingleClick, - new GridBagConstraints(0,0,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(0,0,0,10),0,0) - ); - clickCountPanel.add( - myRbDoubleClick, - new GridBagConstraints(1,0,1,1,1,0,GridBagConstraints.EAST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0) - ); - - ActionListener listener=new ActionListener(){ - public void actionPerformed(ActionEvent e){ - updatePreviewAndConflicts(); - } - }; - myRbSingleClick.addActionListener(listener); - myRbDoubleClick.addActionListener(listener); - - // Click pad - - JPanel clickPadPanel=new JPanel(new BorderLayout()); - panel.add( - clickPadPanel, - new GridBagConstraints(0,1,1,1,1,0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,4,0),0,0) - ); - clickPadPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.click.pad.border"))); - myClickPad.setPreferredSize(new Dimension(260,60)); - clickPadPanel.add(myClickPad,BorderLayout.CENTER); - - // Shortcut preview - - JPanel previewPanel=new JPanel(new GridBagLayout()); - previewPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.shortcut.preview.border"))); - panel.add( - previewPanel, - new GridBagConstraints(0,2,1,1,1,0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,4,0),0,0) - ); - previewPanel.add( - myLblPreview, - new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(2,2,2,2),0,0) - ); - - // Conflicts panel - - JPanel conflictsPanel=new JPanel(new GridBagLayout()); - conflictsPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.conflicts.border"))); - panel.add( - conflictsPanel, - new GridBagConstraints(0,3,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0) - ); - myTarConflicts.setPreferredSize(new Dimension(260,60)); - JScrollPane scrollPane= ScrollPaneFactory.createScrollPane(myTarConflicts); - scrollPane.setBorder(null); - conflictsPanel.add( - scrollPane, - new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0) - ); - - return panel; - } - - /** - * Updates all UI controls - */ - private void updatePreviewAndConflicts(){ - if(myButton==-1||myModifiers==-1){ - return; - } - - myTarConflicts.setText(null); - - // Set text into preview area - - // empty string should have same height - myLblPreview.setText(KeymapUtil.getMouseShortcutText(myButton,myModifiers,myRbSingleClick.isSelected()?1:2) + " "); - - // Detect conflicts - - final MouseShortcut mouseShortcut; - if(myRbSingleClick.isSelected()){ - mouseShortcut=new MouseShortcut(myButton,myModifiers,1); - }else{ - mouseShortcut=new MouseShortcut(myButton,myModifiers,2); - } - - StringBuilder buffer = new StringBuilder(); - String[] actionIds = myKeymap.getActionIds(mouseShortcut); - for (String actionId : actionIds) { - if (actionId.equals(myActionId)) { - continue; - } - - String actionPath = myMainGroup.getActionQualifiedPath(actionId); - // actionPath == null for editor actions having corresponding $-actions - if (actionPath == null) { - continue; - } - - Shortcut[] shortcuts = myKeymap.getShortcuts(actionId); - for (Shortcut shortcut1 : shortcuts) { - if (!(shortcut1 instanceof MouseShortcut)) { - continue; - } - - MouseShortcut shortcut = (MouseShortcut)shortcut1; - - if (shortcut.getButton() != mouseShortcut.getButton() || shortcut.getModifiers() != mouseShortcut.getModifiers()) { - continue; - } - - if (buffer.length() > 1) { - buffer.append('\n'); - } - buffer.append('['); - buffer.append(actionPath); - buffer.append(']'); - break; - } - } - - if (buffer.length() == 0) { - myTarConflicts.setForeground(UIUtil.getTextAreaForeground()); - myTarConflicts.setText(KeyMapBundle.message("mouse.shortcut.dialog.no.conflicts.area")); - } - else { - myTarConflicts.setForeground(Color.red); - myTarConflicts.setText(KeyMapBundle.message("mouse.shortcut.dialog.assigned.to.area", buffer.toString())); - } - } - - private class MyClickPad extends JLabel{ - public MyClickPad(){ - super( - KeyMapBundle.message("mouse.shortcut.label"), - IconLoader.getIcon("/general/mouse.png"), SwingConstants.CENTER - ); - // It's very imporatant that MouseListener is added to the Dialog. If you add - // the same listener, for example, into the MyClickPad component you get fake - // Alt and Meta modifiers. I means that pressing of middle button causes - // Alt+Button2 event. - // See bug ID 4109826 on Sun's bug parade. - //cast is needed in order to compile with mustang - MouseShortcutDialog.this.addMouseListener((MouseListener)new MouseAdapter(){ - public void mouseReleased(MouseEvent e){ - Component component= SwingUtilities.getDeepestComponentAt(e.getComponent(),e.getX(),e.getY()); - if(component== MyClickPad.this){ - e.consume(); - myButton=e.getButton(); - myModifiers=e.getModifiersEx(); - updatePreviewAndConflicts(); - } - } - }); - } - } -} +/* + * 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.keymap.impl.ui; + +import com.intellij.openapi.actionSystem.MouseShortcut; +import com.intellij.openapi.actionSystem.Shortcut; +import com.intellij.openapi.keymap.KeyMapBundle; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.IconLoader; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +/** + * @author Vladimir Kondratyev + */ +class MouseShortcutDialog extends DialogWrapper{ + private final Keymap myKeymap; + private final String myActionId; + private final Group myMainGroup; + + private final JRadioButton myRbSingleClick; + private final JRadioButton myRbDoubleClick; + private final JLabel myLblPreview; + private final MyClickPad myClickPad; + private final JTextArea myTarConflicts; + + private int myButton; + private int myModifiers; + + /** + * @param shortcut dialog will be initialized with this shortcut. It can be null + * if dialog is used to create new mouse shortcut. + */ + public MouseShortcutDialog( + JComponent parentComponent, + MouseShortcut shortcut, + @NotNull Keymap keymap, + @NotNull String actiondId, + @NotNull Group mainGroup + ){ + super(parentComponent,true); + setTitle(KeyMapBundle.message("mouse.shortcut.dialog.title")); + + myKeymap=keymap; + myActionId=actiondId; + myMainGroup=mainGroup; + + myRbSingleClick=new JRadioButton(KeyMapBundle.message("mouse.shortcut.dialog.single.click.radio")); + myRbDoubleClick=new JRadioButton(KeyMapBundle.message("mouse.shortcut.dialog.double.click.radio")); + ButtonGroup buttonGroup=new ButtonGroup(); + buttonGroup.add(myRbSingleClick); + buttonGroup.add(myRbDoubleClick); + + myLblPreview=new JLabel(" "); + + myClickPad=new MyClickPad(); + + myTarConflicts=new JTextArea(); + myTarConflicts.setFocusable(false); + myTarConflicts.setEditable(false); + myTarConflicts.setBackground(UIUtil.getPanelBackground()); + myTarConflicts.setLineWrap(true); + myTarConflicts.setWrapStyleWord(true); + + if(shortcut!=null){ + if(shortcut.getClickCount()==1){ + myRbSingleClick.setSelected(true); + }else{ + myRbDoubleClick.setSelected(true); + } + myButton=shortcut.getButton(); + myModifiers=shortcut.getModifiers(); + }else{ + myRbSingleClick.setSelected(true); + myButton=-1; + myModifiers=-1; + } + + updatePreviewAndConflicts(); + + init(); + } + + /** + * @return created/edited shortcut. Returns null if shortcut is invalid. + */ + public MouseShortcut getMouseShortcut(){ + if(myButton!=-1 && myModifiers!=-1){ + return new MouseShortcut(myButton,myModifiers,myRbSingleClick.isSelected()?1:2); + }else{ + return null; + } + } + + @Override + protected String getHelpId() { + return "preferences.mouse.shortcut"; + } + + protected JComponent createCenterPanel(){ + JPanel panel=new JPanel(new GridBagLayout()); + + // Single/Double click + + JPanel clickCountPanel=new JPanel(new GridBagLayout()); + clickCountPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.click.count.border"))); + panel.add( + clickCountPanel, + new GridBagConstraints(0,0,1,1,1,0,GridBagConstraints.CENTER,GridBagConstraints.HORIZONTAL,new Insets(0,0,4,0),0,0) + ); + clickCountPanel.add( + myRbSingleClick, + new GridBagConstraints(0,0,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.NONE,new Insets(0,0,0,10),0,0) + ); + clickCountPanel.add( + myRbDoubleClick, + new GridBagConstraints(1,0,1,1,1,0,GridBagConstraints.EAST,GridBagConstraints.NONE,new Insets(0,0,0,0),0,0) + ); + + ActionListener listener=new ActionListener(){ + public void actionPerformed(ActionEvent e){ + updatePreviewAndConflicts(); + } + }; + myRbSingleClick.addActionListener(listener); + myRbDoubleClick.addActionListener(listener); + + // Click pad + + JPanel clickPadPanel=new JPanel(new BorderLayout()); + panel.add( + clickPadPanel, + new GridBagConstraints(0,1,1,1,1,0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,4,0),0,0) + ); + clickPadPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.click.pad.border"))); + myClickPad.setPreferredSize(new Dimension(260,60)); + clickPadPanel.add(myClickPad,BorderLayout.CENTER); + + // Shortcut preview + + JPanel previewPanel=new JPanel(new GridBagLayout()); + previewPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.shortcut.preview.border"))); + panel.add( + previewPanel, + new GridBagConstraints(0,2,1,1,1,0,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,4,0),0,0) + ); + previewPanel.add( + myLblPreview, + new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(2,2,2,2),0,0) + ); + + // Conflicts panel + + JPanel conflictsPanel=new JPanel(new GridBagLayout()); + conflictsPanel.setBorder(IdeBorderFactory.createTitledBorder(KeyMapBundle.message("mouse.shortcut.dialog.conflicts.border"))); + panel.add( + conflictsPanel, + new GridBagConstraints(0,3,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0) + ); + myTarConflicts.setPreferredSize(new Dimension(260,60)); + JScrollPane scrollPane= ScrollPaneFactory.createScrollPane(myTarConflicts); + scrollPane.setBorder(null); + conflictsPanel.add( + scrollPane, + new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(0,0,0,0),0,0) + ); + + return panel; + } + + /** + * Updates all UI controls + */ + private void updatePreviewAndConflicts(){ + if(myButton==-1||myModifiers==-1){ + return; + } + + myTarConflicts.setText(null); + + // Set text into preview area + + // empty string should have same height + myLblPreview.setText(KeymapUtil.getMouseShortcutText(myButton,myModifiers,myRbSingleClick.isSelected()?1:2) + " "); + + // Detect conflicts + + final MouseShortcut mouseShortcut; + if(myRbSingleClick.isSelected()){ + mouseShortcut=new MouseShortcut(myButton,myModifiers,1); + }else{ + mouseShortcut=new MouseShortcut(myButton,myModifiers,2); + } + + StringBuilder buffer = new StringBuilder(); + String[] actionIds = myKeymap.getActionIds(mouseShortcut); + for (String actionId : actionIds) { + if (actionId.equals(myActionId)) { + continue; + } + + String actionPath = myMainGroup.getActionQualifiedPath(actionId); + // actionPath == null for editor actions having corresponding $-actions + if (actionPath == null) { + continue; + } + + Shortcut[] shortcuts = myKeymap.getShortcuts(actionId); + for (Shortcut shortcut1 : shortcuts) { + if (!(shortcut1 instanceof MouseShortcut)) { + continue; + } + + MouseShortcut shortcut = (MouseShortcut)shortcut1; + + if (shortcut.getButton() != mouseShortcut.getButton() || shortcut.getModifiers() != mouseShortcut.getModifiers()) { + continue; + } + + if (buffer.length() > 1) { + buffer.append('\n'); + } + buffer.append('['); + buffer.append(actionPath); + buffer.append(']'); + break; + } + } + + if (buffer.length() == 0) { + myTarConflicts.setForeground(UIUtil.getTextAreaForeground()); + myTarConflicts.setText(KeyMapBundle.message("mouse.shortcut.dialog.no.conflicts.area")); + } + else { + myTarConflicts.setForeground(Color.red); + myTarConflicts.setText(KeyMapBundle.message("mouse.shortcut.dialog.assigned.to.area", buffer.toString())); + } + } + + private class MyClickPad extends JLabel{ + public MyClickPad(){ + super( + KeyMapBundle.message("mouse.shortcut.label"), + IconLoader.getIcon("/general/mouse.png"), SwingConstants.CENTER + ); + // It's very imporatant that MouseListener is added to the Dialog. If you add + // the same listener, for example, into the MyClickPad component you get fake + // Alt and Meta modifiers. I means that pressing of middle button causes + // Alt+Button2 event. + // See bug ID 4109826 on Sun's bug parade. + //cast is needed in order to compile with mustang + MouseShortcutDialog.this.addMouseListener((MouseListener)new MouseAdapter(){ + public void mouseReleased(MouseEvent e){ + Component component= SwingUtilities.getDeepestComponentAt(e.getComponent(),e.getX(),e.getY()); + if(component== MyClickPad.this){ + e.consume(); + myButton=e.getButton(); + myModifiers=e.getModifiersEx(); + updatePreviewAndConflicts(); + } + } + }); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.java index a65f11a4c980..1ab3b908604a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.java @@ -1,372 +1,372 @@ -/* - * 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.keymap.impl.ui; - -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.Separator; -import com.intellij.openapi.actionSystem.ex.QuickList; -import com.intellij.openapi.actionSystem.ex.QuickListsManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.IconLoader; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.JBDefaultTreeCellRenderer; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; - -public class QuickListPanel { - private static final Icon EMPTY_ICON = new EmptyIcon(18, 18); - - private static final Icon QUICK_LIST_ICON = IconLoader.getIcon("/actions/quickList.png"); - private JButton myRemoveActionButton; - private JButton myIncludeActionButton; - private JButton myMoveActionDownButton; - private JButton myMoveActionUpButton; - private JPanel myPanel; - private JTree myActionsTree; - private JList myActionsList; - private JTextField myDisplayName; - private JTextField myDescription; - private JButton myAddSeparatorButton; - private final boolean myEditable; - - public QuickListPanel(QuickList origin, final QuickList[] allQuickLists, Project project) { - myEditable = !QuickListsManager.getInstance().getSchemesManager().isShared(origin); - Group rootGroup = ActionsTreeUtil.createMainGroup(project, null, allQuickLists); - DefaultMutableTreeNode root = ActionsTreeUtil.createNode(rootGroup); - DefaultTreeModel model = new DefaultTreeModel(root); - myActionsTree.setModel(model); - myActionsTree.setCellRenderer(new MyTreeCellRenderer(myActionsTree)); - - myActionsList.setModel(new DefaultListModel()); - myActionsList.setCellRenderer(new MyListCellRenderer()); - - myActionsTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { - public void valueChanged(TreeSelectionEvent e) { - update(); - } - }); - - myActionsTree.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2 && !e.isPopupTrigger()) { - includeSelectedAction(); - } - } - }); - - myActionsList.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2 && !e.isPopupTrigger()) { - excludeSelectionAction(); - } - } - }); - - myActionsList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - update(); - } - }); - - myActionsTree.registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - includeSelectedAction(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - myIncludeActionButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - includeSelectedAction(); - } - }); - - myAddSeparatorButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - addSeparator(); - } - }); - - myActionsList.registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - excludeSelectionAction(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - myRemoveActionButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - excludeSelectionAction(); - } - }); - - myMoveActionUpButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - int idx = myActionsList.getSelectedIndex(); - if (idx > 0) { - DefaultListModel listModel = (DefaultListModel)myActionsList.getModel(); - Object oldValue = listModel.get(idx); - listModel.removeElementAt(idx); - listModel.add(--idx, oldValue); - myActionsList.getSelectionModel().setSelectionInterval(idx, idx); - } - } - }); - - myMoveActionDownButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - int idx = myActionsList.getSelectedIndex(); - DefaultListModel listModel = (DefaultListModel)myActionsList.getModel(); - if (idx < listModel.getSize() - 1) { - Object oldValue = listModel.get(idx); - listModel.removeElementAt(idx); - listModel.add(++idx, oldValue); - myActionsList.getSelectionModel().setSelectionInterval(idx, idx); - } - } - }); - - myDisplayName.setText(origin.getDisplayName()); - myDescription.setText(origin.getDescription()); - - String[] ids = origin.getActionIds(); - for (String id : ids) { - includeActionId(id); - } - - update(); - } - - public void addNameListener(DocumentAdapter adapter){ - myDisplayName.getDocument().addDocumentListener(adapter); - } - - public void addDescriptionListener(final DocumentAdapter adapter) { - myDescription.getDocument().addDocumentListener(adapter); - } - - private void excludeSelectionAction() { - int[] ids = myActionsList.getSelectedIndices(); - for (int i = ids.length - 1; i >=0; i--) { - ((DefaultListModel)myActionsList.getModel()).remove(ids[i]); - } - update(); - } - - private void includeSelectedAction() { - String[] ids = getTreeSelectedActionIds(); - for (String id : ids) { - includeActionId(id); - } - DefaultListModel listModel = (DefaultListModel)myActionsList.getModel(); - int size = listModel.getSize(); - ListSelectionModel selectionModel = myActionsList.getSelectionModel(); - if (size > 0) { - selectionModel.removeIndexInterval(0, size - 1); - } - for (String id1 : ids) { - int idx = listModel.lastIndexOf(id1); - if (idx >= 0) { - selectionModel.addSelectionInterval(idx, idx); - } - } - update(); - } - - private void addSeparator() { - DefaultListModel model = (DefaultListModel)myActionsList.getModel(); - model.addElement(QuickList.SEPARATOR_ID); - update(); - } - - public JList getActionsList() { - return myActionsList; - } - - public String getDescription() { - return myDescription.getText(); - } - - public String getDisplayName() { - return myDisplayName.getText(); - } - - private void update() { - if (myEditable) { - myIncludeActionButton.setEnabled(getTreeSelectedActionIds().length > 0); - myRemoveActionButton.setEnabled(myActionsList.getSelectedValues().length > 0); - boolean enableMove = myActionsList.getSelectedValues().length == 1; - myMoveActionUpButton.setEnabled(enableMove && myActionsList.getSelectedIndex() > 0); - myMoveActionDownButton.setEnabled(enableMove && myActionsList.getSelectedIndex() < myActionsList.getModel().getSize() - 1); - } - else { - myIncludeActionButton.setEnabled(false); - myRemoveActionButton.setEnabled(false); - myMoveActionUpButton.setEnabled(false); - myMoveActionDownButton.setEnabled(false); - myAddSeparatorButton.setEnabled(false); - - } - } - - private void includeActionId(String id) { - DefaultListModel model = (DefaultListModel)myActionsList.getModel(); - if (!QuickList.SEPARATOR_ID.equals(id) && model.contains(id)) return; - model.addElement(id); - } - - private String[] getTreeSelectedActionIds() { - TreePath[] paths = myActionsTree.getSelectionPaths(); - if (paths == null) return ArrayUtil.EMPTY_STRING_ARRAY; - - ArrayList actions = new ArrayList(); - for (TreePath path : paths) { - Object node = path.getLastPathComponent(); - if (node instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode defNode = (DefaultMutableTreeNode)node; - Object userObject = defNode.getUserObject(); - if (userObject instanceof String) { - actions.add((String)userObject); - } - else if (userObject instanceof QuickList) { - actions.add(((QuickList)userObject).getActionId()); - } - } - } - return ArrayUtil.toStringArray(actions); - } - - public JPanel getPanel() { - return myPanel; - } - - private class MyTreeCellRenderer extends JBDefaultTreeCellRenderer { - private MyTreeCellRenderer(@NotNull JTree tree) { - super(tree); - } - - public Component getTreeCellRendererComponent(JTree tree, - Object value, - boolean sel, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); - Icon icon = null; - if (value instanceof DefaultMutableTreeNode) { - boolean used = false; - Object userObject = ((DefaultMutableTreeNode)value).getUserObject(); - if (userObject instanceof Group) { - Group group = (Group)userObject; - final String name = group.getName(); - setText(name != null ? name : group.getId()); - icon = expanded ? group.getOpenIcon() : group.getIcon(); - - if (icon == null) { - icon = expanded ? getOpenIcon() : getClosedIcon(); - } - - - } - else if (userObject instanceof String) { - String actionId = (String)userObject; - used = ((DefaultListModel)myActionsList.getModel()).lastIndexOf(actionId) >= 0; - AnAction action = ActionManager.getInstance().getAction(actionId); - String text = action == null ? actionId : action.getTemplatePresentation().getText(); - if (text == null || text.length() == 0) text = actionId; - setText(text); - if (action != null) { - Icon actionIcon = action.getTemplatePresentation().getIcon(); - if (actionIcon != null) { - icon = actionIcon; - } - } - } - else if (userObject instanceof QuickList) { - QuickList list = (QuickList)userObject; - icon = QUICK_LIST_ICON; - setText(list.getDisplayName()); - used = ((DefaultListModel)myActionsList.getModel()).lastIndexOf(list.getActionId()) >= 0; - } - else if (userObject instanceof Separator) { - // TODO[vova,anton]: beautify - setText("-------------"); - - } - else { - throw new IllegalArgumentException("unknown userObject: " + userObject); - } - - setIcon(ActionsTree.getEvenIcon(icon)); - - if (sel) { - setForeground(getSelectionForeground(tree)); - } - else { - Color foreground = used ? UIUtil.getInactiveTextColor() :UIUtil.getTreeForeground(); - setForeground(foreground); - } - } - return this; - } - } - - private static class MyListCellRenderer extends DefaultListCellRenderer { - public Component getListCellRendererComponent(JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus) { - super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - Icon icon = null; - String actionId = (String)value; - if (QuickList.SEPARATOR_ID.equals(actionId)) { - // TODO[vova,anton]: beautify - setText("-------------"); - } - else { - AnAction action = ActionManager.getInstance().getAction(actionId); - setText(action != null ? action.getTemplatePresentation().getText() : actionId); - if (action != null) { - Icon actionIcon = action.getTemplatePresentation().getIcon(); - if (actionIcon != null) { - icon = actionIcon; - } - } - if (actionId.startsWith(QuickList.QUICK_LIST_PREFIX)){ - icon = QUICK_LIST_ICON; - } - setIcon(ActionsTree.getEvenIcon(icon)); - } - - return this; - } - } +/* + * 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.keymap.impl.ui; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.Separator; +import com.intellij.openapi.actionSystem.ex.QuickList; +import com.intellij.openapi.actionSystem.ex.QuickListsManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.JBDefaultTreeCellRenderer; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; + +public class QuickListPanel { + private static final Icon EMPTY_ICON = new EmptyIcon(18, 18); + + private static final Icon QUICK_LIST_ICON = IconLoader.getIcon("/actions/quickList.png"); + private JButton myRemoveActionButton; + private JButton myIncludeActionButton; + private JButton myMoveActionDownButton; + private JButton myMoveActionUpButton; + private JPanel myPanel; + private JTree myActionsTree; + private JList myActionsList; + private JTextField myDisplayName; + private JTextField myDescription; + private JButton myAddSeparatorButton; + private final boolean myEditable; + + public QuickListPanel(QuickList origin, final QuickList[] allQuickLists, Project project) { + myEditable = !QuickListsManager.getInstance().getSchemesManager().isShared(origin); + Group rootGroup = ActionsTreeUtil.createMainGroup(project, null, allQuickLists); + DefaultMutableTreeNode root = ActionsTreeUtil.createNode(rootGroup); + DefaultTreeModel model = new DefaultTreeModel(root); + myActionsTree.setModel(model); + myActionsTree.setCellRenderer(new MyTreeCellRenderer(myActionsTree)); + + myActionsList.setModel(new DefaultListModel()); + myActionsList.setCellRenderer(new MyListCellRenderer()); + + myActionsTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + update(); + } + }); + + myActionsTree.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && !e.isPopupTrigger()) { + includeSelectedAction(); + } + } + }); + + myActionsList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && !e.isPopupTrigger()) { + excludeSelectionAction(); + } + } + }); + + myActionsList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + update(); + } + }); + + myActionsTree.registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + includeSelectedAction(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + myIncludeActionButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + includeSelectedAction(); + } + }); + + myAddSeparatorButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + addSeparator(); + } + }); + + myActionsList.registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent e) { + excludeSelectionAction(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + myRemoveActionButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + excludeSelectionAction(); + } + }); + + myMoveActionUpButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int idx = myActionsList.getSelectedIndex(); + if (idx > 0) { + DefaultListModel listModel = (DefaultListModel)myActionsList.getModel(); + Object oldValue = listModel.get(idx); + listModel.removeElementAt(idx); + listModel.add(--idx, oldValue); + myActionsList.getSelectionModel().setSelectionInterval(idx, idx); + } + } + }); + + myMoveActionDownButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int idx = myActionsList.getSelectedIndex(); + DefaultListModel listModel = (DefaultListModel)myActionsList.getModel(); + if (idx < listModel.getSize() - 1) { + Object oldValue = listModel.get(idx); + listModel.removeElementAt(idx); + listModel.add(++idx, oldValue); + myActionsList.getSelectionModel().setSelectionInterval(idx, idx); + } + } + }); + + myDisplayName.setText(origin.getDisplayName()); + myDescription.setText(origin.getDescription()); + + String[] ids = origin.getActionIds(); + for (String id : ids) { + includeActionId(id); + } + + update(); + } + + public void addNameListener(DocumentAdapter adapter){ + myDisplayName.getDocument().addDocumentListener(adapter); + } + + public void addDescriptionListener(final DocumentAdapter adapter) { + myDescription.getDocument().addDocumentListener(adapter); + } + + private void excludeSelectionAction() { + int[] ids = myActionsList.getSelectedIndices(); + for (int i = ids.length - 1; i >=0; i--) { + ((DefaultListModel)myActionsList.getModel()).remove(ids[i]); + } + update(); + } + + private void includeSelectedAction() { + String[] ids = getTreeSelectedActionIds(); + for (String id : ids) { + includeActionId(id); + } + DefaultListModel listModel = (DefaultListModel)myActionsList.getModel(); + int size = listModel.getSize(); + ListSelectionModel selectionModel = myActionsList.getSelectionModel(); + if (size > 0) { + selectionModel.removeIndexInterval(0, size - 1); + } + for (String id1 : ids) { + int idx = listModel.lastIndexOf(id1); + if (idx >= 0) { + selectionModel.addSelectionInterval(idx, idx); + } + } + update(); + } + + private void addSeparator() { + DefaultListModel model = (DefaultListModel)myActionsList.getModel(); + model.addElement(QuickList.SEPARATOR_ID); + update(); + } + + public JList getActionsList() { + return myActionsList; + } + + public String getDescription() { + return myDescription.getText(); + } + + public String getDisplayName() { + return myDisplayName.getText(); + } + + private void update() { + if (myEditable) { + myIncludeActionButton.setEnabled(getTreeSelectedActionIds().length > 0); + myRemoveActionButton.setEnabled(myActionsList.getSelectedValues().length > 0); + boolean enableMove = myActionsList.getSelectedValues().length == 1; + myMoveActionUpButton.setEnabled(enableMove && myActionsList.getSelectedIndex() > 0); + myMoveActionDownButton.setEnabled(enableMove && myActionsList.getSelectedIndex() < myActionsList.getModel().getSize() - 1); + } + else { + myIncludeActionButton.setEnabled(false); + myRemoveActionButton.setEnabled(false); + myMoveActionUpButton.setEnabled(false); + myMoveActionDownButton.setEnabled(false); + myAddSeparatorButton.setEnabled(false); + + } + } + + private void includeActionId(String id) { + DefaultListModel model = (DefaultListModel)myActionsList.getModel(); + if (!QuickList.SEPARATOR_ID.equals(id) && model.contains(id)) return; + model.addElement(id); + } + + private String[] getTreeSelectedActionIds() { + TreePath[] paths = myActionsTree.getSelectionPaths(); + if (paths == null) return ArrayUtil.EMPTY_STRING_ARRAY; + + ArrayList actions = new ArrayList(); + for (TreePath path : paths) { + Object node = path.getLastPathComponent(); + if (node instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode defNode = (DefaultMutableTreeNode)node; + Object userObject = defNode.getUserObject(); + if (userObject instanceof String) { + actions.add((String)userObject); + } + else if (userObject instanceof QuickList) { + actions.add(((QuickList)userObject).getActionId()); + } + } + } + return ArrayUtil.toStringArray(actions); + } + + public JPanel getPanel() { + return myPanel; + } + + private class MyTreeCellRenderer extends JBDefaultTreeCellRenderer { + private MyTreeCellRenderer(@NotNull JTree tree) { + super(tree); + } + + public Component getTreeCellRendererComponent(JTree tree, + Object value, + boolean sel, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + Icon icon = null; + if (value instanceof DefaultMutableTreeNode) { + boolean used = false; + Object userObject = ((DefaultMutableTreeNode)value).getUserObject(); + if (userObject instanceof Group) { + Group group = (Group)userObject; + final String name = group.getName(); + setText(name != null ? name : group.getId()); + icon = expanded ? group.getOpenIcon() : group.getIcon(); + + if (icon == null) { + icon = expanded ? getOpenIcon() : getClosedIcon(); + } + + + } + else if (userObject instanceof String) { + String actionId = (String)userObject; + used = ((DefaultListModel)myActionsList.getModel()).lastIndexOf(actionId) >= 0; + AnAction action = ActionManager.getInstance().getAction(actionId); + String text = action == null ? actionId : action.getTemplatePresentation().getText(); + if (text == null || text.length() == 0) text = actionId; + setText(text); + if (action != null) { + Icon actionIcon = action.getTemplatePresentation().getIcon(); + if (actionIcon != null) { + icon = actionIcon; + } + } + } + else if (userObject instanceof QuickList) { + QuickList list = (QuickList)userObject; + icon = QUICK_LIST_ICON; + setText(list.getDisplayName()); + used = ((DefaultListModel)myActionsList.getModel()).lastIndexOf(list.getActionId()) >= 0; + } + else if (userObject instanceof Separator) { + // TODO[vova,anton]: beautify + setText("-------------"); + + } + else { + throw new IllegalArgumentException("unknown userObject: " + userObject); + } + + setIcon(ActionsTree.getEvenIcon(icon)); + + if (sel) { + setForeground(getSelectionForeground(tree)); + } + else { + Color foreground = used ? UIUtil.getInactiveTextColor() :UIUtil.getTreeForeground(); + setForeground(foreground); + } + } + return this; + } + } + + private static class MyListCellRenderer extends DefaultListCellRenderer { + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + Icon icon = null; + String actionId = (String)value; + if (QuickList.SEPARATOR_ID.equals(actionId)) { + // TODO[vova,anton]: beautify + setText("-------------"); + } + else { + AnAction action = ActionManager.getInstance().getAction(actionId); + setText(action != null ? action.getTemplatePresentation().getText() : actionId); + if (action != null) { + Icon actionIcon = action.getTemplatePresentation().getIcon(); + if (actionIcon != null) { + icon = actionIcon; + } + } + if (actionId.startsWith(QuickList.QUICK_LIST_PREFIX)){ + icon = QUICK_LIST_ICON; + } + setIcon(ActionsTree.getEvenIcon(icon)); + } + + return this; + } + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java index 2fdf88d31dd3..1bd749692730 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsTree.java @@ -1,815 +1,815 @@ -/* - * 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.options.newEditor; - -import com.intellij.ide.util.treeView.NodeDescriptor; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurableGroup; -import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.ui.*; -import com.intellij.ui.components.panels.NonOpaquePanel; -import com.intellij.ui.treeStructure.*; -import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder; -import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.update.MergingUpdateQueue; -import com.intellij.util.ui.update.Update; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.TreeExpansionEvent; -import javax.swing.event.TreeExpansionListener; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.plaf.TreeUI; -import javax.swing.plaf.basic.BasicTreeUI; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeCellRenderer; -import javax.swing.tree.TreePath; -import javax.swing.tree.TreeSelectionModel; -import java.awt.*; -import java.awt.event.*; -import java.util.*; -import java.util.List; - -public class OptionsTree extends JPanel implements Disposable, OptionsEditorColleague { - Project myProject; - final SimpleTree myTree; - List myGroups; - FilteringTreeBuilder myBuilder; - Root myRoot; - OptionsEditorContext myContext; - - Map myConfigurable2Node = new HashMap(); - - MergingUpdateQueue mySelection; - private final OptionsTree.Renderer myRendrer; - - public OptionsTree(Project project, ConfigurableGroup[] groups, OptionsEditorContext context) { - myProject = project; - myGroups = Arrays.asList(groups); - myContext = context; - - - myRoot = new Root(); - final SimpleTreeStructure structure = new SimpleTreeStructure() { - public Object getRootElement() { - return myRoot; - } - }; - - myTree = new MyTree(); - myTree.setBorder(new EmptyBorder(0, 1, 0, 0)); - - myTree.setRowHeight(-1); - myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); - myRendrer = new Renderer(); - myTree.setCellRenderer(myRendrer); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(false); - myBuilder = new MyBuilder(structure); - myBuilder.setFilteringMerge(300, null); - Disposer.register(this, myBuilder); - - myBuilder.updateFromRoot(); - - setLayout(new BorderLayout()); - - myTree.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(final ComponentEvent e) { - revalidateTree(); - } - - @Override - public void componentMoved(final ComponentEvent e) { - revalidateTree(); - } - - @Override - public void componentShown(final ComponentEvent e) { - revalidateTree(); - } - }); - - final JScrollPane scrolls = ScrollPaneFactory.createScrollPane(myTree); - scrolls.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - add(scrolls, BorderLayout.CENTER); - - mySelection = new MergingUpdateQueue("OptionsTree", 150, false, this, this, this).setRestartTimerOnAdd(true); - myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { - public void valueChanged(final TreeSelectionEvent e) { - final TreePath path = e.getNewLeadSelectionPath(); - if (path == null) { - queueSelection(null); - } - else { - final Base base = extractNode(path.getLastPathComponent()); - queueSelection(base != null ? base.getConfigurable() : null); - } - } - }); - myTree.addKeyListener(new KeyListener() { - public void keyTyped(final KeyEvent e) { - _onTreeKeyEvent(e); - } - - public void keyPressed(final KeyEvent e) { - _onTreeKeyEvent(e); - } - - public void keyReleased(final KeyEvent e) { - _onTreeKeyEvent(e); - } - }); - } - - protected void _onTreeKeyEvent(KeyEvent e) { - final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e); - - final Object action = myTree.getInputMap().get(stroke); - if (action == null) { - onTreeKeyEvent(e); - } - } - - protected void onTreeKeyEvent(KeyEvent e) { - - } - - - ActionCallback select(@Nullable Configurable configurable) { - return queueSelection(configurable); - } - - public void selectFirst() { - for (ConfigurableGroup eachGroup : myGroups) { - final Configurable[] kids = eachGroup.getConfigurables(); - if (kids.length > 0) { - queueSelection(kids[0]); - return; - } - } - } - - ActionCallback queueSelection(final Configurable configurable) { - final ActionCallback callback = new ActionCallback(); - final Update update = new Update(this) { - public void run() { - if (configurable == null) { - myTree.getSelectionModel().clearSelection(); - myContext.fireSelected(null, OptionsTree.this); - } - else { - myBuilder.getReady(this).doWhenDone(new Runnable() { - @Override - public void run() { - final EditorNode editorNode = myConfigurable2Node.get(configurable); - FilteringTreeStructure.Node editorUiNode = myBuilder.getVisibleNodeFor(editorNode); - if (!myBuilder.getSelectedElements().contains(editorUiNode)) { - myBuilder.select(editorUiNode, new Runnable() { - public void run() { - fireSelected(configurable, callback); - } - }); - } else { - myBuilder.scrollSelectionToVisible(new Runnable() { - public void run() { - fireSelected(configurable, callback); - } - }, false); - } - } - }); - } - } - - @Override - public void setRejected() { - super.setRejected(); - callback.setRejected(); - } - }; - mySelection.queue(update); - return callback; - } - - private void fireSelected(Configurable configurable, final ActionCallback callback) { - myContext.fireSelected(configurable, this).doWhenProcessed(new Runnable() { - public void run() { - callback.setDone(); - } - }); - } - - void revalidateTree() { - myTree.invalidate(); - myTree.setRowHeight(myTree.getRowHeight() == -1 ? -2 : -1); - myTree.revalidate(); - myTree.repaint(); - } - - public JTree getTree() { - return myTree; - } - - public List getPathToRoot(final Configurable configurable) { - final ArrayList path = new ArrayList(); - - EditorNode eachNode = myConfigurable2Node.get(configurable); - if (eachNode == null) return path; - - while (eachNode != null) { - path.add(eachNode.getConfigurable()); - final SimpleNode parent = eachNode.getParent(); - if (parent instanceof EditorNode) { - eachNode = (EditorNode)parent; - } - else { - break; - } - } - - return path; - } - - @Nullable - public Configurable getParentFor(final Configurable configurable) { - final List path = getPathToRoot(configurable); - if (path.size() > 1) { - return path.get(1); - } - else { - return null; - } - } - - public SimpleNode findNodeFor(final Configurable toSelect) { - return myConfigurable2Node.get(toSelect); - } - - @Nullable - public T findConfigurable(Class configurableClass) { - for (Configurable configurable : myConfigurable2Node.keySet()) { - if (configurableClass.isInstance(configurable)) { - return configurableClass.cast(configurable); - } - } - return null; - } - - class Renderer extends GroupedElementsRenderer.Tree implements TreeCellRenderer { - - - private JLabel myHandle; - - @Override - protected void layout() { - myRendererComponent.setOpaqueActive(false); - - myRendererComponent.add(mySeparatorComponent, BorderLayout.NORTH); - - final NonOpaquePanel content = new NonOpaquePanel(new BorderLayout()); - myHandle = new JLabel("", JLabel.CENTER); - if (!SystemInfo.isMac) { - myHandle.setBorder(new EmptyBorder(0, 2, 0, 2)); - } - myHandle.setOpaque(false); - content.add(myHandle, BorderLayout.WEST); - content.add(myComponent, BorderLayout.CENTER); - myRendererComponent.add(content, BorderLayout.CENTER); - } - - public Component getTreeCellRendererComponent(final JTree tree, - final Object value, - final boolean selected, - final boolean expanded, - final boolean leaf, - final int row, - final boolean hasFocus) { - - - JComponent result; - Color fg = UIUtil.getTreeTextForeground(); - - final Base base = extractNode(value); - if (base instanceof EditorNode) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - - final EditorNode editor = (EditorNode)base; - ConfigurableGroup group = null; - if (editor.getParent() == myRoot) { - final DefaultMutableTreeNode prevValue = ((DefaultMutableTreeNode)value).getPreviousSibling(); - if (prevValue == null || prevValue instanceof LoadingNode) { - group = editor.getGroup(); - } - else { - final Base prevBase = extractNode(prevValue); - if (prevBase instanceof EditorNode) { - final EditorNode prevEditor = (EditorNode)prevBase; - if (prevEditor.getGroup() != editor.getGroup()) { - group = editor.getGroup(); - } - } - } - } - - int forcedWidth = 2000; - TreePath path = tree.getPathForRow(row); - if (path == null) { - if (value instanceof DefaultMutableTreeNode) { - path = new TreePath(((DefaultMutableTreeNode)value).getPath()); - } - } - - final boolean toStretch = tree.isVisible() && path != null; - - if (toStretch) { - final Rectangle visibleRect = tree.getVisibleRect(); - - int nestingLevel = tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2; - - final int left = UIUtil.getTreeLeftChildIndent(); - final int right = UIUtil.getTreeRightChildIndent(); - - final Insets treeInsets = tree.getInsets(); - - int indent = (left + right) * nestingLevel + (treeInsets != null ? treeInsets.left + treeInsets.right : 0); - - forcedWidth = visibleRect.width > 0 ? visibleRect.width - indent : forcedWidth; - } - - result = configureComponent(base.getText(), base.getText(), null, null, row == -1 ? true : selected, group != null, - group != null ? group.getDisplayName() : null, forcedWidth - 4); - - - if (base.isError()) { - fg = Color.red; - } - else if (base.isModified()) { - fg = Color.blue; - } - - } - else { - result = configureComponent(value.toString(), null, null, null, selected, false, null, -1); - } - - if (value instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; - TreePath nodePath = new TreePath(node.getPath()); - myHandle.setIcon(((SimpleTree)tree).getHandleIcon(node, nodePath)); - } else { - myHandle.setIcon(null); - } - - - myTextLabel.setForeground(selected ? UIUtil.getTreeSelectionForeground() : fg); - - myTextLabel.setOpaque(selected); - - return result; - } - - protected JComponent createItemComponent() { - myTextLabel = new ErrorLabel(); - return myTextLabel; - } - - public boolean isUnderHandle(final Point point) { - final Point handlePoint = SwingUtilities.convertPoint(myRendererComponent, point, myHandle); - final Rectangle bounds = myHandle.getBounds(); - return bounds.x < handlePoint.x && bounds.getMaxX() >= handlePoint.x; - } - } - - @Nullable - private Base extractNode(Object object) { - if (object instanceof DefaultMutableTreeNode) { - final DefaultMutableTreeNode uiNode = (DefaultMutableTreeNode)object; - final Object o = uiNode.getUserObject(); - if (o instanceof FilteringTreeStructure.Node) { - return (Base)((FilteringTreeStructure.Node)o).getDelegate(); - } - } - - return null; - } - - abstract class Base extends CachingSimpleNode { - - protected Base(final SimpleNode aParent) { - super(aParent); - } - - String getText() { - return null; - } - - boolean isModified() { - return false; - } - - boolean isError() { - return false; - } - - Configurable getConfigurable() { - return null; - } - } - - class Root extends Base { - - Root() { - super(null); - } - - protected SimpleNode[] buildChildren() { - List result = new ArrayList(); - for (ConfigurableGroup eachGroup : myGroups) { - result.addAll(buildGroup(eachGroup)); - } - - return result.isEmpty() ? NO_CHILDREN : result.toArray(new SimpleNode[result.size()]); - } - - private List buildGroup(final ConfigurableGroup eachGroup) { - List result = new ArrayList(); - final Configurable[] kids = eachGroup.getConfigurables(); - if (kids.length > 0) { - for (Configurable eachKid : kids) { - if (isInvisibleNode(eachKid)) { - result.addAll(OptionsTree.this.buildChildren(eachKid, this, eachGroup)); - } - else { - result.add(new EditorNode(this, eachKid, eachGroup)); - } - } - - } - return sort(result); - } - } - - private static boolean isInvisibleNode(final Configurable child) { - return child instanceof SearchableConfigurable.Parent && !((SearchableConfigurable.Parent)child).isVisible(); - } - - private static List sort(List c) { - List cc = new ArrayList(c); - Collections.sort(cc, new Comparator() { - public int compare(final EditorNode o1, final EditorNode o2) { - return getConfigurableDisplayName(o1.getConfigurable()).compareToIgnoreCase(getConfigurableDisplayName(o2.getConfigurable())); - } - }); - return cc; - } - - private static String getConfigurableDisplayName(final Configurable c) { - final String name = c.getDisplayName(); - return name != null ? name : "{ Unnamed Page:" + c.getClass().getSimpleName() + " }"; - } - - private List buildChildren(final Configurable configurable, SimpleNode parent, final ConfigurableGroup group) { - if (configurable instanceof Configurable.Composite) { - final Configurable[] kids = ((Configurable.Composite)configurable).getConfigurables(); - final List result = new ArrayList(kids.length); - for (Configurable child : kids) { - if (isInvisibleNode(child)) { - result.addAll(buildChildren(child, parent, group)); - } - result.add(new EditorNode(parent, child, group)); - myContext.registerKid(configurable, child); - } - return result; // TODO: DECIDE IF INNERS SHOULD BE SORTED: sort(result); - } - else { - return Collections.emptyList(); - } - } - - private static final EditorNode[] EMPTY_EN_ARRAY = new EditorNode[0]; - class EditorNode extends Base { - Configurable myConfigurable; - ConfigurableGroup myGroup; - - EditorNode(SimpleNode parent, Configurable configurable, @Nullable ConfigurableGroup group) { - super(parent); - myConfigurable = configurable; - myGroup = group; - myConfigurable2Node.put(configurable, this); - addPlainText(getConfigurableDisplayName(configurable)); - } - - protected EditorNode[] buildChildren() { - List list = OptionsTree.this.buildChildren(myConfigurable, this, null); - return list.isEmpty() ? EMPTY_EN_ARRAY : list.toArray(new EditorNode[list.size()]); - } - - @Override - public boolean isContentHighlighted() { - return getParent() == myRoot; - } - - @Override - Configurable getConfigurable() { - return myConfigurable; - } - - @Override - public int getWeight() { - if (getParent() == myRoot) { - return Integer.MAX_VALUE - myGroups.indexOf(myGroup); - } - else { - return WeightBasedComparator.UNDEFINED_WEIGHT; - } - } - - public ConfigurableGroup getGroup() { - return myGroup; - } - - @Override - String getText() { - return getConfigurableDisplayName(myConfigurable).replace("\n", " "); - } - - @Override - boolean isModified() { - return myContext.getModified().contains(myConfigurable); - } - - @Override - boolean isError() { - return myContext.getErrors().containsKey(myConfigurable); - } - } - - public void dispose() { - } - - public ActionCallback onSelected(final Configurable configurable, final Configurable oldConfigurable) { - return queueSelection(configurable); - } - - public ActionCallback onModifiedAdded(final Configurable colleague) { - myTree.repaint(); - return new ActionCallback.Done(); - } - - public ActionCallback onModifiedRemoved(final Configurable configurable) { - myTree.repaint(); - return new ActionCallback.Done(); - } - - public ActionCallback onErrorsChanged() { - return new ActionCallback.Done(); - } - - public void processTextEvent(KeyEvent e) { - myTree.processKeyEvent(e); - } - - private class MyTree extends SimpleTree { - - private MyTree() { - getInputMap().clear(); - } - - @Override - protected boolean highlightSingleNode() { - return false; - } - - @Override - public void setUI(final TreeUI ui) { - TreeUI actualUI = ui; - if (!(ui instanceof MyTreeUi)) { - actualUI = new MyTreeUi(); - } - super.setUI(actualUI); - } - - @Override - protected boolean isCustomUI() { - return true; - } - - @Override - protected void configureUiHelper(final TreeUIHelper helper) { - } - - @Override - public boolean getScrollableTracksViewportWidth() { - return true; - } - - - @Override - public void processKeyEvent(final KeyEvent e) { - TreePath path = myTree.getSelectionPath(); - if (path != null) { - if (e.getKeyCode() == KeyEvent.VK_LEFT) { - if (isExpanded(path)) { - collapsePath(path); - return; - } - } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { - if (isCollapsed(path)) { - expandPath(path); - return; - } - } - } - - super.processKeyEvent(e); - } - - @Override - protected void processMouseEvent(final MouseEvent e) { - final MyTreeUi ui = (MyTreeUi)myTree.getUI(); - if (e.getID() == MouseEvent.MOUSE_RELEASED && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !ui.isToggleEvent(e)) { - final TreePath path = getPathForLocation(e.getX(), e.getY()); - if (path != null) { - final Rectangle bounds = getPathBounds(path); - if (bounds != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - final boolean selected = isPathSelected(path); - final boolean expanded = isExpanded(path); - final Component comp = - myRendrer.getTreeCellRendererComponent(this, node, selected, expanded, node.isLeaf(), getRowForPath(path), isFocusOwner()); - - comp.setBounds(bounds); - comp.validate(); - - Point point = new Point(e.getX() - bounds.x, e.getY() - bounds.y); - if (myRendrer.isUnderHandle(point)) { - ui.toggleExpandState(path); - e.consume(); - return; - } - } - } - } - - super.processMouseEvent(e); - } - - private class MyTreeUi extends BasicTreeUI { - - @Override - public void toggleExpandState(final TreePath path) { - super.toggleExpandState(path); - } - - @Override - public boolean isToggleEvent(final MouseEvent event) { - return super.isToggleEvent(event); - } - - @Override - protected boolean shouldPaintExpandControl(final TreePath path, - final int row, - final boolean isExpanded, - final boolean hasBeenExpanded, - final boolean isLeaf) { - return false; - } - - @Override - protected void paintHorizontalPartOfLeg(final Graphics g, - final Rectangle clipBounds, - final Insets insets, - final Rectangle bounds, - final TreePath path, - final int row, - final boolean isExpanded, - final boolean hasBeenExpanded, - final boolean isLeaf) { - - } - - @Override - protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) { - } - } - } - - private class MyBuilder extends FilteringTreeBuilder { - - List myToExpandOnResetFilter; - boolean myRefilteringNow; - boolean myWasHoldingFilter; - - public MyBuilder(SimpleTreeStructure structure) { - super(OptionsTree.this.myProject, OptionsTree.this.myTree, OptionsTree.this.myContext.getFilter(), structure, new WeightBasedComparator(false)); - myTree.addTreeExpansionListener(new TreeExpansionListener() { - public void treeExpanded(TreeExpansionEvent event) { - invalidateExpansions(); - } - - public void treeCollapsed(TreeExpansionEvent event) { - invalidateExpansions(); - } - }); - } - - private void invalidateExpansions() { - if (!myRefilteringNow) { - myToExpandOnResetFilter = null; - } - } - - @Override - protected boolean isSelectable(final Object nodeObject) { - return nodeObject instanceof EditorNode; - } - - @Override - public boolean isAutoExpandNode(final NodeDescriptor nodeDescriptor) { - return myContext.isHoldingFilter(); - } - - @Override - public boolean isToEnsureSelectionOnFocusGained() { - return false; - } - - @Override - protected ActionCallback refilterNow(Object preferredSelection, boolean adjustSelection) { - final List toRestore = new ArrayList(); - if (myContext.isHoldingFilter() && !myWasHoldingFilter && myToExpandOnResetFilter == null) { - myToExpandOnResetFilter = myBuilder.getUi().getExpandedElements(); - } else if (!myContext.isHoldingFilter() && myWasHoldingFilter && myToExpandOnResetFilter != null) { - toRestore.addAll(myToExpandOnResetFilter); - myToExpandOnResetFilter = null; - } - - myWasHoldingFilter = myContext.isHoldingFilter(); - - ActionCallback result = super.refilterNow(preferredSelection, adjustSelection); - myRefilteringNow = true; - return result.doWhenDone(new Runnable() { - public void run() { - myRefilteringNow = false; - if (!myContext.isHoldingFilter()) { - restoreExpandedState(toRestore); - } - } - }); - } - - private void restoreExpandedState(List toRestore) { - TreePath[] selected = myTree.getSelectionPaths(); - if (selected == null) { - selected = new TreePath[0]; - } - - List toCollapse = new ArrayList(); - - for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) { - if (!myTree.isExpanded(eachRow)) continue; - - TreePath eachVisiblePath = myTree.getPathForRow(eachRow); - if (eachVisiblePath == null) continue; - - Object eachElement = myBuilder.getElementFor(eachVisiblePath.getLastPathComponent()); - if (toRestore.contains(eachElement)) continue; - - - for (TreePath eachSelected : selected) { - if (!eachVisiblePath.isDescendant(eachSelected)) { - toCollapse.add(eachVisiblePath); - } - } - } - - for (TreePath each : toCollapse) { - myTree.collapsePath(each); - } - - } - } -} +/* + * 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.options.newEditor; + +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurableGroup; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.ui.*; +import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.ui.treeStructure.*; +import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder; +import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.plaf.TreeUI; +import javax.swing.plaf.basic.BasicTreeUI; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +public class OptionsTree extends JPanel implements Disposable, OptionsEditorColleague { + Project myProject; + final SimpleTree myTree; + List myGroups; + FilteringTreeBuilder myBuilder; + Root myRoot; + OptionsEditorContext myContext; + + Map myConfigurable2Node = new HashMap(); + + MergingUpdateQueue mySelection; + private final OptionsTree.Renderer myRendrer; + + public OptionsTree(Project project, ConfigurableGroup[] groups, OptionsEditorContext context) { + myProject = project; + myGroups = Arrays.asList(groups); + myContext = context; + + + myRoot = new Root(); + final SimpleTreeStructure structure = new SimpleTreeStructure() { + public Object getRootElement() { + return myRoot; + } + }; + + myTree = new MyTree(); + myTree.setBorder(new EmptyBorder(0, 1, 0, 0)); + + myTree.setRowHeight(-1); + myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + myRendrer = new Renderer(); + myTree.setCellRenderer(myRendrer); + myTree.setRootVisible(false); + myTree.setShowsRootHandles(false); + myBuilder = new MyBuilder(structure); + myBuilder.setFilteringMerge(300, null); + Disposer.register(this, myBuilder); + + myBuilder.updateFromRoot(); + + setLayout(new BorderLayout()); + + myTree.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(final ComponentEvent e) { + revalidateTree(); + } + + @Override + public void componentMoved(final ComponentEvent e) { + revalidateTree(); + } + + @Override + public void componentShown(final ComponentEvent e) { + revalidateTree(); + } + }); + + final JScrollPane scrolls = ScrollPaneFactory.createScrollPane(myTree); + scrolls.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + add(scrolls, BorderLayout.CENTER); + + mySelection = new MergingUpdateQueue("OptionsTree", 150, false, this, this, this).setRestartTimerOnAdd(true); + myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(final TreeSelectionEvent e) { + final TreePath path = e.getNewLeadSelectionPath(); + if (path == null) { + queueSelection(null); + } + else { + final Base base = extractNode(path.getLastPathComponent()); + queueSelection(base != null ? base.getConfigurable() : null); + } + } + }); + myTree.addKeyListener(new KeyListener() { + public void keyTyped(final KeyEvent e) { + _onTreeKeyEvent(e); + } + + public void keyPressed(final KeyEvent e) { + _onTreeKeyEvent(e); + } + + public void keyReleased(final KeyEvent e) { + _onTreeKeyEvent(e); + } + }); + } + + protected void _onTreeKeyEvent(KeyEvent e) { + final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e); + + final Object action = myTree.getInputMap().get(stroke); + if (action == null) { + onTreeKeyEvent(e); + } + } + + protected void onTreeKeyEvent(KeyEvent e) { + + } + + + ActionCallback select(@Nullable Configurable configurable) { + return queueSelection(configurable); + } + + public void selectFirst() { + for (ConfigurableGroup eachGroup : myGroups) { + final Configurable[] kids = eachGroup.getConfigurables(); + if (kids.length > 0) { + queueSelection(kids[0]); + return; + } + } + } + + ActionCallback queueSelection(final Configurable configurable) { + final ActionCallback callback = new ActionCallback(); + final Update update = new Update(this) { + public void run() { + if (configurable == null) { + myTree.getSelectionModel().clearSelection(); + myContext.fireSelected(null, OptionsTree.this); + } + else { + myBuilder.getReady(this).doWhenDone(new Runnable() { + @Override + public void run() { + final EditorNode editorNode = myConfigurable2Node.get(configurable); + FilteringTreeStructure.Node editorUiNode = myBuilder.getVisibleNodeFor(editorNode); + if (!myBuilder.getSelectedElements().contains(editorUiNode)) { + myBuilder.select(editorUiNode, new Runnable() { + public void run() { + fireSelected(configurable, callback); + } + }); + } else { + myBuilder.scrollSelectionToVisible(new Runnable() { + public void run() { + fireSelected(configurable, callback); + } + }, false); + } + } + }); + } + } + + @Override + public void setRejected() { + super.setRejected(); + callback.setRejected(); + } + }; + mySelection.queue(update); + return callback; + } + + private void fireSelected(Configurable configurable, final ActionCallback callback) { + myContext.fireSelected(configurable, this).doWhenProcessed(new Runnable() { + public void run() { + callback.setDone(); + } + }); + } + + void revalidateTree() { + myTree.invalidate(); + myTree.setRowHeight(myTree.getRowHeight() == -1 ? -2 : -1); + myTree.revalidate(); + myTree.repaint(); + } + + public JTree getTree() { + return myTree; + } + + public List getPathToRoot(final Configurable configurable) { + final ArrayList path = new ArrayList(); + + EditorNode eachNode = myConfigurable2Node.get(configurable); + if (eachNode == null) return path; + + while (eachNode != null) { + path.add(eachNode.getConfigurable()); + final SimpleNode parent = eachNode.getParent(); + if (parent instanceof EditorNode) { + eachNode = (EditorNode)parent; + } + else { + break; + } + } + + return path; + } + + @Nullable + public Configurable getParentFor(final Configurable configurable) { + final List path = getPathToRoot(configurable); + if (path.size() > 1) { + return path.get(1); + } + else { + return null; + } + } + + public SimpleNode findNodeFor(final Configurable toSelect) { + return myConfigurable2Node.get(toSelect); + } + + @Nullable + public T findConfigurable(Class configurableClass) { + for (Configurable configurable : myConfigurable2Node.keySet()) { + if (configurableClass.isInstance(configurable)) { + return configurableClass.cast(configurable); + } + } + return null; + } + + class Renderer extends GroupedElementsRenderer.Tree implements TreeCellRenderer { + + + private JLabel myHandle; + + @Override + protected void layout() { + myRendererComponent.setOpaqueActive(false); + + myRendererComponent.add(mySeparatorComponent, BorderLayout.NORTH); + + final NonOpaquePanel content = new NonOpaquePanel(new BorderLayout()); + myHandle = new JLabel("", JLabel.CENTER); + if (!SystemInfo.isMac) { + myHandle.setBorder(new EmptyBorder(0, 2, 0, 2)); + } + myHandle.setOpaque(false); + content.add(myHandle, BorderLayout.WEST); + content.add(myComponent, BorderLayout.CENTER); + myRendererComponent.add(content, BorderLayout.CENTER); + } + + public Component getTreeCellRendererComponent(final JTree tree, + final Object value, + final boolean selected, + final boolean expanded, + final boolean leaf, + final int row, + final boolean hasFocus) { + + + JComponent result; + Color fg = UIUtil.getTreeTextForeground(); + + final Base base = extractNode(value); + if (base instanceof EditorNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; + + final EditorNode editor = (EditorNode)base; + ConfigurableGroup group = null; + if (editor.getParent() == myRoot) { + final DefaultMutableTreeNode prevValue = ((DefaultMutableTreeNode)value).getPreviousSibling(); + if (prevValue == null || prevValue instanceof LoadingNode) { + group = editor.getGroup(); + } + else { + final Base prevBase = extractNode(prevValue); + if (prevBase instanceof EditorNode) { + final EditorNode prevEditor = (EditorNode)prevBase; + if (prevEditor.getGroup() != editor.getGroup()) { + group = editor.getGroup(); + } + } + } + } + + int forcedWidth = 2000; + TreePath path = tree.getPathForRow(row); + if (path == null) { + if (value instanceof DefaultMutableTreeNode) { + path = new TreePath(((DefaultMutableTreeNode)value).getPath()); + } + } + + final boolean toStretch = tree.isVisible() && path != null; + + if (toStretch) { + final Rectangle visibleRect = tree.getVisibleRect(); + + int nestingLevel = tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2; + + final int left = UIUtil.getTreeLeftChildIndent(); + final int right = UIUtil.getTreeRightChildIndent(); + + final Insets treeInsets = tree.getInsets(); + + int indent = (left + right) * nestingLevel + (treeInsets != null ? treeInsets.left + treeInsets.right : 0); + + forcedWidth = visibleRect.width > 0 ? visibleRect.width - indent : forcedWidth; + } + + result = configureComponent(base.getText(), base.getText(), null, null, row == -1 ? true : selected, group != null, + group != null ? group.getDisplayName() : null, forcedWidth - 4); + + + if (base.isError()) { + fg = Color.red; + } + else if (base.isModified()) { + fg = Color.blue; + } + + } + else { + result = configureComponent(value.toString(), null, null, null, selected, false, null, -1); + } + + if (value instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; + TreePath nodePath = new TreePath(node.getPath()); + myHandle.setIcon(((SimpleTree)tree).getHandleIcon(node, nodePath)); + } else { + myHandle.setIcon(null); + } + + + myTextLabel.setForeground(selected ? UIUtil.getTreeSelectionForeground() : fg); + + myTextLabel.setOpaque(selected); + + return result; + } + + protected JComponent createItemComponent() { + myTextLabel = new ErrorLabel(); + return myTextLabel; + } + + public boolean isUnderHandle(final Point point) { + final Point handlePoint = SwingUtilities.convertPoint(myRendererComponent, point, myHandle); + final Rectangle bounds = myHandle.getBounds(); + return bounds.x < handlePoint.x && bounds.getMaxX() >= handlePoint.x; + } + } + + @Nullable + private Base extractNode(Object object) { + if (object instanceof DefaultMutableTreeNode) { + final DefaultMutableTreeNode uiNode = (DefaultMutableTreeNode)object; + final Object o = uiNode.getUserObject(); + if (o instanceof FilteringTreeStructure.Node) { + return (Base)((FilteringTreeStructure.Node)o).getDelegate(); + } + } + + return null; + } + + abstract class Base extends CachingSimpleNode { + + protected Base(final SimpleNode aParent) { + super(aParent); + } + + String getText() { + return null; + } + + boolean isModified() { + return false; + } + + boolean isError() { + return false; + } + + Configurable getConfigurable() { + return null; + } + } + + class Root extends Base { + + Root() { + super(null); + } + + protected SimpleNode[] buildChildren() { + List result = new ArrayList(); + for (ConfigurableGroup eachGroup : myGroups) { + result.addAll(buildGroup(eachGroup)); + } + + return result.isEmpty() ? NO_CHILDREN : result.toArray(new SimpleNode[result.size()]); + } + + private List buildGroup(final ConfigurableGroup eachGroup) { + List result = new ArrayList(); + final Configurable[] kids = eachGroup.getConfigurables(); + if (kids.length > 0) { + for (Configurable eachKid : kids) { + if (isInvisibleNode(eachKid)) { + result.addAll(OptionsTree.this.buildChildren(eachKid, this, eachGroup)); + } + else { + result.add(new EditorNode(this, eachKid, eachGroup)); + } + } + + } + return sort(result); + } + } + + private static boolean isInvisibleNode(final Configurable child) { + return child instanceof SearchableConfigurable.Parent && !((SearchableConfigurable.Parent)child).isVisible(); + } + + private static List sort(List c) { + List cc = new ArrayList(c); + Collections.sort(cc, new Comparator() { + public int compare(final EditorNode o1, final EditorNode o2) { + return getConfigurableDisplayName(o1.getConfigurable()).compareToIgnoreCase(getConfigurableDisplayName(o2.getConfigurable())); + } + }); + return cc; + } + + private static String getConfigurableDisplayName(final Configurable c) { + final String name = c.getDisplayName(); + return name != null ? name : "{ Unnamed Page:" + c.getClass().getSimpleName() + " }"; + } + + private List buildChildren(final Configurable configurable, SimpleNode parent, final ConfigurableGroup group) { + if (configurable instanceof Configurable.Composite) { + final Configurable[] kids = ((Configurable.Composite)configurable).getConfigurables(); + final List result = new ArrayList(kids.length); + for (Configurable child : kids) { + if (isInvisibleNode(child)) { + result.addAll(buildChildren(child, parent, group)); + } + result.add(new EditorNode(parent, child, group)); + myContext.registerKid(configurable, child); + } + return result; // TODO: DECIDE IF INNERS SHOULD BE SORTED: sort(result); + } + else { + return Collections.emptyList(); + } + } + + private static final EditorNode[] EMPTY_EN_ARRAY = new EditorNode[0]; + class EditorNode extends Base { + Configurable myConfigurable; + ConfigurableGroup myGroup; + + EditorNode(SimpleNode parent, Configurable configurable, @Nullable ConfigurableGroup group) { + super(parent); + myConfigurable = configurable; + myGroup = group; + myConfigurable2Node.put(configurable, this); + addPlainText(getConfigurableDisplayName(configurable)); + } + + protected EditorNode[] buildChildren() { + List list = OptionsTree.this.buildChildren(myConfigurable, this, null); + return list.isEmpty() ? EMPTY_EN_ARRAY : list.toArray(new EditorNode[list.size()]); + } + + @Override + public boolean isContentHighlighted() { + return getParent() == myRoot; + } + + @Override + Configurable getConfigurable() { + return myConfigurable; + } + + @Override + public int getWeight() { + if (getParent() == myRoot) { + return Integer.MAX_VALUE - myGroups.indexOf(myGroup); + } + else { + return WeightBasedComparator.UNDEFINED_WEIGHT; + } + } + + public ConfigurableGroup getGroup() { + return myGroup; + } + + @Override + String getText() { + return getConfigurableDisplayName(myConfigurable).replace("\n", " "); + } + + @Override + boolean isModified() { + return myContext.getModified().contains(myConfigurable); + } + + @Override + boolean isError() { + return myContext.getErrors().containsKey(myConfigurable); + } + } + + public void dispose() { + } + + public ActionCallback onSelected(final Configurable configurable, final Configurable oldConfigurable) { + return queueSelection(configurable); + } + + public ActionCallback onModifiedAdded(final Configurable colleague) { + myTree.repaint(); + return new ActionCallback.Done(); + } + + public ActionCallback onModifiedRemoved(final Configurable configurable) { + myTree.repaint(); + return new ActionCallback.Done(); + } + + public ActionCallback onErrorsChanged() { + return new ActionCallback.Done(); + } + + public void processTextEvent(KeyEvent e) { + myTree.processKeyEvent(e); + } + + private class MyTree extends SimpleTree { + + private MyTree() { + getInputMap().clear(); + } + + @Override + protected boolean highlightSingleNode() { + return false; + } + + @Override + public void setUI(final TreeUI ui) { + TreeUI actualUI = ui; + if (!(ui instanceof MyTreeUi)) { + actualUI = new MyTreeUi(); + } + super.setUI(actualUI); + } + + @Override + protected boolean isCustomUI() { + return true; + } + + @Override + protected void configureUiHelper(final TreeUIHelper helper) { + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + + @Override + public void processKeyEvent(final KeyEvent e) { + TreePath path = myTree.getSelectionPath(); + if (path != null) { + if (e.getKeyCode() == KeyEvent.VK_LEFT) { + if (isExpanded(path)) { + collapsePath(path); + return; + } + } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { + if (isCollapsed(path)) { + expandPath(path); + return; + } + } + } + + super.processKeyEvent(e); + } + + @Override + protected void processMouseEvent(final MouseEvent e) { + final MyTreeUi ui = (MyTreeUi)myTree.getUI(); + if (e.getID() == MouseEvent.MOUSE_RELEASED && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !ui.isToggleEvent(e)) { + final TreePath path = getPathForLocation(e.getX(), e.getY()); + if (path != null) { + final Rectangle bounds = getPathBounds(path); + if (bounds != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); + final boolean selected = isPathSelected(path); + final boolean expanded = isExpanded(path); + final Component comp = + myRendrer.getTreeCellRendererComponent(this, node, selected, expanded, node.isLeaf(), getRowForPath(path), isFocusOwner()); + + comp.setBounds(bounds); + comp.validate(); + + Point point = new Point(e.getX() - bounds.x, e.getY() - bounds.y); + if (myRendrer.isUnderHandle(point)) { + ui.toggleExpandState(path); + e.consume(); + return; + } + } + } + } + + super.processMouseEvent(e); + } + + private class MyTreeUi extends BasicTreeUI { + + @Override + public void toggleExpandState(final TreePath path) { + super.toggleExpandState(path); + } + + @Override + public boolean isToggleEvent(final MouseEvent event) { + return super.isToggleEvent(event); + } + + @Override + protected boolean shouldPaintExpandControl(final TreePath path, + final int row, + final boolean isExpanded, + final boolean hasBeenExpanded, + final boolean isLeaf) { + return false; + } + + @Override + protected void paintHorizontalPartOfLeg(final Graphics g, + final Rectangle clipBounds, + final Insets insets, + final Rectangle bounds, + final TreePath path, + final int row, + final boolean isExpanded, + final boolean hasBeenExpanded, + final boolean isLeaf) { + + } + + @Override + protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) { + } + } + } + + private class MyBuilder extends FilteringTreeBuilder { + + List myToExpandOnResetFilter; + boolean myRefilteringNow; + boolean myWasHoldingFilter; + + public MyBuilder(SimpleTreeStructure structure) { + super(OptionsTree.this.myProject, OptionsTree.this.myTree, OptionsTree.this.myContext.getFilter(), structure, new WeightBasedComparator(false)); + myTree.addTreeExpansionListener(new TreeExpansionListener() { + public void treeExpanded(TreeExpansionEvent event) { + invalidateExpansions(); + } + + public void treeCollapsed(TreeExpansionEvent event) { + invalidateExpansions(); + } + }); + } + + private void invalidateExpansions() { + if (!myRefilteringNow) { + myToExpandOnResetFilter = null; + } + } + + @Override + protected boolean isSelectable(final Object nodeObject) { + return nodeObject instanceof EditorNode; + } + + @Override + public boolean isAutoExpandNode(final NodeDescriptor nodeDescriptor) { + return myContext.isHoldingFilter(); + } + + @Override + public boolean isToEnsureSelectionOnFocusGained() { + return false; + } + + @Override + protected ActionCallback refilterNow(Object preferredSelection, boolean adjustSelection) { + final List toRestore = new ArrayList(); + if (myContext.isHoldingFilter() && !myWasHoldingFilter && myToExpandOnResetFilter == null) { + myToExpandOnResetFilter = myBuilder.getUi().getExpandedElements(); + } else if (!myContext.isHoldingFilter() && myWasHoldingFilter && myToExpandOnResetFilter != null) { + toRestore.addAll(myToExpandOnResetFilter); + myToExpandOnResetFilter = null; + } + + myWasHoldingFilter = myContext.isHoldingFilter(); + + ActionCallback result = super.refilterNow(preferredSelection, adjustSelection); + myRefilteringNow = true; + return result.doWhenDone(new Runnable() { + public void run() { + myRefilteringNow = false; + if (!myContext.isHoldingFilter()) { + restoreExpandedState(toRestore); + } + } + }); + } + + private void restoreExpandedState(List toRestore) { + TreePath[] selected = myTree.getSelectionPaths(); + if (selected == null) { + selected = new TreePath[0]; + } + + List toCollapse = new ArrayList(); + + for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) { + if (!myTree.isExpanded(eachRow)) continue; + + TreePath eachVisiblePath = myTree.getPathForRow(eachRow); + if (eachVisiblePath == null) continue; + + Object eachElement = myBuilder.getElementFor(eachVisiblePath.getLastPathComponent()); + if (toRestore.contains(eachElement)) continue; + + + for (TreePath eachSelected : selected) { + if (!eachVisiblePath.isDescendant(eachSelected)) { + toCollapse.add(eachVisiblePath); + } + } + } + + for (TreePath each : toCollapse) { + myTree.collapsePath(each); + } + + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/project/CacheUpdateRunner.java b/platform/platform-impl/src/com/intellij/openapi/project/CacheUpdateRunner.java index c40463f0a825..d32d11b9a65c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/CacheUpdateRunner.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/CacheUpdateRunner.java @@ -1,237 +1,237 @@ -/* - * 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.project; - -import com.intellij.concurrency.Job; -import com.intellij.concurrency.JobScheduler; -import com.intellij.ide.caches.CacheUpdater; -import com.intellij.ide.caches.FileContent; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationAdapter; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Consumer; -import gnu.trove.THashSet; - -import java.util.Collection; -import java.util.Set; - -class CacheUpdateRunner { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.CacheUpdateRunner"); - private final Project myProject; - private final Collection myUpdaters; - private CacheUpdateSession mySession; - - CacheUpdateRunner(Project project, Collection updaters) { - myProject = project; - myUpdaters = updaters; - } - - public int queryNeededFiles(ProgressIndicator indicator) { - // can be queried twice in DumbService - if (mySession == null) { - mySession = new CacheUpdateSession(myUpdaters, indicator); - } - return mySession.getFilesToUpdate().size(); - } - - public int getNumberOfPendingUpdateJobs(ProgressIndicator indicator) { - if (mySession == null) { - mySession = new CacheUpdateSession(myUpdaters, indicator); - } - return mySession.getNumberOfPendingUpdateJobs(); - } - - public void processFiles(final ProgressIndicator indicator, boolean processInReadAction) { - try { - indicator.checkCanceled(); - final FileContentQueue queue = new FileContentQueue(); - Collection files = mySession.getFilesToUpdate(); - final double total = files.size(); - queue.queue(files, indicator); - - Consumer progressUpdater = new Consumer() { - // need set here to handle queue.pushbacks after checkCancelled() in order - // not to count the same file several times - final Set processed = new THashSet(); - - public void consume(VirtualFile virtualFile) { - indicator.checkCanceled(); - synchronized (processed) { - processed.add(virtualFile); - indicator.setFraction(processed.size() / total); - } - if (virtualFile.isValid()) { - indicator.setText2(virtualFile.getPresentableUrl()); - } - else { - indicator.setText2(""); - } - } - }; - - while (!myProject.isDisposed()) { - indicator.checkCanceled(); - // todo wait for the user... - if (processSomeFilesWhileUserIsInactive(queue, progressUpdater, processInReadAction)) { - break; - } - } - - if (myProject.isDisposed()) { - indicator.cancel(); - indicator.checkCanceled(); - } - } - catch (ProcessCanceledException e) { - mySession.canceled(); - throw e; - } - } - - public void updatingDone() { - try { - mySession.updatingDone(); - } - catch (ProcessCanceledException e) { - mySession.canceled(); - throw e; - } - } - - private boolean processSomeFilesWhileUserIsInactive(final FileContentQueue queue, - final Consumer progressUpdater, - final boolean processInReadAction) { - final ProgressIndicatorBase innerIndicator = new ProgressIndicatorBase(); - final ApplicationAdapter canceller = new ApplicationAdapter() { - @Override - public void beforeWriteActionStart(Object action) { - innerIndicator.cancel(); - } - }; - final Application application = ApplicationManager.getApplication(); - application.addApplicationListener(canceller); - - final Ref isFinished = new Ref(Boolean.FALSE); - try { - int threadsCount = Registry.intValue("caches.indexerThreadsCount"); - if (threadsCount == -1) { - threadsCount = Math.min(Runtime.getRuntime().availableProcessors(), ApplicationManager.getApplication().isUnitTestMode() ? 4 : 2); - } - if (threadsCount == 1) { - Runnable process = new MyRunnable(innerIndicator, queue, isFinished, progressUpdater, processInReadAction, application); - ProgressManager.getInstance().runProcess(process, innerIndicator); - } - else { - final Job job = JobScheduler.getInstance().createJob("Indexing", Thread.NORM_PRIORITY); - final Ref[] finishedRefs = new Ref[threadsCount]; - for (int i = 0; i < threadsCount; i++) { - final Ref ref = new Ref(Boolean.FALSE); - finishedRefs[i] = ref; - Runnable process = new MyRunnable(innerIndicator, queue, ref, progressUpdater, processInReadAction, application); - job.addTask(process); - } - try { - job.scheduleAndWaitForResults(); - - boolean allFinished = true; - for (Ref ref : finishedRefs) { - if (!(Boolean)ref.get()) { - allFinished = false; - break; - } - } - isFinished.set(allFinished); - - } - catch (Throwable throwable) { - LOG.error(throwable); - } - } - } - finally { - application.removeApplicationListener(canceller); - } - - return isFinished.get(); - } - - private class MyRunnable implements Runnable { - private final ProgressIndicatorBase myInnerIndicator; - private final FileContentQueue myQueue; - private final Ref myFinished; - private final Consumer myProgressUpdater; - private final boolean myProcessInReadAction; - private final Application myApplication; - - public MyRunnable(ProgressIndicatorBase innerIndicator, - FileContentQueue queue, - Ref finished, - Consumer progressUpdater, - boolean processInReadAction, Application application) { - myInnerIndicator = innerIndicator; - myQueue = queue; - myFinished = finished; - myProgressUpdater = progressUpdater; - myProcessInReadAction = processInReadAction; - myApplication = application; - } - - public void run() { - while (true) { - if (myProject.isDisposed()) return; - if (myInnerIndicator.isCanceled()) return; - - final FileContent fileContent = myQueue.take(); - if (fileContent == null) { - myFinished.set(Boolean.TRUE); - return; - } - - try { - final Runnable action = new Runnable() { - public void run() { - myInnerIndicator.checkCanceled(); - - if (myProject.isDisposed()) return; - - final VirtualFile file = fileContent.getVirtualFile(); - myProgressUpdater.consume(file); - mySession.processFile(fileContent); - } - }; - if (myProcessInReadAction) { - myApplication.runReadAction(action); - } - else { - action.run(); - } - } - catch (ProcessCanceledException e) { - myQueue.pushback(fileContent); - return; - } - } - } - } -} +/* + * 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.project; + +import com.intellij.concurrency.Job; +import com.intellij.concurrency.JobScheduler; +import com.intellij.ide.caches.CacheUpdater; +import com.intellij.ide.caches.FileContent; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import gnu.trove.THashSet; + +import java.util.Collection; +import java.util.Set; + +class CacheUpdateRunner { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.CacheUpdateRunner"); + private final Project myProject; + private final Collection myUpdaters; + private CacheUpdateSession mySession; + + CacheUpdateRunner(Project project, Collection updaters) { + myProject = project; + myUpdaters = updaters; + } + + public int queryNeededFiles(ProgressIndicator indicator) { + // can be queried twice in DumbService + if (mySession == null) { + mySession = new CacheUpdateSession(myUpdaters, indicator); + } + return mySession.getFilesToUpdate().size(); + } + + public int getNumberOfPendingUpdateJobs(ProgressIndicator indicator) { + if (mySession == null) { + mySession = new CacheUpdateSession(myUpdaters, indicator); + } + return mySession.getNumberOfPendingUpdateJobs(); + } + + public void processFiles(final ProgressIndicator indicator, boolean processInReadAction) { + try { + indicator.checkCanceled(); + final FileContentQueue queue = new FileContentQueue(); + Collection files = mySession.getFilesToUpdate(); + final double total = files.size(); + queue.queue(files, indicator); + + Consumer progressUpdater = new Consumer() { + // need set here to handle queue.pushbacks after checkCancelled() in order + // not to count the same file several times + final Set processed = new THashSet(); + + public void consume(VirtualFile virtualFile) { + indicator.checkCanceled(); + synchronized (processed) { + processed.add(virtualFile); + indicator.setFraction(processed.size() / total); + } + if (virtualFile.isValid()) { + indicator.setText2(virtualFile.getPresentableUrl()); + } + else { + indicator.setText2(""); + } + } + }; + + while (!myProject.isDisposed()) { + indicator.checkCanceled(); + // todo wait for the user... + if (processSomeFilesWhileUserIsInactive(queue, progressUpdater, processInReadAction)) { + break; + } + } + + if (myProject.isDisposed()) { + indicator.cancel(); + indicator.checkCanceled(); + } + } + catch (ProcessCanceledException e) { + mySession.canceled(); + throw e; + } + } + + public void updatingDone() { + try { + mySession.updatingDone(); + } + catch (ProcessCanceledException e) { + mySession.canceled(); + throw e; + } + } + + private boolean processSomeFilesWhileUserIsInactive(final FileContentQueue queue, + final Consumer progressUpdater, + final boolean processInReadAction) { + final ProgressIndicatorBase innerIndicator = new ProgressIndicatorBase(); + final ApplicationAdapter canceller = new ApplicationAdapter() { + @Override + public void beforeWriteActionStart(Object action) { + innerIndicator.cancel(); + } + }; + final Application application = ApplicationManager.getApplication(); + application.addApplicationListener(canceller); + + final Ref isFinished = new Ref(Boolean.FALSE); + try { + int threadsCount = Registry.intValue("caches.indexerThreadsCount"); + if (threadsCount == -1) { + threadsCount = Math.min(Runtime.getRuntime().availableProcessors(), ApplicationManager.getApplication().isUnitTestMode() ? 4 : 2); + } + if (threadsCount == 1) { + Runnable process = new MyRunnable(innerIndicator, queue, isFinished, progressUpdater, processInReadAction, application); + ProgressManager.getInstance().runProcess(process, innerIndicator); + } + else { + final Job job = JobScheduler.getInstance().createJob("Indexing", Thread.NORM_PRIORITY); + final Ref[] finishedRefs = new Ref[threadsCount]; + for (int i = 0; i < threadsCount; i++) { + final Ref ref = new Ref(Boolean.FALSE); + finishedRefs[i] = ref; + Runnable process = new MyRunnable(innerIndicator, queue, ref, progressUpdater, processInReadAction, application); + job.addTask(process); + } + try { + job.scheduleAndWaitForResults(); + + boolean allFinished = true; + for (Ref ref : finishedRefs) { + if (!(Boolean)ref.get()) { + allFinished = false; + break; + } + } + isFinished.set(allFinished); + + } + catch (Throwable throwable) { + LOG.error(throwable); + } + } + } + finally { + application.removeApplicationListener(canceller); + } + + return isFinished.get(); + } + + private class MyRunnable implements Runnable { + private final ProgressIndicatorBase myInnerIndicator; + private final FileContentQueue myQueue; + private final Ref myFinished; + private final Consumer myProgressUpdater; + private final boolean myProcessInReadAction; + private final Application myApplication; + + public MyRunnable(ProgressIndicatorBase innerIndicator, + FileContentQueue queue, + Ref finished, + Consumer progressUpdater, + boolean processInReadAction, Application application) { + myInnerIndicator = innerIndicator; + myQueue = queue; + myFinished = finished; + myProgressUpdater = progressUpdater; + myProcessInReadAction = processInReadAction; + myApplication = application; + } + + public void run() { + while (true) { + if (myProject.isDisposed()) return; + if (myInnerIndicator.isCanceled()) return; + + final FileContent fileContent = myQueue.take(); + if (fileContent == null) { + myFinished.set(Boolean.TRUE); + return; + } + + try { + final Runnable action = new Runnable() { + public void run() { + myInnerIndicator.checkCanceled(); + + if (myProject.isDisposed()) return; + + final VirtualFile file = fileContent.getVirtualFile(); + myProgressUpdater.consume(file); + mySession.processFile(fileContent); + } + }; + if (myProcessInReadAction) { + myApplication.runReadAction(action); + } + else { + action.run(); + } + } + catch (ProcessCanceledException e) { + myQueue.pushback(fileContent); + return; + } + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/project/ex/ProjectManagerEx.java b/platform/platform-impl/src/com/intellij/openapi/project/ex/ProjectManagerEx.java index a451eff530b8..0ad0edc7ed59 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/ex/ProjectManagerEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/ex/ProjectManagerEx.java @@ -1,69 +1,69 @@ -/* - * 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.project.ex; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.vfs.VirtualFile; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.io.IOException; - -/** - * - */ -public abstract class ProjectManagerEx extends ProjectManager { - public static ProjectManagerEx getInstanceEx() { - return (ProjectManagerEx)ApplicationManager.getApplication().getComponent(ProjectManager.class); - } - - @Nullable - public abstract Project newProject(final String projectName, String filePath, boolean useDefaultProjectSettings, boolean isDummy); - - @Nullable - public abstract Project loadProject(String filePath) throws IOException, JDOMException, InvalidDataException; - - public abstract boolean openProject(Project project); - - public abstract boolean isProjectOpened(Project project); - - public abstract boolean canClose(Project project); - - public abstract void saveChangedProjectFile(VirtualFile file, final Project project); - - public abstract boolean isFileSavedToBeReloaded(VirtualFile file); - - public abstract void blockReloadingProjectOnExternalChanges(); - public abstract void unblockReloadingProjectOnExternalChanges(); - - @Nullable - @TestOnly - public abstract Project getCurrentTestProject(); - - @TestOnly - public abstract void setCurrentTestProject(@Nullable Project project); - - @Nullable - public abstract Project loadAndOpenProject(String filePath, boolean convert) throws IOException, JDOMException, InvalidDataException; - - // returns true on success - public abstract boolean closeAndDispose(@NotNull Project project); -} +/* + * 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.project.ex; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.vfs.VirtualFile; +import org.jdom.JDOMException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.io.IOException; + +/** + * + */ +public abstract class ProjectManagerEx extends ProjectManager { + public static ProjectManagerEx getInstanceEx() { + return (ProjectManagerEx)ApplicationManager.getApplication().getComponent(ProjectManager.class); + } + + @Nullable + public abstract Project newProject(final String projectName, String filePath, boolean useDefaultProjectSettings, boolean isDummy); + + @Nullable + public abstract Project loadProject(String filePath) throws IOException, JDOMException, InvalidDataException; + + public abstract boolean openProject(Project project); + + public abstract boolean isProjectOpened(Project project); + + public abstract boolean canClose(Project project); + + public abstract void saveChangedProjectFile(VirtualFile file, final Project project); + + public abstract boolean isFileSavedToBeReloaded(VirtualFile file); + + public abstract void blockReloadingProjectOnExternalChanges(); + public abstract void unblockReloadingProjectOnExternalChanges(); + + @Nullable + @TestOnly + public abstract Project getCurrentTestProject(); + + @TestOnly + public abstract void setCurrentTestProject(@Nullable Project project); + + @Nullable + public abstract Project loadAndOpenProject(String filePath, boolean convert) throws IOException, JDOMException, InvalidDataException; + + // returns true on success + public abstract boolean closeAndDispose(@NotNull Project project); +} diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java index 4fd19232ec30..baf26ab22705 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java @@ -1,508 +1,508 @@ -/* - * 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.project.impl; - -import com.intellij.diagnostic.PluginException; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.ide.startup.StartupManagerEx; -import com.intellij.notification.*; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.application.PathMacros; -import com.intellij.openapi.application.ex.ApplicationEx; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.components.StorageScheme; -import com.intellij.openapi.components.TrackingPathMacroSubstitutor; -import com.intellij.openapi.components.impl.ComponentManagerImpl; -import com.intellij.openapi.components.impl.ProjectPathMacroManager; -import com.intellij.openapi.components.impl.stores.IComponentStore; -import com.intellij.openapi.components.impl.stores.IProjectStore; -import com.intellij.openapi.components.impl.stores.UnknownMacroNotification; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.project.ProjectManagerAdapter; -import com.intellij.openapi.project.ex.ProjectEx; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.ex.MessagesEx; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.openapi.wm.impl.FrameTitleBuilder; -import com.intellij.util.Function; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.picocontainer.*; -import org.picocontainer.defaults.CachingComponentAdapter; -import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; - -import javax.swing.event.HyperlinkEvent; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - - -/** - * - */ -public class ProjectImpl extends ComponentManagerImpl implements ProjectEx { - private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectImpl"); - private static final String PLUGIN_SETTINGS_ERROR = "Plugin Settings Error"; - - private ProjectManagerImpl myManager; - - private MyProjectManagerListener myProjectManagerListener; - - private final AtomicBoolean mySavingInProgress = new AtomicBoolean(false); - - public boolean myOptimiseTestLoadSpeed; - @NonNls public static final String TEMPLATE_PROJECT_NAME = "Default (Template) Project"; - - private String myName; - private String myOldName; - - public static Key CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME"); - - protected ProjectImpl(ProjectManagerImpl manager, String filePath, boolean isOptimiseTestLoadSpeed, String projectName) { - super(ApplicationManager.getApplication()); - putUserData(CREATION_TIME, System.nanoTime()); - - getPicoContainer().registerComponentInstance(Project.class, this); - - getStateStore().setProjectFilePath(filePath); - - myOptimiseTestLoadSpeed = isOptimiseTestLoadSpeed; - - myManager = manager; - myName = isDefault() ? TEMPLATE_PROJECT_NAME : projectName == null ? getStateStore().getProjectName() : projectName; - if (!isDefault() && projectName != null && getStateStore().getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) myOldName = ""; // new project - } - - public void setProjectName(final String projectName) { - if (!projectName.equals(myName)) { - myOldName = myName; - myName = projectName; - WindowManager.getInstance().getFrame(this).setTitle(FrameTitleBuilder.getInstance().getProjectTitle(this)); - } - } - - protected void boostrapPicoContainer() { - Extensions.instantiateArea(PluginManager.AREA_IDEA_PROJECT, this, null); - super.boostrapPicoContainer(); - final MutablePicoContainer picoContainer = getPicoContainer(); - - final ProjectStoreClassProvider projectStoreClassProvider = (ProjectStoreClassProvider)picoContainer.getComponentInstanceOfType(ProjectStoreClassProvider.class); - - - picoContainer.registerComponentImplementation(ProjectPathMacroManager.class); - picoContainer.registerComponent(new ComponentAdapter() { - ComponentAdapter myDelegate; - - - public ComponentAdapter getDelegate() { - if (myDelegate == null) { - - final Class storeClass = projectStoreClassProvider.getProjectStoreClass(isDefault()); - myDelegate = new CachingComponentAdapter( - new ConstructorInjectionComponentAdapter(storeClass, storeClass, null, true)); - } - - return myDelegate; - } - - public Object getComponentKey() { - return IComponentStore.class; - } - - public Class getComponentImplementation() { - return getDelegate().getComponentImplementation(); - } - - public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException { - return getDelegate().getComponentInstance(container); - } - - public void verify(final PicoContainer container) throws PicoIntrospectionException { - getDelegate().verify(container); - } - - public void accept(final PicoVisitor visitor) { - visitor.visitComponentAdapter(this); - getDelegate().accept(visitor); - } - }); - - } - - @NotNull - public IProjectStore getStateStore() { - return (IProjectStore)super.getStateStore(); - } - - public boolean isOpen() { - return ProjectManagerEx.getInstanceEx().isProjectOpened(this); - } - - public boolean isInitialized() { - return isOpen() && !isDisposed() && StartupManagerEx.getInstanceEx(this).startupActivityPassed(); - } - - public void loadProjectComponents() { - final Application app = ApplicationManager.getApplication(); - final IdeaPluginDescriptor[] plugins = app.getPlugins(); - for (IdeaPluginDescriptor plugin : plugins) { - if (PluginManager.shouldSkipPlugin(plugin)) continue; - loadComponentsConfiguration(plugin.getProjectComponents(), plugin, isDefault()); - } - } - - @NotNull - public String getProjectFilePath() { - return getStateStore().getProjectFilePath(); - } - - - @Nullable - public VirtualFile getProjectFile() { - return getStateStore().getProjectFile(); - } - - @Nullable - public VirtualFile getBaseDir() { - return getStateStore().getProjectBaseDir(); - } - - @NotNull - public String getName() { - return myName; - } - - @Nullable - @NonNls - public String getPresentableUrl() { - return getStateStore().getPresentableUrl(); - } - - @NotNull - @NonNls - public String getLocationHash() { - String str = getPresentableUrl(); - if (str == null) str = getName(); - - final String prefix = getStateStore().getStorageScheme() == StorageScheme.DIRECTORY_BASED? "" : getName(); - return prefix + Integer.toHexString(str.hashCode()); - } - - @Nullable - @NonNls - public String getLocation() { - return isDisposed() ? null : getStateStore().getLocation(); - } - - @Nullable - public VirtualFile getWorkspaceFile() { - return getStateStore().getWorkspaceFile(); - } - - public boolean isOptimiseTestLoadSpeed() { - return myOptimiseTestLoadSpeed; - } - - public void setOptimiseTestLoadSpeed(final boolean optimiseTestLoadSpeed) { - myOptimiseTestLoadSpeed = optimiseTestLoadSpeed; - } - - - public void init() { - super.init(); - getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).projectComponentsInitialized(this); - myProjectManagerListener = new MyProjectManagerListener(); - myManager.addProjectManagerListener(this, myProjectManagerListener); - } - - public boolean isToSaveProjectName() { - if (!isDefault()) { - final IProjectStore stateStore = getStateStore(); - if (stateStore.getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) { - final VirtualFile baseDir = stateStore.getProjectBaseDir(); - if (baseDir != null && baseDir.isValid()) { - return myOldName != null && !myOldName.equals(getName()); - } - } - } - - return false; - } - - public void save() { - if (ApplicationManagerEx.getApplicationEx().isDoNotSave()) return; //no need to save - - if (mySavingInProgress.compareAndSet(false, true)) { - try { - if (isToSaveProjectName()) { - final IProjectStore stateStore = getStateStore(); - final VirtualFile baseDir = stateStore.getProjectBaseDir(); - if (baseDir != null && baseDir.isValid()) { - final VirtualFile ideaDir = baseDir.findChild(DIRECTORY_STORE_FOLDER); - if (ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory()) { - final File nameFile = new File(ideaDir.getPath(), ".name"); - try { - FileUtil.writeToFile(nameFile, getName().getBytes("UTF-8"), false); - myOldName = null; - } - catch (IOException e) { - LOG.info("Unable to store project name to: " + nameFile.getPath()); - } - } - } - } - - doSave(); - } - catch (IComponentStore.SaveCancelledException e) { - LOG.info(e); - } - catch (PluginException e) { - PluginManager.disablePlugin(e.getPluginId().getIdString()); - Notifications.Bus.notify(new Notification(PLUGIN_SETTINGS_ERROR, "Unable to save plugin settings!", - "

The plugin " + e.getPluginId() + " failed to save settings and has been disabled. Please restart" + - ApplicationNamesInfo.getInstance().getFullProductName() + "

" + - (ApplicationManagerEx.getApplicationEx().isInternal() ? "

" + StringUtil.getThrowableText(e) + "

": ""), - NotificationType.ERROR), NotificationDisplayType.BALLOON, this); - LOG.info("Unable to save plugin settings",e); - } - catch (IOException e) { - MessagesEx.error(this, ProjectBundle.message("project.save.error", ApplicationManagerEx.getApplicationEx().isInternal() - ? StringUtil.getThrowableText(e) - : e.getMessage())).showLater(); - LOG.info("Error saving project", e); - } finally { - mySavingInProgress.set(false); - ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectSaved.TOPIC).saved(this); - } - } - } - - public synchronized void dispose() { - ApplicationEx application = ApplicationManagerEx.getApplicationEx(); - assert application.isDispatchThread(); - - // can call dispose only via com.intellij.ide.impl.ProjectUtil.closeAndDispose() - LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || !ProjectManagerEx.getInstanceEx().isProjectOpened(this)); - - LOG.assertTrue(!isDisposed()); - if (myProjectManagerListener != null) { - myManager.removeProjectManagerListener(this, myProjectManagerListener); - } - - disposeComponents(); - Extensions.disposeArea(this); - myManager = null; - myProjectManagerListener = null; - - super.dispose(); - - if (!application.isDisposed()) { - application.getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).afterProjectClosed(this); - } - } - - private void projectOpened() { - final ProjectComponent[] components = getComponents(ProjectComponent.class); - for (ProjectComponent component : components) { - try { - component.projectOpened(); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - - private void projectClosed() { - List components = new ArrayList(Arrays.asList(getComponents(ProjectComponent.class))); - Collections.reverse(components); - for (ProjectComponent component : components) { - try { - component.projectClosed(); - } - catch (Throwable e) { - LOG.error(e); - } - } - } - - public T[] getExtensions(final ExtensionPointName extensionPointName) { - return Extensions.getArea(this).getExtensionPoint(extensionPointName).getExtensions(); - } - - public String getDefaultName() { - if (isDefault()) return TEMPLATE_PROJECT_NAME; - - return getStateStore().getProjectName(); - } - - private class MyProjectManagerListener extends ProjectManagerAdapter { - public void projectOpened(Project project) { - LOG.assertTrue(project == ProjectImpl.this); - ProjectImpl.this.projectOpened(); - } - - public void projectClosed(Project project) { - LOG.assertTrue(project == ProjectImpl.this); - ProjectImpl.this.projectClosed(); - } - } - - protected MutablePicoContainer createPicoContainer() { - return Extensions.getArea(this).getPicoContainer(); - } - - public boolean isDefault() { - return false; - } - - public void checkUnknownMacros(final boolean showDialog) { - final IProjectStore stateStore = getStateStore(); - - final TrackingPathMacroSubstitutor[] substitutors = stateStore.getSubstitutors(); - final Set unknownMacros = new HashSet(); - for (final TrackingPathMacroSubstitutor substitutor : substitutors) { - unknownMacros.addAll(substitutor.getUnknownMacros(null)); - } - - if (!unknownMacros.isEmpty()) { - if (!showDialog || ProjectMacrosUtil.checkMacros(this, new HashSet(unknownMacros))) { - final PathMacros pathMacros = PathMacros.getInstance(); - final Set macros2invalidate = new HashSet(unknownMacros); - for (Iterator it = macros2invalidate.iterator(); it.hasNext();) { - final String macro = (String)it.next(); - final String value = pathMacros.getValue(macro); - if ((null == value || value.trim().length() == 0) && !pathMacros.isIgnoredMacroName(macro)) { - it.remove(); - } - } - - if (!macros2invalidate.isEmpty()) { - final Set components = new HashSet(); - for (TrackingPathMacroSubstitutor substitutor : substitutors) { - components.addAll(substitutor.getComponents(macros2invalidate)); - } - - if (stateStore.isReloadPossible(components)) { - for (final TrackingPathMacroSubstitutor substitutor : substitutors) { - substitutor.invalidateUnknownMacros(macros2invalidate); - } - - final UnknownMacroNotification[] notifications = - NotificationsManager.getNotificationsManager().getNotificationsOfType(UnknownMacroNotification.class, this); - for (final UnknownMacroNotification notification : notifications) { - if (macros2invalidate.containsAll(notification.getMacros())) notification.expire(); - } - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - stateStore.reinitComponents(components, true); - } - }); - } - else { - if (Messages.showYesNoDialog(this, "Component could not be reloaded. Reload project?", "Configuration changed", - Messages.getQuestionIcon()) == 0) { - ProjectManagerEx.getInstanceEx().reloadProject(this); - } - } - } - } - } - } - - @Override - public String toString() { - return "Project " - + (isDisposed() ? "(Disposed) " : "") - + (isDefault() ? "(Default) " : "'" + getLocation()+"'") - ; - } - - public static void dropUnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) { - final UnableToSaveProjectNotification[] notifications = - NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, project); - if (notifications.length == 0) { - Notifications.Bus.notify(new UnableToSaveProjectNotification(project, readOnlyFiles), NotificationDisplayType.STICKY_BALLOON, project); - } - } - - public static class UnableToSaveProjectNotification extends Notification { - private Project myProject; - private final String[] myFileNames; - - private UnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) { - super("Project Settings", "Could not save project!", buildMessage(), NotificationType.ERROR, new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - final UnableToSaveProjectNotification unableToSaveProjectNotification = (UnableToSaveProjectNotification)notification; - final Project _project = unableToSaveProjectNotification.getProject(); - notification.expire(); - - if (_project != null && !_project.isDisposed()) { - _project.save(); - } - } - }); - - myProject = project; - myFileNames = ContainerUtil.map(readOnlyFiles, new Function() { - @Override - public String fun(VirtualFile file) { - return file.getPath(); - } - }, new String[readOnlyFiles.length]); - } - - public String[] getFileNames() { - return myFileNames; - } - - private static String buildMessage() { - final StringBuffer sb = new StringBuffer( - "

Unable to save project files. Please ensure project files are writable and you have permissions to modify them."); - return sb.append(" Try to save project again.

").toString(); - } - - public Project getProject() { - return myProject; - } - - @Override - public void expire() { - myProject = null; - super.expire(); - } - } -} +/* + * 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.project.impl; + +import com.intellij.diagnostic.PluginException; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.startup.StartupManagerEx; +import com.intellij.notification.*; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.PathMacros; +import com.intellij.openapi.application.ex.ApplicationEx; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.components.StorageScheme; +import com.intellij.openapi.components.TrackingPathMacroSubstitutor; +import com.intellij.openapi.components.impl.ComponentManagerImpl; +import com.intellij.openapi.components.impl.ProjectPathMacroManager; +import com.intellij.openapi.components.impl.stores.IComponentStore; +import com.intellij.openapi.components.impl.stores.IProjectStore; +import com.intellij.openapi.components.impl.stores.UnknownMacroNotification; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ProjectManagerAdapter; +import com.intellij.openapi.project.ex.ProjectEx; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.ex.MessagesEx; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.impl.FrameTitleBuilder; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.picocontainer.*; +import org.picocontainer.defaults.CachingComponentAdapter; +import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; + +import javax.swing.event.HyperlinkEvent; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * + */ +public class ProjectImpl extends ComponentManagerImpl implements ProjectEx { + private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectImpl"); + private static final String PLUGIN_SETTINGS_ERROR = "Plugin Settings Error"; + + private ProjectManagerImpl myManager; + + private MyProjectManagerListener myProjectManagerListener; + + private final AtomicBoolean mySavingInProgress = new AtomicBoolean(false); + + public boolean myOptimiseTestLoadSpeed; + @NonNls public static final String TEMPLATE_PROJECT_NAME = "Default (Template) Project"; + + private String myName; + private String myOldName; + + public static Key CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME"); + + protected ProjectImpl(ProjectManagerImpl manager, String filePath, boolean isOptimiseTestLoadSpeed, String projectName) { + super(ApplicationManager.getApplication()); + putUserData(CREATION_TIME, System.nanoTime()); + + getPicoContainer().registerComponentInstance(Project.class, this); + + getStateStore().setProjectFilePath(filePath); + + myOptimiseTestLoadSpeed = isOptimiseTestLoadSpeed; + + myManager = manager; + myName = isDefault() ? TEMPLATE_PROJECT_NAME : projectName == null ? getStateStore().getProjectName() : projectName; + if (!isDefault() && projectName != null && getStateStore().getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) myOldName = ""; // new project + } + + public void setProjectName(final String projectName) { + if (!projectName.equals(myName)) { + myOldName = myName; + myName = projectName; + WindowManager.getInstance().getFrame(this).setTitle(FrameTitleBuilder.getInstance().getProjectTitle(this)); + } + } + + protected void boostrapPicoContainer() { + Extensions.instantiateArea(PluginManager.AREA_IDEA_PROJECT, this, null); + super.boostrapPicoContainer(); + final MutablePicoContainer picoContainer = getPicoContainer(); + + final ProjectStoreClassProvider projectStoreClassProvider = (ProjectStoreClassProvider)picoContainer.getComponentInstanceOfType(ProjectStoreClassProvider.class); + + + picoContainer.registerComponentImplementation(ProjectPathMacroManager.class); + picoContainer.registerComponent(new ComponentAdapter() { + ComponentAdapter myDelegate; + + + public ComponentAdapter getDelegate() { + if (myDelegate == null) { + + final Class storeClass = projectStoreClassProvider.getProjectStoreClass(isDefault()); + myDelegate = new CachingComponentAdapter( + new ConstructorInjectionComponentAdapter(storeClass, storeClass, null, true)); + } + + return myDelegate; + } + + public Object getComponentKey() { + return IComponentStore.class; + } + + public Class getComponentImplementation() { + return getDelegate().getComponentImplementation(); + } + + public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException { + return getDelegate().getComponentInstance(container); + } + + public void verify(final PicoContainer container) throws PicoIntrospectionException { + getDelegate().verify(container); + } + + public void accept(final PicoVisitor visitor) { + visitor.visitComponentAdapter(this); + getDelegate().accept(visitor); + } + }); + + } + + @NotNull + public IProjectStore getStateStore() { + return (IProjectStore)super.getStateStore(); + } + + public boolean isOpen() { + return ProjectManagerEx.getInstanceEx().isProjectOpened(this); + } + + public boolean isInitialized() { + return isOpen() && !isDisposed() && StartupManagerEx.getInstanceEx(this).startupActivityPassed(); + } + + public void loadProjectComponents() { + final Application app = ApplicationManager.getApplication(); + final IdeaPluginDescriptor[] plugins = app.getPlugins(); + for (IdeaPluginDescriptor plugin : plugins) { + if (PluginManager.shouldSkipPlugin(plugin)) continue; + loadComponentsConfiguration(plugin.getProjectComponents(), plugin, isDefault()); + } + } + + @NotNull + public String getProjectFilePath() { + return getStateStore().getProjectFilePath(); + } + + + @Nullable + public VirtualFile getProjectFile() { + return getStateStore().getProjectFile(); + } + + @Nullable + public VirtualFile getBaseDir() { + return getStateStore().getProjectBaseDir(); + } + + @NotNull + public String getName() { + return myName; + } + + @Nullable + @NonNls + public String getPresentableUrl() { + return getStateStore().getPresentableUrl(); + } + + @NotNull + @NonNls + public String getLocationHash() { + String str = getPresentableUrl(); + if (str == null) str = getName(); + + final String prefix = getStateStore().getStorageScheme() == StorageScheme.DIRECTORY_BASED? "" : getName(); + return prefix + Integer.toHexString(str.hashCode()); + } + + @Nullable + @NonNls + public String getLocation() { + return isDisposed() ? null : getStateStore().getLocation(); + } + + @Nullable + public VirtualFile getWorkspaceFile() { + return getStateStore().getWorkspaceFile(); + } + + public boolean isOptimiseTestLoadSpeed() { + return myOptimiseTestLoadSpeed; + } + + public void setOptimiseTestLoadSpeed(final boolean optimiseTestLoadSpeed) { + myOptimiseTestLoadSpeed = optimiseTestLoadSpeed; + } + + + public void init() { + super.init(); + getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).projectComponentsInitialized(this); + myProjectManagerListener = new MyProjectManagerListener(); + myManager.addProjectManagerListener(this, myProjectManagerListener); + } + + public boolean isToSaveProjectName() { + if (!isDefault()) { + final IProjectStore stateStore = getStateStore(); + if (stateStore.getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) { + final VirtualFile baseDir = stateStore.getProjectBaseDir(); + if (baseDir != null && baseDir.isValid()) { + return myOldName != null && !myOldName.equals(getName()); + } + } + } + + return false; + } + + public void save() { + if (ApplicationManagerEx.getApplicationEx().isDoNotSave()) return; //no need to save + + if (mySavingInProgress.compareAndSet(false, true)) { + try { + if (isToSaveProjectName()) { + final IProjectStore stateStore = getStateStore(); + final VirtualFile baseDir = stateStore.getProjectBaseDir(); + if (baseDir != null && baseDir.isValid()) { + final VirtualFile ideaDir = baseDir.findChild(DIRECTORY_STORE_FOLDER); + if (ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory()) { + final File nameFile = new File(ideaDir.getPath(), ".name"); + try { + FileUtil.writeToFile(nameFile, getName().getBytes("UTF-8"), false); + myOldName = null; + } + catch (IOException e) { + LOG.info("Unable to store project name to: " + nameFile.getPath()); + } + } + } + } + + doSave(); + } + catch (IComponentStore.SaveCancelledException e) { + LOG.info(e); + } + catch (PluginException e) { + PluginManager.disablePlugin(e.getPluginId().getIdString()); + Notifications.Bus.notify(new Notification(PLUGIN_SETTINGS_ERROR, "Unable to save plugin settings!", + "

The plugin " + e.getPluginId() + " failed to save settings and has been disabled. Please restart" + + ApplicationNamesInfo.getInstance().getFullProductName() + "

" + + (ApplicationManagerEx.getApplicationEx().isInternal() ? "

" + StringUtil.getThrowableText(e) + "

": ""), + NotificationType.ERROR), NotificationDisplayType.BALLOON, this); + LOG.info("Unable to save plugin settings",e); + } + catch (IOException e) { + MessagesEx.error(this, ProjectBundle.message("project.save.error", ApplicationManagerEx.getApplicationEx().isInternal() + ? StringUtil.getThrowableText(e) + : e.getMessage())).showLater(); + LOG.info("Error saving project", e); + } finally { + mySavingInProgress.set(false); + ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectSaved.TOPIC).saved(this); + } + } + } + + public synchronized void dispose() { + ApplicationEx application = ApplicationManagerEx.getApplicationEx(); + assert application.isDispatchThread(); + + // can call dispose only via com.intellij.ide.impl.ProjectUtil.closeAndDispose() + LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || !ProjectManagerEx.getInstanceEx().isProjectOpened(this)); + + LOG.assertTrue(!isDisposed()); + if (myProjectManagerListener != null) { + myManager.removeProjectManagerListener(this, myProjectManagerListener); + } + + disposeComponents(); + Extensions.disposeArea(this); + myManager = null; + myProjectManagerListener = null; + + super.dispose(); + + if (!application.isDisposed()) { + application.getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).afterProjectClosed(this); + } + } + + private void projectOpened() { + final ProjectComponent[] components = getComponents(ProjectComponent.class); + for (ProjectComponent component : components) { + try { + component.projectOpened(); + } + catch (Throwable e) { + LOG.error(e); + } + } + } + + + private void projectClosed() { + List components = new ArrayList(Arrays.asList(getComponents(ProjectComponent.class))); + Collections.reverse(components); + for (ProjectComponent component : components) { + try { + component.projectClosed(); + } + catch (Throwable e) { + LOG.error(e); + } + } + } + + public T[] getExtensions(final ExtensionPointName extensionPointName) { + return Extensions.getArea(this).getExtensionPoint(extensionPointName).getExtensions(); + } + + public String getDefaultName() { + if (isDefault()) return TEMPLATE_PROJECT_NAME; + + return getStateStore().getProjectName(); + } + + private class MyProjectManagerListener extends ProjectManagerAdapter { + public void projectOpened(Project project) { + LOG.assertTrue(project == ProjectImpl.this); + ProjectImpl.this.projectOpened(); + } + + public void projectClosed(Project project) { + LOG.assertTrue(project == ProjectImpl.this); + ProjectImpl.this.projectClosed(); + } + } + + protected MutablePicoContainer createPicoContainer() { + return Extensions.getArea(this).getPicoContainer(); + } + + public boolean isDefault() { + return false; + } + + public void checkUnknownMacros(final boolean showDialog) { + final IProjectStore stateStore = getStateStore(); + + final TrackingPathMacroSubstitutor[] substitutors = stateStore.getSubstitutors(); + final Set unknownMacros = new HashSet(); + for (final TrackingPathMacroSubstitutor substitutor : substitutors) { + unknownMacros.addAll(substitutor.getUnknownMacros(null)); + } + + if (!unknownMacros.isEmpty()) { + if (!showDialog || ProjectMacrosUtil.checkMacros(this, new HashSet(unknownMacros))) { + final PathMacros pathMacros = PathMacros.getInstance(); + final Set macros2invalidate = new HashSet(unknownMacros); + for (Iterator it = macros2invalidate.iterator(); it.hasNext();) { + final String macro = (String)it.next(); + final String value = pathMacros.getValue(macro); + if ((null == value || value.trim().length() == 0) && !pathMacros.isIgnoredMacroName(macro)) { + it.remove(); + } + } + + if (!macros2invalidate.isEmpty()) { + final Set components = new HashSet(); + for (TrackingPathMacroSubstitutor substitutor : substitutors) { + components.addAll(substitutor.getComponents(macros2invalidate)); + } + + if (stateStore.isReloadPossible(components)) { + for (final TrackingPathMacroSubstitutor substitutor : substitutors) { + substitutor.invalidateUnknownMacros(macros2invalidate); + } + + final UnknownMacroNotification[] notifications = + NotificationsManager.getNotificationsManager().getNotificationsOfType(UnknownMacroNotification.class, this); + for (final UnknownMacroNotification notification : notifications) { + if (macros2invalidate.containsAll(notification.getMacros())) notification.expire(); + } + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + stateStore.reinitComponents(components, true); + } + }); + } + else { + if (Messages.showYesNoDialog(this, "Component could not be reloaded. Reload project?", "Configuration changed", + Messages.getQuestionIcon()) == 0) { + ProjectManagerEx.getInstanceEx().reloadProject(this); + } + } + } + } + } + } + + @Override + public String toString() { + return "Project " + + (isDisposed() ? "(Disposed) " : "") + + (isDefault() ? "(Default) " : "'" + getLocation()+"'") + ; + } + + public static void dropUnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) { + final UnableToSaveProjectNotification[] notifications = + NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, project); + if (notifications.length == 0) { + Notifications.Bus.notify(new UnableToSaveProjectNotification(project, readOnlyFiles), NotificationDisplayType.STICKY_BALLOON, project); + } + } + + public static class UnableToSaveProjectNotification extends Notification { + private Project myProject; + private final String[] myFileNames; + + private UnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) { + super("Project Settings", "Could not save project!", buildMessage(), NotificationType.ERROR, new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + final UnableToSaveProjectNotification unableToSaveProjectNotification = (UnableToSaveProjectNotification)notification; + final Project _project = unableToSaveProjectNotification.getProject(); + notification.expire(); + + if (_project != null && !_project.isDisposed()) { + _project.save(); + } + } + }); + + myProject = project; + myFileNames = ContainerUtil.map(readOnlyFiles, new Function() { + @Override + public String fun(VirtualFile file) { + return file.getPath(); + } + }, new String[readOnlyFiles.length]); + } + + public String[] getFileNames() { + return myFileNames; + } + + private static String buildMessage() { + final StringBuffer sb = new StringBuffer( + "

Unable to save project files. Please ensure project files are writable and you have permissions to modify them."); + return sb.append(" Try to save project again.

").toString(); + } + + public Project getProject() { + return myProject; + } + + @Override + public void expire() { + myProject = null; + super.expire(); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java index eb47e40ecf61..f8ca8a5540ef 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java @@ -1,1075 +1,1075 @@ -/* - * 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.project.impl; - -import com.intellij.AppTopics; -import com.intellij.ide.AppLifecycleListener; -import com.intellij.ide.IdeEventQueue; -import com.intellij.ide.highlighter.WorkspaceFileType; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.ide.startup.impl.StartupManagerImpl; -import com.intellij.notification.NotificationsManager; -import com.intellij.openapi.application.*; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.components.ExportableApplicationComponent; -import com.intellij.openapi.components.StateStorage; -import com.intellij.openapi.components.TrackingPathMacroSubstitutor; -import com.intellij.openapi.components.impl.stores.IComponentStore; -import com.intellij.openapi.components.impl.stores.IProjectStore; -import com.intellij.openapi.components.impl.stores.StorageUtil; -import com.intellij.openapi.components.impl.stores.XmlElementStorage; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.project.ProjectManagerListener; -import com.intellij.openapi.project.ProjectReloadState; -import com.intellij.openapi.project.ex.ProjectEx; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileEvent; -import com.intellij.openapi.vfs.VirtualFileManagerListener; -import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.util.Alarm; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.io.fs.IFile; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.TObjectLongHashMap; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; - -public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExternalizable, ExportableApplicationComponent { - private static final boolean LOG_PROJECT_LEAKAGE_IN_TESTS = false; - private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectManagerImpl"); - public static final int CURRENT_FORMAT_VERSION = 4; - - private static final Key> LISTENERS_IN_PROJECT_KEY = Key.create("LISTENERS_IN_PROJECT_KEY"); - @NonNls private static final String ELEMENT_DEFAULT_PROJECT = "defaultProject"; - - @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) - private ProjectImpl myDefaultProject; // Only used asynchronously in save and dispose, which itself are synchronized. - - @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) - private Element myDefaultProjectRootElement; // Only used asynchronously in save and dispose, which itself are synchronized. - - private final List myOpenProjects = new ArrayList(); - private Project[] myOpenProjectsArrayCache = {}; - private final List myListeners = ContainerUtil.createEmptyCOWList(); - - private Project myCurrentTestProject = null; - - private final Map mySavedCopies = new HashMap(); - private final TObjectLongHashMap mySavedTimestamps = new TObjectLongHashMap(); - private final Map>> myChangedProjectFiles = new HashMap>>(); - private final Alarm myChangedFilesAlarm = new Alarm(); - private final List> myChangedApplicationFiles = new ArrayList>(); - private final AtomicInteger myReloadBlockCount = new AtomicInteger(0); - private final Map myProjects = new WeakHashMap(); - private static final int MAX_LEAKY_PROJECTS = 42; - private final ProgressManager myProgressManager; - - private static ProjectManagerListener[] getListeners(Project project) { - List array = project.getUserData(LISTENERS_IN_PROJECT_KEY); - if (array == null) return ProjectManagerListener.EMPTY_ARRAY; - return ContainerUtil.toArray(array, new ProjectManagerListener[array.size()]); - } - - public ProjectManagerImpl(VirtualFileManagerEx virtualFileManagerEx, ProgressManager progressManager) { - myProgressManager = progressManager; - Application app = ApplicationManager.getApplication(); - MessageBus messageBus = app.getMessageBus(); - MessageBusConnection connection = messageBus.connect(app); - connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() { - public void storageFileChanged(final VirtualFileEvent event, @NotNull final StateStorage storage) { - VirtualFile file = event.getFile(); - LOG.debug("[RELOAD] Storage file changed: " + file.getPath()); - if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) { - saveChangedProjectFile(file, null, storage); - } - } - }); - final ProjectManagerListener busPublisher = messageBus.syncPublisher(AppTopics.PROJECTS); - - addProjectManagerListener( - new ProjectManagerListener() { - - public void projectOpened(final Project project) { - MessageBus messageBus = project.getMessageBus(); - MessageBusConnection connection = messageBus.connect(project); - connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() { - public void storageFileChanged(final VirtualFileEvent event, @NotNull final StateStorage storage) { - VirtualFile file = event.getFile(); - if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) { - saveChangedProjectFile(file, project, storage); - } - } - }); - - busPublisher.projectOpened(project); - ProjectManagerListener[] listeners = getListeners(project); - for (ProjectManagerListener listener : listeners) { - listener.projectOpened(project); - } - } - - public void projectClosed(Project project) { - busPublisher.projectClosed(project); - ProjectManagerListener[] listeners = getListeners(project); - for (ProjectManagerListener listener : listeners) { - listener.projectClosed(project); - } - } - - public boolean canCloseProject(Project project) { - ProjectManagerListener[] listeners = getListeners(project); - for (ProjectManagerListener listener : listeners) { - if (!listener.canCloseProject(project)) { - return false; - } - } - return true; - } - - public void projectClosing(Project project) { - busPublisher.projectClosing(project); - ProjectManagerListener[] listeners = getListeners(project); - for (ProjectManagerListener listener : listeners) { - listener.projectClosing(project); - } - } - } - ); - - registerExternalProjectFileListener(virtualFileManagerEx); - } - - public void disposeComponent() { - Disposer.dispose(myChangedFilesAlarm); - if (myDefaultProject != null) { - Disposer.dispose(myDefaultProject); - myDefaultProject = null; - } - } - - public void initComponent() { - } - - @Nullable - public Project newProject(final String projectName, String filePath, boolean useDefaultProjectSettings, boolean isDummy) { - filePath = canonicalize(filePath); - - if (LOG_PROJECT_LEAKAGE_IN_TESTS && ApplicationManager.getApplication().isUnitTestMode()) { - for (int i = 0; i < 42; i++) { - if (myProjects.size() < MAX_LEAKY_PROJECTS) break; - System.gc(); - try { - Thread.sleep(100); - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } - - System.gc(); - } - - if (myProjects.size() >= MAX_LEAKY_PROJECTS) { - List copy = new ArrayList(myProjects.keySet()); - myProjects.clear(); - throw new TooManyProjectLeakedException(copy); - } - } - - try { - ProjectImpl project = - createAndInitProject(projectName, filePath, false, ApplicationManager.getApplication().isUnitTestMode(), - useDefaultProjectSettings ? getDefaultProject() : null); - if (LOG_PROJECT_LEAKAGE_IN_TESTS) { - myProjects.put(project, null); - } - return project; - } - catch (final Exception e) { - LOG.info(e); - Messages.showErrorDialog(message(e), ProjectBundle.message("project.load.default.error")); - } - return null; - } - - @NonNls - private static String message(Throwable e) { - String message = e.getMessage(); - if (message != null) return message; - message = e.getLocalizedMessage(); - if (message != null) return message; - message = e.toString(); - Throwable cause = e.getCause(); - if (cause != null) { - String causeMessage = message(cause); - return message + " (cause: " + causeMessage + ")"; - } - - return message; - } - - private ProjectImpl createAndInitProject(String projectName, String filePath, boolean isDefault, boolean isOptimiseTestLoadSpeed, - @Nullable Project template) throws IOException { - final ProjectImpl project = isDefault ? new DefaultProject(this, filePath, isOptimiseTestLoadSpeed, projectName) : - new ProjectImpl(this, filePath, isOptimiseTestLoadSpeed, projectName); - - ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).beforeProjectLoaded(project); - - try { - if (template != null) { - project.getStateStore().loadProjectFromTemplate((ProjectImpl)template); - } else { - project.getStateStore().load(); - } - } - catch (IOException e) { - scheduleDispose(project); - throw e; - } - catch (final StateStorage.StateStorageException e) { - scheduleDispose(project); - throw e; - } - - project.loadProjectComponents(); - project.init(); - - return project; - } - - private static void scheduleDispose(final ProjectImpl project) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - Disposer.dispose(project); - } - }); - } - - @Nullable - public Project loadProject(String filePath) throws IOException, JDOMException, InvalidDataException { - try { - return doLoadProject(filePath); - } - catch (StateStorage.StateStorageException e) { - throw new IOException(e.getMessage()); - } - } - - @Nullable - private Project doLoadProject(String filePath) throws IOException, StateStorage.StateStorageException { - filePath = canonicalize(filePath); - ProjectImpl project = null; - try { - project = createAndInitProject(null, filePath, false, false, null); - } - catch (ProcessCanceledException e) { - if (project != null) { - scheduleDispose(project); - } - throw e; - } - - return project; - } - - @Nullable - protected static String canonicalize(final String filePath) { - if (filePath == null) return null; - try { - return FileUtil.resolveShortWindowsName(filePath); - } - catch (IOException e) { - // OK. File does not yet exist so it's canonical path will be equal to its original path. - } - - return filePath; - } - - @TestOnly - public synchronized boolean isDefaultProjectInitialized() { - return myDefaultProject != null; - } - @NotNull - public synchronized Project getDefaultProject() { - if (myDefaultProject == null) { - try { - myDefaultProject = createAndInitProject(null, null, true, ApplicationManager.getApplication().isUnitTestMode(), null); - myDefaultProjectRootElement = null; - } - catch (IOException e) { - LOG.error(e); - } - catch (StateStorage.StateStorageException e) { - LOG.error(e); - } - } - return myDefaultProject; - } - - - public Element getDefaultProjectRootElement() { - return myDefaultProjectRootElement; - } - - @NotNull - public Project[] getOpenProjects() { - if (ApplicationManager.getApplication().isUnitTestMode()) { - final Project currentTestProject = myCurrentTestProject; - if (myOpenProjects.isEmpty() && currentTestProject != null && !currentTestProject.isDisposed()) { - return new Project[] {currentTestProject}; - } - } - if (myOpenProjectsArrayCache.length != myOpenProjects.size()) { - LOG.error("Open projects: "+myOpenProjects+"; cache: "+Arrays.asList(myOpenProjectsArrayCache)); - } - if (myOpenProjectsArrayCache.length > 0 && myOpenProjectsArrayCache[0] != myOpenProjects.get(0)) { - LOG.error("Open projects cache corrupted. Open projects: "+myOpenProjects+"; cache: "+Arrays.asList(myOpenProjectsArrayCache)); - } - return myOpenProjectsArrayCache; - } - - public boolean isProjectOpened(Project project) { - if (ApplicationManager.getApplication().isUnitTestMode() && myOpenProjects.isEmpty() && myCurrentTestProject != null) { - return project == myCurrentTestProject; - } - return myOpenProjects.contains(project); - } - - public boolean openProject(final Project project) { - if (myOpenProjects.contains(project)) return false; - if (!ApplicationManager.getApplication().isUnitTestMode() && !((ProjectEx)project).getStateStore().checkVersion()) return false; - - myOpenProjects.add(project); - cacheOpenProjects(); - - fireProjectOpened(project); - - final StartupManagerImpl startupManager = (StartupManagerImpl)StartupManager.getInstance(project); - - boolean ok = myProgressManager.runProcessWithProgressSynchronously(new Runnable() { - public void run() { - startupManager.runStartupActivities(); - } - }, ProjectBundle.message("project.load.progress"), true, project); - - if (!ok) { - closeProject(project, false, false); - notifyProjectOpenFailed(); - return false; - } - - startupManager.runPostStartupActivities(); - - if (!ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode()) { - // should be invoked last - StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() { - public void run() { - final TrackingPathMacroSubstitutor macroSubstitutor = - ((ProjectEx)project).getStateStore().getStateStorageManager().getMacroSubstitutor(); - if (macroSubstitutor != null) { - StorageUtil.notifyUnknownMacros(macroSubstitutor, project, null); - } - } - }); - } - - return true; - } - - private void cacheOpenProjects() { - myOpenProjectsArrayCache = myOpenProjects.toArray(new Project[myOpenProjects.size()]); - } - - public Project loadAndOpenProject(String filePath) throws IOException, JDOMException, InvalidDataException { - return loadAndOpenProject(filePath, true); - } - - @Nullable - protected Project convertAndLoadProject(String filePath, boolean convert) throws IOException { - return loadProjectWithProgress(filePath); - } - - @Nullable - public Project loadAndOpenProject(final String filePath, final boolean convert) throws IOException, JDOMException, InvalidDataException { - try { - - Project project = convertAndLoadProject(filePath, convert); - if (project == null) { - return null; - } - - if (!openProject(project)) { - Disposer.dispose(project); - return null; - } - - return project; - } - catch (StateStorage.StateStorageException e) { - throw new IOException(e.getMessage()); - } - } - - @Nullable - public Project loadProjectWithProgress(final String filePath) throws IOException { - return loadProjectWithProgress(filePath, null); - } - - @Nullable - public Project loadProjectWithProgress(final String filePath, Ref canceled) throws IOException { - final IOException[] io = {null}; - final StateStorage.StateStorageException[] stateStorage = {null}; - - if (filePath != null) { - refreshProjectFiles(filePath); - } - - final Project[] project = new Project[1]; - boolean ok = myProgressManager.runProcessWithProgressSynchronously(new Runnable() { - public void run() { - final ProgressIndicator indicator = myProgressManager.getProgressIndicator(); - try { - if (indicator != null) { - indicator.setText(ProjectBundle.message("loading.components.for", filePath)); - indicator.setIndeterminate(true); - } - project[0] = doLoadProject(filePath); - } - catch (IOException e) { - io[0] = e; - return; - } - catch (StateStorage.StateStorageException e) { - stateStorage[0] = e; - return; - } - - if (indicator != null) { - indicator.setText(ProjectBundle.message("initializing.components")); - } - } - }, ProjectBundle.message("project.load.progress"), true, null); - - if (!ok) { - if (project[0] != null) { - Disposer.dispose(project[0]); - project[0] = null; - } - if (canceled != null) { - canceled.set(true); - } - notifyProjectOpenFailed(); - } - - if (io[0] != null) throw io[0]; - if (stateStorage[0] != null) throw stateStorage[0]; - - if (project[0] == null || !ok) { - return null; - } - return project [0]; - } - - private static void refreshProjectFiles(final String filePath) { - if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isDispatchThread()) { - final File file = new File(filePath); - if (file.isFile()) { - VirtualFile projectFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePath); - if (projectFile != null) { - projectFile.refresh(false, false); - } - - File iwsFile = new File(file.getParentFile(), FileUtil.getNameWithoutExtension(file) + WorkspaceFileType.DOT_DEFAULT_EXTENSION); - VirtualFile wsFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(iwsFile); - if (wsFile != null) { - wsFile.refresh(false, false); - } - - } - else { - VirtualFile projectConfigDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath, Project.DIRECTORY_STORE_FOLDER)); - if (projectConfigDir != null && projectConfigDir.isDirectory()) { - projectConfigDir.getChildren(); - if (projectConfigDir instanceof NewVirtualFile) { - ((NewVirtualFile)projectConfigDir).markDirtyRecursively(); - } - projectConfigDir.refresh(false, true); - - } - } - } - } - - private static void notifyProjectOpenFailed() { - ApplicationManager.getApplication().getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).projectOpenFailed(); - } - - private void registerExternalProjectFileListener(VirtualFileManagerEx virtualFileManager) { - virtualFileManager.addVirtualFileManagerListener(new VirtualFileManagerListener() { - public void beforeRefreshStart(boolean asynchonous) { - } - - public void afterRefreshFinish(boolean asynchonous) { - scheduleReloadApplicationAndProject(); - } - }); - } - - private void askToReloadProjectIfConfigFilesChangedExternally() { - LOG.debug("[RELOAD] myReloadBlockCount = " + myReloadBlockCount.get()); - if (myReloadBlockCount.get() == 0) { - Set projects; - - synchronized (myChangedProjectFiles) { - if (myChangedProjectFiles.isEmpty()) return; - projects = new HashSet(myChangedProjectFiles.keySet()); - } - - List projectsToReload = new ArrayList(); - - for (Project project : projects) { - if (shouldReloadProject(project)) { - projectsToReload.add(project); - } - } - - for (final Project projectToReload : projectsToReload) { - reloadProjectImpl(projectToReload, false, false); - } - } - - } - - private boolean tryToReloadApplication(){ - try { - final Application app = ApplicationManager.getApplication(); - - if (app.isDisposed()) return false; - final HashSet> causes = new HashSet>(myChangedApplicationFiles); - if (causes.isEmpty()) return true; - - final boolean[] reloadOk = {false}; - final LinkedHashSet components = new LinkedHashSet(); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - reloadOk[0] = ((ApplicationImpl)app).getStateStore().reload(causes, components); - } - catch (StateStorage.StateStorageException e) { - Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), - ProjectBundle.message("project.reload.failed.title")); - } - catch (IOException e) { - Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), - ProjectBundle.message("project.reload.failed.title")); - } - } - }); - - if (!reloadOk[0] && !components.isEmpty()) { - String message = "Application components were changed externally and cannot be reloaded:\n"; - for (String component : components) { - message += component + "\n"; - } - - final boolean canRestart = ApplicationManager.getApplication().isRestartCapable(); - message += "Would you like to " + (canRestart ? "restart " : "shutdown "); - message += ApplicationNamesInfo.getInstance().getProductName() + "?"; - - if (Messages.showYesNoDialog(message, - "Application Configuration Reload", Messages.getQuestionIcon()) == 0) { - for (Pair cause : causes) { - StateStorage stateStorage = cause.getSecond(); - if (stateStorage instanceof XmlElementStorage) { - ((XmlElementStorage)stateStorage).disableSaving(); - } - } - if (canRestart) { - ApplicationManagerEx.getApplicationEx().restart(); - } - else { - ApplicationManagerEx.getApplicationEx().exit(true); - } - } - } - - return reloadOk[0]; - } - finally { - myChangedApplicationFiles.clear(); - } - - } - - private boolean shouldReloadProject(final Project project) { - if (project.isDisposed()) return false; - final HashSet> causes = new HashSet>(); - - synchronized (myChangedProjectFiles) { - final List> changes = myChangedProjectFiles.remove(project); - if (changes != null) { - causes.addAll(changes); - } - - if (causes.isEmpty()) return false; - } - - final boolean[] reloadOk = {false}; - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - LOG.debug("[RELOAD] Reloading project/components..."); - reloadOk[0] = ((ProjectEx)project).getStateStore().reload(causes); - } - catch (StateStorage.StateStorageException e) { - Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), - ProjectBundle.message("project.reload.failed.title")); - } - catch (IOException e) { - Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), - ProjectBundle.message("project.reload.failed.title")); - } - } - }); - if (reloadOk[0]) return false; - - String message; - if (causes.size() == 1) { - message = ProjectBundle.message("project.reload.external.change.single", causes.iterator().next().first.getPresentableUrl()); - } - else { - StringBuilder filesBuilder = new StringBuilder(); - boolean first = true; - Set alreadyShown = new HashSet(); - for (Pair cause : causes) { - String url = cause.first.getPresentableUrl(); - if (!alreadyShown.contains(url)) { - if (!first) filesBuilder.append("\n"); - first = false; - filesBuilder.append(url); - alreadyShown.add(url); - } - } - message = ProjectBundle.message("project.reload.external.change.multiple", filesBuilder.toString()); - } - - return Messages.showTwoStepConfirmationDialog(message, ProjectBundle.message("project.reload.external.change.title"), "Reload project", - Messages.getQuestionIcon()) == 0; - } - - public boolean isFileSavedToBeReloaded(VirtualFile candidate) { - return mySavedCopies.containsKey(candidate); - } - - public void blockReloadingProjectOnExternalChanges() { - myReloadBlockCount.incrementAndGet(); - } - - public void unblockReloadingProjectOnExternalChanges() { - if (myReloadBlockCount.decrementAndGet() == 0) scheduleReloadApplicationAndProject(); - } - - private void scheduleReloadApplicationAndProject() { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - IdeEventQueue.getInstance().addIdleListener(new Runnable() { - @Override - public void run() { - IdeEventQueue.getInstance().removeIdleListener(this); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - if (!tryToReloadApplication()) return; - askToReloadProjectIfConfigFilesChangedExternally(); - } - }, ModalityState.NON_MODAL); - } - }, 2000); - } - }, ModalityState.NON_MODAL); - } - - public void setCurrentTestProject(@Nullable final Project project) { - assert ApplicationManager.getApplication().isUnitTestMode(); - myCurrentTestProject = project; - } - - @Nullable - public Project getCurrentTestProject() { - assert ApplicationManager.getApplication().isUnitTestMode(); - return myCurrentTestProject; - } - - public void saveChangedProjectFile(final VirtualFile file, final Project project) { - if (file.exists()) { - copyToTemp(file); - } - registerProjectToReload(project, file, null); - } - - private void saveChangedProjectFile(final VirtualFile file, final Project project, final StateStorage storage) { - if (file.exists()) { - copyToTemp(file); - } - registerProjectToReload(project, file, storage); - } - - private void registerProjectToReload(final Project project, final VirtualFile cause, final StateStorage storage) { - LOG.debug("[RELOAD] Registering project to reload."); - - if (project != null) { - synchronized (myChangedProjectFiles) { - List> changedProjectFiles = myChangedProjectFiles.get(project); - if (changedProjectFiles == null) { - changedProjectFiles = new ArrayList>(); - myChangedProjectFiles.put(project, changedProjectFiles); - } - - changedProjectFiles.add(new Pair(cause, storage)); - } - } - else { - myChangedApplicationFiles.add(new Pair(cause, storage)); - } - - myChangedFilesAlarm.cancelAllRequests(); - myChangedFilesAlarm.addRequest(new Runnable() { - public void run() { - LOG.debug("[RELOAD] Scheduling reload application & project, myReloadBlockCount = " + myReloadBlockCount); - if (myReloadBlockCount.get() == 0) { - scheduleReloadApplicationAndProject(); - } - } - }, 444); - } - - private void copyToTemp(VirtualFile file) { - try { - final byte[] bytes = file.contentsToByteArray(); - mySavedCopies.put(file, bytes); - mySavedTimestamps.put(file, file.getTimeStamp()); - } - catch (IOException e) { - LOG.error(e); - } - } - - private void restoreCopy(VirtualFile file) { - try { - if (file == null) return; // Externally deleted actually. - if (!file.isWritable()) return; // IDEA was unable to save it as well. So no need to restore. - - final byte[] bytes = mySavedCopies.get(file); - if (bytes != null) { - try { - file.setBinaryContent(bytes, -1, mySavedTimestamps.get(file)); - } - catch (IOException e) { - Messages.showWarningDialog(ProjectBundle.message("project.reload.write.failed", file.getPresentableUrl()), - ProjectBundle.message("project.reload.write.failed.title")); - } - } - } - finally { - mySavedCopies.remove(file); - mySavedTimestamps.remove(file); - } - } - - public void reloadProject(final Project p) { - reloadProjectImpl(p, true, false); - } - - public void reloadProjectImpl(final Project p, final boolean clearCopyToRestore, boolean takeMemorySnapshot) { - if (clearCopyToRestore) { - mySavedCopies.clear(); - mySavedTimestamps.clear(); - } - reloadProject(p, takeMemorySnapshot); - } - - public void reloadProject(@NotNull Project p, final boolean takeMemorySnapshot) { - final Project[] project = {p}; - - ProjectReloadState.getInstance(project[0]).onBeforeAutomaticProjectReload(); - final Application application = ApplicationManager.getApplication(); - - application.invokeLater(new Runnable() { - public void run() { - LOG.debug("Reloading project."); - ProjectImpl projectImpl = (ProjectImpl)project[0]; - if (projectImpl.isDisposed()) return; - IProjectStore projectStore = projectImpl.getStateStore(); - final String location = projectImpl.getLocation(); - - final List original; - try { - final IComponentStore.SaveSession saveSession = projectStore.startSave(); - original = saveSession.getAllStorageFiles(true); - saveSession.finishSave(); - } - catch (IOException e) { - LOG.error(e); - return; - } - - if (project[0].isDisposed() || ProjectUtil.closeAndDispose(project[0])) { - application.runWriteAction(new Runnable() { - public void run() { - for (final IFile originalFile : original) { - restoreCopy(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(originalFile)); - } - } - }); - - project[0] = null; // Let it go. - - ProjectUtil.openProject(location, null, true); - } - } - }, ModalityState.NON_MODAL); - } - - /* - public boolean isOpeningProject() { - return myCountOfProjectsBeingOpen > 0; - } - */ - - public boolean closeProject(final Project project) { - return closeProject(project, true, false); - } - - private boolean closeProject(final Project project, final boolean save, final boolean dispose) { - if (!isProjectOpened(project)) return true; - if (!canClose(project)) return false; - - final ShutDownTracker shutDownTracker = ShutDownTracker.getInstance(); - shutDownTracker.registerStopperThread(Thread.currentThread()); - try { - if (save) { - FileDocumentManager.getInstance().saveAllDocuments(); - project.save(); - } - - if (!ensureCouldCloseIfUnableToSave(project)) { - return false; - } - - fireProjectClosing(project); // somebody can start progress here, do not wrap in write action - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - myOpenProjects.remove(project); - cacheOpenProjects(); - - myChangedProjectFiles.remove(project); - - fireProjectClosed(project); - - if (dispose) { - Disposer.dispose(project); - } - } - }); - } - finally { - shutDownTracker.unregisterStopperThread(Thread.currentThread()); - } - - return true; - } - - @Override - public boolean closeAndDispose(@NotNull final Project project) { - return closeProject(project, true, true); - } - - private void fireProjectClosing(Project project) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: fireProjectClosing()"); - } - - for (ProjectManagerListener listener : myListeners) { - try { - listener.projectClosing(project); - } - catch (Exception e) { - LOG.error(e); - } - } - } - - public void addProjectManagerListener(ProjectManagerListener listener) { - myListeners.add(listener); - } - - public void removeProjectManagerListener(ProjectManagerListener listener) { - boolean removed = myListeners.remove(listener); - LOG.assertTrue(removed); - } - - public void addProjectManagerListener(Project project, ProjectManagerListener listener) { - List listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); - if (listeners == null) { - listeners = ((UserDataHolderEx)project).putUserDataIfAbsent(LISTENERS_IN_PROJECT_KEY, new ArrayList()); - } - listeners.add(listener); - } - - public void removeProjectManagerListener(Project project, ProjectManagerListener listener) { - List listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); - if (listeners != null) { - boolean removed = listeners.remove(listener); - LOG.assertTrue(removed); - } - else { - LOG.assertTrue(false); - } - } - - private void fireProjectOpened(Project project) { - if (LOG.isDebugEnabled()) { - LOG.debug("projectOpened"); - } - - for (ProjectManagerListener listener : myListeners) { - try { - listener.projectOpened(project); - } - catch (Exception e) { - LOG.error(e); - } - } - } - - private void fireProjectClosed(Project project) { - if (LOG.isDebugEnabled()) { - LOG.debug("projectClosed"); - } - - for (ProjectManagerListener listener : myListeners) { - try { - listener.projectClosed(project); - } - catch (Exception e) { - LOG.error(e); - } - } - } - - public boolean canClose(Project project) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: canClose()"); - } - - for (ProjectManagerListener listener : myListeners) { - try { - if (!listener.canCloseProject(project)) return false; - } catch (Throwable e) { - LOG.warn(e); // DO NOT LET ANY PLUGIN to prevent closing due to exception - } - } - - return true; - } - - private static boolean ensureCouldCloseIfUnableToSave(@NotNull final Project project) { - final ProjectImpl.UnableToSaveProjectNotification[] notifications = - NotificationsManager.getNotificationsManager().getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, project); - if (notifications.length == 0) return true; - - final String fileNames = StringUtil.join(notifications[0].getFileNames(), "\n"); - - final String msg = String.format("%s was unable to save some project files,\nare you sure you want to close this project anyway?", - ApplicationNamesInfo.getInstance().getProductName()); - return Messages.showDialog(project, msg, "Unsaved project!", "Read-only files:\n\n" + fileNames, new String[]{"Yes", "No"}, 0, 1, - Messages.getWarningIcon()) == 0; - } - - public void writeExternal(Element parentNode) throws WriteExternalException { - if (myDefaultProject != null) { - myDefaultProject.save(); - } - - if (myDefaultProjectRootElement == null) { //read external isn't called if config folder is absent - myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT); - } - - myDefaultProjectRootElement.detach(); - parentNode.addContent(myDefaultProjectRootElement); - } - - - public void setDefaultProjectRootElement(final Element defaultProjectRootElement) { - myDefaultProjectRootElement = defaultProjectRootElement; - } - - public void readExternal(Element parentNode) throws InvalidDataException { - myDefaultProjectRootElement = parentNode.getChild(ELEMENT_DEFAULT_PROJECT); - - if (myDefaultProjectRootElement == null) { - myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT); - } - - myDefaultProjectRootElement.detach(); - } - - public String getExternalFileName() { - return "project.default"; - } - - @NotNull - public String getComponentName() { - return "ProjectManager"; - } - - @NotNull - public File[] getExportFiles() { - return new File[]{PathManager.getOptionsFile(this)}; - } - - @NotNull - public String getPresentableName() { - return ProjectBundle.message("project.default.settings"); - } -} +/* + * 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.project.impl; + +import com.intellij.AppTopics; +import com.intellij.ide.AppLifecycleListener; +import com.intellij.ide.IdeEventQueue; +import com.intellij.ide.highlighter.WorkspaceFileType; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.ide.startup.impl.StartupManagerImpl; +import com.intellij.notification.NotificationsManager; +import com.intellij.openapi.application.*; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.components.ExportableApplicationComponent; +import com.intellij.openapi.components.StateStorage; +import com.intellij.openapi.components.TrackingPathMacroSubstitutor; +import com.intellij.openapi.components.impl.stores.IComponentStore; +import com.intellij.openapi.components.impl.stores.IProjectStore; +import com.intellij.openapi.components.impl.stores.StorageUtil; +import com.intellij.openapi.components.impl.stores.XmlElementStorage; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ProjectManagerListener; +import com.intellij.openapi.project.ProjectReloadState; +import com.intellij.openapi.project.ex.ProjectEx; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileEvent; +import com.intellij.openapi.vfs.VirtualFileManagerListener; +import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.util.Alarm; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.io.fs.IFile; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.TObjectLongHashMap; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExternalizable, ExportableApplicationComponent { + private static final boolean LOG_PROJECT_LEAKAGE_IN_TESTS = false; + private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectManagerImpl"); + public static final int CURRENT_FORMAT_VERSION = 4; + + private static final Key> LISTENERS_IN_PROJECT_KEY = Key.create("LISTENERS_IN_PROJECT_KEY"); + @NonNls private static final String ELEMENT_DEFAULT_PROJECT = "defaultProject"; + + @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) + private ProjectImpl myDefaultProject; // Only used asynchronously in save and dispose, which itself are synchronized. + + @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) + private Element myDefaultProjectRootElement; // Only used asynchronously in save and dispose, which itself are synchronized. + + private final List myOpenProjects = new ArrayList(); + private Project[] myOpenProjectsArrayCache = {}; + private final List myListeners = ContainerUtil.createEmptyCOWList(); + + private Project myCurrentTestProject = null; + + private final Map mySavedCopies = new HashMap(); + private final TObjectLongHashMap mySavedTimestamps = new TObjectLongHashMap(); + private final Map>> myChangedProjectFiles = new HashMap>>(); + private final Alarm myChangedFilesAlarm = new Alarm(); + private final List> myChangedApplicationFiles = new ArrayList>(); + private final AtomicInteger myReloadBlockCount = new AtomicInteger(0); + private final Map myProjects = new WeakHashMap(); + private static final int MAX_LEAKY_PROJECTS = 42; + private final ProgressManager myProgressManager; + + private static ProjectManagerListener[] getListeners(Project project) { + List array = project.getUserData(LISTENERS_IN_PROJECT_KEY); + if (array == null) return ProjectManagerListener.EMPTY_ARRAY; + return ContainerUtil.toArray(array, new ProjectManagerListener[array.size()]); + } + + public ProjectManagerImpl(VirtualFileManagerEx virtualFileManagerEx, ProgressManager progressManager) { + myProgressManager = progressManager; + Application app = ApplicationManager.getApplication(); + MessageBus messageBus = app.getMessageBus(); + MessageBusConnection connection = messageBus.connect(app); + connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() { + public void storageFileChanged(final VirtualFileEvent event, @NotNull final StateStorage storage) { + VirtualFile file = event.getFile(); + LOG.debug("[RELOAD] Storage file changed: " + file.getPath()); + if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) { + saveChangedProjectFile(file, null, storage); + } + } + }); + final ProjectManagerListener busPublisher = messageBus.syncPublisher(AppTopics.PROJECTS); + + addProjectManagerListener( + new ProjectManagerListener() { + + public void projectOpened(final Project project) { + MessageBus messageBus = project.getMessageBus(); + MessageBusConnection connection = messageBus.connect(project); + connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() { + public void storageFileChanged(final VirtualFileEvent event, @NotNull final StateStorage storage) { + VirtualFile file = event.getFile(); + if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) { + saveChangedProjectFile(file, project, storage); + } + } + }); + + busPublisher.projectOpened(project); + ProjectManagerListener[] listeners = getListeners(project); + for (ProjectManagerListener listener : listeners) { + listener.projectOpened(project); + } + } + + public void projectClosed(Project project) { + busPublisher.projectClosed(project); + ProjectManagerListener[] listeners = getListeners(project); + for (ProjectManagerListener listener : listeners) { + listener.projectClosed(project); + } + } + + public boolean canCloseProject(Project project) { + ProjectManagerListener[] listeners = getListeners(project); + for (ProjectManagerListener listener : listeners) { + if (!listener.canCloseProject(project)) { + return false; + } + } + return true; + } + + public void projectClosing(Project project) { + busPublisher.projectClosing(project); + ProjectManagerListener[] listeners = getListeners(project); + for (ProjectManagerListener listener : listeners) { + listener.projectClosing(project); + } + } + } + ); + + registerExternalProjectFileListener(virtualFileManagerEx); + } + + public void disposeComponent() { + Disposer.dispose(myChangedFilesAlarm); + if (myDefaultProject != null) { + Disposer.dispose(myDefaultProject); + myDefaultProject = null; + } + } + + public void initComponent() { + } + + @Nullable + public Project newProject(final String projectName, String filePath, boolean useDefaultProjectSettings, boolean isDummy) { + filePath = canonicalize(filePath); + + if (LOG_PROJECT_LEAKAGE_IN_TESTS && ApplicationManager.getApplication().isUnitTestMode()) { + for (int i = 0; i < 42; i++) { + if (myProjects.size() < MAX_LEAKY_PROJECTS) break; + System.gc(); + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + + System.gc(); + } + + if (myProjects.size() >= MAX_LEAKY_PROJECTS) { + List copy = new ArrayList(myProjects.keySet()); + myProjects.clear(); + throw new TooManyProjectLeakedException(copy); + } + } + + try { + ProjectImpl project = + createAndInitProject(projectName, filePath, false, ApplicationManager.getApplication().isUnitTestMode(), + useDefaultProjectSettings ? getDefaultProject() : null); + if (LOG_PROJECT_LEAKAGE_IN_TESTS) { + myProjects.put(project, null); + } + return project; + } + catch (final Exception e) { + LOG.info(e); + Messages.showErrorDialog(message(e), ProjectBundle.message("project.load.default.error")); + } + return null; + } + + @NonNls + private static String message(Throwable e) { + String message = e.getMessage(); + if (message != null) return message; + message = e.getLocalizedMessage(); + if (message != null) return message; + message = e.toString(); + Throwable cause = e.getCause(); + if (cause != null) { + String causeMessage = message(cause); + return message + " (cause: " + causeMessage + ")"; + } + + return message; + } + + private ProjectImpl createAndInitProject(String projectName, String filePath, boolean isDefault, boolean isOptimiseTestLoadSpeed, + @Nullable Project template) throws IOException { + final ProjectImpl project = isDefault ? new DefaultProject(this, filePath, isOptimiseTestLoadSpeed, projectName) : + new ProjectImpl(this, filePath, isOptimiseTestLoadSpeed, projectName); + + ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).beforeProjectLoaded(project); + + try { + if (template != null) { + project.getStateStore().loadProjectFromTemplate((ProjectImpl)template); + } else { + project.getStateStore().load(); + } + } + catch (IOException e) { + scheduleDispose(project); + throw e; + } + catch (final StateStorage.StateStorageException e) { + scheduleDispose(project); + throw e; + } + + project.loadProjectComponents(); + project.init(); + + return project; + } + + private static void scheduleDispose(final ProjectImpl project) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + Disposer.dispose(project); + } + }); + } + + @Nullable + public Project loadProject(String filePath) throws IOException, JDOMException, InvalidDataException { + try { + return doLoadProject(filePath); + } + catch (StateStorage.StateStorageException e) { + throw new IOException(e.getMessage()); + } + } + + @Nullable + private Project doLoadProject(String filePath) throws IOException, StateStorage.StateStorageException { + filePath = canonicalize(filePath); + ProjectImpl project = null; + try { + project = createAndInitProject(null, filePath, false, false, null); + } + catch (ProcessCanceledException e) { + if (project != null) { + scheduleDispose(project); + } + throw e; + } + + return project; + } + + @Nullable + protected static String canonicalize(final String filePath) { + if (filePath == null) return null; + try { + return FileUtil.resolveShortWindowsName(filePath); + } + catch (IOException e) { + // OK. File does not yet exist so it's canonical path will be equal to its original path. + } + + return filePath; + } + + @TestOnly + public synchronized boolean isDefaultProjectInitialized() { + return myDefaultProject != null; + } + @NotNull + public synchronized Project getDefaultProject() { + if (myDefaultProject == null) { + try { + myDefaultProject = createAndInitProject(null, null, true, ApplicationManager.getApplication().isUnitTestMode(), null); + myDefaultProjectRootElement = null; + } + catch (IOException e) { + LOG.error(e); + } + catch (StateStorage.StateStorageException e) { + LOG.error(e); + } + } + return myDefaultProject; + } + + + public Element getDefaultProjectRootElement() { + return myDefaultProjectRootElement; + } + + @NotNull + public Project[] getOpenProjects() { + if (ApplicationManager.getApplication().isUnitTestMode()) { + final Project currentTestProject = myCurrentTestProject; + if (myOpenProjects.isEmpty() && currentTestProject != null && !currentTestProject.isDisposed()) { + return new Project[] {currentTestProject}; + } + } + if (myOpenProjectsArrayCache.length != myOpenProjects.size()) { + LOG.error("Open projects: "+myOpenProjects+"; cache: "+Arrays.asList(myOpenProjectsArrayCache)); + } + if (myOpenProjectsArrayCache.length > 0 && myOpenProjectsArrayCache[0] != myOpenProjects.get(0)) { + LOG.error("Open projects cache corrupted. Open projects: "+myOpenProjects+"; cache: "+Arrays.asList(myOpenProjectsArrayCache)); + } + return myOpenProjectsArrayCache; + } + + public boolean isProjectOpened(Project project) { + if (ApplicationManager.getApplication().isUnitTestMode() && myOpenProjects.isEmpty() && myCurrentTestProject != null) { + return project == myCurrentTestProject; + } + return myOpenProjects.contains(project); + } + + public boolean openProject(final Project project) { + if (myOpenProjects.contains(project)) return false; + if (!ApplicationManager.getApplication().isUnitTestMode() && !((ProjectEx)project).getStateStore().checkVersion()) return false; + + myOpenProjects.add(project); + cacheOpenProjects(); + + fireProjectOpened(project); + + final StartupManagerImpl startupManager = (StartupManagerImpl)StartupManager.getInstance(project); + + boolean ok = myProgressManager.runProcessWithProgressSynchronously(new Runnable() { + public void run() { + startupManager.runStartupActivities(); + } + }, ProjectBundle.message("project.load.progress"), true, project); + + if (!ok) { + closeProject(project, false, false); + notifyProjectOpenFailed(); + return false; + } + + startupManager.runPostStartupActivities(); + + if (!ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode()) { + // should be invoked last + StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() { + public void run() { + final TrackingPathMacroSubstitutor macroSubstitutor = + ((ProjectEx)project).getStateStore().getStateStorageManager().getMacroSubstitutor(); + if (macroSubstitutor != null) { + StorageUtil.notifyUnknownMacros(macroSubstitutor, project, null); + } + } + }); + } + + return true; + } + + private void cacheOpenProjects() { + myOpenProjectsArrayCache = myOpenProjects.toArray(new Project[myOpenProjects.size()]); + } + + public Project loadAndOpenProject(String filePath) throws IOException, JDOMException, InvalidDataException { + return loadAndOpenProject(filePath, true); + } + + @Nullable + protected Project convertAndLoadProject(String filePath, boolean convert) throws IOException { + return loadProjectWithProgress(filePath); + } + + @Nullable + public Project loadAndOpenProject(final String filePath, final boolean convert) throws IOException, JDOMException, InvalidDataException { + try { + + Project project = convertAndLoadProject(filePath, convert); + if (project == null) { + return null; + } + + if (!openProject(project)) { + Disposer.dispose(project); + return null; + } + + return project; + } + catch (StateStorage.StateStorageException e) { + throw new IOException(e.getMessage()); + } + } + + @Nullable + public Project loadProjectWithProgress(final String filePath) throws IOException { + return loadProjectWithProgress(filePath, null); + } + + @Nullable + public Project loadProjectWithProgress(final String filePath, Ref canceled) throws IOException { + final IOException[] io = {null}; + final StateStorage.StateStorageException[] stateStorage = {null}; + + if (filePath != null) { + refreshProjectFiles(filePath); + } + + final Project[] project = new Project[1]; + boolean ok = myProgressManager.runProcessWithProgressSynchronously(new Runnable() { + public void run() { + final ProgressIndicator indicator = myProgressManager.getProgressIndicator(); + try { + if (indicator != null) { + indicator.setText(ProjectBundle.message("loading.components.for", filePath)); + indicator.setIndeterminate(true); + } + project[0] = doLoadProject(filePath); + } + catch (IOException e) { + io[0] = e; + return; + } + catch (StateStorage.StateStorageException e) { + stateStorage[0] = e; + return; + } + + if (indicator != null) { + indicator.setText(ProjectBundle.message("initializing.components")); + } + } + }, ProjectBundle.message("project.load.progress"), true, null); + + if (!ok) { + if (project[0] != null) { + Disposer.dispose(project[0]); + project[0] = null; + } + if (canceled != null) { + canceled.set(true); + } + notifyProjectOpenFailed(); + } + + if (io[0] != null) throw io[0]; + if (stateStorage[0] != null) throw stateStorage[0]; + + if (project[0] == null || !ok) { + return null; + } + return project [0]; + } + + private static void refreshProjectFiles(final String filePath) { + if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isDispatchThread()) { + final File file = new File(filePath); + if (file.isFile()) { + VirtualFile projectFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePath); + if (projectFile != null) { + projectFile.refresh(false, false); + } + + File iwsFile = new File(file.getParentFile(), FileUtil.getNameWithoutExtension(file) + WorkspaceFileType.DOT_DEFAULT_EXTENSION); + VirtualFile wsFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(iwsFile); + if (wsFile != null) { + wsFile.refresh(false, false); + } + + } + else { + VirtualFile projectConfigDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath, Project.DIRECTORY_STORE_FOLDER)); + if (projectConfigDir != null && projectConfigDir.isDirectory()) { + projectConfigDir.getChildren(); + if (projectConfigDir instanceof NewVirtualFile) { + ((NewVirtualFile)projectConfigDir).markDirtyRecursively(); + } + projectConfigDir.refresh(false, true); + + } + } + } + } + + private static void notifyProjectOpenFailed() { + ApplicationManager.getApplication().getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).projectOpenFailed(); + } + + private void registerExternalProjectFileListener(VirtualFileManagerEx virtualFileManager) { + virtualFileManager.addVirtualFileManagerListener(new VirtualFileManagerListener() { + public void beforeRefreshStart(boolean asynchonous) { + } + + public void afterRefreshFinish(boolean asynchonous) { + scheduleReloadApplicationAndProject(); + } + }); + } + + private void askToReloadProjectIfConfigFilesChangedExternally() { + LOG.debug("[RELOAD] myReloadBlockCount = " + myReloadBlockCount.get()); + if (myReloadBlockCount.get() == 0) { + Set projects; + + synchronized (myChangedProjectFiles) { + if (myChangedProjectFiles.isEmpty()) return; + projects = new HashSet(myChangedProjectFiles.keySet()); + } + + List projectsToReload = new ArrayList(); + + for (Project project : projects) { + if (shouldReloadProject(project)) { + projectsToReload.add(project); + } + } + + for (final Project projectToReload : projectsToReload) { + reloadProjectImpl(projectToReload, false, false); + } + } + + } + + private boolean tryToReloadApplication(){ + try { + final Application app = ApplicationManager.getApplication(); + + if (app.isDisposed()) return false; + final HashSet> causes = new HashSet>(myChangedApplicationFiles); + if (causes.isEmpty()) return true; + + final boolean[] reloadOk = {false}; + final LinkedHashSet components = new LinkedHashSet(); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + reloadOk[0] = ((ApplicationImpl)app).getStateStore().reload(causes, components); + } + catch (StateStorage.StateStorageException e) { + Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), + ProjectBundle.message("project.reload.failed.title")); + } + catch (IOException e) { + Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), + ProjectBundle.message("project.reload.failed.title")); + } + } + }); + + if (!reloadOk[0] && !components.isEmpty()) { + String message = "Application components were changed externally and cannot be reloaded:\n"; + for (String component : components) { + message += component + "\n"; + } + + final boolean canRestart = ApplicationManager.getApplication().isRestartCapable(); + message += "Would you like to " + (canRestart ? "restart " : "shutdown "); + message += ApplicationNamesInfo.getInstance().getProductName() + "?"; + + if (Messages.showYesNoDialog(message, + "Application Configuration Reload", Messages.getQuestionIcon()) == 0) { + for (Pair cause : causes) { + StateStorage stateStorage = cause.getSecond(); + if (stateStorage instanceof XmlElementStorage) { + ((XmlElementStorage)stateStorage).disableSaving(); + } + } + if (canRestart) { + ApplicationManagerEx.getApplicationEx().restart(); + } + else { + ApplicationManagerEx.getApplicationEx().exit(true); + } + } + } + + return reloadOk[0]; + } + finally { + myChangedApplicationFiles.clear(); + } + + } + + private boolean shouldReloadProject(final Project project) { + if (project.isDisposed()) return false; + final HashSet> causes = new HashSet>(); + + synchronized (myChangedProjectFiles) { + final List> changes = myChangedProjectFiles.remove(project); + if (changes != null) { + causes.addAll(changes); + } + + if (causes.isEmpty()) return false; + } + + final boolean[] reloadOk = {false}; + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + LOG.debug("[RELOAD] Reloading project/components..."); + reloadOk[0] = ((ProjectEx)project).getStateStore().reload(causes); + } + catch (StateStorage.StateStorageException e) { + Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), + ProjectBundle.message("project.reload.failed.title")); + } + catch (IOException e) { + Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), + ProjectBundle.message("project.reload.failed.title")); + } + } + }); + if (reloadOk[0]) return false; + + String message; + if (causes.size() == 1) { + message = ProjectBundle.message("project.reload.external.change.single", causes.iterator().next().first.getPresentableUrl()); + } + else { + StringBuilder filesBuilder = new StringBuilder(); + boolean first = true; + Set alreadyShown = new HashSet(); + for (Pair cause : causes) { + String url = cause.first.getPresentableUrl(); + if (!alreadyShown.contains(url)) { + if (!first) filesBuilder.append("\n"); + first = false; + filesBuilder.append(url); + alreadyShown.add(url); + } + } + message = ProjectBundle.message("project.reload.external.change.multiple", filesBuilder.toString()); + } + + return Messages.showTwoStepConfirmationDialog(message, ProjectBundle.message("project.reload.external.change.title"), "Reload project", + Messages.getQuestionIcon()) == 0; + } + + public boolean isFileSavedToBeReloaded(VirtualFile candidate) { + return mySavedCopies.containsKey(candidate); + } + + public void blockReloadingProjectOnExternalChanges() { + myReloadBlockCount.incrementAndGet(); + } + + public void unblockReloadingProjectOnExternalChanges() { + if (myReloadBlockCount.decrementAndGet() == 0) scheduleReloadApplicationAndProject(); + } + + private void scheduleReloadApplicationAndProject() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + IdeEventQueue.getInstance().addIdleListener(new Runnable() { + @Override + public void run() { + IdeEventQueue.getInstance().removeIdleListener(this); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (!tryToReloadApplication()) return; + askToReloadProjectIfConfigFilesChangedExternally(); + } + }, ModalityState.NON_MODAL); + } + }, 2000); + } + }, ModalityState.NON_MODAL); + } + + public void setCurrentTestProject(@Nullable final Project project) { + assert ApplicationManager.getApplication().isUnitTestMode(); + myCurrentTestProject = project; + } + + @Nullable + public Project getCurrentTestProject() { + assert ApplicationManager.getApplication().isUnitTestMode(); + return myCurrentTestProject; + } + + public void saveChangedProjectFile(final VirtualFile file, final Project project) { + if (file.exists()) { + copyToTemp(file); + } + registerProjectToReload(project, file, null); + } + + private void saveChangedProjectFile(final VirtualFile file, final Project project, final StateStorage storage) { + if (file.exists()) { + copyToTemp(file); + } + registerProjectToReload(project, file, storage); + } + + private void registerProjectToReload(final Project project, final VirtualFile cause, final StateStorage storage) { + LOG.debug("[RELOAD] Registering project to reload."); + + if (project != null) { + synchronized (myChangedProjectFiles) { + List> changedProjectFiles = myChangedProjectFiles.get(project); + if (changedProjectFiles == null) { + changedProjectFiles = new ArrayList>(); + myChangedProjectFiles.put(project, changedProjectFiles); + } + + changedProjectFiles.add(new Pair(cause, storage)); + } + } + else { + myChangedApplicationFiles.add(new Pair(cause, storage)); + } + + myChangedFilesAlarm.cancelAllRequests(); + myChangedFilesAlarm.addRequest(new Runnable() { + public void run() { + LOG.debug("[RELOAD] Scheduling reload application & project, myReloadBlockCount = " + myReloadBlockCount); + if (myReloadBlockCount.get() == 0) { + scheduleReloadApplicationAndProject(); + } + } + }, 444); + } + + private void copyToTemp(VirtualFile file) { + try { + final byte[] bytes = file.contentsToByteArray(); + mySavedCopies.put(file, bytes); + mySavedTimestamps.put(file, file.getTimeStamp()); + } + catch (IOException e) { + LOG.error(e); + } + } + + private void restoreCopy(VirtualFile file) { + try { + if (file == null) return; // Externally deleted actually. + if (!file.isWritable()) return; // IDEA was unable to save it as well. So no need to restore. + + final byte[] bytes = mySavedCopies.get(file); + if (bytes != null) { + try { + file.setBinaryContent(bytes, -1, mySavedTimestamps.get(file)); + } + catch (IOException e) { + Messages.showWarningDialog(ProjectBundle.message("project.reload.write.failed", file.getPresentableUrl()), + ProjectBundle.message("project.reload.write.failed.title")); + } + } + } + finally { + mySavedCopies.remove(file); + mySavedTimestamps.remove(file); + } + } + + public void reloadProject(final Project p) { + reloadProjectImpl(p, true, false); + } + + public void reloadProjectImpl(final Project p, final boolean clearCopyToRestore, boolean takeMemorySnapshot) { + if (clearCopyToRestore) { + mySavedCopies.clear(); + mySavedTimestamps.clear(); + } + reloadProject(p, takeMemorySnapshot); + } + + public void reloadProject(@NotNull Project p, final boolean takeMemorySnapshot) { + final Project[] project = {p}; + + ProjectReloadState.getInstance(project[0]).onBeforeAutomaticProjectReload(); + final Application application = ApplicationManager.getApplication(); + + application.invokeLater(new Runnable() { + public void run() { + LOG.debug("Reloading project."); + ProjectImpl projectImpl = (ProjectImpl)project[0]; + if (projectImpl.isDisposed()) return; + IProjectStore projectStore = projectImpl.getStateStore(); + final String location = projectImpl.getLocation(); + + final List original; + try { + final IComponentStore.SaveSession saveSession = projectStore.startSave(); + original = saveSession.getAllStorageFiles(true); + saveSession.finishSave(); + } + catch (IOException e) { + LOG.error(e); + return; + } + + if (project[0].isDisposed() || ProjectUtil.closeAndDispose(project[0])) { + application.runWriteAction(new Runnable() { + public void run() { + for (final IFile originalFile : original) { + restoreCopy(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(originalFile)); + } + } + }); + + project[0] = null; // Let it go. + + ProjectUtil.openProject(location, null, true); + } + } + }, ModalityState.NON_MODAL); + } + + /* + public boolean isOpeningProject() { + return myCountOfProjectsBeingOpen > 0; + } + */ + + public boolean closeProject(final Project project) { + return closeProject(project, true, false); + } + + private boolean closeProject(final Project project, final boolean save, final boolean dispose) { + if (!isProjectOpened(project)) return true; + if (!canClose(project)) return false; + + final ShutDownTracker shutDownTracker = ShutDownTracker.getInstance(); + shutDownTracker.registerStopperThread(Thread.currentThread()); + try { + if (save) { + FileDocumentManager.getInstance().saveAllDocuments(); + project.save(); + } + + if (!ensureCouldCloseIfUnableToSave(project)) { + return false; + } + + fireProjectClosing(project); // somebody can start progress here, do not wrap in write action + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + myOpenProjects.remove(project); + cacheOpenProjects(); + + myChangedProjectFiles.remove(project); + + fireProjectClosed(project); + + if (dispose) { + Disposer.dispose(project); + } + } + }); + } + finally { + shutDownTracker.unregisterStopperThread(Thread.currentThread()); + } + + return true; + } + + @Override + public boolean closeAndDispose(@NotNull final Project project) { + return closeProject(project, true, true); + } + + private void fireProjectClosing(Project project) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: fireProjectClosing()"); + } + + for (ProjectManagerListener listener : myListeners) { + try { + listener.projectClosing(project); + } + catch (Exception e) { + LOG.error(e); + } + } + } + + public void addProjectManagerListener(ProjectManagerListener listener) { + myListeners.add(listener); + } + + public void removeProjectManagerListener(ProjectManagerListener listener) { + boolean removed = myListeners.remove(listener); + LOG.assertTrue(removed); + } + + public void addProjectManagerListener(Project project, ProjectManagerListener listener) { + List listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); + if (listeners == null) { + listeners = ((UserDataHolderEx)project).putUserDataIfAbsent(LISTENERS_IN_PROJECT_KEY, new ArrayList()); + } + listeners.add(listener); + } + + public void removeProjectManagerListener(Project project, ProjectManagerListener listener) { + List listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY); + if (listeners != null) { + boolean removed = listeners.remove(listener); + LOG.assertTrue(removed); + } + else { + LOG.assertTrue(false); + } + } + + private void fireProjectOpened(Project project) { + if (LOG.isDebugEnabled()) { + LOG.debug("projectOpened"); + } + + for (ProjectManagerListener listener : myListeners) { + try { + listener.projectOpened(project); + } + catch (Exception e) { + LOG.error(e); + } + } + } + + private void fireProjectClosed(Project project) { + if (LOG.isDebugEnabled()) { + LOG.debug("projectClosed"); + } + + for (ProjectManagerListener listener : myListeners) { + try { + listener.projectClosed(project); + } + catch (Exception e) { + LOG.error(e); + } + } + } + + public boolean canClose(Project project) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: canClose()"); + } + + for (ProjectManagerListener listener : myListeners) { + try { + if (!listener.canCloseProject(project)) return false; + } catch (Throwable e) { + LOG.warn(e); // DO NOT LET ANY PLUGIN to prevent closing due to exception + } + } + + return true; + } + + private static boolean ensureCouldCloseIfUnableToSave(@NotNull final Project project) { + final ProjectImpl.UnableToSaveProjectNotification[] notifications = + NotificationsManager.getNotificationsManager().getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, project); + if (notifications.length == 0) return true; + + final String fileNames = StringUtil.join(notifications[0].getFileNames(), "\n"); + + final String msg = String.format("%s was unable to save some project files,\nare you sure you want to close this project anyway?", + ApplicationNamesInfo.getInstance().getProductName()); + return Messages.showDialog(project, msg, "Unsaved project!", "Read-only files:\n\n" + fileNames, new String[]{"Yes", "No"}, 0, 1, + Messages.getWarningIcon()) == 0; + } + + public void writeExternal(Element parentNode) throws WriteExternalException { + if (myDefaultProject != null) { + myDefaultProject.save(); + } + + if (myDefaultProjectRootElement == null) { //read external isn't called if config folder is absent + myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT); + } + + myDefaultProjectRootElement.detach(); + parentNode.addContent(myDefaultProjectRootElement); + } + + + public void setDefaultProjectRootElement(final Element defaultProjectRootElement) { + myDefaultProjectRootElement = defaultProjectRootElement; + } + + public void readExternal(Element parentNode) throws InvalidDataException { + myDefaultProjectRootElement = parentNode.getChild(ELEMENT_DEFAULT_PROJECT); + + if (myDefaultProjectRootElement == null) { + myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT); + } + + myDefaultProjectRootElement.detach(); + } + + public String getExternalFileName() { + return "project.default"; + } + + @NotNull + public String getComponentName() { + return "ProjectManager"; + } + + @NotNull + public File[] getExportFiles() { + return new File[]{PathManager.getOptionsFile(this)}; + } + + @NotNull + public String getPresentableName() { + return ProjectBundle.message("project.default.settings"); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java b/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java index 7ef19085dc24..6e6273b5553c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java @@ -1,355 +1,355 @@ -/* - * 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.ui; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.impl.ApplicationInfoImpl; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ProjectManagerAdapter; -import com.intellij.openapi.project.ProjectManagerListener; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.DimensionService; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.wm.*; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.openapi.wm.impl.IdeFrameImpl; -import com.intellij.openapi.wm.impl.IdeGlassPaneImpl; -import com.intellij.ui.FocusTrackback; -import com.intellij.util.ImageLoader; -import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.io.File; -import java.util.Map; - -public class FrameWrapper implements Disposable { - private String myDimensionKey = null; - private JComponent myComponent = null; - private JComponent myPreferedFocus = null; - private String myTitle = ""; - private Image myImage = ImageLoader.loadFromResource(ApplicationInfoImpl.getShadowInstance().getIconUrl()); - private boolean myCloseOnEsc = false; - private JFrame myFrame; - private final Map myDatas = new HashMap(); - private Project myProject; - private final ProjectManagerListener myProjectListener = new MyProjectManagerListener(); - private FocusTrackback myFocusTrackback; - private FocusWatcher myFocusWatcher; - - private ActionCallback myFocusedCallback; - private boolean myDisposed; - - protected StatusBar myStatusBar; - - public FrameWrapper(Project project) { - this(project, null); - } - - public FrameWrapper(Project project, @NonNls String dimensionServiceKey) { - myProject = project; - myDimensionKey = dimensionServiceKey; - } - - public void setDimensionKey(String dimensionKey) { - myDimensionKey = dimensionKey; - } - - public void setData(String dataId, Object data) { - myDatas.put(dataId, data); - } - - public void setProject(Project project) { - myProject = project; - setData(PlatformDataKeys.PROJECT.getName(), project); - ProjectManager.getInstance().addProjectManagerListener(project, myProjectListener); - } - - public void show() { - myFocusedCallback = new ActionCallback(); - - if (myProject != null) { - IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(myFocusedCallback); - } - - final JFrame frame = getFrame(); - - if (myStatusBar != null) { - myStatusBar.install((IdeFrame)frame); - } - - myFocusTrackback = new FocusTrackback(this, null, true); - - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - WindowAdapter focusListener = new WindowAdapter() { - public void windowOpened(WindowEvent e) { - if (myPreferedFocus != null) { - myPreferedFocus.requestFocusInWindow(); - myFocusTrackback.registerFocusComponent(myPreferedFocus); - } - - myFocusedCallback.setDone(); - } - }; - frame.addWindowListener(focusListener); - if (myCloseOnEsc) addCloseOnEsc(frame); - frame.getContentPane().add(myComponent, BorderLayout.CENTER); - frame.setTitle(myTitle); - frame.setIconImage(myImage); - loadFrameState(myProject, myDimensionKey, frame); - - myFocusWatcher = new FocusWatcher() { - protected void focusLostImpl(final FocusEvent e) { - myFocusTrackback.consume(); - } - }; - myFocusWatcher.install(myComponent); - - frame.setVisible(true); - } - - public void close() { - Disposer.dispose(this); - } - - public void dispose() { - if (isDisposed()) return; - - getFrame().setVisible(false); - getFrame().dispose(); - - if (myStatusBar != null) { - Disposer.dispose(myStatusBar); - myStatusBar = null; - } - - myDisposed = true; - } - - public boolean isDisposed() { - return myDisposed; - } - - private void addCloseOnEsc(final JFrame frame) { - frame.getRootPane().registerKeyboardAction( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); - MenuElement[] selectedPath = menuSelectionManager.getSelectedPath(); - if (selectedPath.length > 0) { // hide popup menu if any - menuSelectionManager.clearSelectedPath(); - } - else { - close(); - } - } - }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_IN_FOCUSED_WINDOW - ); - } - - public JFrame getFrame() { - assert !myDisposed : "Already disposed!"; - - if (myFrame == null) { - myFrame = createJFrame(WindowManager.getInstance().getIdeFrame(myProject)); - } - return myFrame; - } - - protected JFrame createJFrame(IdeFrame parent) { - return new MyJFrame(parent); - } - - public void setComponent(JComponent component) { - myComponent = component; - } - - public void setPreferredFocusedComponent(JComponent preferedFocus) { - myPreferedFocus = preferedFocus; - } - - public void closeOnEsc() { - myCloseOnEsc = true; - } - - public void setImage(Image image) { - myImage = image; - } - - private static void loadFrameState(Project project, String dimensionKey, JFrame frame) { - final Point location; - final Dimension size; - final int extendedState; - DimensionService dimensionService = DimensionService.getInstance(); - if (dimensionKey == null || dimensionService == null) { - location = null; - size = null; - extendedState = -1; - } - else { - location = dimensionService.getLocation(dimensionKey); - size = dimensionService.getSize(dimensionKey); - extendedState = dimensionService.getExtendedState(dimensionKey); - } - - if (size != null && location != null) { - frame.setLocation(location); - frame.setSize(size); - frame.getRootPane().revalidate(); - } - else { - frame.pack(); - frame.setBounds(WindowManagerEx.getInstanceEx().getIdeFrame(project).suggestChildFrameBounds()); - } - - if (extendedState == Frame.ICONIFIED || extendedState == Frame.MAXIMIZED_BOTH) { - frame.setExtendedState(extendedState); - } - } - - private static void saveFrameState(String dimensionKey, JFrame frame) { - DimensionService dimensionService = DimensionService.getInstance(); - if (dimensionKey == null || dimensionService == null) return; - dimensionService.setLocation(dimensionKey, frame.getLocation()); - dimensionService.setSize(dimensionKey, frame.getSize()); - dimensionService.setExtendedState(dimensionKey, frame.getExtendedState()); - } - - public void setTitle(String title) { - myTitle = title; - } - - public void addDisposable(Disposable disposable) { - Disposer.register(this, disposable); - } - - protected void setStatusBar(StatusBar statusBar) { - if (myStatusBar != null) { - Disposer.dispose(myStatusBar); - } - myStatusBar = statusBar; - } - - private class MyJFrame extends JFrame implements DataProvider, IdeFrame.Child { - - private boolean myDisposing; - private IdeFrame myParent; - - private String myFrameTitle; - private String myFileTitle; - private File myFile; - - private MyJFrame(IdeFrame parent) throws HeadlessException { - myParent = parent; - setGlassPane(new IdeGlassPaneImpl(getRootPane())); - } - - @Override - public StatusBar getStatusBar() { - return myStatusBar != null ? myStatusBar : myParent.getStatusBar(); - } - - @Override - public Rectangle suggestChildFrameBounds() { - return myParent.suggestChildFrameBounds(); - } - - @Override - public Project getProject() { - return myParent.getProject(); - } - - @Override - public void setFrameTitle(String title) { - myFrameTitle = title; - updateTitle(); - } - - @Override - public void setFileTitle(String fileTitle, File ioFile) { - myFileTitle = fileTitle; - myFile = ioFile; - updateTitle(); - } - - private void updateTitle() { - IdeFrameImpl.updateTitle(this, myFrameTitle, myFileTitle, myFile); - } - - @Override - public IdeFrame getParentFrame() { - return myParent; - } - - public void dispose() { - if (myDisposing) return; - myDisposing = true; - saveFrameState(myDimensionKey, this); - Disposer.dispose(FrameWrapper.this); - myDatas.clear(); - if (myProject != null) { - ProjectManager.getInstance().removeProjectManagerListener(myProject, myProjectListener); - myProject = null; - } - myPreferedFocus = null; - - if (myFocusTrackback != null) { - myFocusTrackback.restoreFocus(); - } - if (myComponent != null && myFocusWatcher != null) { - myFocusWatcher.deinstall(myComponent); - } - myFocusWatcher = null; - myFocusedCallback = null; - - super.dispose(); - } - - public Object getData(String dataId) { - return myDatas.get(dataId); - } - - @Override - public void paint(Graphics g) { - UIUtil.applyRenderingHints(g); - super.paint(g); - } - } - - public void setLocation(Point location) { - getFrame().setLocation(location); - } - - public void setSize(Dimension size) { - getFrame().setSize(size); - } - - private class MyProjectManagerListener extends ProjectManagerAdapter { - public void projectClosing(Project project) { - if (project == myProject) { - close(); - } - } - } -} +/* + * 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.ui; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.impl.ApplicationInfoImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerAdapter; +import com.intellij.openapi.project.ProjectManagerListener; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.DimensionService; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.wm.*; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.openapi.wm.impl.IdeFrameImpl; +import com.intellij.openapi.wm.impl.IdeGlassPaneImpl; +import com.intellij.ui.FocusTrackback; +import com.intellij.util.ImageLoader; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.util.Map; + +public class FrameWrapper implements Disposable { + private String myDimensionKey = null; + private JComponent myComponent = null; + private JComponent myPreferedFocus = null; + private String myTitle = ""; + private Image myImage = ImageLoader.loadFromResource(ApplicationInfoImpl.getShadowInstance().getIconUrl()); + private boolean myCloseOnEsc = false; + private JFrame myFrame; + private final Map myDatas = new HashMap(); + private Project myProject; + private final ProjectManagerListener myProjectListener = new MyProjectManagerListener(); + private FocusTrackback myFocusTrackback; + private FocusWatcher myFocusWatcher; + + private ActionCallback myFocusedCallback; + private boolean myDisposed; + + protected StatusBar myStatusBar; + + public FrameWrapper(Project project) { + this(project, null); + } + + public FrameWrapper(Project project, @NonNls String dimensionServiceKey) { + myProject = project; + myDimensionKey = dimensionServiceKey; + } + + public void setDimensionKey(String dimensionKey) { + myDimensionKey = dimensionKey; + } + + public void setData(String dataId, Object data) { + myDatas.put(dataId, data); + } + + public void setProject(Project project) { + myProject = project; + setData(PlatformDataKeys.PROJECT.getName(), project); + ProjectManager.getInstance().addProjectManagerListener(project, myProjectListener); + } + + public void show() { + myFocusedCallback = new ActionCallback(); + + if (myProject != null) { + IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(myFocusedCallback); + } + + final JFrame frame = getFrame(); + + if (myStatusBar != null) { + myStatusBar.install((IdeFrame)frame); + } + + myFocusTrackback = new FocusTrackback(this, null, true); + + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + WindowAdapter focusListener = new WindowAdapter() { + public void windowOpened(WindowEvent e) { + if (myPreferedFocus != null) { + myPreferedFocus.requestFocusInWindow(); + myFocusTrackback.registerFocusComponent(myPreferedFocus); + } + + myFocusedCallback.setDone(); + } + }; + frame.addWindowListener(focusListener); + if (myCloseOnEsc) addCloseOnEsc(frame); + frame.getContentPane().add(myComponent, BorderLayout.CENTER); + frame.setTitle(myTitle); + frame.setIconImage(myImage); + loadFrameState(myProject, myDimensionKey, frame); + + myFocusWatcher = new FocusWatcher() { + protected void focusLostImpl(final FocusEvent e) { + myFocusTrackback.consume(); + } + }; + myFocusWatcher.install(myComponent); + + frame.setVisible(true); + } + + public void close() { + Disposer.dispose(this); + } + + public void dispose() { + if (isDisposed()) return; + + getFrame().setVisible(false); + getFrame().dispose(); + + if (myStatusBar != null) { + Disposer.dispose(myStatusBar); + myStatusBar = null; + } + + myDisposed = true; + } + + public boolean isDisposed() { + return myDisposed; + } + + private void addCloseOnEsc(final JFrame frame) { + frame.getRootPane().registerKeyboardAction( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); + MenuElement[] selectedPath = menuSelectionManager.getSelectedPath(); + if (selectedPath.length > 0) { // hide popup menu if any + menuSelectionManager.clearSelectedPath(); + } + else { + close(); + } + } + }, + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_IN_FOCUSED_WINDOW + ); + } + + public JFrame getFrame() { + assert !myDisposed : "Already disposed!"; + + if (myFrame == null) { + myFrame = createJFrame(WindowManager.getInstance().getIdeFrame(myProject)); + } + return myFrame; + } + + protected JFrame createJFrame(IdeFrame parent) { + return new MyJFrame(parent); + } + + public void setComponent(JComponent component) { + myComponent = component; + } + + public void setPreferredFocusedComponent(JComponent preferedFocus) { + myPreferedFocus = preferedFocus; + } + + public void closeOnEsc() { + myCloseOnEsc = true; + } + + public void setImage(Image image) { + myImage = image; + } + + private static void loadFrameState(Project project, String dimensionKey, JFrame frame) { + final Point location; + final Dimension size; + final int extendedState; + DimensionService dimensionService = DimensionService.getInstance(); + if (dimensionKey == null || dimensionService == null) { + location = null; + size = null; + extendedState = -1; + } + else { + location = dimensionService.getLocation(dimensionKey); + size = dimensionService.getSize(dimensionKey); + extendedState = dimensionService.getExtendedState(dimensionKey); + } + + if (size != null && location != null) { + frame.setLocation(location); + frame.setSize(size); + frame.getRootPane().revalidate(); + } + else { + frame.pack(); + frame.setBounds(WindowManagerEx.getInstanceEx().getIdeFrame(project).suggestChildFrameBounds()); + } + + if (extendedState == Frame.ICONIFIED || extendedState == Frame.MAXIMIZED_BOTH) { + frame.setExtendedState(extendedState); + } + } + + private static void saveFrameState(String dimensionKey, JFrame frame) { + DimensionService dimensionService = DimensionService.getInstance(); + if (dimensionKey == null || dimensionService == null) return; + dimensionService.setLocation(dimensionKey, frame.getLocation()); + dimensionService.setSize(dimensionKey, frame.getSize()); + dimensionService.setExtendedState(dimensionKey, frame.getExtendedState()); + } + + public void setTitle(String title) { + myTitle = title; + } + + public void addDisposable(Disposable disposable) { + Disposer.register(this, disposable); + } + + protected void setStatusBar(StatusBar statusBar) { + if (myStatusBar != null) { + Disposer.dispose(myStatusBar); + } + myStatusBar = statusBar; + } + + private class MyJFrame extends JFrame implements DataProvider, IdeFrame.Child { + + private boolean myDisposing; + private IdeFrame myParent; + + private String myFrameTitle; + private String myFileTitle; + private File myFile; + + private MyJFrame(IdeFrame parent) throws HeadlessException { + myParent = parent; + setGlassPane(new IdeGlassPaneImpl(getRootPane())); + } + + @Override + public StatusBar getStatusBar() { + return myStatusBar != null ? myStatusBar : myParent.getStatusBar(); + } + + @Override + public Rectangle suggestChildFrameBounds() { + return myParent.suggestChildFrameBounds(); + } + + @Override + public Project getProject() { + return myParent.getProject(); + } + + @Override + public void setFrameTitle(String title) { + myFrameTitle = title; + updateTitle(); + } + + @Override + public void setFileTitle(String fileTitle, File ioFile) { + myFileTitle = fileTitle; + myFile = ioFile; + updateTitle(); + } + + private void updateTitle() { + IdeFrameImpl.updateTitle(this, myFrameTitle, myFileTitle, myFile); + } + + @Override + public IdeFrame getParentFrame() { + return myParent; + } + + public void dispose() { + if (myDisposing) return; + myDisposing = true; + saveFrameState(myDimensionKey, this); + Disposer.dispose(FrameWrapper.this); + myDatas.clear(); + if (myProject != null) { + ProjectManager.getInstance().removeProjectManagerListener(myProject, myProjectListener); + myProject = null; + } + myPreferedFocus = null; + + if (myFocusTrackback != null) { + myFocusTrackback.restoreFocus(); + } + if (myComponent != null && myFocusWatcher != null) { + myFocusWatcher.deinstall(myComponent); + } + myFocusWatcher = null; + myFocusedCallback = null; + + super.dispose(); + } + + public Object getData(String dataId) { + return myDatas.get(dataId); + } + + @Override + public void paint(Graphics g) { + UIUtil.applyRenderingHints(g); + super.paint(g); + } + } + + public void setLocation(Point location) { + getFrame().setLocation(location); + } + + public void setSize(Dimension size) { + getFrame().setSize(size); + } + + private class MyProjectManagerListener extends ProjectManagerAdapter { + public void projectClosing(Project project) { + if (project == myProject) { + close(); + } + } + } +} 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 601c89cc5af8..f4ec0419369e 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 @@ -1,1190 +1,1190 @@ -/* - * 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.ui.impl; - -import com.intellij.ide.DataManager; -import com.intellij.ide.IdeEventQueue; -import com.intellij.ide.impl.TypeSafeDataProviderAdapter; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.impl.LaterInvocator; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.CommandProcessorEx; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.DialogWrapperDialog; -import com.intellij.openapi.ui.DialogWrapperPeer; -import com.intellij.openapi.ui.Queryable; -import com.intellij.openapi.ui.popup.StackingPopupDispatcher; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.FocusCommand; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.KeyEventProcessor; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.openapi.wm.impl.IdeFrameImpl; -import com.intellij.openapi.wm.impl.IdeGlassPaneImpl; -import com.intellij.ui.AppUIUtil; -import com.intellij.ui.FocusTrackback; -import com.intellij.ui.ScreenUtil; -import com.intellij.ui.SpeedSearchBase; -import com.intellij.ui.popup.StackingPopupDispatcherImpl; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.awt.image.BufferStrategy; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTrackbackProvider { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.DialogWrapper"); - - private DialogWrapper myWrapper; - private AbstractDialog myDialog; - private boolean myCanBeParent = true; - /* - * Default dialog's actions. - */ - private WindowManagerEx myWindowManager; - private final java.util.List myDisposeActions = new ArrayList(); - private Project myProject; - - private final ActionCallback myWindowFocusedCallback = new ActionCallback("DialogFocusedCallback"); - private final ActionCallback myTypeAheadDone = new ActionCallback("DialogTypeAheadDone"); - - /** - * Creates modal DialogWrapper. The currently active window will be the dialog's parent. - * - * @param project parent window for the dialog will be calculated based on focused window for the - * specified project. This parameter can be null. In this case parent window - * will be suggested based on current focused window. - * @param canBeParent specifies whether the dialog can be parent for other windows. This parameter is used - * by WindowManager. - */ - protected DialogWrapperPeerImpl(DialogWrapper wrapper, Project project, boolean canBeParent) { - myWrapper = wrapper; - myWindowManager = null; - Application application = ApplicationManager.getApplication(); - if (application != null && application.hasComponent(WindowManager.class)) { - myWindowManager = (WindowManagerEx)WindowManager.getInstance(); - } - - Window window = null; - if (myWindowManager != null) { - - if (project == null) { - project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); - } - - myProject = project; - - window = myWindowManager.suggestParentWindow(project); - if (window == null) { - Window focusedWindow = myWindowManager.getMostRecentFocusedWindow(); - if (focusedWindow instanceof IdeFrameImpl) { - window = focusedWindow; - } - } - } - - Window owner; - if (window != null) { - owner = window; - } - else { - if (!isHeadless()) { - owner = JOptionPane.getRootFrame(); - } else { - owner = null; - } - } - - createDialog(owner, canBeParent); - } - - protected DialogWrapperPeerImpl(DialogWrapper wrapper, boolean canBeParent) { - this(wrapper, (Project)null, canBeParent); - } - - public boolean isHeadless() { - return isHeadlessEnv(); - } - - public static boolean isHeadlessEnv() { - Application app = ApplicationManager.getApplication(); - if (app == null) return GraphicsEnvironment.isHeadless(); - - return app.isUnitTestMode() || app.isHeadlessEnvironment(); - } - - /** - * @param parent parent component whicg is used to canculate heavy weight window ancestor. - * parent cannot be null and must be showing. - */ - protected DialogWrapperPeerImpl(DialogWrapper wrapper, @NotNull Component parent, boolean canBeParent) { - myWrapper = wrapper; - if (!parent.isShowing() && parent != JOptionPane.getRootFrame()) { - throw new IllegalArgumentException("parent must be showing: " + parent); - } - myWindowManager = null; - Application application = ApplicationManager.getApplication(); - if (application != null && application.hasComponent(WindowManager.class)) { - myWindowManager = (WindowManagerEx)WindowManager.getInstance(); - } - - Window owner = parent instanceof Window ? (Window)parent : (Window)SwingUtilities.getAncestorOfClass(Window.class, parent); - if (!(owner instanceof Dialog) && !(owner instanceof Frame)) { - owner = JOptionPane.getRootFrame(); - } - createDialog(owner, canBeParent); - } - - public DialogWrapperPeerImpl(final DialogWrapper wrapper, final boolean canBeParent, final boolean tryToolkitModal) { - myWrapper = wrapper; - myWindowManager = null; - Application application = ApplicationManager.getApplication(); - if (application != null && application.hasComponent(WindowManager.class)) { - myWindowManager = (WindowManagerEx)WindowManager.getInstance(); - } - if (UIUtil.hasJdk6Dialogs()) { - createDialog(null, canBeParent); - if (tryToolkitModal && !isHeadless()) { - UIUtil.setToolkitModal((MyDialog)myDialog); - } - } - else { - createDialog(JOptionPane.getRootFrame(), canBeParent); - } - } - - public void setUndecorated(boolean undecorated) { - myDialog.setUndecorated(undecorated); - } - - public void addMouseListener(MouseListener listener) { - myDialog.addMouseListener(listener); - } - - public void addMouseListener(MouseMotionListener listener) { - myDialog.addMouseMotionListener(listener); - } - - public void addKeyListener(KeyListener listener) { - myDialog.addKeyListener(listener); - } - - private void createDialog(Window owner, boolean canBeParent) { - if (isHeadless()) { - myDialog = new HeadlessDialog(); - return; - } - - if (owner instanceof Frame) { - myDialog = new MyDialog((Frame)owner, myWrapper, myProject, myWindowFocusedCallback, myTypeAheadDone); - } - else { - myDialog = new MyDialog((Dialog)owner, myWrapper, myProject, myWindowFocusedCallback, myTypeAheadDone); - } - myDialog.setModal(true); - myCanBeParent = canBeParent; - - } - - - public void toFront() { - myDialog.toFront(); - } - - public void toBack() { - myDialog.toBack(); - } - - protected void dispose() { - LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only"); - for (Runnable runnable : myDisposeActions) { - runnable.run(); - } - myDisposeActions.clear(); - final JRootPane root = myDialog.getRootPane(); - - Runnable disposer = new Runnable() { - public void run() { - myDialog.dispose(); - myProject = null; - /* - if (myWindowManager == null) { - myDialog.dispose(); - } - else { - myWindowManager.hideDialog(myDialog, myProject); - } - */ - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - if (myDialog != null && root != null) { - myDialog.remove(root); - } - } - }); - } - }; - - if (EventQueue.isDispatchThread()) { - disposer.run(); - } - else { - SwingUtilities.invokeLater(disposer); - } - } - - private boolean isProgressDialog() { - return myWrapper.isModalProgress(); - } - - @Nullable - public Container getContentPane() { - return getRootPane() != null ? myDialog.getContentPane() : null; - } - - /** - * @see javax.swing.JDialog#validate - */ - public void validate() { - myDialog.validate(); - } - - /** - * @see javax.swing.JDialog#repaint - */ - public void repaint() { - myDialog.repaint(); - } - - public Window getOwner() { - return myDialog.getOwner(); - } - - public Window getWindow() { - return myDialog.getWindow(); - } - - public JRootPane getRootPane() { - return myDialog.getRootPane(); - } - - public Dimension getSize() { - return myDialog.getSize(); - } - - public String getTitle() { - return myDialog.getTitle(); - } - - /** - * @see java.awt.Window#pack - */ - public void pack() { - myDialog.pack(); - } - - public void setIconImages(final List image) { - UIUtil.updateDialogIcon(myDialog.getWindow(), image); - } - - public void setAppIcons() { - setIconImages(AppUIUtil.getAppIconImages()); - } - - public Dimension getPreferredSize() { - return myDialog.getPreferredSize(); - } - - public void setModal(boolean modal) { - myDialog.setModal(modal); - } - - public boolean isVisible() { - return myDialog.isVisible(); - } - - public boolean isShowing() { - return myDialog.isShowing(); - } - - public void setSize(int width, int height) { - myDialog.setSize(width, height); - } - - public void setTitle(String title) { - myDialog.setTitle(title); - } - - public void isResizable() { - myDialog.isResizable(); - } - - public void setResizable(boolean resizable) { - myDialog.setResizable(resizable); - } - - public Point getLocation() { - return myDialog.getLocation(); - } - - public void setLocation(Point p) { - myDialog.setLocation(p); - } - - public void setLocation(int x, int y) { - myDialog.setLocation(x, y); - } - - public ActionCallback show() { - final ActionCallback result = new ActionCallback(); - - LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only"); - - final AnCancelAction anCancelAction = new AnCancelAction(); - final JRootPane rootPane = getRootPane(); - anCancelAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)), rootPane); - myDisposeActions.add(new Runnable() { - public void run() { - anCancelAction.unregisterCustomShortcutSet(rootPane); - } - }); - - if (!myCanBeParent && myWindowManager != null) { - myWindowManager.doNotSuggestAsParent(myDialog.getWindow()); - } - - final CommandProcessorEx commandProcessor = - ApplicationManager.getApplication() != null ? (CommandProcessorEx)CommandProcessor.getInstance() : null; - final boolean appStarted = commandProcessor != null; - - if (myDialog.isModal() && !isProgressDialog()) { - /* - if (ApplicationManager.getApplication() != null) { - if (ApplicationManager.getApplication().getCurrentWriteAction(null) != null) { - LOG.warn( - "Showing of a modal dialog inside write-action may be dangerous and resulting in unpredictable behavior! Current modalityState=" + ModalityState.current(), new Exception()); - } - } - */ - if (appStarted) { - commandProcessor.enterModal(); - LaterInvocator.enterModal(myDialog); - } - } - - if (appStarted) { - hidePopupsIfNeeded(); - } - - try { - myDialog.show(); - } - finally { - if (myDialog.isModal() && !isProgressDialog()) { - if (appStarted) { - commandProcessor.leaveModal(); - LaterInvocator.leaveModal(myDialog); - } - } - - myDialog.getFocusManager().doWhenFocusSettlesDown(new Runnable() { - public void run() { - result.setDone(); - } - }); - } - - return result; - } - -//[kirillk] for now it only deals with the TaskWindow under Mac OS X: modal dialogs are shown behind JBPopup - - //hopefully this whole code will go away - private void hidePopupsIfNeeded() { - if (!SystemInfo.isMac) return; - - StackingPopupDispatcherImpl.getInstance().hidePersistentPopups(); - myDisposeActions.add(new Runnable() { - public void run() { - StackingPopupDispatcherImpl.getInstance().restorePersistentPopups(); - } - }); - } - - public FocusTrackback getFocusTrackback() { - return myDialog.getFocusTrackback(); - } - - private class AnCancelAction extends AnAction implements DumbAware { - public void update(AnActionEvent e) { - Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - e.getPresentation().setEnabled(false); - if (focusOwner instanceof JComponent && SpeedSearchBase.hasActiveSpeedSearch((JComponent)focusOwner)) { - return; - } - - if (StackingPopupDispatcher.getInstance().isPopupFocused()) return; - - if (focusOwner instanceof JTree) { - JTree tree = (JTree)focusOwner; - if (!tree.isEditing()) { - e.getPresentation().setEnabled(true); - } - } - else if (focusOwner instanceof JTable) { - JTable table = (JTable)focusOwner; - if (!table.isEditing()) { - e.getPresentation().setEnabled(true); - } - } - } - - public void actionPerformed(AnActionEvent e) { - myWrapper.doCancelAction(e.getInputEvent()); - } - } - - - private static class MyDialog extends JDialog implements DialogWrapperDialog, DataProvider, FocusTrackback.Provider, Queryable, AbstractDialog { - private final WeakReference myDialogWrapper; - /** - * Initial size of the dialog. When the dialog is being closed and - * current size of the dialog is not equals to the initial sizethen the - * current (changed) size is stored in the DimensionService. - */ - private Dimension myInitialSize; - private String myDimensionServiceKey; - private boolean myOpened = false; - - private FocusTrackback myFocusTrackback; - private MyDialog.MyWindowListener myWindowListener; - - private final WeakReference myProject; - private ActionCallback myFocusedCallback; - private ActionCallback myTypeAheadDone; - - public MyDialog(Dialog owner, DialogWrapper dialogWrapper, Project project, ActionCallback focused, ActionCallback typeAheadDone) { - super(owner); - myDialogWrapper = new WeakReference(dialogWrapper); - myProject = project != null ? new WeakReference(project) : null; - initDialog(focused, typeAheadDone); - } - - public MyDialog(Frame owner, DialogWrapper dialogWrapper, Project project, ActionCallback focused, ActionCallback typeAheadDone) { - super(owner); - myDialogWrapper = new WeakReference(dialogWrapper); - myProject = project != null ? new WeakReference(project) : null; - initDialog(focused, typeAheadDone); - } - - private void initDialog(ActionCallback focused, ActionCallback typeAheadDone) { - myFocusedCallback = focused; - myTypeAheadDone = typeAheadDone; - - final long typeAhead = getDialogWrapper().getTypeAheadTimeoutMs(); - if (typeAhead <= 0) { - myTypeAheadDone.setDone(); - } - - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - myWindowListener = new MyWindowListener(); - addWindowListener(myWindowListener); - } - - public JDialog getWindow() { - return this; - } - - public void putInfo(Map info) { - info.put("dialog", getTitle()); - } - - public FocusTrackback getFocusTrackback() { - return myFocusTrackback; - } - - public DialogWrapper getDialogWrapper() { - return myDialogWrapper.get(); - } - - public void centerInParent() { - setLocationRelativeTo(getOwner()); - } - - public Object getData(String dataId) { - final DialogWrapper wrapper = myDialogWrapper.get(); - if (wrapper instanceof DataProvider) { - return ((DataProvider)wrapper).getData(dataId); - } - else if (wrapper instanceof TypeSafeDataProvider) { - TypeSafeDataProviderAdapter adapter = new TypeSafeDataProviderAdapter((TypeSafeDataProvider)wrapper); - return adapter.getData(dataId); - } - return null; - } - - public void setSize(int width, int height) { - Point location = getLocation(); - Rectangle rect = new Rectangle(location.x, location.y, width, height); - ScreenUtil.fitToScreen(rect); - if (location.x != rect.x || location.y != rect.y) { - setLocation(rect.x, rect.y); - } - - super.setSize(rect.width, rect.height); - } - - public void setBounds(int x, int y, int width, int height) { - Rectangle rect = new Rectangle(x, y, width, height); - ScreenUtil.fitToScreen(rect); - super.setBounds(rect.x, rect.y, rect.width, rect.height); - } - - public void setBounds(Rectangle r) { - ScreenUtil.fitToScreen(r); - super.setBounds(r); - } - - protected JRootPane createRootPane() { - return new DialogRootPane(); - } - - public void show() { - myFocusTrackback = new FocusTrackback(myDialogWrapper, this, true); - - final DialogWrapper dialogWrapper = getDialogWrapper(); - - pack(); - setSize((int)(getWidth() * dialogWrapper.getHorizontalStretch()), (int)(getHeight() * dialogWrapper.getVerticalStretch())); - - // Restore dialog's size and location - - myDimensionServiceKey = dialogWrapper.getDimensionKey(); - Point location = null; - - if (myDimensionServiceKey != null) { - final Project projectGuess = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(this)); - location = DimensionService.getInstance().getLocation(myDimensionServiceKey, projectGuess); - Dimension size = DimensionService.getInstance().getSize(myDimensionServiceKey, projectGuess); - if (size != null) { - myInitialSize = (Dimension)size.clone(); - setSize(myInitialSize); - } - } - - if (myInitialSize == null) { - myInitialSize = getSize(); - } - - if (location == null) { - location = dialogWrapper.getInitialLocation(); - } - - if (location != null) { - setLocation(location); - } - else { - setLocationRelativeTo(getOwner()); - } - - final Rectangle bounds = getBounds(); - ScreenUtil.fitToScreen(bounds); - setBounds(bounds); - - addWindowListener(new WindowAdapter() { - public void windowActivated(final WindowEvent e) { - final DialogWrapper wrapper = getDialogWrapper(); - if (wrapper != null && myFocusTrackback != null) { - myFocusTrackback.registerFocusComponent(new FocusTrackback.ComponentQuery() { - public Component getComponent() { - return wrapper.getPreferredFocusedComponent(); - } - }); - } - } - - public void windowDeactivated(final WindowEvent e) { - if (!isModal()) { - final Ref focusManager = new Ref(null); - if (myProject != null && myProject.get() != null && !myProject.get().isDisposed()) { - focusManager.set(getFocusManager()); - focusManager.get().doWhenFocusSettlesDown(new Runnable() { - public void run() { - disposeFocusTrackbackIfNoChildWindowFocused(focusManager.get()); - } - }); - } - else { - disposeFocusTrackbackIfNoChildWindowFocused(focusManager.get()); - } - } - } - }); - - if (Registry.is("actionSystem.fixLostTyping")) { - final IdeEventQueue queue = IdeEventQueue.getInstance(); - if (queue != null) { - queue.getKeyEventDispatcher().resetState(); - } - - if (myProject != null) { - Project project = myProject.get(); - if (project != null && !project.isDisposed() && project.isInitialized()) { - IdeFocusManager.findInstanceByComponent(this).requestFocus(new MyFocusCommand(dialogWrapper), true); - } - } - } - - super.show(); - } - - public IdeFocusManager getFocusManager() { - if (myProject != null && myProject.get() != null && !myProject.get().isDisposed()) { - return IdeFocusManager.getInstance(myProject.get()); - } else { - return IdeFocusManager.findInstance(); - } - } - - private void disposeFocusTrackbackIfNoChildWindowFocused(@Nullable IdeFocusManager focusManager) { - if (myFocusTrackback == null) return; - - final DialogWrapper wrapper = myDialogWrapper.get(); - if (wrapper == null || !wrapper.isShowing()) { - myFocusTrackback.dispose(); - return; - } - - if (focusManager != null) { - final Component c = focusManager.getFocusedDescendantFor(wrapper.getContentPane()); - if (c == null) { - myFocusTrackback.dispose(); - } - } - else { - final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (owner == null || !SwingUtilities.isDescendingFrom(owner, wrapper.getContentPane())) { - myFocusTrackback.dispose(); - } - } - } - - @Deprecated - public void hide() { - super.hide(); - if (myFocusTrackback != null && !(myFocusTrackback.isSheduledForRestore() || myFocusTrackback.isWillBeSheduledForRestore())) { - myFocusTrackback.setWillBeSheduledForRestore(); - IdeFocusManager mgr = getFocusManager(); - Runnable r = new Runnable() { - public void run() { - if (myFocusTrackback != null) myFocusTrackback.restoreFocus(); - myFocusTrackback = null; - } - }; - mgr.doWhenFocusSettlesDown(r); - } - } - - public void dispose() { - if (isShowing()) { - hide(); - } - - if (myWindowListener != null) { - myWindowListener.saveSize(); - removeWindowListener(myWindowListener); - myWindowListener = null; - } - - if (myFocusTrackback != null && !(myFocusTrackback.isSheduledForRestore() || myFocusTrackback.isWillBeSheduledForRestore())) { - myFocusTrackback.dispose(); - myFocusTrackback = null; - } - - - final BufferStrategy strategy = getBufferStrategy(); - if (strategy != null) { - strategy.dispose(); - } - super.dispose(); - - - if (rootPane != null) { // Workaround for bug in native code to hold rootPane - try { - Field field = rootPane.getClass().getDeclaredField("glassPane"); - field.setAccessible(true); - field.set(rootPane, null); - - field = rootPane.getClass().getDeclaredField("contentPane"); - field.setAccessible(true); - field.set(rootPane, null); - rootPane = null; - - field = Window.class.getDeclaredField("windowListener"); - field.setAccessible(true); - field.set(this, null); - } - catch (Exception ignored) { - } - } - - // http://bugs.sun.com/view_bug.do?bug_id=6614056 - try { - final Field field = Dialog.class.getDeclaredField("modalDialogs"); - field.setAccessible(true); - final List list = (List)field.get(null); - list.remove(this); - } - catch (final Exception ignored) { - } - } - - @Override - public Component getMostRecentFocusOwner() { - if (!myOpened) { - final DialogWrapper wrapper = getDialogWrapper(); - if (wrapper != null) { - JComponent toFocus = wrapper.getPreferredFocusedComponent(); - if (toFocus != null) { - return toFocus; - } - } - } - return super.getMostRecentFocusOwner(); - } - - @Override - public void paint(Graphics g) { - UIUtil.applyRenderingHints(g); - super.paint(g); - } - - private class MyWindowListener extends WindowAdapter { - public void windowClosing(WindowEvent e) { - DialogWrapper dialogWrapper = getDialogWrapper(); - if (dialogWrapper.shouldCloseOnCross()) { - dialogWrapper.doCancelAction(e); - } - } - - public void windowClosed(WindowEvent e) { - saveSize(); - } - - public void saveSize() { - if (myDimensionServiceKey != null && - myInitialSize != null && - myOpened) { // myInitialSize can be null only if dialog is disposed before first showing - final Project projectGuess = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(MyDialog.this)); - - // Save location - Point location = getLocation(); - DimensionService.getInstance().setLocation(myDimensionServiceKey, location, projectGuess); - // Save size - Dimension size = getSize(); - if (!myInitialSize.equals(size)) { - DimensionService.getInstance().setSize(myDimensionServiceKey, size, projectGuess); - } - myOpened = false; - } - } - - public void windowOpened(final WindowEvent e) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myOpened = true; - final DialogWrapper activeWrapper = getActiveWrapper(); - if (activeWrapper == null) { - myFocusedCallback.setRejected(); - myTypeAheadDone.setRejected(); - return; - } - - JComponent toFocus = activeWrapper.getPreferredFocusedComponent(); - if (toFocus == null) { - toFocus = getRootPane().getDefaultButton(); - } - - moveMousePointerOnButton(getRootPane().getDefaultButton()); - setupSelectionOnPreferredComponent(toFocus); - - if (toFocus != null) { - final JComponent toRequest = toFocus; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - IdeFocusManager.findInstanceByComponent(e.getWindow()).requestFocus(toRequest, true); - notifyFocused(activeWrapper); - } - }); - } else { - notifyFocused(activeWrapper); - } - } - }); - } - - private void notifyFocused(DialogWrapper wrapper) { - myFocusedCallback.setDone(); - final long timeout = wrapper.getTypeAheadTimeoutMs(); - if (timeout > 0) { - SimpleTimer.getInstance().setUp(new EdtRunnable() { - public void runEdt() { - myTypeAheadDone.setDone(); - } - }, timeout); - } - } - - private DialogWrapper getActiveWrapper() { - DialogWrapper activeWrapper = getDialogWrapper(); - if (activeWrapper == null || !activeWrapper.isShowing()) { - return null; - } - - return activeWrapper; - } - - private void moveMousePointerOnButton(final JButton button) { - Application application = ApplicationManager.getApplication(); - if (application != null && application.hasComponent(UISettings.class)) { - if (button != null && UISettings.getInstance().MOVE_MOUSE_ON_DEFAULT_BUTTON) { - Point p = button.getLocationOnScreen(); - Rectangle r = button.getBounds(); - try { - Robot robot = new Robot(); - robot.mouseMove(p.x + r.width / 2, p.y + r.height / 2); - } - catch (AWTException exc) { - exc.printStackTrace(); - } - } - } - } - } - - private class DialogRootPane extends JRootPane implements DataProvider { - - private final boolean myGlassPaneIsSet; - - private DialogRootPane() { - setGlassPane(new IdeGlassPaneImpl(this)); - myGlassPaneIsSet = true; - } - - @Override - public void setGlassPane(final Component glass) { - if (myGlassPaneIsSet) { - LOG.warn("Setting of glass pane for DialogWrapper is prohibited", new Exception()); - return; - } - - super.setGlassPane(glass); - } - - public Object getData(@NonNls String dataId) { - final DialogWrapper wrapper = myDialogWrapper.get(); - return PlatformDataKeys.UI_DISPOSABLE.is(dataId) ? wrapper.getDisposable() : null; - } - } - - - private class MyFocusCommand extends FocusCommand implements KeyEventProcessor { - - private Context myContextOnFinish; - private final ArrayList myEvents = new ArrayList(); - private final DialogWrapper myWrapper; - - private MyFocusCommand(DialogWrapper wrapper) { - myWrapper = getDialogWrapper(); - - Disposer.register(wrapper.getDisposable(), new Disposable() { - public void dispose() { - if (!myTypeAheadDone.isProcessed()) { - myTypeAheadDone.setDone(); - } - - flushEvents(); - } - }); - } - - public ActionCallback run() { - return myTypeAheadDone; - } - - @Override - public KeyEventProcessor getProcessor() { - return this; - } - - public Boolean dispatch(KeyEvent e, Context context) { - if (myWrapper == null || myTypeAheadDone.isProcessed()) return null; - - myEvents.addAll(context.getQueue()); - context.getQueue().clear(); - - if (isToDipatchToDialogNow(e)) { - return false; - } else { - myEvents.add(e); - return true; - } - } - - private boolean isToDipatchToDialogNow(KeyEvent e) { - return e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_ESCAPE || e.getKeyCode() == KeyEvent.VK_TAB; - } - - public void finish(Context context) { - myContextOnFinish = context; - } - - private void flushEvents() { - if (myWrapper.isToDispatchTypeAhead() && myContextOnFinish != null) { - myContextOnFinish.dispatch(myEvents); - } - } - } - } - - private static void setupSelectionOnPreferredComponent(final JComponent component) { - if (component instanceof JTextField) { - JTextField field = (JTextField)component; - String text = field.getText(); - if (text != null && field.getClientProperty(HAVE_INITIAL_SELECTION) == null) { - field.setSelectionStart(0); - field.setSelectionEnd(text.length()); - } - } - else if (component instanceof JComboBox) { - JComboBox combobox = (JComboBox)component; - combobox.getEditor().selectAll(); - } - } - - public void setContentPane(JComponent content) { - myDialog.setContentPane(content); - } - - public void centerInParent() { - myDialog.centerInParent(); - } - - interface AbstractDialog extends Disposable { - void setUndecorated(boolean undecorated); - - void addMouseListener(MouseListener listener); - - void addMouseMotionListener(MouseMotionListener listener); - - void addKeyListener(KeyListener listener); - - void setModal(boolean b); - - void toFront(); - - void setContentPane(Container content); - - void centerInParent(); - - void toBack(); - - JRootPane getRootPane(); - - void remove(Component root); - - Container getContentPane(); - - void validate(); - - void repaint(); - - Window getOwner(); - - JDialog getWindow(); - - Dimension getSize(); - - String getTitle(); - - void pack(); - - Dimension getPreferredSize(); - - boolean isVisible(); - - boolean isShowing(); - - void setSize(int width, int height); - - void setTitle(String title); - - boolean isResizable(); - - void setResizable(boolean resizable); - - Point getLocation(); - - void setLocation(Point p); - - void setLocation(int x, int y); - - boolean isModal(); - - void show(); - - IdeFocusManager getFocusManager(); - - FocusTrackback getFocusTrackback(); - } - - class HeadlessDialog implements AbstractDialog { - public void setUndecorated(boolean undecorated) { - } - - public void addMouseListener(MouseListener listener) { - } - - public void addMouseMotionListener(MouseMotionListener listener) { - } - - public void addKeyListener(KeyListener listener) { - } - - public void setModal(boolean b) { - } - - public void toFront() { - } - - public void setContentPane(Container content) { - } - - public void centerInParent() { - } - - public void toBack() { - } - - public JRootPane getRootPane() { - return null; - } - - public void remove(Component root) { - } - - public Container getContentPane() { - return null; - } - - public void validate() { - } - - public void repaint() { - } - - public Window getOwner() { - return null; - } - - public JDialog getWindow() { - return null; - } - - public Dimension getSize() { - return null; - } - - public String getTitle() { - return null; - } - - public void pack() { - } - - public Dimension getPreferredSize() { - return null; - } - - public boolean isVisible() { - return false; - } - - public boolean isShowing() { - return false; - } - - public void setSize(int width, int height) { - } - - public void setTitle(String title) { - } - - public boolean isResizable() { - return false; - } - - public void setResizable(boolean resizable) { - } - - public Point getLocation() { - return null; - } - - public void setLocation(Point p) { - } - - public void setLocation(int x, int y) { - } - - public boolean isModal() { - return false; - } - - public void show() { - } - - public IdeFocusManager getFocusManager() { - return null; - } - - public FocusTrackback getFocusTrackback() { - return null; - } - - public void dispose() { - } - } -} +/* + * 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.ui.impl; + +import com.intellij.ide.DataManager; +import com.intellij.ide.IdeEventQueue; +import com.intellij.ide.impl.TypeSafeDataProviderAdapter; +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.LaterInvocator; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.CommandProcessorEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.DialogWrapperDialog; +import com.intellij.openapi.ui.DialogWrapperPeer; +import com.intellij.openapi.ui.Queryable; +import com.intellij.openapi.ui.popup.StackingPopupDispatcher; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.FocusCommand; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.KeyEventProcessor; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.openapi.wm.impl.IdeFrameImpl; +import com.intellij.openapi.wm.impl.IdeGlassPaneImpl; +import com.intellij.ui.AppUIUtil; +import com.intellij.ui.FocusTrackback; +import com.intellij.ui.ScreenUtil; +import com.intellij.ui.SpeedSearchBase; +import com.intellij.ui.popup.StackingPopupDispatcherImpl; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferStrategy; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTrackbackProvider { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.DialogWrapper"); + + private DialogWrapper myWrapper; + private AbstractDialog myDialog; + private boolean myCanBeParent = true; + /* + * Default dialog's actions. + */ + private WindowManagerEx myWindowManager; + private final java.util.List myDisposeActions = new ArrayList(); + private Project myProject; + + private final ActionCallback myWindowFocusedCallback = new ActionCallback("DialogFocusedCallback"); + private final ActionCallback myTypeAheadDone = new ActionCallback("DialogTypeAheadDone"); + + /** + * Creates modal DialogWrapper. The currently active window will be the dialog's parent. + * + * @param project parent window for the dialog will be calculated based on focused window for the + * specified project. This parameter can be null. In this case parent window + * will be suggested based on current focused window. + * @param canBeParent specifies whether the dialog can be parent for other windows. This parameter is used + * by WindowManager. + */ + protected DialogWrapperPeerImpl(DialogWrapper wrapper, Project project, boolean canBeParent) { + myWrapper = wrapper; + myWindowManager = null; + Application application = ApplicationManager.getApplication(); + if (application != null && application.hasComponent(WindowManager.class)) { + myWindowManager = (WindowManagerEx)WindowManager.getInstance(); + } + + Window window = null; + if (myWindowManager != null) { + + if (project == null) { + project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); + } + + myProject = project; + + window = myWindowManager.suggestParentWindow(project); + if (window == null) { + Window focusedWindow = myWindowManager.getMostRecentFocusedWindow(); + if (focusedWindow instanceof IdeFrameImpl) { + window = focusedWindow; + } + } + } + + Window owner; + if (window != null) { + owner = window; + } + else { + if (!isHeadless()) { + owner = JOptionPane.getRootFrame(); + } else { + owner = null; + } + } + + createDialog(owner, canBeParent); + } + + protected DialogWrapperPeerImpl(DialogWrapper wrapper, boolean canBeParent) { + this(wrapper, (Project)null, canBeParent); + } + + public boolean isHeadless() { + return isHeadlessEnv(); + } + + public static boolean isHeadlessEnv() { + Application app = ApplicationManager.getApplication(); + if (app == null) return GraphicsEnvironment.isHeadless(); + + return app.isUnitTestMode() || app.isHeadlessEnvironment(); + } + + /** + * @param parent parent component whicg is used to canculate heavy weight window ancestor. + * parent cannot be null and must be showing. + */ + protected DialogWrapperPeerImpl(DialogWrapper wrapper, @NotNull Component parent, boolean canBeParent) { + myWrapper = wrapper; + if (!parent.isShowing() && parent != JOptionPane.getRootFrame()) { + throw new IllegalArgumentException("parent must be showing: " + parent); + } + myWindowManager = null; + Application application = ApplicationManager.getApplication(); + if (application != null && application.hasComponent(WindowManager.class)) { + myWindowManager = (WindowManagerEx)WindowManager.getInstance(); + } + + Window owner = parent instanceof Window ? (Window)parent : (Window)SwingUtilities.getAncestorOfClass(Window.class, parent); + if (!(owner instanceof Dialog) && !(owner instanceof Frame)) { + owner = JOptionPane.getRootFrame(); + } + createDialog(owner, canBeParent); + } + + public DialogWrapperPeerImpl(final DialogWrapper wrapper, final boolean canBeParent, final boolean tryToolkitModal) { + myWrapper = wrapper; + myWindowManager = null; + Application application = ApplicationManager.getApplication(); + if (application != null && application.hasComponent(WindowManager.class)) { + myWindowManager = (WindowManagerEx)WindowManager.getInstance(); + } + if (UIUtil.hasJdk6Dialogs()) { + createDialog(null, canBeParent); + if (tryToolkitModal && !isHeadless()) { + UIUtil.setToolkitModal((MyDialog)myDialog); + } + } + else { + createDialog(JOptionPane.getRootFrame(), canBeParent); + } + } + + public void setUndecorated(boolean undecorated) { + myDialog.setUndecorated(undecorated); + } + + public void addMouseListener(MouseListener listener) { + myDialog.addMouseListener(listener); + } + + public void addMouseListener(MouseMotionListener listener) { + myDialog.addMouseMotionListener(listener); + } + + public void addKeyListener(KeyListener listener) { + myDialog.addKeyListener(listener); + } + + private void createDialog(Window owner, boolean canBeParent) { + if (isHeadless()) { + myDialog = new HeadlessDialog(); + return; + } + + if (owner instanceof Frame) { + myDialog = new MyDialog((Frame)owner, myWrapper, myProject, myWindowFocusedCallback, myTypeAheadDone); + } + else { + myDialog = new MyDialog((Dialog)owner, myWrapper, myProject, myWindowFocusedCallback, myTypeAheadDone); + } + myDialog.setModal(true); + myCanBeParent = canBeParent; + + } + + + public void toFront() { + myDialog.toFront(); + } + + public void toBack() { + myDialog.toBack(); + } + + protected void dispose() { + LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only"); + for (Runnable runnable : myDisposeActions) { + runnable.run(); + } + myDisposeActions.clear(); + final JRootPane root = myDialog.getRootPane(); + + Runnable disposer = new Runnable() { + public void run() { + myDialog.dispose(); + myProject = null; + /* + if (myWindowManager == null) { + myDialog.dispose(); + } + else { + myWindowManager.hideDialog(myDialog, myProject); + } + */ + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (myDialog != null && root != null) { + myDialog.remove(root); + } + } + }); + } + }; + + if (EventQueue.isDispatchThread()) { + disposer.run(); + } + else { + SwingUtilities.invokeLater(disposer); + } + } + + private boolean isProgressDialog() { + return myWrapper.isModalProgress(); + } + + @Nullable + public Container getContentPane() { + return getRootPane() != null ? myDialog.getContentPane() : null; + } + + /** + * @see javax.swing.JDialog#validate + */ + public void validate() { + myDialog.validate(); + } + + /** + * @see javax.swing.JDialog#repaint + */ + public void repaint() { + myDialog.repaint(); + } + + public Window getOwner() { + return myDialog.getOwner(); + } + + public Window getWindow() { + return myDialog.getWindow(); + } + + public JRootPane getRootPane() { + return myDialog.getRootPane(); + } + + public Dimension getSize() { + return myDialog.getSize(); + } + + public String getTitle() { + return myDialog.getTitle(); + } + + /** + * @see java.awt.Window#pack + */ + public void pack() { + myDialog.pack(); + } + + public void setIconImages(final List image) { + UIUtil.updateDialogIcon(myDialog.getWindow(), image); + } + + public void setAppIcons() { + setIconImages(AppUIUtil.getAppIconImages()); + } + + public Dimension getPreferredSize() { + return myDialog.getPreferredSize(); + } + + public void setModal(boolean modal) { + myDialog.setModal(modal); + } + + public boolean isVisible() { + return myDialog.isVisible(); + } + + public boolean isShowing() { + return myDialog.isShowing(); + } + + public void setSize(int width, int height) { + myDialog.setSize(width, height); + } + + public void setTitle(String title) { + myDialog.setTitle(title); + } + + public void isResizable() { + myDialog.isResizable(); + } + + public void setResizable(boolean resizable) { + myDialog.setResizable(resizable); + } + + public Point getLocation() { + return myDialog.getLocation(); + } + + public void setLocation(Point p) { + myDialog.setLocation(p); + } + + public void setLocation(int x, int y) { + myDialog.setLocation(x, y); + } + + public ActionCallback show() { + final ActionCallback result = new ActionCallback(); + + LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only"); + + final AnCancelAction anCancelAction = new AnCancelAction(); + final JRootPane rootPane = getRootPane(); + anCancelAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)), rootPane); + myDisposeActions.add(new Runnable() { + public void run() { + anCancelAction.unregisterCustomShortcutSet(rootPane); + } + }); + + if (!myCanBeParent && myWindowManager != null) { + myWindowManager.doNotSuggestAsParent(myDialog.getWindow()); + } + + final CommandProcessorEx commandProcessor = + ApplicationManager.getApplication() != null ? (CommandProcessorEx)CommandProcessor.getInstance() : null; + final boolean appStarted = commandProcessor != null; + + if (myDialog.isModal() && !isProgressDialog()) { + /* + if (ApplicationManager.getApplication() != null) { + if (ApplicationManager.getApplication().getCurrentWriteAction(null) != null) { + LOG.warn( + "Showing of a modal dialog inside write-action may be dangerous and resulting in unpredictable behavior! Current modalityState=" + ModalityState.current(), new Exception()); + } + } + */ + if (appStarted) { + commandProcessor.enterModal(); + LaterInvocator.enterModal(myDialog); + } + } + + if (appStarted) { + hidePopupsIfNeeded(); + } + + try { + myDialog.show(); + } + finally { + if (myDialog.isModal() && !isProgressDialog()) { + if (appStarted) { + commandProcessor.leaveModal(); + LaterInvocator.leaveModal(myDialog); + } + } + + myDialog.getFocusManager().doWhenFocusSettlesDown(new Runnable() { + public void run() { + result.setDone(); + } + }); + } + + return result; + } + +//[kirillk] for now it only deals with the TaskWindow under Mac OS X: modal dialogs are shown behind JBPopup + + //hopefully this whole code will go away + private void hidePopupsIfNeeded() { + if (!SystemInfo.isMac) return; + + StackingPopupDispatcherImpl.getInstance().hidePersistentPopups(); + myDisposeActions.add(new Runnable() { + public void run() { + StackingPopupDispatcherImpl.getInstance().restorePersistentPopups(); + } + }); + } + + public FocusTrackback getFocusTrackback() { + return myDialog.getFocusTrackback(); + } + + private class AnCancelAction extends AnAction implements DumbAware { + public void update(AnActionEvent e) { + Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + e.getPresentation().setEnabled(false); + if (focusOwner instanceof JComponent && SpeedSearchBase.hasActiveSpeedSearch((JComponent)focusOwner)) { + return; + } + + if (StackingPopupDispatcher.getInstance().isPopupFocused()) return; + + if (focusOwner instanceof JTree) { + JTree tree = (JTree)focusOwner; + if (!tree.isEditing()) { + e.getPresentation().setEnabled(true); + } + } + else if (focusOwner instanceof JTable) { + JTable table = (JTable)focusOwner; + if (!table.isEditing()) { + e.getPresentation().setEnabled(true); + } + } + } + + public void actionPerformed(AnActionEvent e) { + myWrapper.doCancelAction(e.getInputEvent()); + } + } + + + private static class MyDialog extends JDialog implements DialogWrapperDialog, DataProvider, FocusTrackback.Provider, Queryable, AbstractDialog { + private final WeakReference myDialogWrapper; + /** + * Initial size of the dialog. When the dialog is being closed and + * current size of the dialog is not equals to the initial sizethen the + * current (changed) size is stored in the DimensionService. + */ + private Dimension myInitialSize; + private String myDimensionServiceKey; + private boolean myOpened = false; + + private FocusTrackback myFocusTrackback; + private MyDialog.MyWindowListener myWindowListener; + + private final WeakReference myProject; + private ActionCallback myFocusedCallback; + private ActionCallback myTypeAheadDone; + + public MyDialog(Dialog owner, DialogWrapper dialogWrapper, Project project, ActionCallback focused, ActionCallback typeAheadDone) { + super(owner); + myDialogWrapper = new WeakReference(dialogWrapper); + myProject = project != null ? new WeakReference(project) : null; + initDialog(focused, typeAheadDone); + } + + public MyDialog(Frame owner, DialogWrapper dialogWrapper, Project project, ActionCallback focused, ActionCallback typeAheadDone) { + super(owner); + myDialogWrapper = new WeakReference(dialogWrapper); + myProject = project != null ? new WeakReference(project) : null; + initDialog(focused, typeAheadDone); + } + + private void initDialog(ActionCallback focused, ActionCallback typeAheadDone) { + myFocusedCallback = focused; + myTypeAheadDone = typeAheadDone; + + final long typeAhead = getDialogWrapper().getTypeAheadTimeoutMs(); + if (typeAhead <= 0) { + myTypeAheadDone.setDone(); + } + + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + myWindowListener = new MyWindowListener(); + addWindowListener(myWindowListener); + } + + public JDialog getWindow() { + return this; + } + + public void putInfo(Map info) { + info.put("dialog", getTitle()); + } + + public FocusTrackback getFocusTrackback() { + return myFocusTrackback; + } + + public DialogWrapper getDialogWrapper() { + return myDialogWrapper.get(); + } + + public void centerInParent() { + setLocationRelativeTo(getOwner()); + } + + public Object getData(String dataId) { + final DialogWrapper wrapper = myDialogWrapper.get(); + if (wrapper instanceof DataProvider) { + return ((DataProvider)wrapper).getData(dataId); + } + else if (wrapper instanceof TypeSafeDataProvider) { + TypeSafeDataProviderAdapter adapter = new TypeSafeDataProviderAdapter((TypeSafeDataProvider)wrapper); + return adapter.getData(dataId); + } + return null; + } + + public void setSize(int width, int height) { + Point location = getLocation(); + Rectangle rect = new Rectangle(location.x, location.y, width, height); + ScreenUtil.fitToScreen(rect); + if (location.x != rect.x || location.y != rect.y) { + setLocation(rect.x, rect.y); + } + + super.setSize(rect.width, rect.height); + } + + public void setBounds(int x, int y, int width, int height) { + Rectangle rect = new Rectangle(x, y, width, height); + ScreenUtil.fitToScreen(rect); + super.setBounds(rect.x, rect.y, rect.width, rect.height); + } + + public void setBounds(Rectangle r) { + ScreenUtil.fitToScreen(r); + super.setBounds(r); + } + + protected JRootPane createRootPane() { + return new DialogRootPane(); + } + + public void show() { + myFocusTrackback = new FocusTrackback(myDialogWrapper, this, true); + + final DialogWrapper dialogWrapper = getDialogWrapper(); + + pack(); + setSize((int)(getWidth() * dialogWrapper.getHorizontalStretch()), (int)(getHeight() * dialogWrapper.getVerticalStretch())); + + // Restore dialog's size and location + + myDimensionServiceKey = dialogWrapper.getDimensionKey(); + Point location = null; + + if (myDimensionServiceKey != null) { + final Project projectGuess = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(this)); + location = DimensionService.getInstance().getLocation(myDimensionServiceKey, projectGuess); + Dimension size = DimensionService.getInstance().getSize(myDimensionServiceKey, projectGuess); + if (size != null) { + myInitialSize = (Dimension)size.clone(); + setSize(myInitialSize); + } + } + + if (myInitialSize == null) { + myInitialSize = getSize(); + } + + if (location == null) { + location = dialogWrapper.getInitialLocation(); + } + + if (location != null) { + setLocation(location); + } + else { + setLocationRelativeTo(getOwner()); + } + + final Rectangle bounds = getBounds(); + ScreenUtil.fitToScreen(bounds); + setBounds(bounds); + + addWindowListener(new WindowAdapter() { + public void windowActivated(final WindowEvent e) { + final DialogWrapper wrapper = getDialogWrapper(); + if (wrapper != null && myFocusTrackback != null) { + myFocusTrackback.registerFocusComponent(new FocusTrackback.ComponentQuery() { + public Component getComponent() { + return wrapper.getPreferredFocusedComponent(); + } + }); + } + } + + public void windowDeactivated(final WindowEvent e) { + if (!isModal()) { + final Ref focusManager = new Ref(null); + if (myProject != null && myProject.get() != null && !myProject.get().isDisposed()) { + focusManager.set(getFocusManager()); + focusManager.get().doWhenFocusSettlesDown(new Runnable() { + public void run() { + disposeFocusTrackbackIfNoChildWindowFocused(focusManager.get()); + } + }); + } + else { + disposeFocusTrackbackIfNoChildWindowFocused(focusManager.get()); + } + } + } + }); + + if (Registry.is("actionSystem.fixLostTyping")) { + final IdeEventQueue queue = IdeEventQueue.getInstance(); + if (queue != null) { + queue.getKeyEventDispatcher().resetState(); + } + + if (myProject != null) { + Project project = myProject.get(); + if (project != null && !project.isDisposed() && project.isInitialized()) { + IdeFocusManager.findInstanceByComponent(this).requestFocus(new MyFocusCommand(dialogWrapper), true); + } + } + } + + super.show(); + } + + public IdeFocusManager getFocusManager() { + if (myProject != null && myProject.get() != null && !myProject.get().isDisposed()) { + return IdeFocusManager.getInstance(myProject.get()); + } else { + return IdeFocusManager.findInstance(); + } + } + + private void disposeFocusTrackbackIfNoChildWindowFocused(@Nullable IdeFocusManager focusManager) { + if (myFocusTrackback == null) return; + + final DialogWrapper wrapper = myDialogWrapper.get(); + if (wrapper == null || !wrapper.isShowing()) { + myFocusTrackback.dispose(); + return; + } + + if (focusManager != null) { + final Component c = focusManager.getFocusedDescendantFor(wrapper.getContentPane()); + if (c == null) { + myFocusTrackback.dispose(); + } + } + else { + final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (owner == null || !SwingUtilities.isDescendingFrom(owner, wrapper.getContentPane())) { + myFocusTrackback.dispose(); + } + } + } + + @Deprecated + public void hide() { + super.hide(); + if (myFocusTrackback != null && !(myFocusTrackback.isSheduledForRestore() || myFocusTrackback.isWillBeSheduledForRestore())) { + myFocusTrackback.setWillBeSheduledForRestore(); + IdeFocusManager mgr = getFocusManager(); + Runnable r = new Runnable() { + public void run() { + if (myFocusTrackback != null) myFocusTrackback.restoreFocus(); + myFocusTrackback = null; + } + }; + mgr.doWhenFocusSettlesDown(r); + } + } + + public void dispose() { + if (isShowing()) { + hide(); + } + + if (myWindowListener != null) { + myWindowListener.saveSize(); + removeWindowListener(myWindowListener); + myWindowListener = null; + } + + if (myFocusTrackback != null && !(myFocusTrackback.isSheduledForRestore() || myFocusTrackback.isWillBeSheduledForRestore())) { + myFocusTrackback.dispose(); + myFocusTrackback = null; + } + + + final BufferStrategy strategy = getBufferStrategy(); + if (strategy != null) { + strategy.dispose(); + } + super.dispose(); + + + if (rootPane != null) { // Workaround for bug in native code to hold rootPane + try { + Field field = rootPane.getClass().getDeclaredField("glassPane"); + field.setAccessible(true); + field.set(rootPane, null); + + field = rootPane.getClass().getDeclaredField("contentPane"); + field.setAccessible(true); + field.set(rootPane, null); + rootPane = null; + + field = Window.class.getDeclaredField("windowListener"); + field.setAccessible(true); + field.set(this, null); + } + catch (Exception ignored) { + } + } + + // http://bugs.sun.com/view_bug.do?bug_id=6614056 + try { + final Field field = Dialog.class.getDeclaredField("modalDialogs"); + field.setAccessible(true); + final List list = (List)field.get(null); + list.remove(this); + } + catch (final Exception ignored) { + } + } + + @Override + public Component getMostRecentFocusOwner() { + if (!myOpened) { + final DialogWrapper wrapper = getDialogWrapper(); + if (wrapper != null) { + JComponent toFocus = wrapper.getPreferredFocusedComponent(); + if (toFocus != null) { + return toFocus; + } + } + } + return super.getMostRecentFocusOwner(); + } + + @Override + public void paint(Graphics g) { + UIUtil.applyRenderingHints(g); + super.paint(g); + } + + private class MyWindowListener extends WindowAdapter { + public void windowClosing(WindowEvent e) { + DialogWrapper dialogWrapper = getDialogWrapper(); + if (dialogWrapper.shouldCloseOnCross()) { + dialogWrapper.doCancelAction(e); + } + } + + public void windowClosed(WindowEvent e) { + saveSize(); + } + + public void saveSize() { + if (myDimensionServiceKey != null && + myInitialSize != null && + myOpened) { // myInitialSize can be null only if dialog is disposed before first showing + final Project projectGuess = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(MyDialog.this)); + + // Save location + Point location = getLocation(); + DimensionService.getInstance().setLocation(myDimensionServiceKey, location, projectGuess); + // Save size + Dimension size = getSize(); + if (!myInitialSize.equals(size)) { + DimensionService.getInstance().setSize(myDimensionServiceKey, size, projectGuess); + } + myOpened = false; + } + } + + public void windowOpened(final WindowEvent e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myOpened = true; + final DialogWrapper activeWrapper = getActiveWrapper(); + if (activeWrapper == null) { + myFocusedCallback.setRejected(); + myTypeAheadDone.setRejected(); + return; + } + + JComponent toFocus = activeWrapper.getPreferredFocusedComponent(); + if (toFocus == null) { + toFocus = getRootPane().getDefaultButton(); + } + + moveMousePointerOnButton(getRootPane().getDefaultButton()); + setupSelectionOnPreferredComponent(toFocus); + + if (toFocus != null) { + final JComponent toRequest = toFocus; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + IdeFocusManager.findInstanceByComponent(e.getWindow()).requestFocus(toRequest, true); + notifyFocused(activeWrapper); + } + }); + } else { + notifyFocused(activeWrapper); + } + } + }); + } + + private void notifyFocused(DialogWrapper wrapper) { + myFocusedCallback.setDone(); + final long timeout = wrapper.getTypeAheadTimeoutMs(); + if (timeout > 0) { + SimpleTimer.getInstance().setUp(new EdtRunnable() { + public void runEdt() { + myTypeAheadDone.setDone(); + } + }, timeout); + } + } + + private DialogWrapper getActiveWrapper() { + DialogWrapper activeWrapper = getDialogWrapper(); + if (activeWrapper == null || !activeWrapper.isShowing()) { + return null; + } + + return activeWrapper; + } + + private void moveMousePointerOnButton(final JButton button) { + Application application = ApplicationManager.getApplication(); + if (application != null && application.hasComponent(UISettings.class)) { + if (button != null && UISettings.getInstance().MOVE_MOUSE_ON_DEFAULT_BUTTON) { + Point p = button.getLocationOnScreen(); + Rectangle r = button.getBounds(); + try { + Robot robot = new Robot(); + robot.mouseMove(p.x + r.width / 2, p.y + r.height / 2); + } + catch (AWTException exc) { + exc.printStackTrace(); + } + } + } + } + } + + private class DialogRootPane extends JRootPane implements DataProvider { + + private final boolean myGlassPaneIsSet; + + private DialogRootPane() { + setGlassPane(new IdeGlassPaneImpl(this)); + myGlassPaneIsSet = true; + } + + @Override + public void setGlassPane(final Component glass) { + if (myGlassPaneIsSet) { + LOG.warn("Setting of glass pane for DialogWrapper is prohibited", new Exception()); + return; + } + + super.setGlassPane(glass); + } + + public Object getData(@NonNls String dataId) { + final DialogWrapper wrapper = myDialogWrapper.get(); + return PlatformDataKeys.UI_DISPOSABLE.is(dataId) ? wrapper.getDisposable() : null; + } + } + + + private class MyFocusCommand extends FocusCommand implements KeyEventProcessor { + + private Context myContextOnFinish; + private final ArrayList myEvents = new ArrayList(); + private final DialogWrapper myWrapper; + + private MyFocusCommand(DialogWrapper wrapper) { + myWrapper = getDialogWrapper(); + + Disposer.register(wrapper.getDisposable(), new Disposable() { + public void dispose() { + if (!myTypeAheadDone.isProcessed()) { + myTypeAheadDone.setDone(); + } + + flushEvents(); + } + }); + } + + public ActionCallback run() { + return myTypeAheadDone; + } + + @Override + public KeyEventProcessor getProcessor() { + return this; + } + + public Boolean dispatch(KeyEvent e, Context context) { + if (myWrapper == null || myTypeAheadDone.isProcessed()) return null; + + myEvents.addAll(context.getQueue()); + context.getQueue().clear(); + + if (isToDipatchToDialogNow(e)) { + return false; + } else { + myEvents.add(e); + return true; + } + } + + private boolean isToDipatchToDialogNow(KeyEvent e) { + return e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_ESCAPE || e.getKeyCode() == KeyEvent.VK_TAB; + } + + public void finish(Context context) { + myContextOnFinish = context; + } + + private void flushEvents() { + if (myWrapper.isToDispatchTypeAhead() && myContextOnFinish != null) { + myContextOnFinish.dispatch(myEvents); + } + } + } + } + + private static void setupSelectionOnPreferredComponent(final JComponent component) { + if (component instanceof JTextField) { + JTextField field = (JTextField)component; + String text = field.getText(); + if (text != null && field.getClientProperty(HAVE_INITIAL_SELECTION) == null) { + field.setSelectionStart(0); + field.setSelectionEnd(text.length()); + } + } + else if (component instanceof JComboBox) { + JComboBox combobox = (JComboBox)component; + combobox.getEditor().selectAll(); + } + } + + public void setContentPane(JComponent content) { + myDialog.setContentPane(content); + } + + public void centerInParent() { + myDialog.centerInParent(); + } + + interface AbstractDialog extends Disposable { + void setUndecorated(boolean undecorated); + + void addMouseListener(MouseListener listener); + + void addMouseMotionListener(MouseMotionListener listener); + + void addKeyListener(KeyListener listener); + + void setModal(boolean b); + + void toFront(); + + void setContentPane(Container content); + + void centerInParent(); + + void toBack(); + + JRootPane getRootPane(); + + void remove(Component root); + + Container getContentPane(); + + void validate(); + + void repaint(); + + Window getOwner(); + + JDialog getWindow(); + + Dimension getSize(); + + String getTitle(); + + void pack(); + + Dimension getPreferredSize(); + + boolean isVisible(); + + boolean isShowing(); + + void setSize(int width, int height); + + void setTitle(String title); + + boolean isResizable(); + + void setResizable(boolean resizable); + + Point getLocation(); + + void setLocation(Point p); + + void setLocation(int x, int y); + + boolean isModal(); + + void show(); + + IdeFocusManager getFocusManager(); + + FocusTrackback getFocusTrackback(); + } + + class HeadlessDialog implements AbstractDialog { + public void setUndecorated(boolean undecorated) { + } + + public void addMouseListener(MouseListener listener) { + } + + public void addMouseMotionListener(MouseMotionListener listener) { + } + + public void addKeyListener(KeyListener listener) { + } + + public void setModal(boolean b) { + } + + public void toFront() { + } + + public void setContentPane(Container content) { + } + + public void centerInParent() { + } + + public void toBack() { + } + + public JRootPane getRootPane() { + return null; + } + + public void remove(Component root) { + } + + public Container getContentPane() { + return null; + } + + public void validate() { + } + + public void repaint() { + } + + public Window getOwner() { + return null; + } + + public JDialog getWindow() { + return null; + } + + public Dimension getSize() { + return null; + } + + public String getTitle() { + return null; + } + + public void pack() { + } + + public Dimension getPreferredSize() { + return null; + } + + public boolean isVisible() { + return false; + } + + public boolean isShowing() { + return false; + } + + public void setSize(int width, int height) { + } + + public void setTitle(String title) { + } + + public boolean isResizable() { + return false; + } + + public void setResizable(boolean resizable) { + } + + public Point getLocation() { + return null; + } + + public void setLocation(Point p) { + } + + public void setLocation(int x, int y) { + } + + public boolean isModal() { + return false; + } + + public void show() { + } + + public IdeFocusManager getFocusManager() { + return null; + } + + public FocusTrackback getFocusTrackback() { + return null; + } + + public void dispose() { + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java index ec811a6c9de3..d8ee30ef372b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java @@ -1,309 +1,309 @@ -/* - * 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. - */ - -/* - * User: anna - * Date: 10-Aug-2007 - */ -package com.intellij.openapi.updateSettings.impl; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.ide.plugins.RepositoryHelper; -import com.intellij.ide.startup.StartupActionScriptManager; -import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.BuildNumber; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.StreamUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.io.UrlConnectionUtil; -import com.intellij.util.io.ZipUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; - -public class PluginDownloader { - - private static final Logger LOG = Logger.getInstance("#" + PluginDownloader.class.getName()); - - @NonNls private static final String FILENAME = "filename="; - - private final String myPluginId; - private final String myPluginUrl; - private String myPluginVersion; - - private String myFileName; - private String myPluginName; - - private File myFile; - private File myOldFile; - - public PluginDownloader(final String pluginId, final String pluginUrl, final String pluginVersion) { - myPluginId = pluginId; - myPluginUrl = pluginUrl; - myPluginVersion = pluginVersion; - } - - public PluginDownloader(final String pluginId, - final String pluginUrl, - final String pluginVersion, - final String fileName, - final String pluginName) { - myPluginId = pluginId; - myPluginUrl = pluginUrl; - myPluginVersion = pluginVersion; - myFileName = fileName; - myPluginName = pluginName; - } - - public boolean prepareToInstall() throws IOException { - return prepareToInstall(new ProgressIndicatorBase()); - } - - public boolean prepareToInstall(ProgressIndicator pi) throws IOException { - IdeaPluginDescriptor ideaPluginDescriptor = null; - if (PluginManager.isPluginInstalled(PluginId.getId(myPluginId))) { - //store old plugins file - ideaPluginDescriptor = PluginManager.getPlugin(PluginId.getId(myPluginId)); - LOG.assertTrue(ideaPluginDescriptor != null); - if (myPluginVersion != null && StringUtil.compareVersionNumbers(ideaPluginDescriptor.getVersion(), myPluginVersion) >= 0) { - LOG.info("Plugin " + myPluginId + ": current version (max) " + myPluginVersion); - return false; - } - myOldFile = ideaPluginDescriptor.getPath(); - } - // download plugin - String errorMessage = IdeBundle.message("unknown.error"); - try { - myFile = downloadPlugin(pi); - } - catch (IOException ex) { - myFile = null; - errorMessage = ex.getMessage(); - } - if (myFile == null) { - final String errorMessage1 = errorMessage; - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - Messages.showErrorDialog(IdeBundle.message("error.plugin.was.not.installed", getPluginName(), errorMessage1), - IdeBundle.message("title.failed.to.download")); - } - }); - return false; - } - - IdeaPluginDescriptorImpl descriptor = PluginManager.loadDescriptorFromJar(myFile); - if (descriptor == null) { - if (myFile.getName().endsWith(".zip")) { - final File outputDir = FileUtil.createTempDirectory("plugin", ""); - try { - ZipUtil.extract(myFile, outputDir, new FilenameFilter() { - public boolean accept(final File dir, final String name) { - return true; - } - }); - descriptor = PluginManager.loadDescriptor(new File(outputDir, FileUtil.getNameWithoutExtension(myFile)), PluginManager.PLUGIN_XML); - } - finally { - FileUtil.delete(outputDir); - } - } - } - if (descriptor != null) { - myPluginVersion = descriptor.getVersion(); - if (ideaPluginDescriptor != null && StringUtil.compareVersionNumbers(ideaPluginDescriptor.getVersion(), descriptor.getVersion()) >= 0) { - LOG.info("Plugin " + myPluginId + ": current version (max) " + myPluginVersion); - return false; //was not updated - } - final BuildNumber currentBuildNumber = ApplicationInfo.getInstance().getBuild(); - final BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild()); - if (sinceBuild != null && sinceBuild.compareTo(currentBuildNumber) > 0) { - return false; - } - final BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild()); - if (untilBuild != null && untilBuild.compareTo(currentBuildNumber) < 0) { - return false; - } - } - return true; - } - - public void install() throws IOException { - LOG.assertTrue(myFile != null); - if (myOldFile != null) { - // add command to delete the 'action script' file - StartupActionScriptManager.ActionCommand deleteOld = new StartupActionScriptManager.DeleteCommand(myOldFile); - StartupActionScriptManager.addActionCommand(deleteOld); - } - - //noinspection HardCodedStringLiteral - if (myFile.getName().endsWith(".jar")) { - // add command to copy file to the IDEA/plugins path - StartupActionScriptManager.ActionCommand copyPlugin = - new StartupActionScriptManager.CopyCommand(myFile, new File(PathManager.getPluginsPath() + File.separator + myFile.getName())); - StartupActionScriptManager.addActionCommand(copyPlugin); - } - else { - // add command to unzip file to the IDEA/plugins path - String unzipPath; - if (ZipUtil.isZipContainsFolder(myFile)) { - unzipPath = PathManager.getPluginsPath(); - } - else { - unzipPath = PathManager.getPluginsPath() + File.separator + getPluginName(); - } - - StartupActionScriptManager.ActionCommand unzip = new StartupActionScriptManager.UnzipCommand(myFile, new File(unzipPath)); - StartupActionScriptManager.addActionCommand(unzip); - } - - // add command to remove temp plugin file - StartupActionScriptManager.ActionCommand deleteTemp = new StartupActionScriptManager.DeleteCommand(myFile); - StartupActionScriptManager.addActionCommand(deleteTemp); - } - - private File downloadPlugin(ProgressIndicator pi) throws IOException { - HttpURLConnection connection = (HttpURLConnection)new URL(myPluginUrl).openConnection(); - try - { - pi.setText(IdeBundle.message("progress.connecting")); - - InputStream is = UrlConnectionUtil.getConnectionInputStream(connection, pi); - - if (is == null) { - throw new IOException("Failed to open connection"); - } - - pi.setText(IdeBundle.message("progress.downloading.plugin", getPluginName())); - - final File pluginsTemp = new File(PathManager.getPluginTempPath()); - - if (!pluginsTemp.exists()) { - pluginsTemp.mkdirs(); - } - - File file = FileUtil.createTempFile(pluginsTemp, "plugin", "download", true); - - int responseCode = connection.getResponseCode(); - switch (responseCode) { - case HttpURLConnection.HTTP_OK: - break; - default: - // some problems - throw new IOException(IdeBundle.message("error.connection.failed.with.http.code.N", responseCode)); - } - - pi.setIndeterminate(connection.getContentLength() == -1); - - OutputStream fos = null; - try { - fos = new BufferedOutputStream(new FileOutputStream(file, false)); - StreamUtil.copyStreamContent(is, fos); - } - finally { - if (fos != null) { - fos.close(); - } - is.close(); - } - if (myFileName == null) { - String contentDisposition = connection.getHeaderField("Content-Disposition"); - if (contentDisposition == null || contentDisposition.indexOf(FILENAME) < 0) { - // try to find filename in URL - String usedURL = connection.getURL().toString(); - int startPos = usedURL.lastIndexOf("/"); - - myFileName = usedURL.substring(startPos + 1); - if (myFileName.length() == 0 || myFileName.contains("?")) { - myFileName = myPluginUrl.substring(myPluginUrl.lastIndexOf("/") + 1); - } - } - else { - int startIdx = contentDisposition.indexOf(FILENAME); - myFileName = contentDisposition.substring(startIdx + FILENAME.length(), contentDisposition.length()); - // according to the HTTP spec, the filename is a quoted string, but some servers don't quote it - // for example: http://www.jspformat.com/Download.do?formAction=d&id=8 - if (myFileName.startsWith("\"") && myFileName.endsWith("\"")) { - myFileName = myFileName.substring(1, myFileName.length()-1); - } - if (myFileName.indexOf('\\') >= 0 || myFileName.indexOf('/') >= 0 || myFileName.indexOf(File.separatorChar) >= 0 || - myFileName.indexOf('\"') >= 0) { - // invalid path name passed by the server - fail to download - FileUtil.delete(file); - throw new IOException("Invalid filename returned by server"); - } - } - } - - File newFile = new File (file.getParentFile(), myFileName); - FileUtil.rename(file, newFile); - return newFile; - } - finally { - connection.disconnect(); - } - } - - public String getPluginId() { - return myPluginId; - } - - public String getFileName() { - if (myFileName == null) { - myFileName = myPluginUrl.substring(myPluginUrl.lastIndexOf("/") + 1); - } - return myFileName; - } - - public String getPluginName() { - if (myPluginName == null) { - myPluginName = FileUtil.getNameWithoutExtension(getFileName()); - } - return myPluginName; - } - - public String getPluginVersion() { - return myPluginVersion; - } - - /** - * Updates given plugin from Repository - * @param pluginId given plugin id - * @param pluginVersion available version or null if plugin must be uploaded even if current version is greater than uploading - * @throws IOException - */ - public static void updateFromRepository(final String pluginId, final @Nullable String pluginVersion) throws IOException { - @NonNls final String url = - RepositoryHelper.DOWNLOAD_URL + URLEncoder.encode(pluginId, "UTF8") + "&build=" + ApplicationInfo.getInstance().getBuild().asString(); - final PluginDownloader downloader = new PluginDownloader(pluginId, url, pluginVersion); - if (downloader.prepareToInstall()) { - downloader.install(); - } - } -} +/* + * 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. + */ + +/* + * User: anna + * Date: 10-Aug-2007 + */ +package com.intellij.openapi.updateSettings.impl; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.plugins.RepositoryHelper; +import com.intellij.ide.startup.StartupActionScriptManager; +import com.intellij.openapi.application.ApplicationInfo; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.BuildNumber; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.StreamUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.io.UrlConnectionUtil; +import com.intellij.util.io.ZipUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +public class PluginDownloader { + + private static final Logger LOG = Logger.getInstance("#" + PluginDownloader.class.getName()); + + @NonNls private static final String FILENAME = "filename="; + + private final String myPluginId; + private final String myPluginUrl; + private String myPluginVersion; + + private String myFileName; + private String myPluginName; + + private File myFile; + private File myOldFile; + + public PluginDownloader(final String pluginId, final String pluginUrl, final String pluginVersion) { + myPluginId = pluginId; + myPluginUrl = pluginUrl; + myPluginVersion = pluginVersion; + } + + public PluginDownloader(final String pluginId, + final String pluginUrl, + final String pluginVersion, + final String fileName, + final String pluginName) { + myPluginId = pluginId; + myPluginUrl = pluginUrl; + myPluginVersion = pluginVersion; + myFileName = fileName; + myPluginName = pluginName; + } + + public boolean prepareToInstall() throws IOException { + return prepareToInstall(new ProgressIndicatorBase()); + } + + public boolean prepareToInstall(ProgressIndicator pi) throws IOException { + IdeaPluginDescriptor ideaPluginDescriptor = null; + if (PluginManager.isPluginInstalled(PluginId.getId(myPluginId))) { + //store old plugins file + ideaPluginDescriptor = PluginManager.getPlugin(PluginId.getId(myPluginId)); + LOG.assertTrue(ideaPluginDescriptor != null); + if (myPluginVersion != null && StringUtil.compareVersionNumbers(ideaPluginDescriptor.getVersion(), myPluginVersion) >= 0) { + LOG.info("Plugin " + myPluginId + ": current version (max) " + myPluginVersion); + return false; + } + myOldFile = ideaPluginDescriptor.getPath(); + } + // download plugin + String errorMessage = IdeBundle.message("unknown.error"); + try { + myFile = downloadPlugin(pi); + } + catch (IOException ex) { + myFile = null; + errorMessage = ex.getMessage(); + } + if (myFile == null) { + final String errorMessage1 = errorMessage; + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + Messages.showErrorDialog(IdeBundle.message("error.plugin.was.not.installed", getPluginName(), errorMessage1), + IdeBundle.message("title.failed.to.download")); + } + }); + return false; + } + + IdeaPluginDescriptorImpl descriptor = PluginManager.loadDescriptorFromJar(myFile); + if (descriptor == null) { + if (myFile.getName().endsWith(".zip")) { + final File outputDir = FileUtil.createTempDirectory("plugin", ""); + try { + ZipUtil.extract(myFile, outputDir, new FilenameFilter() { + public boolean accept(final File dir, final String name) { + return true; + } + }); + descriptor = PluginManager.loadDescriptor(new File(outputDir, FileUtil.getNameWithoutExtension(myFile)), PluginManager.PLUGIN_XML); + } + finally { + FileUtil.delete(outputDir); + } + } + } + if (descriptor != null) { + myPluginVersion = descriptor.getVersion(); + if (ideaPluginDescriptor != null && StringUtil.compareVersionNumbers(ideaPluginDescriptor.getVersion(), descriptor.getVersion()) >= 0) { + LOG.info("Plugin " + myPluginId + ": current version (max) " + myPluginVersion); + return false; //was not updated + } + final BuildNumber currentBuildNumber = ApplicationInfo.getInstance().getBuild(); + final BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild()); + if (sinceBuild != null && sinceBuild.compareTo(currentBuildNumber) > 0) { + return false; + } + final BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild()); + if (untilBuild != null && untilBuild.compareTo(currentBuildNumber) < 0) { + return false; + } + } + return true; + } + + public void install() throws IOException { + LOG.assertTrue(myFile != null); + if (myOldFile != null) { + // add command to delete the 'action script' file + StartupActionScriptManager.ActionCommand deleteOld = new StartupActionScriptManager.DeleteCommand(myOldFile); + StartupActionScriptManager.addActionCommand(deleteOld); + } + + //noinspection HardCodedStringLiteral + if (myFile.getName().endsWith(".jar")) { + // add command to copy file to the IDEA/plugins path + StartupActionScriptManager.ActionCommand copyPlugin = + new StartupActionScriptManager.CopyCommand(myFile, new File(PathManager.getPluginsPath() + File.separator + myFile.getName())); + StartupActionScriptManager.addActionCommand(copyPlugin); + } + else { + // add command to unzip file to the IDEA/plugins path + String unzipPath; + if (ZipUtil.isZipContainsFolder(myFile)) { + unzipPath = PathManager.getPluginsPath(); + } + else { + unzipPath = PathManager.getPluginsPath() + File.separator + getPluginName(); + } + + StartupActionScriptManager.ActionCommand unzip = new StartupActionScriptManager.UnzipCommand(myFile, new File(unzipPath)); + StartupActionScriptManager.addActionCommand(unzip); + } + + // add command to remove temp plugin file + StartupActionScriptManager.ActionCommand deleteTemp = new StartupActionScriptManager.DeleteCommand(myFile); + StartupActionScriptManager.addActionCommand(deleteTemp); + } + + private File downloadPlugin(ProgressIndicator pi) throws IOException { + HttpURLConnection connection = (HttpURLConnection)new URL(myPluginUrl).openConnection(); + try + { + pi.setText(IdeBundle.message("progress.connecting")); + + InputStream is = UrlConnectionUtil.getConnectionInputStream(connection, pi); + + if (is == null) { + throw new IOException("Failed to open connection"); + } + + pi.setText(IdeBundle.message("progress.downloading.plugin", getPluginName())); + + final File pluginsTemp = new File(PathManager.getPluginTempPath()); + + if (!pluginsTemp.exists()) { + pluginsTemp.mkdirs(); + } + + File file = FileUtil.createTempFile(pluginsTemp, "plugin", "download", true); + + int responseCode = connection.getResponseCode(); + switch (responseCode) { + case HttpURLConnection.HTTP_OK: + break; + default: + // some problems + throw new IOException(IdeBundle.message("error.connection.failed.with.http.code.N", responseCode)); + } + + pi.setIndeterminate(connection.getContentLength() == -1); + + OutputStream fos = null; + try { + fos = new BufferedOutputStream(new FileOutputStream(file, false)); + StreamUtil.copyStreamContent(is, fos); + } + finally { + if (fos != null) { + fos.close(); + } + is.close(); + } + if (myFileName == null) { + String contentDisposition = connection.getHeaderField("Content-Disposition"); + if (contentDisposition == null || contentDisposition.indexOf(FILENAME) < 0) { + // try to find filename in URL + String usedURL = connection.getURL().toString(); + int startPos = usedURL.lastIndexOf("/"); + + myFileName = usedURL.substring(startPos + 1); + if (myFileName.length() == 0 || myFileName.contains("?")) { + myFileName = myPluginUrl.substring(myPluginUrl.lastIndexOf("/") + 1); + } + } + else { + int startIdx = contentDisposition.indexOf(FILENAME); + myFileName = contentDisposition.substring(startIdx + FILENAME.length(), contentDisposition.length()); + // according to the HTTP spec, the filename is a quoted string, but some servers don't quote it + // for example: http://www.jspformat.com/Download.do?formAction=d&id=8 + if (myFileName.startsWith("\"") && myFileName.endsWith("\"")) { + myFileName = myFileName.substring(1, myFileName.length()-1); + } + if (myFileName.indexOf('\\') >= 0 || myFileName.indexOf('/') >= 0 || myFileName.indexOf(File.separatorChar) >= 0 || + myFileName.indexOf('\"') >= 0) { + // invalid path name passed by the server - fail to download + FileUtil.delete(file); + throw new IOException("Invalid filename returned by server"); + } + } + } + + File newFile = new File (file.getParentFile(), myFileName); + FileUtil.rename(file, newFile); + return newFile; + } + finally { + connection.disconnect(); + } + } + + public String getPluginId() { + return myPluginId; + } + + public String getFileName() { + if (myFileName == null) { + myFileName = myPluginUrl.substring(myPluginUrl.lastIndexOf("/") + 1); + } + return myFileName; + } + + public String getPluginName() { + if (myPluginName == null) { + myPluginName = FileUtil.getNameWithoutExtension(getFileName()); + } + return myPluginName; + } + + public String getPluginVersion() { + return myPluginVersion; + } + + /** + * Updates given plugin from Repository + * @param pluginId given plugin id + * @param pluginVersion available version or null if plugin must be uploaded even if current version is greater than uploading + * @throws IOException + */ + public static void updateFromRepository(final String pluginId, final @Nullable String pluginVersion) throws IOException { + @NonNls final String url = + RepositoryHelper.DOWNLOAD_URL + URLEncoder.encode(pluginId, "UTF8") + "&build=" + ApplicationInfo.getInstance().getBuild().asString(); + final PluginDownloader downloader = new PluginDownloader(pluginId, url, pluginVersion); + if (downloader.prepareToInstall()) { + downloader.install(); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java index 680875a92cff..789a97b5e969 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java @@ -1,197 +1,197 @@ -/* - * 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.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.event.DocumentAdapter; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.DumbAwareRunnable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.NotNullLazyValue; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.FileStatusListener; -import com.intellij.openapi.vcs.FileStatusManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * @author mike - */ -public class FileStatusManagerImpl extends FileStatusManager implements ProjectComponent { - private final Map myCachedStatuses = Collections.synchronizedMap(new HashMap()); - - private final Project myProject; - private final List myListeners = ContainerUtil.createEmptyCOWList(); - private FileStatusProvider myFileStatusProvider; - private final NotNullLazyValue myExtensions = new NotNullLazyValue() { - @NotNull - @Override - protected FileStatusProvider[] compute() { - return Extensions.getExtensions(FileStatusProvider.EP_NAME, myProject); - } - }; - - public FileStatusManagerImpl(Project project, StartupManager startupManager) { - myProject = project; - - startupManager.registerPostStartupActivity(new DumbAwareRunnable() { - public void run() { - fileStatusesChanged(); - } - }); - } - - public void setFileStatusProvider(final FileStatusProvider fileStatusProvider) { - myFileStatusProvider = fileStatusProvider; - } - - public FileStatus calcStatus(@NotNull VirtualFile virtualFile) { - for (FileStatusProvider extension : myExtensions.getValue()) { - FileStatus status = extension.getFileStatus(virtualFile); - if (status != null) { - return status; - } - } - if (virtualFile.isInLocalFileSystem() && myFileStatusProvider != null) { - return myFileStatusProvider.getFileStatus(virtualFile); - } else { - return FileStatus.NOT_CHANGED; - } - } - - public void projectClosed() { - } - - public void projectOpened() { - DocumentAdapter documentListener = new DocumentAdapter() { - public void documentChanged(DocumentEvent event) { - VirtualFile file = FileDocumentManager.getInstance().getFile(event.getDocument()); - if (file != null) { - refreshFileStatusFromDocument(file, event.getDocument()); - } - } - }; - EditorFactory.getInstance().getEventMulticaster().addDocumentListener(documentListener, myProject); - } - - public void disposeComponent() { - myCachedStatuses.clear(); - } - - @NotNull - public String getComponentName() { - return "FileStatusManager"; - } - - public void initComponent() { } - - public void addFileStatusListener(@NotNull FileStatusListener listener) { - myListeners.add(listener); - } - - public void addFileStatusListener(final FileStatusListener listener, Disposable parentDisposable) { - addFileStatusListener(listener); - Disposer.register(parentDisposable, new Disposable() { - public void dispose() { - removeFileStatusListener(listener); - } - }); - } - - public void fileStatusesChanged() { - if (myProject.isDisposed()) { - return; - } - if (!ApplicationManager.getApplication().isDispatchThread()) { - ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { - public void run() { - fileStatusesChanged(); - } - }, ModalityState.NON_MODAL); - return; - } - - myCachedStatuses.clear(); - - for (FileStatusListener listener : myListeners) { - listener.fileStatusesChanged(); - } - } - - public void fileStatusChanged(final VirtualFile file) { - final Application application = ApplicationManager.getApplication(); - if (!application.isDispatchThread() && !application.isUnitTestMode()) { - ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { - public void run() { - fileStatusChanged(file); - } - }); - return; - } - - if ((file == null) || (! file.isValid())) return; - FileStatus cachedStatus = getCachedStatus(file); - if (cachedStatus == null) return; - FileStatus newStatus = calcStatus(file); - if (cachedStatus == newStatus) return; - myCachedStatuses.put(file, newStatus); - - for (FileStatusListener listener : myListeners) { - listener.fileStatusChanged(file); - } - } - - public FileStatus getStatus(final VirtualFile file) { - FileStatus status = getCachedStatus(file); - if (status == null) { - status = calcStatus(file); - myCachedStatuses.put(file, status); - } - - return status; - } - - public FileStatus getCachedStatus(final VirtualFile file) { - return myCachedStatuses.get(file); - } - - public void removeFileStatusListener(FileStatusListener listener) { - myListeners.remove(listener); - } - - public void refreshFileStatusFromDocument(final VirtualFile file, final Document doc) { - if (myFileStatusProvider != null) { - myFileStatusProvider.refreshFileStatusFromDocument(file, doc); - } - } - -} +/* + * 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.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.NotNullLazyValue; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusListener; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * @author mike + */ +public class FileStatusManagerImpl extends FileStatusManager implements ProjectComponent { + private final Map myCachedStatuses = Collections.synchronizedMap(new HashMap()); + + private final Project myProject; + private final List myListeners = ContainerUtil.createEmptyCOWList(); + private FileStatusProvider myFileStatusProvider; + private final NotNullLazyValue myExtensions = new NotNullLazyValue() { + @NotNull + @Override + protected FileStatusProvider[] compute() { + return Extensions.getExtensions(FileStatusProvider.EP_NAME, myProject); + } + }; + + public FileStatusManagerImpl(Project project, StartupManager startupManager) { + myProject = project; + + startupManager.registerPostStartupActivity(new DumbAwareRunnable() { + public void run() { + fileStatusesChanged(); + } + }); + } + + public void setFileStatusProvider(final FileStatusProvider fileStatusProvider) { + myFileStatusProvider = fileStatusProvider; + } + + public FileStatus calcStatus(@NotNull VirtualFile virtualFile) { + for (FileStatusProvider extension : myExtensions.getValue()) { + FileStatus status = extension.getFileStatus(virtualFile); + if (status != null) { + return status; + } + } + if (virtualFile.isInLocalFileSystem() && myFileStatusProvider != null) { + return myFileStatusProvider.getFileStatus(virtualFile); + } else { + return FileStatus.NOT_CHANGED; + } + } + + public void projectClosed() { + } + + public void projectOpened() { + DocumentAdapter documentListener = new DocumentAdapter() { + public void documentChanged(DocumentEvent event) { + VirtualFile file = FileDocumentManager.getInstance().getFile(event.getDocument()); + if (file != null) { + refreshFileStatusFromDocument(file, event.getDocument()); + } + } + }; + EditorFactory.getInstance().getEventMulticaster().addDocumentListener(documentListener, myProject); + } + + public void disposeComponent() { + myCachedStatuses.clear(); + } + + @NotNull + public String getComponentName() { + return "FileStatusManager"; + } + + public void initComponent() { } + + public void addFileStatusListener(@NotNull FileStatusListener listener) { + myListeners.add(listener); + } + + public void addFileStatusListener(final FileStatusListener listener, Disposable parentDisposable) { + addFileStatusListener(listener); + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + removeFileStatusListener(listener); + } + }); + } + + public void fileStatusesChanged() { + if (myProject.isDisposed()) { + return; + } + if (!ApplicationManager.getApplication().isDispatchThread()) { + ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { + public void run() { + fileStatusesChanged(); + } + }, ModalityState.NON_MODAL); + return; + } + + myCachedStatuses.clear(); + + for (FileStatusListener listener : myListeners) { + listener.fileStatusesChanged(); + } + } + + public void fileStatusChanged(final VirtualFile file) { + final Application application = ApplicationManager.getApplication(); + if (!application.isDispatchThread() && !application.isUnitTestMode()) { + ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { + public void run() { + fileStatusChanged(file); + } + }); + return; + } + + if ((file == null) || (! file.isValid())) return; + FileStatus cachedStatus = getCachedStatus(file); + if (cachedStatus == null) return; + FileStatus newStatus = calcStatus(file); + if (cachedStatus == newStatus) return; + myCachedStatuses.put(file, newStatus); + + for (FileStatusListener listener : myListeners) { + listener.fileStatusChanged(file); + } + } + + public FileStatus getStatus(final VirtualFile file) { + FileStatus status = getCachedStatus(file); + if (status == null) { + status = calcStatus(file); + myCachedStatuses.put(file, status); + } + + return status; + } + + public FileStatus getCachedStatus(final VirtualFile file) { + return myCachedStatuses.get(file); + } + + public void removeFileStatusListener(FileStatusListener listener) { + myListeners.remove(listener); + } + + public void refreshFileStatusFromDocument(final VirtualFile file, final Document doc) { + if (myFileStatusProvider != null) { + myFileStatusProvider.refreshFileStatusFromDocument(file, doc); + } + } + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java index db18f6c49162..14857fc3bd50 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java @@ -1,186 +1,186 @@ -/* - * 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.vfs.impl; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.pointers.VirtualFilePointer; -import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener; -import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; -import com.intellij.util.PathUtil; -import org.jetbrains.annotations.NotNull; - -import java.io.PrintStream; -import java.io.PrintWriter; - -public class VirtualFilePointerImpl extends UserDataHolderBase implements VirtualFilePointer, Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.VirtualFilePointerImpl"); - private String myUrl; // is null when myFile is not null - private VirtualFile myFile; - private final VirtualFileManager myVirtualFileManager; - private final VirtualFilePointerListener myListener; - private boolean disposed = false; - int useCount; - private long myLastUpdated = -1; - - private static final Key CREATE_TRACE = Key.create("CREATION_TRACE"); - private static final Key KILL_TRACE = Key.create("KILL_TRACE"); - private static final boolean TRACE_CREATION = /*true || */LOG.isDebugEnabled(); - - VirtualFilePointerImpl(VirtualFile file, @NotNull String url, @NotNull VirtualFileManager virtualFileManager, VirtualFilePointerListener listener, @NotNull Disposable parentDisposable) { - myFile = file; - myUrl = url; - myVirtualFileManager = virtualFileManager; - myListener = listener; - useCount = 0; - if (TRACE_CREATION) { - putUserData(CREATE_TRACE, new Throwable("parent = '"+parentDisposable+"' ("+parentDisposable.getClass()+")")); - } - } - - @NotNull - public String getFileName() { - update(); - - if (myFile != null) { - return myFile.getName(); - } - int index = myUrl.lastIndexOf('/'); - return index >= 0 ? myUrl.substring(index + 1) : myUrl; - } - - public VirtualFile getFile() { - checkDisposed(); - - update(); - return myFile; - } - - @NotNull - public String getUrl() { - //checkDisposed(); no check here since Disposer might want to compute hashcode during dispose() - update(); - return getUrlNoUpdate(); - } - - private String getUrlNoUpdate() { - return myUrl == null ? myFile.getUrl() : myUrl; - } - - @NotNull - public String getPresentableUrl() { - checkDisposed(); - update(); - - return PathUtil.toPresentableUrl(getUrl()); - } - - private void checkDisposed() { - if (disposed) throw new MyException("Already disposed: "+toString(), getUserData(CREATE_TRACE), getUserData(KILL_TRACE)); - } - - public void throwNotDisposedError(String msg) throws RuntimeException { - Throwable trace = getUserData(CREATE_TRACE); - throw new RuntimeException(msg+"\n" + - "url=" + this + - "\nCreation trace " + (trace==null?"null":"some")+ - "\n", trace); - } - - public int incrementUsageCount() { - return ++useCount; - } - - private static class MyException extends RuntimeException { - private final Throwable e1; - private final Throwable e2; - - private MyException(String message, Throwable e1, Throwable e2) { - super(message); - this.e1 = e1; - this.e2 = e2; - } - - @Override - public void printStackTrace(PrintStream s) { - printStackTrace(new PrintWriter(s)); - } - - @Override - public void printStackTrace(PrintWriter s) { - super.printStackTrace(s); - if (e1 != null) { - s.println("--------------Creation trace: "); - e1.printStackTrace(s); - } - if (e2 != null) { - s.println("--------------Kill trace: "); - e2.printStackTrace(s); - } - } - } - - public boolean isValid() { - update(); - return !disposed && myFile != null; - } - - void update() { - if (disposed) return; - long fsModCount = myVirtualFileManager.getModificationCount(); - if (myLastUpdated == fsModCount) return; - myLastUpdated = fsModCount; - - if (myFile != null && !myFile.isValid()) { - myUrl = myFile.getUrl(); - myFile = null; - } - if (myFile == null) { - LOG.assertTrue(myUrl != null, "Both file & url are null"); - myFile = myVirtualFileManager.findFileByUrl(myUrl); - if (myFile != null) { - myUrl = null; - } - } - else if (!myFile.exists()) { - myUrl = myFile.getUrl(); - myFile = null; - } - } - - @Override - public String toString() { - return getUrlNoUpdate(); - } - - public void dispose() { - if (disposed) { - throw new MyException("Punching the dead horse.\nurl="+toString(), getUserData(CREATE_TRACE), getUserData(KILL_TRACE)); - } - if (--useCount == 0) { - if (TRACE_CREATION) { - putUserData(KILL_TRACE, new Throwable()); - } - String url = getUrlNoUpdate(); - disposed = true; - ((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).clearPointerCaches(url, myListener); - } - } -} +/* + * 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.vfs.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; +import com.intellij.util.PathUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.PrintStream; +import java.io.PrintWriter; + +public class VirtualFilePointerImpl extends UserDataHolderBase implements VirtualFilePointer, Disposable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.VirtualFilePointerImpl"); + private String myUrl; // is null when myFile is not null + private VirtualFile myFile; + private final VirtualFileManager myVirtualFileManager; + private final VirtualFilePointerListener myListener; + private boolean disposed = false; + int useCount; + private long myLastUpdated = -1; + + private static final Key CREATE_TRACE = Key.create("CREATION_TRACE"); + private static final Key KILL_TRACE = Key.create("KILL_TRACE"); + private static final boolean TRACE_CREATION = /*true || */LOG.isDebugEnabled(); + + VirtualFilePointerImpl(VirtualFile file, @NotNull String url, @NotNull VirtualFileManager virtualFileManager, VirtualFilePointerListener listener, @NotNull Disposable parentDisposable) { + myFile = file; + myUrl = url; + myVirtualFileManager = virtualFileManager; + myListener = listener; + useCount = 0; + if (TRACE_CREATION) { + putUserData(CREATE_TRACE, new Throwable("parent = '"+parentDisposable+"' ("+parentDisposable.getClass()+")")); + } + } + + @NotNull + public String getFileName() { + update(); + + if (myFile != null) { + return myFile.getName(); + } + int index = myUrl.lastIndexOf('/'); + return index >= 0 ? myUrl.substring(index + 1) : myUrl; + } + + public VirtualFile getFile() { + checkDisposed(); + + update(); + return myFile; + } + + @NotNull + public String getUrl() { + //checkDisposed(); no check here since Disposer might want to compute hashcode during dispose() + update(); + return getUrlNoUpdate(); + } + + private String getUrlNoUpdate() { + return myUrl == null ? myFile.getUrl() : myUrl; + } + + @NotNull + public String getPresentableUrl() { + checkDisposed(); + update(); + + return PathUtil.toPresentableUrl(getUrl()); + } + + private void checkDisposed() { + if (disposed) throw new MyException("Already disposed: "+toString(), getUserData(CREATE_TRACE), getUserData(KILL_TRACE)); + } + + public void throwNotDisposedError(String msg) throws RuntimeException { + Throwable trace = getUserData(CREATE_TRACE); + throw new RuntimeException(msg+"\n" + + "url=" + this + + "\nCreation trace " + (trace==null?"null":"some")+ + "\n", trace); + } + + public int incrementUsageCount() { + return ++useCount; + } + + private static class MyException extends RuntimeException { + private final Throwable e1; + private final Throwable e2; + + private MyException(String message, Throwable e1, Throwable e2) { + super(message); + this.e1 = e1; + this.e2 = e2; + } + + @Override + public void printStackTrace(PrintStream s) { + printStackTrace(new PrintWriter(s)); + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + if (e1 != null) { + s.println("--------------Creation trace: "); + e1.printStackTrace(s); + } + if (e2 != null) { + s.println("--------------Kill trace: "); + e2.printStackTrace(s); + } + } + } + + public boolean isValid() { + update(); + return !disposed && myFile != null; + } + + void update() { + if (disposed) return; + long fsModCount = myVirtualFileManager.getModificationCount(); + if (myLastUpdated == fsModCount) return; + myLastUpdated = fsModCount; + + if (myFile != null && !myFile.isValid()) { + myUrl = myFile.getUrl(); + myFile = null; + } + if (myFile == null) { + LOG.assertTrue(myUrl != null, "Both file & url are null"); + myFile = myVirtualFileManager.findFileByUrl(myUrl); + if (myFile != null) { + myUrl = null; + } + } + else if (!myFile.exists()) { + myUrl = myFile.getUrl(); + myFile = null; + } + } + + @Override + public String toString() { + return getUrlNoUpdate(); + } + + public void dispose() { + if (disposed) { + throw new MyException("Punching the dead horse.\nurl="+toString(), getUserData(CREATE_TRACE), getUserData(KILL_TRACE)); + } + if (--useCount == 0) { + if (TRACE_CREATION) { + putUserData(KILL_TRACE, new Throwable()); + } + String url = getUrlNoUpdate(); + disposed = true; + ((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).clearPointerCaches(url, myListener); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java index 27686aa23e54..a810941971bb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java @@ -1,109 +1,109 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.openapi.vfs.newvfs.impl; - -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collection; -import java.util.Collections; - -public class VirtualFileImpl extends VirtualFileSystemEntry { - public VirtualFileImpl(final String name, final VirtualDirectoryImpl parent, final int id) { - super(name, parent, id); - } - - @Nullable - public NewVirtualFile findChild(@NotNull @NonNls final String name) { - return null; - } - - public Collection getCachedChildren() { - return Collections.emptyList(); - } - - @Override - public Iterable iterInDbChildren() { - return ContainerUtil.emptyIterable(); - } - - @NotNull - public NewVirtualFileSystem getFileSystem() { - final VirtualFileSystemEntry parent = getParent(); - assert parent != null; - - return parent.getFileSystem(); - } - - @Nullable - public NewVirtualFile refreshAndFindChild(final String name) { - return null; - } - - @Nullable - public NewVirtualFile findChildIfCached(final String name) { - return null; - } - - public VirtualFile[] getChildren() { - return EMPTY_ARRAY; - } - - public boolean isDirectory() { - return false; - } - - @NotNull - public InputStream getInputStream() throws IOException { - return ourPersistence.getInputStream(this); - } - - @NotNull - public byte[] contentsToByteArray() throws IOException { - return contentsToByteArray(true); - } - - @NotNull - @Override - public byte[] contentsToByteArray(boolean cacheContent) throws IOException { - return ourPersistence.contentsToByteArray(this, cacheContent); - } - - @NotNull - public OutputStream getOutputStream(final Object requestor, final long modStamp, final long timeStamp) throws IOException { - return ourPersistence.getOutputStream(this, requestor, modStamp, timeStamp); - } - - public NewVirtualFile findChildById(int id) { - return null; - } - - public NewVirtualFile findChildByIdIfCached(int id) { - return null; - } +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.openapi.vfs.newvfs.impl; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Collections; + +public class VirtualFileImpl extends VirtualFileSystemEntry { + public VirtualFileImpl(final String name, final VirtualDirectoryImpl parent, final int id) { + super(name, parent, id); + } + + @Nullable + public NewVirtualFile findChild(@NotNull @NonNls final String name) { + return null; + } + + public Collection getCachedChildren() { + return Collections.emptyList(); + } + + @Override + public Iterable iterInDbChildren() { + return ContainerUtil.emptyIterable(); + } + + @NotNull + public NewVirtualFileSystem getFileSystem() { + final VirtualFileSystemEntry parent = getParent(); + assert parent != null; + + return parent.getFileSystem(); + } + + @Nullable + public NewVirtualFile refreshAndFindChild(final String name) { + return null; + } + + @Nullable + public NewVirtualFile findChildIfCached(final String name) { + return null; + } + + public VirtualFile[] getChildren() { + return EMPTY_ARRAY; + } + + public boolean isDirectory() { + return false; + } + + @NotNull + public InputStream getInputStream() throws IOException { + return ourPersistence.getInputStream(this); + } + + @NotNull + public byte[] contentsToByteArray() throws IOException { + return contentsToByteArray(true); + } + + @NotNull + @Override + public byte[] contentsToByteArray(boolean cacheContent) throws IOException { + return ourPersistence.contentsToByteArray(this, cacheContent); + } + + @NotNull + public OutputStream getOutputStream(final Object requestor, final long modStamp, final long timeStamp) throws IOException { + return ourPersistence.getOutputStream(this, requestor, modStamp, timeStamp); + } + + public NewVirtualFile findChildById(int id) { + return null; + } + + public NewVirtualFile findChildByIdIfCached(int id) { + return null; + } } \ No newline at end of file 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 57204c6fb092..88de9c6d37b7 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 @@ -1,1281 +1,1281 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.openapi.vfs.newvfs.persistent; - -import com.intellij.concurrency.JobScheduler; -import com.intellij.openapi.Forceable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.IntArrayList; -import com.intellij.util.io.PagedFileStorage; -import com.intellij.util.io.PersistentStringEnumerator; -import com.intellij.util.io.ResizeableMappedFile; -import com.intellij.util.io.storage.AbstractStorage; -import com.intellij.util.io.storage.HeavyProcessLatch; -import com.intellij.util.io.storage.RefCountingStorage; -import com.intellij.util.io.storage.Storage; -import gnu.trove.TIntArrayList; -import gnu.trove.TObjectIntHashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.io.*; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings({"PointlessArithmeticExpression", "HardCodedStringLiteral"}) -public class FSRecords implements Forceable { - private static final Logger LOG = Logger.getInstance("#com.intellij.vfs.persistent.FSRecords"); - - private static final int VERSION = 12; - - private static final int PARENT_OFFSET = 0; - private static final int PARENT_SIZE = 4; - private static final int NAME_OFFSET = PARENT_OFFSET + PARENT_SIZE; - private static final int NAME_SIZE = 4; - private static final int FLAGS_OFFSET = NAME_OFFSET + NAME_SIZE; - private static final int FLAGS_SIZE = 4; - private static final int ATTREF_OFFSET = FLAGS_OFFSET + FLAGS_SIZE; - private static final int ATTREF_SIZE = 4; - private static final int CONTENT_OFFSET = ATTREF_OFFSET + ATTREF_SIZE; - private static final int CONTENT_SIZE = 4; - private static final int TIMESTAMP_OFFSET = CONTENT_OFFSET + CONTENT_SIZE; - private static final int TIMESTAMP_SIZE = 8; - private static final int MODCOUNT_OFFSET = TIMESTAMP_OFFSET + TIMESTAMP_SIZE; - private static final int MODCOUNT_SIZE = 4; - private static final int LENGTH_OFFSET = MODCOUNT_OFFSET + MODCOUNT_SIZE; - private static final int LENGTH_SIZE = 8; - - private static final int RECORD_SIZE = LENGTH_OFFSET + LENGTH_SIZE; - - private static final byte[] ZEROES = new byte[RECORD_SIZE]; - - private static final int HEADER_VERSION_OFFSET = 0; - private static final int HEADER_RESERVED_4BYTES_OFFSET = 4; // Reserved - private static final int HEADER_GLOBAL_MODCOUNT_OFFSET = 8; - private static final int HEADER_CONNECTION_STATUS_OFFSET = 12; - private static final int HEADER_TIMESTAMP_OFFSET = 16; - private static final int HEADER_SIZE = HEADER_TIMESTAMP_OFFSET + 8; - - private static final int CONNECTED_MAGIC = 0x12ad34e4; - private static final int SAFELY_CLOSED_MAGIC = 0x1f2f3f4f; - private static final int CORRUPTED_MAGIC = 0xabcf7f7f; - - private static final String CHILDREN_ATT = "FsRecords.DIRECTORY_CHILDREN"; - private static final Object lock = new Object(); - - private volatile static int ourLocalModificationCount = 0; - - private static final int FREE_RECORD_FLAG = 0x100; - private static final int ALL_VALID_FLAGS = PersistentFS.ALL_VALID_FLAGS | FREE_RECORD_FLAG; - - static { - //noinspection ConstantConditions - assert HEADER_SIZE <= RECORD_SIZE; - } - - private static class DbConnection { - private static int refCount = 0; - private static final Object LOCK = new Object(); - private static final TObjectIntHashMap myAttributeIds = new TObjectIntHashMap(); - - private static PersistentStringEnumerator myNames; - private static Storage myAttributes; - private static RefCountingStorage myContents; - private static ResizeableMappedFile myRecords; - private static final TIntArrayList myFreeRecords = new TIntArrayList(); - - private static boolean myDirty = false; - private static ScheduledFuture myFlushingFuture; - private static boolean myCorrupted = false; - - public static DbConnection connect() { - synchronized (LOCK) { - if (refCount == 0) { - init(); - scanFreeRecords(); - setupFlushing(); - } - refCount++; - } - - return new DbConnection(); - } - - private static void scanFreeRecords() { - final int filelength = (int)getRecords().length(); - LOG.assertTrue(filelength % RECORD_SIZE == 0); - - int count = filelength / RECORD_SIZE; - for (int n = 2; n < count; n++) { - if ((getFlags(n) & FREE_RECORD_FLAG) != 0) { - addFreeRecord(n); - } - } - } - - public static int getFreeRecord() { - if (myFreeRecords.isEmpty()) return 0; - return myFreeRecords.remove(myFreeRecords.size() - 1); - } - - private static void createBrokenMarkerFile() { - File brokenMarker = getCorruptionMarkerFile(); - try { - final FileWriter writer = new FileWriter(brokenMarker); - writer.write("These files are corrupted and must be rebuilt from the scratch on next startup"); - writer.close(); - } - catch (IOException e) { - // No luck. - } - } - - private static File getCorruptionMarkerFile() { - return new File(basePath(), "corruption.marker"); - } - - private static void init() { - final File basePath = basePath(); - basePath.mkdirs(); - - final File namesFile = new File(basePath, "names.dat"); - final File attributesFile = new File(basePath, "attrib.dat"); - final File contentsFile = new File(basePath, "content.dat"); - final File recordsFile = new File(basePath, "records.dat"); - - if (!namesFile.exists()) { - invalidateIndex(); - } - - try { - if (getCorruptionMarkerFile().exists()) { - invalidateIndex(); - throw new IOException("Corruption marker file found"); - } - - myNames = new PersistentStringEnumerator(namesFile); - myAttributes = new Storage(attributesFile.getCanonicalPath()); - myContents = new RefCountingStorage(contentsFile.getCanonicalPath()); - myRecords = new ResizeableMappedFile(recordsFile, 20 * 1024, new PagedFileStorage.StorageLock(false)); - - if (myRecords.length() == 0) { - cleanRecord(0); // Clean header - cleanRecord(1); // Create root record - setCurrentVersion(); - } - - if (getVersion() != VERSION) { - throw new IOException("FS repository version mismatch"); - } - - if (myRecords.getInt(HEADER_CONNECTION_STATUS_OFFSET) != SAFELY_CLOSED_MAGIC) { - throw new IOException("FS repository wasn't safely shut down"); - } - markDirty(); - } - catch (IOException e) { - LOG.info("Filesystem storage is corrupted or does not exist. [Re]Building. Reason: " + e.getMessage()); - try { - closeFiles(); - - boolean deleted = true; - deleted &= FileUtil.delete(getCorruptionMarkerFile()); - deleted &= deleteWithSubordinates(namesFile); - deleted &= Storage.deleteFiles(attributesFile.getCanonicalPath()); - deleted &= Storage.deleteFiles(contentsFile.getCanonicalPath()); - deleted &= deleteWithSubordinates(recordsFile); - - if (!deleted) { - throw new IOException("Cannot delete filesystem storage files"); - } - } - catch (final IOException e1) { - final Runnable warnAndShutdown = new Runnable() { - public void run() { - boolean unitTest = ApplicationManager.getApplication().isUnitTestMode(); - if (!(unitTest || ApplicationManager.getApplication().isHeadlessEnvironment())) { - JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), - "Files in " + basePath.getPath() + " are locked. IntelliJ IDEA will not be able to start up", - "Fatal Error", - JOptionPane.ERROR_MESSAGE); - } - if (unitTest) { - e1.printStackTrace(); - } - Runtime.getRuntime().halt(1); - } - }; - - if (EventQueue.isDispatchThread()) { - warnAndShutdown.run(); - } - else { - //noinspection SSBasedInspection - SwingUtilities.invokeLater(warnAndShutdown); - } - - throw new RuntimeException("Can't rebuild filesystem storage ", e1); - } - - init(); - } - } - - private static void invalidateIndex() { - LOG.info("Marking VFS as corrupted"); - FileUtil.createIfDoesntExist(new File(PathManager.getIndexRoot(), "corruption.marker")); - } - - private static File basePath() { - return new File(PathManager.getSystemPath() + "/caches/"); - } - - private static boolean deleteWithSubordinates(File file) { - final String baseName = file.getName(); - final File[] files = file.getParentFile().listFiles(new FileFilter() { - public boolean accept(final File pathname) { - return pathname.getName().startsWith(baseName); - } - }); - - boolean ok = true; - if (files != null) { - for (File f : files) { - ok &= FileUtil.delete(f); - } - } - - return ok; - } - - private static void markDirty() throws IOException { - if (!myDirty) { - myDirty = true; - myRecords.putInt(HEADER_CONNECTION_STATUS_OFFSET, CONNECTED_MAGIC); - } - } - - private static void setupFlushing() { - myFlushingFuture = JobScheduler.getScheduler().scheduleAtFixedRate(new Runnable() { - int lastModCount = 0; - - public void run() { - if (lastModCount == ourLocalModificationCount && !HeavyProcessLatch.INSTANCE.isRunning()) { - flushSome(); - } - lastModCount = ourLocalModificationCount; - } - }, 5000, 5000, TimeUnit.MILLISECONDS); - } - - public static void force() { - synchronized (lock) { - try { - if (myRecords != null) { - markClean(); - } - } - catch (IOException e) { - LOG.info(e); - } - if (myNames != null) { - myNames.force(); - myAttributes.force(); - myContents.force(); - myRecords.force(); - } - } - } - - public static void flushSome() { - synchronized (lock) { - if (myFlushingFuture == null) { - return; // avoid NPE when close has already taken place - } - myNames.force(); - - final boolean attribsFlushed = myAttributes.flushSome(); - final boolean contentsFlushed = myContents.flushSome(); - if (attribsFlushed && contentsFlushed) { - try { - markClean(); - } - catch (IOException e) { - // Ignore - } - myRecords.force(); - } - } - } - - public static boolean isDirty() { - return myDirty || myNames.isDirty() || myAttributes.isDirty() || myContents.isDirty() || myRecords.isDirty(); - } - - - private static int getVersion() throws IOException { - final int recordsVersion = myRecords.getInt(HEADER_VERSION_OFFSET); - if (myAttributes.getVersion() != recordsVersion || myContents.getVersion() != recordsVersion) return -1; - - return recordsVersion; - } - - public static long getTimestamp() { - return myRecords.getLong(HEADER_TIMESTAMP_OFFSET); - } - - private static void setCurrentVersion() throws IOException { - myRecords.putInt(HEADER_VERSION_OFFSET, VERSION); - myRecords.putLong(HEADER_TIMESTAMP_OFFSET, System.currentTimeMillis()); - myAttributes.setVersion(VERSION); - myContents.setVersion(VERSION); - myRecords.putInt(HEADER_CONNECTION_STATUS_OFFSET, SAFELY_CLOSED_MAGIC); - } - - public static void cleanRecord(final int id) throws IOException { - myRecords.put(id * RECORD_SIZE, ZEROES, 0, RECORD_SIZE); - } - - public static PersistentStringEnumerator getNames() { - return myNames; - } - - public static ResizeableMappedFile getRecords() { - return myRecords; - } - - public void dispose() throws IOException { - synchronized (LOCK) { - refCount--; - if (refCount == 0) { - closeFiles(); - } - } - } - - private static void closeFiles() throws IOException { - if (myFlushingFuture != null) { - myFlushingFuture.cancel(false); - myFlushingFuture = null; - } - - if (myNames != null) { - myNames.close(); - myNames = null; - } - - if (myAttributes != null) { - myAttributes.dispose(); - myAttributes = null; - } - - if (myContents != null) { - myContents.dispose(); - myContents = null; - } - - if (myRecords != null) { - markClean(); - myRecords.close(); - myRecords = null; - } - } - - private static void markClean() throws IOException { - if (myDirty) { - myDirty = false; - myRecords.putInt(HEADER_CONNECTION_STATUS_OFFSET, myCorrupted ? CORRUPTED_MAGIC : SAFELY_CLOSED_MAGIC); - } - } - - private static int getAttributeId(String attId) throws IOException { - if (myAttributeIds.containsKey(attId)) { - return myAttributeIds.get(attId); - } - - int id = myNames.enumerate(attId); - myAttributeIds.put(attId, id); - - return id; - } - - private static RuntimeException handleError(final Throwable e) { - if (!myCorrupted) { - createBrokenMarkerFile(); - myCorrupted = true; - force(); - } - - return new RuntimeException(e); - } - - public static void addFreeRecord(final int id) { - myFreeRecords.add(id); - } - } - - public FSRecords() { - } - - public void connect() { - DbConnection.connect(); - } - - public long getCreationTimestamp() { - synchronized (lock) { - return DbConnection.getTimestamp(); - } - } - - private static ResizeableMappedFile getRecords() { - return DbConnection.getRecords(); - } - - private static RefCountingStorage getContentStorage() { - return DbConnection.myContents; - } - - private static Storage getAttributesStorage() { - return DbConnection.myAttributes; - } - - public static PersistentStringEnumerator getNames() { - return DbConnection.getNames(); - } - - public static int createRecord() { - synchronized (lock) { - try { - DbConnection.markDirty(); - - final int free = DbConnection.getFreeRecord(); - if (free == 0) { - final int filelength = (int)getRecords().length(); - LOG.assertTrue(filelength % RECORD_SIZE == 0); - int newrecord = filelength / RECORD_SIZE; - DbConnection.cleanRecord(newrecord); - assert filelength + RECORD_SIZE == getRecords().length(); - return newrecord; - } - else { - DbConnection.cleanRecord(free); - return free; - } - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public void deleteRecordRecursively(int id) { - synchronized (lock) { - try { - DbConnection.markDirty(); - incModCount(id); - doDeleteRecursively(id); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - private void doDeleteRecursively(final int id) { - for (int subrecord : list(id)) { - doDeleteRecursively(subrecord); - } - - deleteRecord(id); - } - - private void deleteRecord(final int id) { - synchronized (lock) { - try { - DbConnection.markDirty(); - deleteContentAndAttributes(id); - - DbConnection.cleanRecord(id); - addToFreeRecordsList(id); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - private void deleteContentAndAttributes(int id) throws IOException { - int content_page = getContentRecordId(id); - if (content_page != 0) { - getContentStorage().releaseRecord(content_page); - } - - int att_page = getAttributeRecordId(id); - if (att_page != 0) { - final DataInputStream attStream = getAttributesStorage().readStream(att_page); - while (attStream.available() > 0) { - attStream.readInt(); // Attribute ID; - int attAddress = attStream.readInt(); - getAttributesStorage().deleteRecord(attAddress); - } - attStream.close(); - getAttributesStorage().deleteRecord(att_page); - } - } - - private void addToFreeRecordsList(int id) throws IOException { - DbConnection.addFreeRecord(id); - setFlags(id, FREE_RECORD_FLAG, false); - } - - public int[] listRoots() throws IOException { - synchronized (lock) { - DbConnection.markDirty(); - final DataInputStream input = readAttribute(1, CHILDREN_ATT); - if (input == null) return ArrayUtil.EMPTY_INT_ARRAY; - - int[] result; - try { - final int count = input.readInt(); - result = ArrayUtil.newIntArray(count); - for (int i = 0; i < count; i++) { - input.readInt(); // Name - result[i] = input.readInt(); // Id - } - } - finally { - input.close(); - } - - return result; - } - } - - public void force() { - DbConnection.force(); - } - - public boolean isDirty() { - return DbConnection.isDirty(); - } - - public int findRootRecord(String rootUrl) throws IOException { - synchronized (lock) { - DbConnection.markDirty(); - final int root = getNames().enumerate(rootUrl); - - final DataInputStream input = readAttribute(1, CHILDREN_ATT); - int[] names = ArrayUtil.EMPTY_INT_ARRAY; - int[] ids = ArrayUtil.EMPTY_INT_ARRAY; - - if (input != null) { - try { - final int count = input.readInt(); - names = ArrayUtil.newIntArray(count); - ids = ArrayUtil.newIntArray(count); - for (int i = 0; i < count; i++) { - final int name = input.readInt(); - final int id = input.readInt(); - if (name == root) { - return id; - } - - names[i] = name; - ids[i] = id; - } - } - finally { - input.close(); - } - } - - final DataOutputStream output = writeAttribute(1, CHILDREN_ATT, false); - int id; - try { - id = createRecord(); - output.writeInt(names.length + 1); - for (int i = 0; i < names.length; i++) { - output.writeInt(names[i]); - output.writeInt(ids[i]); - } - output.writeInt(root); - output.writeInt(id); - } - finally { - output.close(); - } - - return id; - } - } - - public void deleteRootRecord(int id) throws IOException { - synchronized (lock) { - DbConnection.markDirty(); - final DataInputStream input = readAttribute(1, CHILDREN_ATT); - assert input != null; - int count; - int[] names; - int[] ids; - try { - count = input.readInt(); - - names = ArrayUtil.newIntArray(count); - ids = ArrayUtil.newIntArray(count); - for (int i = 0; i < count; i++) { - names[i] = input.readInt(); - ids[i] = input.readInt(); - } - } - finally { - input.close(); - } - - final int index = ArrayUtil.find(ids, id); - assert index >= 0; - - names = ArrayUtil.remove(names, index); - ids = ArrayUtil.remove(ids, index); - - final DataOutputStream output = writeAttribute(1, CHILDREN_ATT, false); - try { - output.writeInt(count - 1); - for (int i = 0; i < names.length; i++) { - output.writeInt(names[i]); - output.writeInt(ids[i]); - } - } - finally { - output.close(); - } - } - } - - public int[] list(int id) { - synchronized (lock) { - try { - final DataInputStream input = readAttribute(id, CHILDREN_ATT); - if (input == null) return ArrayUtil.EMPTY_INT_ARRAY; - - final int count = input.readInt(); - final int[] result = ArrayUtil.newIntArray(count); - for (int i = 0; i < count; i++) { - result[i] = input.readInt(); - } - input.close(); - return result; - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public boolean wereChildrenAccessed(int id) { - try { - synchronized (lock) { - return findAttributePage(id, CHILDREN_ATT, false) != 0; - } - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - public void updateList(int id, int[] children) { - synchronized (lock) { - try { - DbConnection.markDirty(); - final DataOutputStream record = writeAttribute(id, CHILDREN_ATT, false); - record.writeInt(children.length); - for (int child : children) { - if (child == id) { - LOG.error("Cyclic parent child relations"); - } - else { - record.writeInt(child); - } - } - record.close(); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - private static void incModCount(int id) throws IOException { - ourLocalModificationCount++; - final int count = getModCount() + 1; - getRecords().putInt(HEADER_GLOBAL_MODCOUNT_OFFSET, count); - - int parent = id; - while (parent != 0) { - setModCount(parent, count); - parent = getParent(parent); - } - } - - public static int getLocalModCount() { - return ourLocalModificationCount; // This is volatile, only modified under Application.runWriteAction() lock. - } - - public static int getModCount() { - synchronized (lock) { - return getRecords().getInt(HEADER_GLOBAL_MODCOUNT_OFFSET); - } - } - - public static int getParent(int id) { - synchronized (lock) { - try { - final int parentId = getRecordInt(id, PARENT_OFFSET); - if (parentId == id) { - LOG.error("Cyclic parent child relations in the database. id = " + id); - return 0; - } - - return parentId; - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public void setParent(int id, int parent) { - if (id == parent) { - LOG.error("Cyclic parent/child relations"); - return; - } - - synchronized (lock) { - try { - DbConnection.markDirty(); - incModCount(id); - putRecordInt(id, PARENT_OFFSET, parent); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public static String getName(int id) { - synchronized (lock) { - try { - final int nameId = getRecordInt(id, NAME_OFFSET); - return nameId != 0 ? getNames().valueOf(nameId) : ""; - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public void setName(int id, String name) { - synchronized (lock) { - try { - DbConnection.markDirty(); - incModCount(id); - putRecordInt(id, NAME_OFFSET, getNames().enumerate(name)); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public static int getFlags(int id) { - synchronized (lock) { - return getRecordInt(id, FLAGS_OFFSET); - } - } - - public void setFlags(int id, int flags, final boolean markAsChange) { - synchronized (lock) { - try { - if (markAsChange) { - DbConnection.markDirty(); - incModCount(id); - } - putRecordInt(id, FLAGS_OFFSET, flags); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public static long getLength(int id) { - synchronized (lock) { - return getRecords().getLong(getOffset(id, LENGTH_OFFSET)); - } - } - - public void setLength(int id, long len) { - synchronized (lock) { - try { - DbConnection.markDirty(); - incModCount(id); - getRecords().putLong(getOffset(id, LENGTH_OFFSET), len); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public static long getTimestamp(int id) { - synchronized (lock) { - return getRecords().getLong(getOffset(id, TIMESTAMP_OFFSET)); - } - } - - public void setTimestamp(int id, long value) { - synchronized (lock) { - try { - DbConnection.markDirty(); - incModCount(id); - getRecords().putLong(getOffset(id, TIMESTAMP_OFFSET), value); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public static int getModCount(int id) { - synchronized (lock) { - return getRecordInt(id, MODCOUNT_OFFSET); - } - } - - private static void setModCount(int id, int value) throws IOException { - putRecordInt(id, MODCOUNT_OFFSET, value); - } - - private static int getContentRecordId(int fileId) throws IOException { - return getRecordInt(fileId, CONTENT_OFFSET); - } - - private static void setContentRecordId(int id, int value) throws IOException { - putRecordInt(id, CONTENT_OFFSET, value); - } - - private static int getAttributeRecordId(int id) throws IOException { - return getRecordInt(id, ATTREF_OFFSET); - } - - private static void setAttributeRecordId(int id, int value) throws IOException { - putRecordInt(id, ATTREF_OFFSET, value); - } - - private static int getRecordInt(int id, int offset) { - return getRecords().getInt(getOffset(id, offset)); - } - - private static void putRecordInt(int id, int offset, int value) { - getRecords().putInt(getOffset(id, offset), value); - } - - private static int getOffset(int id, int offset) { - return id * RECORD_SIZE + offset; - } - - @Nullable - public DataInputStream readContent(int fileId) { - try { - int page; - synchronized (lock) { - page = findContentPage(fileId, false); - if (page == 0) return null; - } - return getContentStorage().readStream(page); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - @Nullable - public DataInputStream readContentById(int contentId) { - try { - return getContentStorage().readStream(contentId); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - @Nullable - public DataInputStream readAttribute(int fileId, String attId) { - try { - synchronized (attId) { - int page; - synchronized (lock) { - page = findAttributePage(fileId, attId, false); - if (page == 0) return null; - } - return getAttributesStorage().readStream(page); - } - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - private int findContentPage(int fileId, boolean toWrite) throws IOException { - checkFileIsValid(fileId); - - int recordId = getContentRecordId(fileId); - if (toWrite) { - if (recordId == 0 || getContentStorage().getRefCount(recordId) > 1) { - recordId = getContentStorage().acquireNewRecord(); - setContentRecordId(fileId, recordId); - } - } - - return recordId; - } - - private int findAttributePage(int fileId, String attrId, boolean toWrite) throws IOException { - checkFileIsValid(fileId); - - Storage storage = getAttributesStorage(); - - int encodedAttrId = DbConnection.getAttributeId(attrId); - int recordId = getAttributeRecordId(fileId); - - if (recordId == 0) { - if (!toWrite) return 0; - - recordId = storage.createNewRecord(); - setAttributeRecordId(fileId, recordId); - } - else { - DataInputStream attrRefs = storage.readStream(recordId); - try { - while (attrRefs.available() > 0) { - final int attIdOnPage = attrRefs.readInt(); - final int attrAddress = attrRefs.readInt(); - - if (attIdOnPage == encodedAttrId) return attrAddress; - } - } - finally { - attrRefs.close(); - } - } - - if (toWrite) { - Storage.AppenderStream appender = storage.appendStream(recordId); - appender.writeInt(encodedAttrId); - int attrAddress = storage.createNewRecord(); - appender.writeInt(attrAddress); - appender.close(); - return attrAddress; - } - - return 0; - } - - private void checkFileIsValid(int fileId) { - assert fileId > 0 : "assert fileId > 0 failed"; - // TODO: This assertion is a bit timey, will remove when bug is caught. - assert (getFlags(fileId) & FREE_RECORD_FLAG) == 0 : "Trying to find an attribute of deleted page"; - } - - public int acquireFileContent(int fileId) { - try { - synchronized (lock) { - int record = getContentRecordId(fileId); - if (record > 0) getContentStorage().acquireRecord(record); - return record; - } - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - public void releaseContent(int contentId) { - try { - getContentStorage().releaseRecord(contentId); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - public int getContentId(int fileId) { - try { - synchronized (lock) { - return getContentRecordId(fileId); - } - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - @NotNull - public DataOutputStream writeContent(int fileId, boolean readOnly) { - return new ContentOutputStream(fileId, readOnly); - } - - public void writeContent(int fileId, byte[] bytes, boolean readOnly) throws IOException { - new ContentOutputStream(fileId, readOnly).writeBytes(bytes, fileId); - } - - public int storeUnlinkedContent(byte[] bytes) { - try { - int recordId = getContentStorage().acquireNewRecord(); - AbstractStorage.StorageDataOutput output = getContentStorage().writeStream(recordId); - output.write(bytes); - output.close(); - return recordId; - } - catch (IOException e) { - throw DbConnection.handleError(e); - } - } - - @NotNull - public DataOutputStream writeAttribute(final int fileId, final String attId, boolean fixedSize) { - return new AttributeOutputStream(fileId, attId, fixedSize); - } - - private class ContentOutputStream extends BaseOutputStream { - - private ContentOutputStream(final int fileId, boolean readOnly) { - super(fileId, readOnly); - } - - @Override - protected int findOrCreatePage() throws IOException { - return findContentPage(myFileId, true); - } - - @Override - protected AbstractStorage getStorage() { - return getContentStorage(); - } - } - - private class AttributeOutputStream extends BaseOutputStream { - private final String myAttributeId; - - private AttributeOutputStream(final int fileId, final String attributeId, boolean fixedSize) { - super(fileId, fixedSize); - myAttributeId = attributeId; - } - - @Override - protected void doFlush() throws IOException { - synchronized (myAttributeId) { - super.doFlush(); - } - } - - @Override - protected int findOrCreatePage() throws IOException { - return findAttributePage(myFileId, myAttributeId, true); - } - - @Override - protected AbstractStorage getStorage() { - return getAttributesStorage(); - } - } - - private abstract static class BaseOutputStream extends DataOutputStream { - protected final int myFileId; - protected final boolean myFixedSize; - - private BaseOutputStream(final int fileId, boolean fixedSize) { - super(new ByteArrayOutputStream()); - myFileId = fileId; - myFixedSize = fixedSize; - } - - public void close() throws IOException { - super.close(); - - try { - doFlush(); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - - protected void doFlush() throws IOException { - writeBytes(((ByteArrayOutputStream)out).toByteArray(), myFileId); - } - - public void writeBytes(byte[] bytes, int fileId) throws IOException { - final int page; - synchronized (lock) { - DbConnection.markDirty(); - incModCount(fileId); - page = findOrCreatePage(); - } - - getStorage().writeBytes(page, bytes, myFixedSize); - } - - protected abstract int findOrCreatePage() throws IOException; - - protected abstract AbstractStorage getStorage(); - } - - public void dispose() { - synchronized (lock) { - try { - DbConnection.force(); - DbConnection.closeFiles(); - } - catch (Throwable e) { - throw DbConnection.handleError(e); - } - } - } - - public static void invalidateCaches() { - DbConnection.createBrokenMarkerFile(); - } - - public static void checkSanity() { - long startTime = System.currentTimeMillis(); - synchronized (lock) { - final int fileLength = (int)getRecords().length(); - assert fileLength % RECORD_SIZE == 0; - int recordCount = fileLength / RECORD_SIZE; - - IntArrayList usedAttributeRecordIds = new IntArrayList(); - IntArrayList validAttributeIds = new IntArrayList(); - for (int id = 2; id < recordCount; id++) { - int flags = getFlags(id); - assert (flags & ~ALL_VALID_FLAGS) == 0; - if ((flags & FREE_RECORD_FLAG) != 0) { - LOG.assertTrue(DbConnection.myFreeRecords.contains(id), "Record, marked free, not in free list: " + id); - } - else { - LOG.assertTrue(!DbConnection.myFreeRecords.contains(id), "Record, not marked free, in free list: " + id); - checkRecordSanity(id, recordCount, usedAttributeRecordIds, validAttributeIds); - } - } - } - - long endTime = System.currentTimeMillis(); - //System.out.println("Sanity check took " + (endTime-startTime) + " ms"); - } - - - private static void checkRecordSanity(final int id, final int recordCount, final IntArrayList usedAttributeRecordIds, - final IntArrayList validAttributeIds) { - int parentId = getParent(id); - assert parentId >= 0 && parentId < recordCount; - if (parentId > 0) { - final int parentFlags = getFlags(parentId); - assert (parentFlags & FREE_RECORD_FLAG) == 0; - assert (parentFlags & PersistentFS.IS_DIRECTORY_FLAG) != 0; - } - - String name = getName(id); - LOG.assertTrue(parentId == 0 || name.length() > 0, "File with empty name found under " + getName(parentId) + ", id=" + id); - - checkContentsStorageSanity(id); - checkAttributesStorageSanity(id, usedAttributeRecordIds, validAttributeIds); - - long length = getLength(id); - assert length >= -1 : "Invalid file length found for " + name + ": " + length; - } - - private static void checkContentsStorageSanity(int id) { - try { - int recordId = getContentRecordId(id); - assert recordId >= 0; - if (recordId > 0) { - getContentStorage().checkSanity(recordId); - } - } - catch (IOException e) { - throw DbConnection.handleError(e); - } - } - - private static void checkAttributesStorageSanity(int id, IntArrayList usedAttributeRecordIds, IntArrayList validAttributeIds) { - int attributeRecordId; - try { - attributeRecordId = getAttributeRecordId(id); - } - catch (IOException ex) { - throw DbConnection.handleError(ex); - } - - assert attributeRecordId >= 0; - if (attributeRecordId > 0) { - try { - checkAttributesSanity(attributeRecordId, usedAttributeRecordIds, validAttributeIds); - } - catch (IOException ex) { - throw DbConnection.handleError(ex); - } - } - } - - private static void checkAttributesSanity(final int attributeRecordId, final IntArrayList usedAttributeRecordIds, - final IntArrayList validAttributeIds) throws IOException { - assert !usedAttributeRecordIds.contains(attributeRecordId); - usedAttributeRecordIds.add(attributeRecordId); - - final DataInputStream dataInputStream = getAttributesStorage().readStream(attributeRecordId); - try { - final int streamSize = dataInputStream.available(); - assert (streamSize % 8) == 0; - for (int i = 0; i < streamSize / 8; i++) { - int attId = dataInputStream.readInt(); - int attDataRecordId = dataInputStream.readInt(); - assert !usedAttributeRecordIds.contains(attDataRecordId); - usedAttributeRecordIds.add(attDataRecordId); - if (!validAttributeIds.contains(attId)) { - assert getNames().valueOf(attId).length() > 0; - validAttributeIds.add(attId); - } - getAttributesStorage().checkSanity(attDataRecordId); - } - } - finally { - dataInputStream.close(); - } - } - - public static RuntimeException handleError(Throwable e) { - return DbConnection.handleError(e); - } -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.openapi.vfs.newvfs.persistent; + +import com.intellij.concurrency.JobScheduler; +import com.intellij.openapi.Forceable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.IntArrayList; +import com.intellij.util.io.PagedFileStorage; +import com.intellij.util.io.PersistentStringEnumerator; +import com.intellij.util.io.ResizeableMappedFile; +import com.intellij.util.io.storage.AbstractStorage; +import com.intellij.util.io.storage.HeavyProcessLatch; +import com.intellij.util.io.storage.RefCountingStorage; +import com.intellij.util.io.storage.Storage; +import gnu.trove.TIntArrayList; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.io.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings({"PointlessArithmeticExpression", "HardCodedStringLiteral"}) +public class FSRecords implements Forceable { + private static final Logger LOG = Logger.getInstance("#com.intellij.vfs.persistent.FSRecords"); + + private static final int VERSION = 12; + + private static final int PARENT_OFFSET = 0; + private static final int PARENT_SIZE = 4; + private static final int NAME_OFFSET = PARENT_OFFSET + PARENT_SIZE; + private static final int NAME_SIZE = 4; + private static final int FLAGS_OFFSET = NAME_OFFSET + NAME_SIZE; + private static final int FLAGS_SIZE = 4; + private static final int ATTREF_OFFSET = FLAGS_OFFSET + FLAGS_SIZE; + private static final int ATTREF_SIZE = 4; + private static final int CONTENT_OFFSET = ATTREF_OFFSET + ATTREF_SIZE; + private static final int CONTENT_SIZE = 4; + private static final int TIMESTAMP_OFFSET = CONTENT_OFFSET + CONTENT_SIZE; + private static final int TIMESTAMP_SIZE = 8; + private static final int MODCOUNT_OFFSET = TIMESTAMP_OFFSET + TIMESTAMP_SIZE; + private static final int MODCOUNT_SIZE = 4; + private static final int LENGTH_OFFSET = MODCOUNT_OFFSET + MODCOUNT_SIZE; + private static final int LENGTH_SIZE = 8; + + private static final int RECORD_SIZE = LENGTH_OFFSET + LENGTH_SIZE; + + private static final byte[] ZEROES = new byte[RECORD_SIZE]; + + private static final int HEADER_VERSION_OFFSET = 0; + private static final int HEADER_RESERVED_4BYTES_OFFSET = 4; // Reserved + private static final int HEADER_GLOBAL_MODCOUNT_OFFSET = 8; + private static final int HEADER_CONNECTION_STATUS_OFFSET = 12; + private static final int HEADER_TIMESTAMP_OFFSET = 16; + private static final int HEADER_SIZE = HEADER_TIMESTAMP_OFFSET + 8; + + private static final int CONNECTED_MAGIC = 0x12ad34e4; + private static final int SAFELY_CLOSED_MAGIC = 0x1f2f3f4f; + private static final int CORRUPTED_MAGIC = 0xabcf7f7f; + + private static final String CHILDREN_ATT = "FsRecords.DIRECTORY_CHILDREN"; + private static final Object lock = new Object(); + + private volatile static int ourLocalModificationCount = 0; + + private static final int FREE_RECORD_FLAG = 0x100; + private static final int ALL_VALID_FLAGS = PersistentFS.ALL_VALID_FLAGS | FREE_RECORD_FLAG; + + static { + //noinspection ConstantConditions + assert HEADER_SIZE <= RECORD_SIZE; + } + + private static class DbConnection { + private static int refCount = 0; + private static final Object LOCK = new Object(); + private static final TObjectIntHashMap myAttributeIds = new TObjectIntHashMap(); + + private static PersistentStringEnumerator myNames; + private static Storage myAttributes; + private static RefCountingStorage myContents; + private static ResizeableMappedFile myRecords; + private static final TIntArrayList myFreeRecords = new TIntArrayList(); + + private static boolean myDirty = false; + private static ScheduledFuture myFlushingFuture; + private static boolean myCorrupted = false; + + public static DbConnection connect() { + synchronized (LOCK) { + if (refCount == 0) { + init(); + scanFreeRecords(); + setupFlushing(); + } + refCount++; + } + + return new DbConnection(); + } + + private static void scanFreeRecords() { + final int filelength = (int)getRecords().length(); + LOG.assertTrue(filelength % RECORD_SIZE == 0); + + int count = filelength / RECORD_SIZE; + for (int n = 2; n < count; n++) { + if ((getFlags(n) & FREE_RECORD_FLAG) != 0) { + addFreeRecord(n); + } + } + } + + public static int getFreeRecord() { + if (myFreeRecords.isEmpty()) return 0; + return myFreeRecords.remove(myFreeRecords.size() - 1); + } + + private static void createBrokenMarkerFile() { + File brokenMarker = getCorruptionMarkerFile(); + try { + final FileWriter writer = new FileWriter(brokenMarker); + writer.write("These files are corrupted and must be rebuilt from the scratch on next startup"); + writer.close(); + } + catch (IOException e) { + // No luck. + } + } + + private static File getCorruptionMarkerFile() { + return new File(basePath(), "corruption.marker"); + } + + private static void init() { + final File basePath = basePath(); + basePath.mkdirs(); + + final File namesFile = new File(basePath, "names.dat"); + final File attributesFile = new File(basePath, "attrib.dat"); + final File contentsFile = new File(basePath, "content.dat"); + final File recordsFile = new File(basePath, "records.dat"); + + if (!namesFile.exists()) { + invalidateIndex(); + } + + try { + if (getCorruptionMarkerFile().exists()) { + invalidateIndex(); + throw new IOException("Corruption marker file found"); + } + + myNames = new PersistentStringEnumerator(namesFile); + myAttributes = new Storage(attributesFile.getCanonicalPath()); + myContents = new RefCountingStorage(contentsFile.getCanonicalPath()); + myRecords = new ResizeableMappedFile(recordsFile, 20 * 1024, new PagedFileStorage.StorageLock(false)); + + if (myRecords.length() == 0) { + cleanRecord(0); // Clean header + cleanRecord(1); // Create root record + setCurrentVersion(); + } + + if (getVersion() != VERSION) { + throw new IOException("FS repository version mismatch"); + } + + if (myRecords.getInt(HEADER_CONNECTION_STATUS_OFFSET) != SAFELY_CLOSED_MAGIC) { + throw new IOException("FS repository wasn't safely shut down"); + } + markDirty(); + } + catch (IOException e) { + LOG.info("Filesystem storage is corrupted or does not exist. [Re]Building. Reason: " + e.getMessage()); + try { + closeFiles(); + + boolean deleted = true; + deleted &= FileUtil.delete(getCorruptionMarkerFile()); + deleted &= deleteWithSubordinates(namesFile); + deleted &= Storage.deleteFiles(attributesFile.getCanonicalPath()); + deleted &= Storage.deleteFiles(contentsFile.getCanonicalPath()); + deleted &= deleteWithSubordinates(recordsFile); + + if (!deleted) { + throw new IOException("Cannot delete filesystem storage files"); + } + } + catch (final IOException e1) { + final Runnable warnAndShutdown = new Runnable() { + public void run() { + boolean unitTest = ApplicationManager.getApplication().isUnitTestMode(); + if (!(unitTest || ApplicationManager.getApplication().isHeadlessEnvironment())) { + JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), + "Files in " + basePath.getPath() + " are locked. IntelliJ IDEA will not be able to start up", + "Fatal Error", + JOptionPane.ERROR_MESSAGE); + } + if (unitTest) { + e1.printStackTrace(); + } + Runtime.getRuntime().halt(1); + } + }; + + if (EventQueue.isDispatchThread()) { + warnAndShutdown.run(); + } + else { + //noinspection SSBasedInspection + SwingUtilities.invokeLater(warnAndShutdown); + } + + throw new RuntimeException("Can't rebuild filesystem storage ", e1); + } + + init(); + } + } + + private static void invalidateIndex() { + LOG.info("Marking VFS as corrupted"); + FileUtil.createIfDoesntExist(new File(PathManager.getIndexRoot(), "corruption.marker")); + } + + private static File basePath() { + return new File(PathManager.getSystemPath() + "/caches/"); + } + + private static boolean deleteWithSubordinates(File file) { + final String baseName = file.getName(); + final File[] files = file.getParentFile().listFiles(new FileFilter() { + public boolean accept(final File pathname) { + return pathname.getName().startsWith(baseName); + } + }); + + boolean ok = true; + if (files != null) { + for (File f : files) { + ok &= FileUtil.delete(f); + } + } + + return ok; + } + + private static void markDirty() throws IOException { + if (!myDirty) { + myDirty = true; + myRecords.putInt(HEADER_CONNECTION_STATUS_OFFSET, CONNECTED_MAGIC); + } + } + + private static void setupFlushing() { + myFlushingFuture = JobScheduler.getScheduler().scheduleAtFixedRate(new Runnable() { + int lastModCount = 0; + + public void run() { + if (lastModCount == ourLocalModificationCount && !HeavyProcessLatch.INSTANCE.isRunning()) { + flushSome(); + } + lastModCount = ourLocalModificationCount; + } + }, 5000, 5000, TimeUnit.MILLISECONDS); + } + + public static void force() { + synchronized (lock) { + try { + if (myRecords != null) { + markClean(); + } + } + catch (IOException e) { + LOG.info(e); + } + if (myNames != null) { + myNames.force(); + myAttributes.force(); + myContents.force(); + myRecords.force(); + } + } + } + + public static void flushSome() { + synchronized (lock) { + if (myFlushingFuture == null) { + return; // avoid NPE when close has already taken place + } + myNames.force(); + + final boolean attribsFlushed = myAttributes.flushSome(); + final boolean contentsFlushed = myContents.flushSome(); + if (attribsFlushed && contentsFlushed) { + try { + markClean(); + } + catch (IOException e) { + // Ignore + } + myRecords.force(); + } + } + } + + public static boolean isDirty() { + return myDirty || myNames.isDirty() || myAttributes.isDirty() || myContents.isDirty() || myRecords.isDirty(); + } + + + private static int getVersion() throws IOException { + final int recordsVersion = myRecords.getInt(HEADER_VERSION_OFFSET); + if (myAttributes.getVersion() != recordsVersion || myContents.getVersion() != recordsVersion) return -1; + + return recordsVersion; + } + + public static long getTimestamp() { + return myRecords.getLong(HEADER_TIMESTAMP_OFFSET); + } + + private static void setCurrentVersion() throws IOException { + myRecords.putInt(HEADER_VERSION_OFFSET, VERSION); + myRecords.putLong(HEADER_TIMESTAMP_OFFSET, System.currentTimeMillis()); + myAttributes.setVersion(VERSION); + myContents.setVersion(VERSION); + myRecords.putInt(HEADER_CONNECTION_STATUS_OFFSET, SAFELY_CLOSED_MAGIC); + } + + public static void cleanRecord(final int id) throws IOException { + myRecords.put(id * RECORD_SIZE, ZEROES, 0, RECORD_SIZE); + } + + public static PersistentStringEnumerator getNames() { + return myNames; + } + + public static ResizeableMappedFile getRecords() { + return myRecords; + } + + public void dispose() throws IOException { + synchronized (LOCK) { + refCount--; + if (refCount == 0) { + closeFiles(); + } + } + } + + private static void closeFiles() throws IOException { + if (myFlushingFuture != null) { + myFlushingFuture.cancel(false); + myFlushingFuture = null; + } + + if (myNames != null) { + myNames.close(); + myNames = null; + } + + if (myAttributes != null) { + myAttributes.dispose(); + myAttributes = null; + } + + if (myContents != null) { + myContents.dispose(); + myContents = null; + } + + if (myRecords != null) { + markClean(); + myRecords.close(); + myRecords = null; + } + } + + private static void markClean() throws IOException { + if (myDirty) { + myDirty = false; + myRecords.putInt(HEADER_CONNECTION_STATUS_OFFSET, myCorrupted ? CORRUPTED_MAGIC : SAFELY_CLOSED_MAGIC); + } + } + + private static int getAttributeId(String attId) throws IOException { + if (myAttributeIds.containsKey(attId)) { + return myAttributeIds.get(attId); + } + + int id = myNames.enumerate(attId); + myAttributeIds.put(attId, id); + + return id; + } + + private static RuntimeException handleError(final Throwable e) { + if (!myCorrupted) { + createBrokenMarkerFile(); + myCorrupted = true; + force(); + } + + return new RuntimeException(e); + } + + public static void addFreeRecord(final int id) { + myFreeRecords.add(id); + } + } + + public FSRecords() { + } + + public void connect() { + DbConnection.connect(); + } + + public long getCreationTimestamp() { + synchronized (lock) { + return DbConnection.getTimestamp(); + } + } + + private static ResizeableMappedFile getRecords() { + return DbConnection.getRecords(); + } + + private static RefCountingStorage getContentStorage() { + return DbConnection.myContents; + } + + private static Storage getAttributesStorage() { + return DbConnection.myAttributes; + } + + public static PersistentStringEnumerator getNames() { + return DbConnection.getNames(); + } + + public static int createRecord() { + synchronized (lock) { + try { + DbConnection.markDirty(); + + final int free = DbConnection.getFreeRecord(); + if (free == 0) { + final int filelength = (int)getRecords().length(); + LOG.assertTrue(filelength % RECORD_SIZE == 0); + int newrecord = filelength / RECORD_SIZE; + DbConnection.cleanRecord(newrecord); + assert filelength + RECORD_SIZE == getRecords().length(); + return newrecord; + } + else { + DbConnection.cleanRecord(free); + return free; + } + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public void deleteRecordRecursively(int id) { + synchronized (lock) { + try { + DbConnection.markDirty(); + incModCount(id); + doDeleteRecursively(id); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + private void doDeleteRecursively(final int id) { + for (int subrecord : list(id)) { + doDeleteRecursively(subrecord); + } + + deleteRecord(id); + } + + private void deleteRecord(final int id) { + synchronized (lock) { + try { + DbConnection.markDirty(); + deleteContentAndAttributes(id); + + DbConnection.cleanRecord(id); + addToFreeRecordsList(id); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + private void deleteContentAndAttributes(int id) throws IOException { + int content_page = getContentRecordId(id); + if (content_page != 0) { + getContentStorage().releaseRecord(content_page); + } + + int att_page = getAttributeRecordId(id); + if (att_page != 0) { + final DataInputStream attStream = getAttributesStorage().readStream(att_page); + while (attStream.available() > 0) { + attStream.readInt(); // Attribute ID; + int attAddress = attStream.readInt(); + getAttributesStorage().deleteRecord(attAddress); + } + attStream.close(); + getAttributesStorage().deleteRecord(att_page); + } + } + + private void addToFreeRecordsList(int id) throws IOException { + DbConnection.addFreeRecord(id); + setFlags(id, FREE_RECORD_FLAG, false); + } + + public int[] listRoots() throws IOException { + synchronized (lock) { + DbConnection.markDirty(); + final DataInputStream input = readAttribute(1, CHILDREN_ATT); + if (input == null) return ArrayUtil.EMPTY_INT_ARRAY; + + int[] result; + try { + final int count = input.readInt(); + result = ArrayUtil.newIntArray(count); + for (int i = 0; i < count; i++) { + input.readInt(); // Name + result[i] = input.readInt(); // Id + } + } + finally { + input.close(); + } + + return result; + } + } + + public void force() { + DbConnection.force(); + } + + public boolean isDirty() { + return DbConnection.isDirty(); + } + + public int findRootRecord(String rootUrl) throws IOException { + synchronized (lock) { + DbConnection.markDirty(); + final int root = getNames().enumerate(rootUrl); + + final DataInputStream input = readAttribute(1, CHILDREN_ATT); + int[] names = ArrayUtil.EMPTY_INT_ARRAY; + int[] ids = ArrayUtil.EMPTY_INT_ARRAY; + + if (input != null) { + try { + final int count = input.readInt(); + names = ArrayUtil.newIntArray(count); + ids = ArrayUtil.newIntArray(count); + for (int i = 0; i < count; i++) { + final int name = input.readInt(); + final int id = input.readInt(); + if (name == root) { + return id; + } + + names[i] = name; + ids[i] = id; + } + } + finally { + input.close(); + } + } + + final DataOutputStream output = writeAttribute(1, CHILDREN_ATT, false); + int id; + try { + id = createRecord(); + output.writeInt(names.length + 1); + for (int i = 0; i < names.length; i++) { + output.writeInt(names[i]); + output.writeInt(ids[i]); + } + output.writeInt(root); + output.writeInt(id); + } + finally { + output.close(); + } + + return id; + } + } + + public void deleteRootRecord(int id) throws IOException { + synchronized (lock) { + DbConnection.markDirty(); + final DataInputStream input = readAttribute(1, CHILDREN_ATT); + assert input != null; + int count; + int[] names; + int[] ids; + try { + count = input.readInt(); + + names = ArrayUtil.newIntArray(count); + ids = ArrayUtil.newIntArray(count); + for (int i = 0; i < count; i++) { + names[i] = input.readInt(); + ids[i] = input.readInt(); + } + } + finally { + input.close(); + } + + final int index = ArrayUtil.find(ids, id); + assert index >= 0; + + names = ArrayUtil.remove(names, index); + ids = ArrayUtil.remove(ids, index); + + final DataOutputStream output = writeAttribute(1, CHILDREN_ATT, false); + try { + output.writeInt(count - 1); + for (int i = 0; i < names.length; i++) { + output.writeInt(names[i]); + output.writeInt(ids[i]); + } + } + finally { + output.close(); + } + } + } + + public int[] list(int id) { + synchronized (lock) { + try { + final DataInputStream input = readAttribute(id, CHILDREN_ATT); + if (input == null) return ArrayUtil.EMPTY_INT_ARRAY; + + final int count = input.readInt(); + final int[] result = ArrayUtil.newIntArray(count); + for (int i = 0; i < count; i++) { + result[i] = input.readInt(); + } + input.close(); + return result; + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public boolean wereChildrenAccessed(int id) { + try { + synchronized (lock) { + return findAttributePage(id, CHILDREN_ATT, false) != 0; + } + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + public void updateList(int id, int[] children) { + synchronized (lock) { + try { + DbConnection.markDirty(); + final DataOutputStream record = writeAttribute(id, CHILDREN_ATT, false); + record.writeInt(children.length); + for (int child : children) { + if (child == id) { + LOG.error("Cyclic parent child relations"); + } + else { + record.writeInt(child); + } + } + record.close(); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + private static void incModCount(int id) throws IOException { + ourLocalModificationCount++; + final int count = getModCount() + 1; + getRecords().putInt(HEADER_GLOBAL_MODCOUNT_OFFSET, count); + + int parent = id; + while (parent != 0) { + setModCount(parent, count); + parent = getParent(parent); + } + } + + public static int getLocalModCount() { + return ourLocalModificationCount; // This is volatile, only modified under Application.runWriteAction() lock. + } + + public static int getModCount() { + synchronized (lock) { + return getRecords().getInt(HEADER_GLOBAL_MODCOUNT_OFFSET); + } + } + + public static int getParent(int id) { + synchronized (lock) { + try { + final int parentId = getRecordInt(id, PARENT_OFFSET); + if (parentId == id) { + LOG.error("Cyclic parent child relations in the database. id = " + id); + return 0; + } + + return parentId; + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public void setParent(int id, int parent) { + if (id == parent) { + LOG.error("Cyclic parent/child relations"); + return; + } + + synchronized (lock) { + try { + DbConnection.markDirty(); + incModCount(id); + putRecordInt(id, PARENT_OFFSET, parent); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public static String getName(int id) { + synchronized (lock) { + try { + final int nameId = getRecordInt(id, NAME_OFFSET); + return nameId != 0 ? getNames().valueOf(nameId) : ""; + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public void setName(int id, String name) { + synchronized (lock) { + try { + DbConnection.markDirty(); + incModCount(id); + putRecordInt(id, NAME_OFFSET, getNames().enumerate(name)); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public static int getFlags(int id) { + synchronized (lock) { + return getRecordInt(id, FLAGS_OFFSET); + } + } + + public void setFlags(int id, int flags, final boolean markAsChange) { + synchronized (lock) { + try { + if (markAsChange) { + DbConnection.markDirty(); + incModCount(id); + } + putRecordInt(id, FLAGS_OFFSET, flags); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public static long getLength(int id) { + synchronized (lock) { + return getRecords().getLong(getOffset(id, LENGTH_OFFSET)); + } + } + + public void setLength(int id, long len) { + synchronized (lock) { + try { + DbConnection.markDirty(); + incModCount(id); + getRecords().putLong(getOffset(id, LENGTH_OFFSET), len); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public static long getTimestamp(int id) { + synchronized (lock) { + return getRecords().getLong(getOffset(id, TIMESTAMP_OFFSET)); + } + } + + public void setTimestamp(int id, long value) { + synchronized (lock) { + try { + DbConnection.markDirty(); + incModCount(id); + getRecords().putLong(getOffset(id, TIMESTAMP_OFFSET), value); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public static int getModCount(int id) { + synchronized (lock) { + return getRecordInt(id, MODCOUNT_OFFSET); + } + } + + private static void setModCount(int id, int value) throws IOException { + putRecordInt(id, MODCOUNT_OFFSET, value); + } + + private static int getContentRecordId(int fileId) throws IOException { + return getRecordInt(fileId, CONTENT_OFFSET); + } + + private static void setContentRecordId(int id, int value) throws IOException { + putRecordInt(id, CONTENT_OFFSET, value); + } + + private static int getAttributeRecordId(int id) throws IOException { + return getRecordInt(id, ATTREF_OFFSET); + } + + private static void setAttributeRecordId(int id, int value) throws IOException { + putRecordInt(id, ATTREF_OFFSET, value); + } + + private static int getRecordInt(int id, int offset) { + return getRecords().getInt(getOffset(id, offset)); + } + + private static void putRecordInt(int id, int offset, int value) { + getRecords().putInt(getOffset(id, offset), value); + } + + private static int getOffset(int id, int offset) { + return id * RECORD_SIZE + offset; + } + + @Nullable + public DataInputStream readContent(int fileId) { + try { + int page; + synchronized (lock) { + page = findContentPage(fileId, false); + if (page == 0) return null; + } + return getContentStorage().readStream(page); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + @Nullable + public DataInputStream readContentById(int contentId) { + try { + return getContentStorage().readStream(contentId); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + @Nullable + public DataInputStream readAttribute(int fileId, String attId) { + try { + synchronized (attId) { + int page; + synchronized (lock) { + page = findAttributePage(fileId, attId, false); + if (page == 0) return null; + } + return getAttributesStorage().readStream(page); + } + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + private int findContentPage(int fileId, boolean toWrite) throws IOException { + checkFileIsValid(fileId); + + int recordId = getContentRecordId(fileId); + if (toWrite) { + if (recordId == 0 || getContentStorage().getRefCount(recordId) > 1) { + recordId = getContentStorage().acquireNewRecord(); + setContentRecordId(fileId, recordId); + } + } + + return recordId; + } + + private int findAttributePage(int fileId, String attrId, boolean toWrite) throws IOException { + checkFileIsValid(fileId); + + Storage storage = getAttributesStorage(); + + int encodedAttrId = DbConnection.getAttributeId(attrId); + int recordId = getAttributeRecordId(fileId); + + if (recordId == 0) { + if (!toWrite) return 0; + + recordId = storage.createNewRecord(); + setAttributeRecordId(fileId, recordId); + } + else { + DataInputStream attrRefs = storage.readStream(recordId); + try { + while (attrRefs.available() > 0) { + final int attIdOnPage = attrRefs.readInt(); + final int attrAddress = attrRefs.readInt(); + + if (attIdOnPage == encodedAttrId) return attrAddress; + } + } + finally { + attrRefs.close(); + } + } + + if (toWrite) { + Storage.AppenderStream appender = storage.appendStream(recordId); + appender.writeInt(encodedAttrId); + int attrAddress = storage.createNewRecord(); + appender.writeInt(attrAddress); + appender.close(); + return attrAddress; + } + + return 0; + } + + private void checkFileIsValid(int fileId) { + assert fileId > 0 : "assert fileId > 0 failed"; + // TODO: This assertion is a bit timey, will remove when bug is caught. + assert (getFlags(fileId) & FREE_RECORD_FLAG) == 0 : "Trying to find an attribute of deleted page"; + } + + public int acquireFileContent(int fileId) { + try { + synchronized (lock) { + int record = getContentRecordId(fileId); + if (record > 0) getContentStorage().acquireRecord(record); + return record; + } + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + public void releaseContent(int contentId) { + try { + getContentStorage().releaseRecord(contentId); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + public int getContentId(int fileId) { + try { + synchronized (lock) { + return getContentRecordId(fileId); + } + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + @NotNull + public DataOutputStream writeContent(int fileId, boolean readOnly) { + return new ContentOutputStream(fileId, readOnly); + } + + public void writeContent(int fileId, byte[] bytes, boolean readOnly) throws IOException { + new ContentOutputStream(fileId, readOnly).writeBytes(bytes, fileId); + } + + public int storeUnlinkedContent(byte[] bytes) { + try { + int recordId = getContentStorage().acquireNewRecord(); + AbstractStorage.StorageDataOutput output = getContentStorage().writeStream(recordId); + output.write(bytes); + output.close(); + return recordId; + } + catch (IOException e) { + throw DbConnection.handleError(e); + } + } + + @NotNull + public DataOutputStream writeAttribute(final int fileId, final String attId, boolean fixedSize) { + return new AttributeOutputStream(fileId, attId, fixedSize); + } + + private class ContentOutputStream extends BaseOutputStream { + + private ContentOutputStream(final int fileId, boolean readOnly) { + super(fileId, readOnly); + } + + @Override + protected int findOrCreatePage() throws IOException { + return findContentPage(myFileId, true); + } + + @Override + protected AbstractStorage getStorage() { + return getContentStorage(); + } + } + + private class AttributeOutputStream extends BaseOutputStream { + private final String myAttributeId; + + private AttributeOutputStream(final int fileId, final String attributeId, boolean fixedSize) { + super(fileId, fixedSize); + myAttributeId = attributeId; + } + + @Override + protected void doFlush() throws IOException { + synchronized (myAttributeId) { + super.doFlush(); + } + } + + @Override + protected int findOrCreatePage() throws IOException { + return findAttributePage(myFileId, myAttributeId, true); + } + + @Override + protected AbstractStorage getStorage() { + return getAttributesStorage(); + } + } + + private abstract static class BaseOutputStream extends DataOutputStream { + protected final int myFileId; + protected final boolean myFixedSize; + + private BaseOutputStream(final int fileId, boolean fixedSize) { + super(new ByteArrayOutputStream()); + myFileId = fileId; + myFixedSize = fixedSize; + } + + public void close() throws IOException { + super.close(); + + try { + doFlush(); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + + protected void doFlush() throws IOException { + writeBytes(((ByteArrayOutputStream)out).toByteArray(), myFileId); + } + + public void writeBytes(byte[] bytes, int fileId) throws IOException { + final int page; + synchronized (lock) { + DbConnection.markDirty(); + incModCount(fileId); + page = findOrCreatePage(); + } + + getStorage().writeBytes(page, bytes, myFixedSize); + } + + protected abstract int findOrCreatePage() throws IOException; + + protected abstract AbstractStorage getStorage(); + } + + public void dispose() { + synchronized (lock) { + try { + DbConnection.force(); + DbConnection.closeFiles(); + } + catch (Throwable e) { + throw DbConnection.handleError(e); + } + } + } + + public static void invalidateCaches() { + DbConnection.createBrokenMarkerFile(); + } + + public static void checkSanity() { + long startTime = System.currentTimeMillis(); + synchronized (lock) { + final int fileLength = (int)getRecords().length(); + assert fileLength % RECORD_SIZE == 0; + int recordCount = fileLength / RECORD_SIZE; + + IntArrayList usedAttributeRecordIds = new IntArrayList(); + IntArrayList validAttributeIds = new IntArrayList(); + for (int id = 2; id < recordCount; id++) { + int flags = getFlags(id); + assert (flags & ~ALL_VALID_FLAGS) == 0; + if ((flags & FREE_RECORD_FLAG) != 0) { + LOG.assertTrue(DbConnection.myFreeRecords.contains(id), "Record, marked free, not in free list: " + id); + } + else { + LOG.assertTrue(!DbConnection.myFreeRecords.contains(id), "Record, not marked free, in free list: " + id); + checkRecordSanity(id, recordCount, usedAttributeRecordIds, validAttributeIds); + } + } + } + + long endTime = System.currentTimeMillis(); + //System.out.println("Sanity check took " + (endTime-startTime) + " ms"); + } + + + private static void checkRecordSanity(final int id, final int recordCount, final IntArrayList usedAttributeRecordIds, + final IntArrayList validAttributeIds) { + int parentId = getParent(id); + assert parentId >= 0 && parentId < recordCount; + if (parentId > 0) { + final int parentFlags = getFlags(parentId); + assert (parentFlags & FREE_RECORD_FLAG) == 0; + assert (parentFlags & PersistentFS.IS_DIRECTORY_FLAG) != 0; + } + + String name = getName(id); + LOG.assertTrue(parentId == 0 || name.length() > 0, "File with empty name found under " + getName(parentId) + ", id=" + id); + + checkContentsStorageSanity(id); + checkAttributesStorageSanity(id, usedAttributeRecordIds, validAttributeIds); + + long length = getLength(id); + assert length >= -1 : "Invalid file length found for " + name + ": " + length; + } + + private static void checkContentsStorageSanity(int id) { + try { + int recordId = getContentRecordId(id); + assert recordId >= 0; + if (recordId > 0) { + getContentStorage().checkSanity(recordId); + } + } + catch (IOException e) { + throw DbConnection.handleError(e); + } + } + + private static void checkAttributesStorageSanity(int id, IntArrayList usedAttributeRecordIds, IntArrayList validAttributeIds) { + int attributeRecordId; + try { + attributeRecordId = getAttributeRecordId(id); + } + catch (IOException ex) { + throw DbConnection.handleError(ex); + } + + assert attributeRecordId >= 0; + if (attributeRecordId > 0) { + try { + checkAttributesSanity(attributeRecordId, usedAttributeRecordIds, validAttributeIds); + } + catch (IOException ex) { + throw DbConnection.handleError(ex); + } + } + } + + private static void checkAttributesSanity(final int attributeRecordId, final IntArrayList usedAttributeRecordIds, + final IntArrayList validAttributeIds) throws IOException { + assert !usedAttributeRecordIds.contains(attributeRecordId); + usedAttributeRecordIds.add(attributeRecordId); + + final DataInputStream dataInputStream = getAttributesStorage().readStream(attributeRecordId); + try { + final int streamSize = dataInputStream.available(); + assert (streamSize % 8) == 0; + for (int i = 0; i < streamSize / 8; i++) { + int attId = dataInputStream.readInt(); + int attDataRecordId = dataInputStream.readInt(); + assert !usedAttributeRecordIds.contains(attDataRecordId); + usedAttributeRecordIds.add(attDataRecordId); + if (!validAttributeIds.contains(attId)) { + assert getNames().valueOf(attId).length() > 0; + validAttributeIds.add(attId); + } + getAttributesStorage().checkSanity(attDataRecordId); + } + } + finally { + dataInputStream.close(); + } + } + + public static RuntimeException handleError(Throwable e) { + return DbConnection.handleError(e); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java index 6b8d18c8ed64..bb914dcb93d2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java @@ -1,982 +1,982 @@ -/* - * 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. - */ - -/* - * @author max - */ -package com.intellij.openapi.vfs.newvfs.persistent; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.ApplicationEx; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.ShutDownTracker; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.newvfs.*; -import com.intellij.openapi.vfs.newvfs.events.*; -import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile; -import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; -import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap; -import com.intellij.util.io.DupOutputStream; -import com.intellij.util.io.ReplicatorInputStream; -import com.intellij.util.messages.MessageBus; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -public class PersistentFS extends ManagingFS implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS"); - - private static final int CHILDREN_CACHED_FLAG = 0x01; - static final int IS_DIRECTORY_FLAG = 0x02; - private static final int IS_READ_ONLY = 0x04; - private static final int MUST_RELOAD_CONTENT = 0x08; - - static final int ALL_VALID_FLAGS = CHILDREN_CACHED_FLAG | IS_DIRECTORY_FLAG | IS_READ_ONLY | MUST_RELOAD_CONTENT; - - public static final long FILE_LENGTH_TO_CACHE_THRESHOLD = 20 * 1024 * 1024; // 20 megabytes - - private final FSRecords myRecords; - private final MessageBus myEventsBus; - - private final Map myRoots = new HashMap(); - private final Object INPUT_LOCK = new Object(); - /** - * always in range [0, PersistentFS.FILE_LENGTH_TO_CACHE_THRESHOLD] - */ - public static final int MAX_INTELLISENSE_FILESIZE = maxIntellisenseFileSize(); - @NonNls private static final String MAX_INTELLISENSE_SIZE_PROPERTY = "idea.max.intellisense.filesize"; - - public PersistentFS(MessageBus bus) { - myEventsBus = bus; - myRecords = new FSRecords(); - - /* - PropertiesComponent properties = PropertiesComponent.getInstance(); - if (properties != null && !properties.isTrueValue("AntivirusActivityReporter.disabled")) { - AntivirusDetector.getInstance().enable(new Runnable() { - public void run() { - LOG.info("Antivirus activity detected. Please make sure IDEA system and caches directory as well as project foler are exluded from antivirus on-the-fly check list."); - } - }); - } - */ - ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { - public void run() { - performShutdown(); - } - }); - } - - public void disposeComponent() { - performShutdown(); - } - - private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(Boolean.FALSE); - private void performShutdown() { - if (!myShutdownPerformed.getAndSet(Boolean.TRUE)) { - LOG.info("VFS dispose started"); - myRecords.dispose(); - LOG.info("VFS dispose completed"); - } - } - - @NonNls - @NotNull - public String getComponentName() { - return "app.component.PersistentFS"; - } - - public void initComponent() { - myRecords.connect(); - } - - public boolean areChildrenLoaded(final VirtualFile dir) { - return areChildrenLoaded(getFileId(dir)); - } - - public long getCreationTimestamp() { - return myRecords.getCreationTimestamp(); - } - - private static NewVirtualFileSystem getDelegate(VirtualFile file) { - return (NewVirtualFileSystem)file.getFileSystem(); - } - - public boolean wereChildrenAccessed(final VirtualFile dir) { - return myRecords.wereChildrenAccessed(getFileId(dir)); - } - - public String[] list(final VirtualFile file) { - int id = getFileId(file); - if (areChildrenLoaded(id)) { - return listPersisted(file); - } - else { - return persistAllChildren(file); - } - } - - public String[] listPersisted(final VirtualFile file) { - return listPersisted(getFileId(file)); - } - - private String[] listPersisted(final int id) { - final int[] childrenIds = myRecords.list(id); - String[] names = ArrayUtil.newStringArray(childrenIds.length); - for (int i = 0; i < childrenIds.length; i++) { - names[i] = FSRecords.getName(childrenIds[i]); - } - return names; - } - - private String[] persistAllChildren(final VirtualFile file) { - int id = getFileId(file); - String[] currentNames = listPersisted(file); - int[] currentIds = myRecords.list(id); - - final NewVirtualFileSystem delegate = getDelegate(file); - String[] delegateNames = VfsUtil.filterNames(delegate.list(file)); - if (delegateNames.length == 0 && currentNames.length > 0) return currentNames; - - final String[] names; - if (currentNames.length == 0) { - names = delegateNames; - } - else { - Set allNamesSet = new LinkedHashSet((currentNames.length + delegateNames.length) * 2); - ContainerUtil.addAll(allNamesSet, currentNames); - ContainerUtil.addAll(allNamesSet, delegateNames); - names = ArrayUtil.toStringArray(allNamesSet); - } - - final int[] childrenIds = ArrayUtil.newIntArray(names.length); - - for (int i = 0; i < names.length; i++) { - final String name = names[i]; - int idx = ArrayUtil.indexOf(currentNames, name); - if (idx >= 0) { - childrenIds[i] = currentIds[idx]; - } - else { - int childId = FSRecords.createRecord(); - copyRecordFromDelegateFS(childId, id, new FakeVirtualFile(file, name), delegate); - childrenIds[i] = childId; - } - } - - myRecords.updateList(id, childrenIds); - int flags = FSRecords.getFlags(id); - myRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true); - - return names; - } - - public int[] listIds(VirtualFile parent) { - final int parentId = getFileId(parent); - - if (!areChildrenLoaded(parentId)) { - list(parent); - } - - return myRecords.list(parentId); - } - - - private static boolean areChildrenLoaded(final int parentId) { - final int mask = CHILDREN_CACHED_FLAG; - return (FSRecords.getFlags(parentId) & mask) != 0; - } - - @Nullable - public DataInputStream readAttribute(final VirtualFile file, final FileAttribute att) { - return myRecords.readAttribute(getFileId(file), att.getId()); - } - - public DataOutputStream writeAttribute(final VirtualFile file, final FileAttribute att) { - return myRecords.writeAttribute(getFileId(file), att.getId(), att.isFixedSize()); - } - - @Nullable - private DataInputStream readContent(VirtualFile file) { - return myRecords.readContent(getFileId(file)); - } - - @Nullable - private DataInputStream readContentById(int contentId) { - return myRecords.readContentById(contentId); - } - - private DataOutputStream writeContent(VirtualFile file, boolean readOnly) { - return myRecords.writeContent(getFileId(file), readOnly); - } - - private void writeContent(VirtualFile file, byte[] content, boolean readOnly) throws IOException { - myRecords.writeContent(getFileId(file), content, readOnly); - } - - public int storeUnlinkedContent(byte[] bytes) { - return myRecords.storeUnlinkedContent(bytes); - } - - public int getModificationCount(final VirtualFile file) { - final int id = getFileId(file); - return FSRecords.getModCount(id); - } - - @Override - public int getCheapFileSystemModificationCount() { - return FSRecords.getLocalModCount(); - } - - public int getFilesystemModificationCount() { - return FSRecords.getModCount(); - } - - private void copyRecordFromDelegateFS(final int id, final int parentId, final VirtualFile file, NewVirtualFileSystem delegate) { - if (id == parentId) { - LOG.error("Cyclic parent-child relations for file: " + file); - return; - } - - String name = file.getName(); - - if (name.length() > 0 && namesEqual(delegate, name, FSRecords.getName(id))) return; // TODO: Handle root attributes change. - - if (name.length() == 0) { // TODO: hack - if (areChildrenLoaded(id)) return; - } - - myRecords.setParent(id, parentId); - myRecords.setName(id, name); - - myRecords.setTimestamp(id, delegate.getTimeStamp(file)); - myRecords.setFlags(id, (delegate.isDirectory(file) ? IS_DIRECTORY_FLAG : 0) | (delegate.isWritable(file) ? 0 : IS_READ_ONLY), true); - - myRecords.setLength(id, -1L); - - // TODO!!!: More attributes? - } - - public boolean isDirectory(final VirtualFile file) { - final int id = getFileId(file); - return isDirectory(id); - } - - public boolean isDirectory(final int id) { - assert id > 0; - return (FSRecords.getFlags(id) & IS_DIRECTORY_FLAG) != 0; - } - - public int getParent(final int id) { - assert id > 0; - return FSRecords.getParent(id); - } - - private static boolean namesEqual(VirtualFileSystem fs, String n1, String n2) { - return ((NewVirtualFileSystem)fs).isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2); - } - - public boolean exists(final VirtualFile fileOrDirectory) { - return ((VirtualFileWithId)fileOrDirectory).getId() > 0; - } - - public long getTimeStamp(final VirtualFile file) { - final int id = getFileId(file); - return FSRecords.getTimestamp(id); - } - - public void setTimeStamp(final VirtualFile file, final long modstamp) throws IOException { - final int id = getFileId(file); - - myRecords.setTimestamp(id, modstamp); - getDelegate(file).setTimeStamp(file, modstamp); - } - - private static int getFileId(final VirtualFile file) { - final int id = ((VirtualFileWithId)file).getId(); - if (id <= 0) { - throw new InvalidVirtualFileAccessException(file); - } - return id; - } - - public boolean isWritable(final VirtualFile file) { - final int id = getFileId(file); - - return (FSRecords.getFlags(id) & IS_READ_ONLY) == 0; - } - - public void setWritable(final VirtualFile file, final boolean writableFlag) throws IOException { - getDelegate(file).setWritable(file, writableFlag); - processEvent(new VFilePropertyChangeEvent(this, file, VirtualFile.PROP_WRITABLE, isWritable(file), writableFlag, false)); - } - - public int getId(final VirtualFile parent, final String childName) { - final NewVirtualFileSystem delegate = getDelegate(parent); - final int parentId = getFileId(parent); - - final int[] children = myRecords.list(parentId); - for (final int childId : children) { - if (namesEqual(delegate, childName, FSRecords.getName(childId))) return childId; - } - - VirtualFile fake = new FakeVirtualFile(parent, childName); - if (delegate.exists(fake)) { - int child = FSRecords.createRecord(); - copyRecordFromDelegateFS(child, parentId, fake, delegate); - myRecords.updateList(parentId, ArrayUtil.append(children, child)); - return child; - } - - return 0; - } - - public long getLength(final VirtualFile file) { - final int id = getFileId(file); - - long len = FSRecords.getLength(id); - if (len == -1) { - len = (int)getDelegate(file).getLength(file); - myRecords.setLength(id, len); - } - - return len; - } - - public VirtualFile copyFile(final Object requestor, final VirtualFile file, final VirtualFile newParent, final String copyName) - throws IOException { - getDelegate(file).copyFile(requestor, file, newParent, copyName); - processEvent(new VFileCopyEvent(requestor, file, newParent, copyName)); - - final VirtualFile child = newParent.findChild(copyName); - if (child == null) { - throw new IOException("Cannot create child"); - } - return child; - } - - public VirtualFile createChildDirectory(final Object requestor, final VirtualFile parent, final String dir) throws IOException { - getDelegate(parent).createChildDirectory(requestor, parent, dir); - processEvent(new VFileCreateEvent(requestor, parent, dir, true, false)); - - final VirtualFile child = parent.findChild(dir); - if (child == null) { - throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath()); - } - return child; - } - - public VirtualFile createChildFile(final Object requestor, final VirtualFile parent, final String file) throws IOException { - getDelegate(parent).createChildFile(requestor, parent, file); - processEvent(new VFileCreateEvent(requestor, parent, file, false, false)); - - final VirtualFile child = parent.findChild(file); - if (child == null) { - throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath()); - } - return child; - } - - public void deleteFile(final Object requestor, final VirtualFile file) throws IOException { - final NewVirtualFileSystem delegate = getDelegate(file); - delegate.deleteFile(requestor, file); - - if (!delegate.exists(file)) { - processEvent(new VFileDeleteEvent(requestor, file, false)); - } - } - - public void renameFile(final Object requestor, final VirtualFile file, final String newName) throws IOException { - getDelegate(file).renameFile(requestor, file, newName); - processEvent(new VFilePropertyChangeEvent(requestor, file, VirtualFile.PROP_NAME, file.getName(), newName, false)); - } - - private static final boolean noCaching = Boolean.getBoolean("idea.no.content.caching" ); - - @NotNull - public byte[] contentsToByteArray(final VirtualFile file) throws IOException { - return contentsToByteArray(file, true); - } - - @NotNull - public byte[] contentsToByteArray(final VirtualFile file, boolean cacheContent) throws IOException { - InputStream contentStream = null; - boolean reloadFromDelegate; - synchronized (INPUT_LOCK) { - reloadFromDelegate = mustReloadContent(file) || (contentStream = readContent(file)) == null; - } - - if (reloadFromDelegate) { - final NewVirtualFileSystem delegate = getDelegate(file); - final byte[] content = delegate.contentsToByteArray(file); - - ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication(); - if ((!delegate.isReadOnly() || (!application.isInternal() && !application.isUnitTestMode())) && - !noCaching && content.length <= FILE_LENGTH_TO_CACHE_THRESHOLD) { - synchronized (INPUT_LOCK) { - writeContent(file, content, delegate.isReadOnly()); - - myRecords.setLength(getFileId(file), content.length); - setFlag(file, MUST_RELOAD_CONTENT, false); - } - } - - return content; - } - else { - try { - return FileUtil.loadBytes(contentStream, (int)file.getLength()); - } - catch (IOException e) { - throw FSRecords.handleError(e); - } - } - } - - public byte[] contentsToByteArray(int contentId) throws IOException { - return FileUtil.loadBytes(readContentById(contentId)); - } - - @NotNull - public InputStream getInputStream(final VirtualFile file) throws IOException { - synchronized (INPUT_LOCK) { - InputStream contentStream; - if (mustReloadContent(file) || (contentStream = readContent(file)) == null) { - final NewVirtualFileSystem delegate = getDelegate(file); - final long len = delegate.getLength(file); - final InputStream nativeStream = delegate.getInputStream(file); - - if (len > FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream; - - return createReplicator(file, nativeStream, len, delegate.isReadOnly()); - } - else { - return contentStream; - } - } - } - - private ReplicatorInputStream createReplicator(final VirtualFile file, final InputStream nativeStream, final long len, final boolean readOnly) { - final ByteArrayOutputStream cache = new ByteArrayOutputStream((int)len); - return new ReplicatorInputStream(nativeStream, cache) { - public void close() throws IOException { - super.close(); - - synchronized (INPUT_LOCK) { - if (getBytesRead() == len) { - writeContent(file, cache.toByteArray(), readOnly); - myRecords.setLength(getFileId(file), len); - setFlag(file, MUST_RELOAD_CONTENT, false); - } - else { - setFlag(file, MUST_RELOAD_CONTENT, true); - } - } - } - }; - } - - private static boolean mustReloadContent(final VirtualFile file) { - return checkFlag(file, MUST_RELOAD_CONTENT) || FSRecords.getLength(getFileId(file)) == -1L; - } - - @NotNull - public OutputStream getOutputStream(final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp) - throws IOException { - final VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false); - - final List events = Collections.singletonList(event); - - final BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES); - publisher.before(events); - - final ByteArrayOutputStream stream = new ByteArrayOutputStream() { - public void close() throws IOException { - super.close(); - - NewVirtualFileSystem delegate = getDelegate(file); - final OutputStream outputStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp); - - //noinspection IOResourceOpenedButNotSafelyClosed - final DupOutputStream sink = new DupOutputStream(new BufferedOutputStream(writeContent(file, delegate.isReadOnly())), outputStream) { - public void close() throws IOException { - try { - super.close(); - } - finally { - executeTouch(file, false, event.getModificationStamp()); - publisher.after(events); - } - } - }; - - try { - sink.write(buf, 0, count); - } - finally { - sink.close(); - } - } - }; - - final byte[] bom = file.getBOM(); - if (bom != null) { - stream.write(bom); - } - - return stream; - } - - public void restoreContent(VirtualFile file, Object requestor, final long modStamp, final long timeStamp) { - - } - - public int acquireContent(VirtualFile file) { - return myRecords.acquireFileContent(getFileId(file)); - } - - public void releaseContent(int contentId) { - myRecords.releaseContent(contentId); - } - - public int getCurrentContentId(VirtualFile file) { - return myRecords.getContentId(getFileId(file)); - } - - public void moveFile(final Object requestor, final VirtualFile file, final VirtualFile newParent) throws IOException { - getDelegate(file).moveFile(requestor, file, newParent); - processEvent(new VFileMoveEvent(requestor, file, newParent)); - } - - private void processEvent(VFileEvent event) { - processEvents(Collections.singletonList(event)); - } - - private static final Comparator DEPTH_COMPARATOR = new Comparator() { - public int compare(final VFileDeleteEvent o1, final VFileDeleteEvent o2) { - return o1.getFileDepth() - o2.getFileDepth(); - } - }; - - private static List validateEvents(List events) { - List filtered = new ArrayList(events.size()); - List deletionList = new ArrayList(); - - for (VFileEvent event : events) { - if (event.isValid()) { - if (event instanceof VFileDeleteEvent) { - deletionList.add((VFileDeleteEvent)event); - } - else { - filtered.add(event); - } - } - } - - ContainerUtil.quickSort(deletionList, DEPTH_COMPARATOR); - List filesToBeDeleted = new ArrayList(); - for (VFileDeleteEvent event : deletionList) { - boolean ok = true; - VirtualFile candidate = event.getFile(); - for (VirtualFile file : filesToBeDeleted) { - if (VfsUtil.isAncestor(file, candidate, false)) { - ok = false; - break; - } - } - - if (ok) { - filtered.add(event); - if (candidate.isDirectory()) { - filesToBeDeleted.add(candidate); - } - } - } - - return filtered; - } - - public void processEvents(List events) { - ApplicationManager.getApplication().assertWriteAccessAllowed(); - - events = validateEvents(events); - - myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES).before(events); - for (VFileEvent event : events) { - applyEvent(event); - } - myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES).after(events); - } - - public static final Object LOCK = new Object(); - - @Nullable - public NewVirtualFile findRoot(final String basePath, final NewVirtualFileSystem fs) { // TODO: read/write locks instead of sycnrhonized - synchronized (LOCK) { - final String rootUrl = fs.getProtocol() + "://" + basePath; - NewVirtualFile root = myRoots.get(rootUrl); - if (root == null) { - try { - final int rootId = myRecords.findRootRecord(rootUrl); - if (basePath.length() > 0) { - root = new VirtualDirectoryImpl(basePath, null, fs, rootId); - } - else { - // fake root for windows - root = new VirtualDirectoryImpl(basePath, null, fs, rootId) { - @NotNull - public VirtualFile[] getChildren() { - return getRoots(fs); - } - }; - } - if (!fs.exists(root)) return null; - - copyRecordFromDelegateFS(rootId, 0, root, fs); - } - catch (IOException e) { - throw new RuntimeException(e); - } - - if (basePath.length() > 0) { - myRoots.put(rootUrl, root); - } - } - - return root; - } - } - - public void refresh(final boolean asynchronous) { - final NewVirtualFile[] roots; - synchronized (LOCK) { - Collection values = myRoots.values(); - roots = values.toArray(new NewVirtualFile[values.size()]); - } - - RefreshQueue.getInstance().refresh(asynchronous, true, null, roots); - } - - public VirtualFile[] getLocalRoots() { - List roots; - synchronized (LOCK) { - roots = new ArrayList(myRoots.values()); - - final Iterator it = roots.iterator(); - while (it.hasNext()) { - NewVirtualFile file = it.next(); - if (!file.isInLocalFileSystem()) { - it.remove(); - } - } - } - - return VfsUtil.toVirtualFileArray(roots); - } - - //guarded by dirCacheReadLock/dirCacheWriteLock - private final StripedLockIntObjectConcurrentHashMap myIdToDirCache = new StripedLockIntObjectConcurrentHashMap(); - - public void clearIdCache() { - myIdToDirCache.clear(); - } - - @Nullable - public NewVirtualFile findFileById(final int id) { - return _findFileById(id, false); - } - - - @Nullable - public NewVirtualFile findFileByIdIfCached(final int id) { - return _findFileById(id, true); - } - - private NewVirtualFile _findFileById(int id, final boolean cachedOnly) { - final NewVirtualFile cached = myIdToDirCache.get(id); - if (cached != null) { - return cached; - } - - NewVirtualFile result = doFindFile(id, cachedOnly); - - if (result != null && result.isDirectory()) { - NewVirtualFile old = myIdToDirCache.putIfAbsent(id, result); - if (old != null) result = old; - } - return result; - } - - @Nullable - private NewVirtualFile doFindFile(final int id, boolean cachedOnly) { - final int parentId = getParent(id); - if (parentId == 0) { - synchronized (LOCK) { - for (NewVirtualFile root : myRoots.values()) { - if (root.getId() == id) return root; - } - return null; - } - } - else { - NewVirtualFile parentFile = _findFileById(parentId, cachedOnly); - if (parentFile == null) { - return null; - } - return cachedOnly ? parentFile.findChildByIdIfCached(id) : parentFile.findChildById(id); - } - } - - public VirtualFile[] getRoots() { - synchronized (LOCK) { - final Collection roots = myRoots.values(); - return VfsUtil.toVirtualFileArray(roots); - } - } - - public VirtualFile[] getRoots(final NewVirtualFileSystem fs) { - List roots = new ArrayList(); - synchronized (LOCK) { - for (NewVirtualFile root : myRoots.values()) { - if (root.getFileSystem() == fs) { - roots.add(root); - } - } - } - - return VfsUtil.toVirtualFileArray(roots); - } - - private void applyEvent(final VFileEvent event) { - /*System.out.println("Apply: " + event);*/ - - try { - if (event instanceof VFileCreateEvent) { - final VFileCreateEvent createEvent = (VFileCreateEvent)event; - executeCreateChild(createEvent.getParent(), createEvent.getChildName()); - } - else if (event instanceof VFileDeleteEvent) { - final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; - executeDelete(deleteEvent.getFile()); - } - else if (event instanceof VFileContentChangeEvent) { - final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent)event; - executeTouch(contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp()); - } - else if (event instanceof VFileCopyEvent) { - final VFileCopyEvent copyEvent = (VFileCopyEvent)event; - executeCopy(copyEvent.getFile(), copyEvent.getNewParent(), copyEvent.getNewChildName()); - } - else if (event instanceof VFileMoveEvent) { - final VFileMoveEvent moveEvent = (VFileMoveEvent)event; - executeMove(moveEvent.getFile(), moveEvent.getNewParent()); - } - else if (event instanceof VFilePropertyChangeEvent) { - final VFilePropertyChangeEvent propertyChangeEvent = (VFilePropertyChangeEvent)event; - if (VirtualFile.PROP_NAME.equals(propertyChangeEvent.getPropertyName())) { - executeRename(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue()); - } - else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) { - executeSetWritable(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue()); - } - } - } - catch (Exception e) { - // Exception applying single event should not prevent other events from applying. - LOG.error(e); - } - } - - @NonNls - public String toString() { - return "PersistentFS"; - } - - private void executeCreateChild(final VirtualFile parent, final String name) { - final NewVirtualFileSystem delegate = getDelegate(parent); - VirtualFile fakeFile = new FakeVirtualFile(parent, name); - if (delegate.exists(fakeFile)) { - final int parentId = getFileId(parent); - int childId = FSRecords.createRecord(); - copyRecordFromDelegateFS(childId, parentId, fakeFile, delegate); - - appendIdToParentList(parentId, childId); - final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent; - dir.addChild(dir.createChild(name, childId)); - } - } - - private void appendIdToParentList(final int parentId, final int childId) { - int[] childrenlist = myRecords.list(parentId); - childrenlist = ArrayUtil.append(childrenlist, childId); - myRecords.updateList(parentId, childrenlist); - } - - private void executeDelete(final VirtualFile file) { - if (!file.exists()) { - LOG.error("Deleting a file, which does not exist: " + file.getPath()); - } - else { - clearIdCache(); - - final int id = getFileId(file); - - final VirtualFile parent = file.getParent(); - final int parentId = parent != null ? getFileId(parent) : 0; - - myRecords.deleteRecordRecursively(id); - - if (parentId != 0) { - removeIdFromParentList(parentId, id); - VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent(); - assert directory != null; - - directory.removeChild(file); - } - else { - synchronized (LOCK) { - myRoots.remove(file.getUrl()); - try { - myRecords.deleteRootRecord(id); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - invalidateSubtree(file); - } - } - - private static void invalidateSubtree(final VirtualFile file) { - final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file; - impl.invalidate(); - for (VirtualFile child : impl.getCachedChildren()) { - invalidateSubtree(child); - } - } - - private void removeIdFromParentList(final int parentId, final int id) { - int[] childList = myRecords.list(parentId); - childList = ArrayUtil.remove(childList, ArrayUtil.indexOf(childList, id)); - myRecords.updateList(parentId, childList); - } - - private void executeRename(final VirtualFile file, final String newName) { - ((VirtualFileSystemEntry)file).setName(newName); - final int id = getFileId(file); - myRecords.setName(id, newName); - } - - private void executeSetWritable(final VirtualFile file, final boolean writableFlag) { - setFlag(file, IS_READ_ONLY, !writableFlag); - } - - private void setFlag(VirtualFile file, int mask, boolean value) { - setFlag(getFileId(file), mask, value); - } - - private void setFlag(final int id, final int mask, final boolean value) { - int oldFlags = FSRecords.getFlags(id); - int flags = value ? oldFlags | mask : oldFlags & ~mask; - - if (oldFlags != flags) { - myRecords.setFlags(id, flags, true); - } - } - - private static boolean checkFlag(VirtualFile file, int mask) { - return (FSRecords.getFlags(getFileId(file)) & mask) != 0; - } - - private void executeTouch(final VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) { - if (reloadContentFromDelegate) { - setFlag(file, MUST_RELOAD_CONTENT, true); - } - - final NewVirtualFileSystem delegate = getDelegate(file); - myRecords.setLength(getFileId(file), delegate.getLength(file)); - myRecords.setTimestamp(getFileId(file), delegate.getTimeStamp(file)); - - ((NewVirtualFile)file).setModificationStamp(newModificationStamp); - } - - @SuppressWarnings({"UnusedDeclaration"}) - private void executeCopy(final VirtualFile from, final VirtualFile newParent, final String copyName) { - executeCreateChild(newParent, copyName); - } - - private void executeMove(final VirtualFile what, final VirtualFile newParent) { - final int whatId = getFileId(what); - final int newParentId = getFileId(newParent); - final int oldParentId = getFileId(what.getParent()); - - removeIdFromParentList(oldParentId, whatId); - appendIdToParentList(newParentId, whatId); - - ((VirtualFileSystemEntry)what).setParent(newParent); - myRecords.setParent(whatId, newParentId); - } - - public String getName(final int id) { - assert id > 0; - return FSRecords.getName(id); - } - - public void cleanPersistedContents() { - try { - final int[] roots = myRecords.listRoots(); - for (int root : roots) { - cleanPersistedContentsRecursively(root); - } - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void cleanPersistedContentsRecursively(int id) { - if (isDirectory(id)) { - for (int child : myRecords.list(id)) { - cleanPersistedContentsRecursively(child); - } - } - else { - setFlag(id, MUST_RELOAD_CONTENT, true); - } - } - - private static int maxIntellisenseFileSize() { - final int maxLimitBytes = (int)FILE_LENGTH_TO_CACHE_THRESHOLD; - final String userLimitKb = System.getProperty(MAX_INTELLISENSE_SIZE_PROPERTY); - try { - return userLimitKb != null ? Math.min(Integer.parseInt(userLimitKb) * 1024, maxLimitBytes) : maxLimitBytes; - } - catch (NumberFormatException ignored) { - return maxLimitBytes; - } - } -} +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.openapi.vfs.newvfs.persistent; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationEx; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.ShutDownTracker; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.newvfs.*; +import com.intellij.openapi.vfs.newvfs.events.*; +import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile; +import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; +import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap; +import com.intellij.util.io.DupOutputStream; +import com.intellij.util.io.ReplicatorInputStream; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class PersistentFS extends ManagingFS implements ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS"); + + private static final int CHILDREN_CACHED_FLAG = 0x01; + static final int IS_DIRECTORY_FLAG = 0x02; + private static final int IS_READ_ONLY = 0x04; + private static final int MUST_RELOAD_CONTENT = 0x08; + + static final int ALL_VALID_FLAGS = CHILDREN_CACHED_FLAG | IS_DIRECTORY_FLAG | IS_READ_ONLY | MUST_RELOAD_CONTENT; + + public static final long FILE_LENGTH_TO_CACHE_THRESHOLD = 20 * 1024 * 1024; // 20 megabytes + + private final FSRecords myRecords; + private final MessageBus myEventsBus; + + private final Map myRoots = new HashMap(); + private final Object INPUT_LOCK = new Object(); + /** + * always in range [0, PersistentFS.FILE_LENGTH_TO_CACHE_THRESHOLD] + */ + public static final int MAX_INTELLISENSE_FILESIZE = maxIntellisenseFileSize(); + @NonNls private static final String MAX_INTELLISENSE_SIZE_PROPERTY = "idea.max.intellisense.filesize"; + + public PersistentFS(MessageBus bus) { + myEventsBus = bus; + myRecords = new FSRecords(); + + /* + PropertiesComponent properties = PropertiesComponent.getInstance(); + if (properties != null && !properties.isTrueValue("AntivirusActivityReporter.disabled")) { + AntivirusDetector.getInstance().enable(new Runnable() { + public void run() { + LOG.info("Antivirus activity detected. Please make sure IDEA system and caches directory as well as project foler are exluded from antivirus on-the-fly check list."); + } + }); + } + */ + ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { + public void run() { + performShutdown(); + } + }); + } + + public void disposeComponent() { + performShutdown(); + } + + private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(Boolean.FALSE); + private void performShutdown() { + if (!myShutdownPerformed.getAndSet(Boolean.TRUE)) { + LOG.info("VFS dispose started"); + myRecords.dispose(); + LOG.info("VFS dispose completed"); + } + } + + @NonNls + @NotNull + public String getComponentName() { + return "app.component.PersistentFS"; + } + + public void initComponent() { + myRecords.connect(); + } + + public boolean areChildrenLoaded(final VirtualFile dir) { + return areChildrenLoaded(getFileId(dir)); + } + + public long getCreationTimestamp() { + return myRecords.getCreationTimestamp(); + } + + private static NewVirtualFileSystem getDelegate(VirtualFile file) { + return (NewVirtualFileSystem)file.getFileSystem(); + } + + public boolean wereChildrenAccessed(final VirtualFile dir) { + return myRecords.wereChildrenAccessed(getFileId(dir)); + } + + public String[] list(final VirtualFile file) { + int id = getFileId(file); + if (areChildrenLoaded(id)) { + return listPersisted(file); + } + else { + return persistAllChildren(file); + } + } + + public String[] listPersisted(final VirtualFile file) { + return listPersisted(getFileId(file)); + } + + private String[] listPersisted(final int id) { + final int[] childrenIds = myRecords.list(id); + String[] names = ArrayUtil.newStringArray(childrenIds.length); + for (int i = 0; i < childrenIds.length; i++) { + names[i] = FSRecords.getName(childrenIds[i]); + } + return names; + } + + private String[] persistAllChildren(final VirtualFile file) { + int id = getFileId(file); + String[] currentNames = listPersisted(file); + int[] currentIds = myRecords.list(id); + + final NewVirtualFileSystem delegate = getDelegate(file); + String[] delegateNames = VfsUtil.filterNames(delegate.list(file)); + if (delegateNames.length == 0 && currentNames.length > 0) return currentNames; + + final String[] names; + if (currentNames.length == 0) { + names = delegateNames; + } + else { + Set allNamesSet = new LinkedHashSet((currentNames.length + delegateNames.length) * 2); + ContainerUtil.addAll(allNamesSet, currentNames); + ContainerUtil.addAll(allNamesSet, delegateNames); + names = ArrayUtil.toStringArray(allNamesSet); + } + + final int[] childrenIds = ArrayUtil.newIntArray(names.length); + + for (int i = 0; i < names.length; i++) { + final String name = names[i]; + int idx = ArrayUtil.indexOf(currentNames, name); + if (idx >= 0) { + childrenIds[i] = currentIds[idx]; + } + else { + int childId = FSRecords.createRecord(); + copyRecordFromDelegateFS(childId, id, new FakeVirtualFile(file, name), delegate); + childrenIds[i] = childId; + } + } + + myRecords.updateList(id, childrenIds); + int flags = FSRecords.getFlags(id); + myRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true); + + return names; + } + + public int[] listIds(VirtualFile parent) { + final int parentId = getFileId(parent); + + if (!areChildrenLoaded(parentId)) { + list(parent); + } + + return myRecords.list(parentId); + } + + + private static boolean areChildrenLoaded(final int parentId) { + final int mask = CHILDREN_CACHED_FLAG; + return (FSRecords.getFlags(parentId) & mask) != 0; + } + + @Nullable + public DataInputStream readAttribute(final VirtualFile file, final FileAttribute att) { + return myRecords.readAttribute(getFileId(file), att.getId()); + } + + public DataOutputStream writeAttribute(final VirtualFile file, final FileAttribute att) { + return myRecords.writeAttribute(getFileId(file), att.getId(), att.isFixedSize()); + } + + @Nullable + private DataInputStream readContent(VirtualFile file) { + return myRecords.readContent(getFileId(file)); + } + + @Nullable + private DataInputStream readContentById(int contentId) { + return myRecords.readContentById(contentId); + } + + private DataOutputStream writeContent(VirtualFile file, boolean readOnly) { + return myRecords.writeContent(getFileId(file), readOnly); + } + + private void writeContent(VirtualFile file, byte[] content, boolean readOnly) throws IOException { + myRecords.writeContent(getFileId(file), content, readOnly); + } + + public int storeUnlinkedContent(byte[] bytes) { + return myRecords.storeUnlinkedContent(bytes); + } + + public int getModificationCount(final VirtualFile file) { + final int id = getFileId(file); + return FSRecords.getModCount(id); + } + + @Override + public int getCheapFileSystemModificationCount() { + return FSRecords.getLocalModCount(); + } + + public int getFilesystemModificationCount() { + return FSRecords.getModCount(); + } + + private void copyRecordFromDelegateFS(final int id, final int parentId, final VirtualFile file, NewVirtualFileSystem delegate) { + if (id == parentId) { + LOG.error("Cyclic parent-child relations for file: " + file); + return; + } + + String name = file.getName(); + + if (name.length() > 0 && namesEqual(delegate, name, FSRecords.getName(id))) return; // TODO: Handle root attributes change. + + if (name.length() == 0) { // TODO: hack + if (areChildrenLoaded(id)) return; + } + + myRecords.setParent(id, parentId); + myRecords.setName(id, name); + + myRecords.setTimestamp(id, delegate.getTimeStamp(file)); + myRecords.setFlags(id, (delegate.isDirectory(file) ? IS_DIRECTORY_FLAG : 0) | (delegate.isWritable(file) ? 0 : IS_READ_ONLY), true); + + myRecords.setLength(id, -1L); + + // TODO!!!: More attributes? + } + + public boolean isDirectory(final VirtualFile file) { + final int id = getFileId(file); + return isDirectory(id); + } + + public boolean isDirectory(final int id) { + assert id > 0; + return (FSRecords.getFlags(id) & IS_DIRECTORY_FLAG) != 0; + } + + public int getParent(final int id) { + assert id > 0; + return FSRecords.getParent(id); + } + + private static boolean namesEqual(VirtualFileSystem fs, String n1, String n2) { + return ((NewVirtualFileSystem)fs).isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2); + } + + public boolean exists(final VirtualFile fileOrDirectory) { + return ((VirtualFileWithId)fileOrDirectory).getId() > 0; + } + + public long getTimeStamp(final VirtualFile file) { + final int id = getFileId(file); + return FSRecords.getTimestamp(id); + } + + public void setTimeStamp(final VirtualFile file, final long modstamp) throws IOException { + final int id = getFileId(file); + + myRecords.setTimestamp(id, modstamp); + getDelegate(file).setTimeStamp(file, modstamp); + } + + private static int getFileId(final VirtualFile file) { + final int id = ((VirtualFileWithId)file).getId(); + if (id <= 0) { + throw new InvalidVirtualFileAccessException(file); + } + return id; + } + + public boolean isWritable(final VirtualFile file) { + final int id = getFileId(file); + + return (FSRecords.getFlags(id) & IS_READ_ONLY) == 0; + } + + public void setWritable(final VirtualFile file, final boolean writableFlag) throws IOException { + getDelegate(file).setWritable(file, writableFlag); + processEvent(new VFilePropertyChangeEvent(this, file, VirtualFile.PROP_WRITABLE, isWritable(file), writableFlag, false)); + } + + public int getId(final VirtualFile parent, final String childName) { + final NewVirtualFileSystem delegate = getDelegate(parent); + final int parentId = getFileId(parent); + + final int[] children = myRecords.list(parentId); + for (final int childId : children) { + if (namesEqual(delegate, childName, FSRecords.getName(childId))) return childId; + } + + VirtualFile fake = new FakeVirtualFile(parent, childName); + if (delegate.exists(fake)) { + int child = FSRecords.createRecord(); + copyRecordFromDelegateFS(child, parentId, fake, delegate); + myRecords.updateList(parentId, ArrayUtil.append(children, child)); + return child; + } + + return 0; + } + + public long getLength(final VirtualFile file) { + final int id = getFileId(file); + + long len = FSRecords.getLength(id); + if (len == -1) { + len = (int)getDelegate(file).getLength(file); + myRecords.setLength(id, len); + } + + return len; + } + + public VirtualFile copyFile(final Object requestor, final VirtualFile file, final VirtualFile newParent, final String copyName) + throws IOException { + getDelegate(file).copyFile(requestor, file, newParent, copyName); + processEvent(new VFileCopyEvent(requestor, file, newParent, copyName)); + + final VirtualFile child = newParent.findChild(copyName); + if (child == null) { + throw new IOException("Cannot create child"); + } + return child; + } + + public VirtualFile createChildDirectory(final Object requestor, final VirtualFile parent, final String dir) throws IOException { + getDelegate(parent).createChildDirectory(requestor, parent, dir); + processEvent(new VFileCreateEvent(requestor, parent, dir, true, false)); + + final VirtualFile child = parent.findChild(dir); + if (child == null) { + throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath()); + } + return child; + } + + public VirtualFile createChildFile(final Object requestor, final VirtualFile parent, final String file) throws IOException { + getDelegate(parent).createChildFile(requestor, parent, file); + processEvent(new VFileCreateEvent(requestor, parent, file, false, false)); + + final VirtualFile child = parent.findChild(file); + if (child == null) { + throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath()); + } + return child; + } + + public void deleteFile(final Object requestor, final VirtualFile file) throws IOException { + final NewVirtualFileSystem delegate = getDelegate(file); + delegate.deleteFile(requestor, file); + + if (!delegate.exists(file)) { + processEvent(new VFileDeleteEvent(requestor, file, false)); + } + } + + public void renameFile(final Object requestor, final VirtualFile file, final String newName) throws IOException { + getDelegate(file).renameFile(requestor, file, newName); + processEvent(new VFilePropertyChangeEvent(requestor, file, VirtualFile.PROP_NAME, file.getName(), newName, false)); + } + + private static final boolean noCaching = Boolean.getBoolean("idea.no.content.caching" ); + + @NotNull + public byte[] contentsToByteArray(final VirtualFile file) throws IOException { + return contentsToByteArray(file, true); + } + + @NotNull + public byte[] contentsToByteArray(final VirtualFile file, boolean cacheContent) throws IOException { + InputStream contentStream = null; + boolean reloadFromDelegate; + synchronized (INPUT_LOCK) { + reloadFromDelegate = mustReloadContent(file) || (contentStream = readContent(file)) == null; + } + + if (reloadFromDelegate) { + final NewVirtualFileSystem delegate = getDelegate(file); + final byte[] content = delegate.contentsToByteArray(file); + + ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication(); + if ((!delegate.isReadOnly() || (!application.isInternal() && !application.isUnitTestMode())) && + !noCaching && content.length <= FILE_LENGTH_TO_CACHE_THRESHOLD) { + synchronized (INPUT_LOCK) { + writeContent(file, content, delegate.isReadOnly()); + + myRecords.setLength(getFileId(file), content.length); + setFlag(file, MUST_RELOAD_CONTENT, false); + } + } + + return content; + } + else { + try { + return FileUtil.loadBytes(contentStream, (int)file.getLength()); + } + catch (IOException e) { + throw FSRecords.handleError(e); + } + } + } + + public byte[] contentsToByteArray(int contentId) throws IOException { + return FileUtil.loadBytes(readContentById(contentId)); + } + + @NotNull + public InputStream getInputStream(final VirtualFile file) throws IOException { + synchronized (INPUT_LOCK) { + InputStream contentStream; + if (mustReloadContent(file) || (contentStream = readContent(file)) == null) { + final NewVirtualFileSystem delegate = getDelegate(file); + final long len = delegate.getLength(file); + final InputStream nativeStream = delegate.getInputStream(file); + + if (len > FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream; + + return createReplicator(file, nativeStream, len, delegate.isReadOnly()); + } + else { + return contentStream; + } + } + } + + private ReplicatorInputStream createReplicator(final VirtualFile file, final InputStream nativeStream, final long len, final boolean readOnly) { + final ByteArrayOutputStream cache = new ByteArrayOutputStream((int)len); + return new ReplicatorInputStream(nativeStream, cache) { + public void close() throws IOException { + super.close(); + + synchronized (INPUT_LOCK) { + if (getBytesRead() == len) { + writeContent(file, cache.toByteArray(), readOnly); + myRecords.setLength(getFileId(file), len); + setFlag(file, MUST_RELOAD_CONTENT, false); + } + else { + setFlag(file, MUST_RELOAD_CONTENT, true); + } + } + } + }; + } + + private static boolean mustReloadContent(final VirtualFile file) { + return checkFlag(file, MUST_RELOAD_CONTENT) || FSRecords.getLength(getFileId(file)) == -1L; + } + + @NotNull + public OutputStream getOutputStream(final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp) + throws IOException { + final VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false); + + final List events = Collections.singletonList(event); + + final BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES); + publisher.before(events); + + final ByteArrayOutputStream stream = new ByteArrayOutputStream() { + public void close() throws IOException { + super.close(); + + NewVirtualFileSystem delegate = getDelegate(file); + final OutputStream outputStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp); + + //noinspection IOResourceOpenedButNotSafelyClosed + final DupOutputStream sink = new DupOutputStream(new BufferedOutputStream(writeContent(file, delegate.isReadOnly())), outputStream) { + public void close() throws IOException { + try { + super.close(); + } + finally { + executeTouch(file, false, event.getModificationStamp()); + publisher.after(events); + } + } + }; + + try { + sink.write(buf, 0, count); + } + finally { + sink.close(); + } + } + }; + + final byte[] bom = file.getBOM(); + if (bom != null) { + stream.write(bom); + } + + return stream; + } + + public void restoreContent(VirtualFile file, Object requestor, final long modStamp, final long timeStamp) { + + } + + public int acquireContent(VirtualFile file) { + return myRecords.acquireFileContent(getFileId(file)); + } + + public void releaseContent(int contentId) { + myRecords.releaseContent(contentId); + } + + public int getCurrentContentId(VirtualFile file) { + return myRecords.getContentId(getFileId(file)); + } + + public void moveFile(final Object requestor, final VirtualFile file, final VirtualFile newParent) throws IOException { + getDelegate(file).moveFile(requestor, file, newParent); + processEvent(new VFileMoveEvent(requestor, file, newParent)); + } + + private void processEvent(VFileEvent event) { + processEvents(Collections.singletonList(event)); + } + + private static final Comparator DEPTH_COMPARATOR = new Comparator() { + public int compare(final VFileDeleteEvent o1, final VFileDeleteEvent o2) { + return o1.getFileDepth() - o2.getFileDepth(); + } + }; + + private static List validateEvents(List events) { + List filtered = new ArrayList(events.size()); + List deletionList = new ArrayList(); + + for (VFileEvent event : events) { + if (event.isValid()) { + if (event instanceof VFileDeleteEvent) { + deletionList.add((VFileDeleteEvent)event); + } + else { + filtered.add(event); + } + } + } + + ContainerUtil.quickSort(deletionList, DEPTH_COMPARATOR); + List filesToBeDeleted = new ArrayList(); + for (VFileDeleteEvent event : deletionList) { + boolean ok = true; + VirtualFile candidate = event.getFile(); + for (VirtualFile file : filesToBeDeleted) { + if (VfsUtil.isAncestor(file, candidate, false)) { + ok = false; + break; + } + } + + if (ok) { + filtered.add(event); + if (candidate.isDirectory()) { + filesToBeDeleted.add(candidate); + } + } + } + + return filtered; + } + + public void processEvents(List events) { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + + events = validateEvents(events); + + myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES).before(events); + for (VFileEvent event : events) { + applyEvent(event); + } + myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES).after(events); + } + + public static final Object LOCK = new Object(); + + @Nullable + public NewVirtualFile findRoot(final String basePath, final NewVirtualFileSystem fs) { // TODO: read/write locks instead of sycnrhonized + synchronized (LOCK) { + final String rootUrl = fs.getProtocol() + "://" + basePath; + NewVirtualFile root = myRoots.get(rootUrl); + if (root == null) { + try { + final int rootId = myRecords.findRootRecord(rootUrl); + if (basePath.length() > 0) { + root = new VirtualDirectoryImpl(basePath, null, fs, rootId); + } + else { + // fake root for windows + root = new VirtualDirectoryImpl(basePath, null, fs, rootId) { + @NotNull + public VirtualFile[] getChildren() { + return getRoots(fs); + } + }; + } + if (!fs.exists(root)) return null; + + copyRecordFromDelegateFS(rootId, 0, root, fs); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + if (basePath.length() > 0) { + myRoots.put(rootUrl, root); + } + } + + return root; + } + } + + public void refresh(final boolean asynchronous) { + final NewVirtualFile[] roots; + synchronized (LOCK) { + Collection values = myRoots.values(); + roots = values.toArray(new NewVirtualFile[values.size()]); + } + + RefreshQueue.getInstance().refresh(asynchronous, true, null, roots); + } + + public VirtualFile[] getLocalRoots() { + List roots; + synchronized (LOCK) { + roots = new ArrayList(myRoots.values()); + + final Iterator it = roots.iterator(); + while (it.hasNext()) { + NewVirtualFile file = it.next(); + if (!file.isInLocalFileSystem()) { + it.remove(); + } + } + } + + return VfsUtil.toVirtualFileArray(roots); + } + + //guarded by dirCacheReadLock/dirCacheWriteLock + private final StripedLockIntObjectConcurrentHashMap myIdToDirCache = new StripedLockIntObjectConcurrentHashMap(); + + public void clearIdCache() { + myIdToDirCache.clear(); + } + + @Nullable + public NewVirtualFile findFileById(final int id) { + return _findFileById(id, false); + } + + + @Nullable + public NewVirtualFile findFileByIdIfCached(final int id) { + return _findFileById(id, true); + } + + private NewVirtualFile _findFileById(int id, final boolean cachedOnly) { + final NewVirtualFile cached = myIdToDirCache.get(id); + if (cached != null) { + return cached; + } + + NewVirtualFile result = doFindFile(id, cachedOnly); + + if (result != null && result.isDirectory()) { + NewVirtualFile old = myIdToDirCache.putIfAbsent(id, result); + if (old != null) result = old; + } + return result; + } + + @Nullable + private NewVirtualFile doFindFile(final int id, boolean cachedOnly) { + final int parentId = getParent(id); + if (parentId == 0) { + synchronized (LOCK) { + for (NewVirtualFile root : myRoots.values()) { + if (root.getId() == id) return root; + } + return null; + } + } + else { + NewVirtualFile parentFile = _findFileById(parentId, cachedOnly); + if (parentFile == null) { + return null; + } + return cachedOnly ? parentFile.findChildByIdIfCached(id) : parentFile.findChildById(id); + } + } + + public VirtualFile[] getRoots() { + synchronized (LOCK) { + final Collection roots = myRoots.values(); + return VfsUtil.toVirtualFileArray(roots); + } + } + + public VirtualFile[] getRoots(final NewVirtualFileSystem fs) { + List roots = new ArrayList(); + synchronized (LOCK) { + for (NewVirtualFile root : myRoots.values()) { + if (root.getFileSystem() == fs) { + roots.add(root); + } + } + } + + return VfsUtil.toVirtualFileArray(roots); + } + + private void applyEvent(final VFileEvent event) { + /*System.out.println("Apply: " + event);*/ + + try { + if (event instanceof VFileCreateEvent) { + final VFileCreateEvent createEvent = (VFileCreateEvent)event; + executeCreateChild(createEvent.getParent(), createEvent.getChildName()); + } + else if (event instanceof VFileDeleteEvent) { + final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; + executeDelete(deleteEvent.getFile()); + } + else if (event instanceof VFileContentChangeEvent) { + final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent)event; + executeTouch(contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp()); + } + else if (event instanceof VFileCopyEvent) { + final VFileCopyEvent copyEvent = (VFileCopyEvent)event; + executeCopy(copyEvent.getFile(), copyEvent.getNewParent(), copyEvent.getNewChildName()); + } + else if (event instanceof VFileMoveEvent) { + final VFileMoveEvent moveEvent = (VFileMoveEvent)event; + executeMove(moveEvent.getFile(), moveEvent.getNewParent()); + } + else if (event instanceof VFilePropertyChangeEvent) { + final VFilePropertyChangeEvent propertyChangeEvent = (VFilePropertyChangeEvent)event; + if (VirtualFile.PROP_NAME.equals(propertyChangeEvent.getPropertyName())) { + executeRename(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue()); + } + else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) { + executeSetWritable(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue()); + } + } + } + catch (Exception e) { + // Exception applying single event should not prevent other events from applying. + LOG.error(e); + } + } + + @NonNls + public String toString() { + return "PersistentFS"; + } + + private void executeCreateChild(final VirtualFile parent, final String name) { + final NewVirtualFileSystem delegate = getDelegate(parent); + VirtualFile fakeFile = new FakeVirtualFile(parent, name); + if (delegate.exists(fakeFile)) { + final int parentId = getFileId(parent); + int childId = FSRecords.createRecord(); + copyRecordFromDelegateFS(childId, parentId, fakeFile, delegate); + + appendIdToParentList(parentId, childId); + final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent; + dir.addChild(dir.createChild(name, childId)); + } + } + + private void appendIdToParentList(final int parentId, final int childId) { + int[] childrenlist = myRecords.list(parentId); + childrenlist = ArrayUtil.append(childrenlist, childId); + myRecords.updateList(parentId, childrenlist); + } + + private void executeDelete(final VirtualFile file) { + if (!file.exists()) { + LOG.error("Deleting a file, which does not exist: " + file.getPath()); + } + else { + clearIdCache(); + + final int id = getFileId(file); + + final VirtualFile parent = file.getParent(); + final int parentId = parent != null ? getFileId(parent) : 0; + + myRecords.deleteRecordRecursively(id); + + if (parentId != 0) { + removeIdFromParentList(parentId, id); + VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent(); + assert directory != null; + + directory.removeChild(file); + } + else { + synchronized (LOCK) { + myRoots.remove(file.getUrl()); + try { + myRecords.deleteRootRecord(id); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + invalidateSubtree(file); + } + } + + private static void invalidateSubtree(final VirtualFile file) { + final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file; + impl.invalidate(); + for (VirtualFile child : impl.getCachedChildren()) { + invalidateSubtree(child); + } + } + + private void removeIdFromParentList(final int parentId, final int id) { + int[] childList = myRecords.list(parentId); + childList = ArrayUtil.remove(childList, ArrayUtil.indexOf(childList, id)); + myRecords.updateList(parentId, childList); + } + + private void executeRename(final VirtualFile file, final String newName) { + ((VirtualFileSystemEntry)file).setName(newName); + final int id = getFileId(file); + myRecords.setName(id, newName); + } + + private void executeSetWritable(final VirtualFile file, final boolean writableFlag) { + setFlag(file, IS_READ_ONLY, !writableFlag); + } + + private void setFlag(VirtualFile file, int mask, boolean value) { + setFlag(getFileId(file), mask, value); + } + + private void setFlag(final int id, final int mask, final boolean value) { + int oldFlags = FSRecords.getFlags(id); + int flags = value ? oldFlags | mask : oldFlags & ~mask; + + if (oldFlags != flags) { + myRecords.setFlags(id, flags, true); + } + } + + private static boolean checkFlag(VirtualFile file, int mask) { + return (FSRecords.getFlags(getFileId(file)) & mask) != 0; + } + + private void executeTouch(final VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) { + if (reloadContentFromDelegate) { + setFlag(file, MUST_RELOAD_CONTENT, true); + } + + final NewVirtualFileSystem delegate = getDelegate(file); + myRecords.setLength(getFileId(file), delegate.getLength(file)); + myRecords.setTimestamp(getFileId(file), delegate.getTimeStamp(file)); + + ((NewVirtualFile)file).setModificationStamp(newModificationStamp); + } + + @SuppressWarnings({"UnusedDeclaration"}) + private void executeCopy(final VirtualFile from, final VirtualFile newParent, final String copyName) { + executeCreateChild(newParent, copyName); + } + + private void executeMove(final VirtualFile what, final VirtualFile newParent) { + final int whatId = getFileId(what); + final int newParentId = getFileId(newParent); + final int oldParentId = getFileId(what.getParent()); + + removeIdFromParentList(oldParentId, whatId); + appendIdToParentList(newParentId, whatId); + + ((VirtualFileSystemEntry)what).setParent(newParent); + myRecords.setParent(whatId, newParentId); + } + + public String getName(final int id) { + assert id > 0; + return FSRecords.getName(id); + } + + public void cleanPersistedContents() { + try { + final int[] roots = myRecords.listRoots(); + for (int root : roots) { + cleanPersistedContentsRecursively(root); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void cleanPersistedContentsRecursively(int id) { + if (isDirectory(id)) { + for (int child : myRecords.list(id)) { + cleanPersistedContentsRecursively(child); + } + } + else { + setFlag(id, MUST_RELOAD_CONTENT, true); + } + } + + private static int maxIntellisenseFileSize() { + final int maxLimitBytes = (int)FILE_LENGTH_TO_CACHE_THRESHOLD; + final String userLimitKb = System.getProperty(MAX_INTELLISENSE_SIZE_PROPERTY); + try { + return userLimitKb != null ? Math.min(Integer.parseInt(userLimitKb) * 1024, maxLimitBytes) : maxLimitBytes; + } + catch (NumberFormatException ignored) { + return maxLimitBytes; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/CommandProcessor.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/CommandProcessor.java index 465f55c7ff53..15996cb6c624 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/CommandProcessor.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/CommandProcessor.java @@ -1,130 +1,130 @@ -/* - * 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.wm.impl; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.wm.impl.commands.FinalizableCommand; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public final class CommandProcessor implements Runnable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.CommandProcessor"); - private final Object myLock = new Object(); - - private final List myCommandGroupList = new ArrayList(); - private int myCommandCount; - - public final int getCommandCount() { - synchronized (myLock) { - return myCommandCount; - } - } - - /** - * Executes passed batch of commands. Note, that the processor surround the - * commands with BlockFocusEventsCmd - UnbockFocusEventsCmd. It's required to - * prevent focus handling of events which is caused by the commands to be executed. - */ - public final void execute(@NotNull List commandList, @NotNull Condition expired) { - synchronized (myLock) { - final boolean isBusy = myCommandCount > 0; - - final CommandGroup commandGroup = new CommandGroup(commandList, expired); - myCommandGroupList.add(commandGroup); - myCommandCount += commandList.size(); - - if (!isBusy) { - run(); - } - } - } - - public final void run() { - synchronized (myLock) { - final CommandGroup commandGroup = getNextCommandGroup(); - if (commandGroup == null || commandGroup.isEmpty()) return; - - final Condition conditionForGroup = commandGroup.getExpireCondition(); - - final FinalizableCommand command = commandGroup.takeNextCommand(); - myCommandCount--; - - final Condition expire = command.getExpireCondition() != null ? command.getExpireCondition() : conditionForGroup; - - if (LOG.isDebugEnabled()) { - LOG.debug("CommandProcessor.run " + command); - } - // max. I'm not actually quite sure this should have NON_MODAL modality but it should - // definitely have some since runnables in command list may (and do) request some PSI activity - final boolean queueNext = myCommandCount > 0; - Application application = ApplicationManager.getApplication(); - application.getInvokator().invokeLater(command, ModalityState.NON_MODAL, expire == null ? application.getDisposed() : expire).doWhenDone(new Runnable() { - public void run() { - if (queueNext) { - CommandProcessor.this.run(); - } - } - }); - } - } - - @Nullable - private CommandGroup getNextCommandGroup() { - while (!myCommandGroupList.isEmpty()) { - final CommandGroup candidate = myCommandGroupList.get(0); - if (!candidate.isEmpty()) { - return candidate; - } - myCommandGroupList.remove(candidate); - } - - return null; - } - - private static class CommandGroup { - private final List myList; - private Condition myExpireCondition; - - private CommandGroup(@NotNull List list, @NotNull Condition expireCondition) { - myList = list; - myExpireCondition = expireCondition; - } - - public Condition getExpireCondition() { - return myExpireCondition; - } - - public boolean isEmpty() { - return myList.isEmpty(); - } - - public FinalizableCommand takeNextCommand() { - FinalizableCommand command = myList.remove(0); - if (isEmpty()) { - // memory leak otherwise - myExpireCondition = Condition.TRUE; - } - return command; - } - } -} +/* + * 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.wm.impl; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.wm.impl.commands.FinalizableCommand; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public final class CommandProcessor implements Runnable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.CommandProcessor"); + private final Object myLock = new Object(); + + private final List myCommandGroupList = new ArrayList(); + private int myCommandCount; + + public final int getCommandCount() { + synchronized (myLock) { + return myCommandCount; + } + } + + /** + * Executes passed batch of commands. Note, that the processor surround the + * commands with BlockFocusEventsCmd - UnbockFocusEventsCmd. It's required to + * prevent focus handling of events which is caused by the commands to be executed. + */ + public final void execute(@NotNull List commandList, @NotNull Condition expired) { + synchronized (myLock) { + final boolean isBusy = myCommandCount > 0; + + final CommandGroup commandGroup = new CommandGroup(commandList, expired); + myCommandGroupList.add(commandGroup); + myCommandCount += commandList.size(); + + if (!isBusy) { + run(); + } + } + } + + public final void run() { + synchronized (myLock) { + final CommandGroup commandGroup = getNextCommandGroup(); + if (commandGroup == null || commandGroup.isEmpty()) return; + + final Condition conditionForGroup = commandGroup.getExpireCondition(); + + final FinalizableCommand command = commandGroup.takeNextCommand(); + myCommandCount--; + + final Condition expire = command.getExpireCondition() != null ? command.getExpireCondition() : conditionForGroup; + + if (LOG.isDebugEnabled()) { + LOG.debug("CommandProcessor.run " + command); + } + // max. I'm not actually quite sure this should have NON_MODAL modality but it should + // definitely have some since runnables in command list may (and do) request some PSI activity + final boolean queueNext = myCommandCount > 0; + Application application = ApplicationManager.getApplication(); + application.getInvokator().invokeLater(command, ModalityState.NON_MODAL, expire == null ? application.getDisposed() : expire).doWhenDone(new Runnable() { + public void run() { + if (queueNext) { + CommandProcessor.this.run(); + } + } + }); + } + } + + @Nullable + private CommandGroup getNextCommandGroup() { + while (!myCommandGroupList.isEmpty()) { + final CommandGroup candidate = myCommandGroupList.get(0); + if (!candidate.isEmpty()) { + return candidate; + } + myCommandGroupList.remove(candidate); + } + + return null; + } + + private static class CommandGroup { + private final List myList; + private Condition myExpireCondition; + + private CommandGroup(@NotNull List list, @NotNull Condition expireCondition) { + myList = list; + myExpireCondition = expireCondition; + } + + public Condition getExpireCondition() { + return myExpireCondition; + } + + public boolean isEmpty() { + return myList.isEmpty(); + } + + public FinalizableCommand takeNextCommand() { + FinalizableCommand command = myList.remove(0); + if (isEmpty()) { + // memory leak otherwise + myExpireCondition = Condition.TRUE; + } + return command; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java index 6f80e86c1b7e..3d15040d4cf4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java @@ -1,715 +1,715 @@ -/* - * 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.wm.impl; - -import com.intellij.ide.IdeEventQueue; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationAdapter; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.EdtRunnable; -import com.intellij.openapi.util.Expirable; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.*; -import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; -import com.intellij.ui.FocusTrackback; -import com.intellij.util.Alarm; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.FocusEvent; -import java.awt.event.KeyEvent; -import java.awt.event.WindowEvent; -import java.lang.ref.WeakReference; -import java.util.*; -import java.util.List; - -public class FocusManagerImpl extends IdeFocusManager implements Disposable { - - private final Application myApp; - - private FocusCommand myRequestFocusCmd; - private final ArrayList myFocusRequests = new ArrayList(); - - private final ArrayList myToDispatchOnDone = new ArrayList(); - private int myFlushingIdleRequestsEntryCount = 0; - - private WeakReference myLastForcedRequest = new WeakReference(null); - - private FocusCommand myFocusCommandOnAppActivation; - private ActionCallback myCallbackOnActivation; - - private final IdeEventQueue myQueue; - private final KeyProcessorConext myKeyProcessorContext = new KeyProcessorConext(); - - private long myCmdTimestamp; - private long myForcedCmdTimestamp; - - final EdtAlarm myFocusedComponentAlaram; - private final EdtAlarm myForcedFocusRequestsAlarm; - - private final EdtAlarm myIdleAlarm; - private final Set myIdleRequests = new LinkedHashSet(); - private final EdtRunnable myIdleRunnable = new EdtRunnable() { - public void runEdt() { - if (canFlushIdleRequests()) { - flushIdleRequests(); - } - else { - restartIdleAlarm(); - } - } - }; - private boolean myFlushWasDelayedToFixFocus; - - private boolean canFlushIdleRequests() { - return isFocusTransferReady() && !isIdleQueueEmpty(); - } - - private final Map> myLastFocused = new HashMap>(); - private final Map> myLastFocusedAtDeactivation = new HashMap>(); - - private DataContext myRunContext; - - public FocusManagerImpl(WindowManager wm) { - myApp = ApplicationManager.getApplication(); - myQueue = IdeEventQueue.getInstance(); - - myFocusedComponentAlaram = new EdtAlarm(this); - myForcedFocusRequestsAlarm = new EdtAlarm(this); - myIdleAlarm = new EdtAlarm(this); - - final AppListener myAppListener = new AppListener(); - myApp.addApplicationListener(myAppListener); - - IdeEventQueue.getInstance().addDispatcher(new IdeEventQueue.EventDispatcher() { - public boolean dispatch(AWTEvent e) { - if (e instanceof FocusEvent) { - final FocusEvent fe = (FocusEvent)e; - final Component c = fe.getComponent(); - if (c instanceof Window || c == null) return false; - - Component parent = UIUtil.findUltimateParent(c); - if (parent instanceof IdeFrame) { - myLastFocused.put((IdeFrame)parent, new WeakReference(c)); - } - } else if (e instanceof WindowEvent) { - if (e.getID() == WindowEvent.WINDOW_CLOSED) { - Window wnd = ((WindowEvent)e).getWindow(); - if (wnd instanceof IdeFrame) { - myLastFocused.remove((IdeFrame)wnd); - myLastFocusedAtDeactivation.remove((IdeFrame)wnd); - } - } - } - - return false; - } - }, this); - - } - - @NotNull - public ActionCallback requestFocus(@NotNull final Component c, final boolean forced) { - return requestFocus(new FocusCommand.ByComponent(c), forced); - } - - @NotNull - public ActionCallback requestFocus(@NotNull final FocusCommand command, final boolean forced) { - final ActionCallback result = new ActionCallback(); - - if (!forced) { - if (!myFocusRequests.contains(command)) { - myFocusRequests.add(command); - } - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - resetUnforcedCommand(command); - _requestFocus(command, forced, result); - } - }); - } - else { - _requestFocus(command, forced, result); - } - - result.doWhenProcessed(new Runnable() { - public void run() { - restartIdleAlarm(); - } - }); - - return result; - } - - private void _requestFocus(final FocusCommand command, final boolean forced, final ActionCallback result) { - if (checkForRejectOrByPass(command, forced, result)) return; - - setCommand(command); - command.setCallback(result); - - if (forced) { - myForcedFocusRequestsAlarm.cancelAllRequests(); - setLastEffectiveForcedRequest(command); - } - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - if (checkForRejectOrByPass(command, forced, result)) return; - - if (myRequestFocusCmd == command) { - final ActionCallback.TimedOut focusTimeout = - new ActionCallback.TimedOut(Registry.intValue("actionSystem.commandProcessingTimeout"), - "Focus command timed out, cmd=" + command, command.getAllocation(), true) { - @Override - protected void onTimeout() { - forceFinishFocusSettledown(command, result); - } - }; - - myCmdTimestamp++; - if (forced) { - myForcedCmdTimestamp++; - } - - command.run().doWhenDone(new Runnable() { - public void run() { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - result.setDone(); - } - }); - } - }).doWhenRejected(new Runnable() { - public void run() { - result.setRejected(); - } - }).doWhenProcessed(new Runnable() { - public void run() { - resetCommand(command, false); - - if (forced) { - myForcedFocusRequestsAlarm.addRequest(new EdtRunnable() { - public void runEdt() { - setLastEffectiveForcedRequest(null); - } - }, 250); - } - } - }).notify(focusTimeout); - } - else { - rejectCommand(command, result); - } - } - }); - } - - private boolean checkForRejectOrByPass(final FocusCommand cmd, final boolean forced, final ActionCallback result) { - if (cmd.isExpired()) { - rejectCommand(cmd, result); - return true; - } - - final FocusCommand lastRequest = getLastEffectiveForcedRequest(); - - if (!forced && !isUnforcedRequestAllowed()) { - if (cmd.equals(lastRequest)) { - resetCommand(cmd, false); - result.setDone(); - } - else { - rejectCommand(cmd, result); - } - return true; - } - - - if (lastRequest != null && lastRequest.dominatesOver(cmd)) { - rejectCommand(cmd, result); - return true; - } - - boolean doNotExecuteBecauseAppIsInactive = - !myApp.isActive() && !canExecuteOnInactiveApplication(cmd) && Registry.is("actionSystem.suspendFocusTransferIfApplicationInactive"); - - if (doNotExecuteBecauseAppIsInactive) { - if (myCallbackOnActivation != null) { - myCallbackOnActivation.setRejected(); - if (myFocusCommandOnAppActivation != null) { - resetCommand(myFocusCommandOnAppActivation, true); - } - } - - myFocusCommandOnAppActivation = cmd; - myCallbackOnActivation = result; - - return true; - } - - return false; - } - - private void setCommand(FocusCommand command) { - myRequestFocusCmd = command; - - if (!myFocusRequests.contains(command)) { - myFocusRequests.add(command); - } - } - - private void resetCommand(FocusCommand cmd, boolean reject) { - if (cmd == myRequestFocusCmd) { - myRequestFocusCmd = null; - } - - final KeyEventProcessor processor = cmd.getProcessor(); - if (processor != null) { - processor.finish(myKeyProcessorContext); - } - - myFocusRequests.remove(cmd); - - if (reject) { - ActionCallback cb = cmd.getCallback(); - if (cb != null && !cb.isProcessed()) { - cmd.getCallback().setRejected(); - } - } - } - - private void resetUnforcedCommand(FocusCommand cmd) { - myFocusRequests.remove(cmd); - } - - private static boolean canExecuteOnInactiveApplication(FocusCommand cmd) { - return cmd.canExecuteOnInactiveApp(); - } - - private void setLastEffectiveForcedRequest(FocusCommand command) { - myLastForcedRequest = new WeakReference(command); - } - - @Nullable - private FocusCommand getLastEffectiveForcedRequest() { - if (myLastForcedRequest == null) return null; - final FocusCommand request = myLastForcedRequest.get(); - return request != null && !request.isExpired() ? request : null; - } - - boolean isUnforcedRequestAllowed() { - return getLastEffectiveForcedRequest() == null; - } - - public static FocusManagerImpl getInstance() { - return (FocusManagerImpl)ApplicationManager.getApplication().getComponent(IdeFocusManager.class); - } - - public void dispose() { - } - - private class KeyProcessorConext implements KeyEventProcessor.Context { - public List getQueue() { - return myToDispatchOnDone; - } - - public void dispatch(final List events) { - doWhenFocusSettlesDown(new Runnable() { - public void run() { - myToDispatchOnDone.addAll(events); - restartIdleAlarm(); - } - }); - } - } - - public void doWhenFocusSettlesDown(@NotNull final Runnable runnable) { - UIUtil.invokeLaterIfNeeded(new Runnable() { - @Override - public void run() { - if (isFlushingIdleRequests()) { - SwingUtilities.invokeLater(this); - return; - } - - if (myRunContext != null) { - runnable.run(); - return; - } - - final boolean needsRestart = isIdleQueueEmpty(); - myIdleRequests.add(runnable); - - if (canFlushIdleRequests()) { - flushIdleRequests(); - } else { - if (needsRestart) { - restartIdleAlarm(); - } - } - } - }); - } - - private void restartIdleAlarm() { - myIdleAlarm.cancelAllRequests(); - myIdleAlarm.addRequest(myIdleRunnable, Registry.intValue("actionSystem.focusIdleTimeout")); - } - - private void flushIdleRequests() { - try { - myFlushingIdleRequestsEntryCount++; - - final KeyEvent[] events = myToDispatchOnDone.toArray(new KeyEvent[myToDispatchOnDone.size()]); - if (events.length > 0) { - IdeEventQueue.getInstance().getKeyEventDispatcher().resetState(); - } - - boolean keyWasPressed = false; - - for (KeyEvent each : events) { - if (!isFocusTransferReady()) break; - - if (!keyWasPressed) { - if (each.getID() == KeyEvent.KEY_PRESSED) { - keyWasPressed = true; - } - else { - myToDispatchOnDone.remove(each); - continue; - } - } - - Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (owner == null) { - owner = JOptionPane.getRootFrame(); - } - - KeyEvent keyEvent = new KeyEvent(owner, each.getID(), each.getWhen(), each.getModifiersEx(), each.getKeyCode(), each.getKeyChar(), - each.getKeyLocation()); - - myToDispatchOnDone.remove(each); - - if (owner != null && SwingUtilities.getWindowAncestor(owner) != null) { - IdeEventQueue.getInstance().dispatchEvent(keyEvent); - } - else { - myQueue._dispatchEvent(keyEvent, true); - } - } - - - if (isPendingKeyEventsRedispatched()) { - boolean focusOk = getFocusOwner() != null || myFlushWasDelayedToFixFocus; - if (!focusOk) { - IdeEventQueue.getInstance().fixStickyFocusedComponents(null); - myFlushWasDelayedToFixFocus = true; - } - - if (canFlushIdleRequests() && myFlushingIdleRequestsEntryCount <= 1 && (focusOk || !myFlushWasDelayedToFixFocus)) { - myFlushWasDelayedToFixFocus = false; - flushNow(); - } - } - } - finally { - myFlushingIdleRequestsEntryCount--; - if (!isIdleQueueEmpty()) { - restartIdleAlarm(); - } - } - } - - private void flushNow() { - final Runnable[] all = myIdleRequests.toArray(new Runnable[myIdleRequests.size()]); - myIdleRequests.clear(); - for (Runnable each : all) { - if (each != null) { - each.run(); - } - } - } - - public boolean isFocusTransferReady() { - if (myRunContext != null) return true; - - invalidateFocusRequestsQueue(); - - if (!myFocusRequests.isEmpty()) return false; - if (myQueue == null) return true; - - return !myQueue.isSuspendMode() && !myQueue.hasFocusEventsPending(); - } - - private void invalidateFocusRequestsQueue() { - if (myFocusRequests.isEmpty()) return; - - FocusCommand[] requests = myFocusRequests.toArray(new FocusCommand[myFocusRequests.size()]); - boolean wasChanged = false; - for (FocusCommand each : requests) { - if (each.isExpired()) { - resetCommand(each, true); - wasChanged = true; - } - } - - if (wasChanged && myFocusRequests.isEmpty()) { - restartIdleAlarm(); - } - } - - private boolean isIdleQueueEmpty() { - return isPendingKeyEventsRedispatched() && myIdleRequests.isEmpty(); - } - - private boolean isPendingKeyEventsRedispatched() { - return myToDispatchOnDone.isEmpty(); - } - - public boolean dispatch(KeyEvent e) { - if (!Registry.is("actionSystem.fixLostTyping")) return false; - - if (isFlushingIdleRequests()) return false; - - if (!isFocusTransferReady() || !isPendingKeyEventsRedispatched()) { - for (FocusCommand each : myFocusRequests) { - final KeyEventProcessor processor = each.getProcessor(); - if (processor != null) { - final Boolean result = processor.dispatch(e, myKeyProcessorContext); - if (result != null) { - return result.booleanValue(); - } - } - } - - myToDispatchOnDone.add(e); - restartIdleAlarm(); - - return true; - } - else { - return false; - } - } - - private boolean isFlushingIdleRequests() { - return myFlushingIdleRequestsEntryCount > 0; - } - - public void suspendKeyProcessingUntil(@NotNull final ActionCallback done) { - requestFocus(new FocusCommand(done) { - public ActionCallback run() { - return done; - } - }.saveAllocation(), true); - } - - public Expirable getTimestamp(final boolean trackOnlyForcedCommands) { - return new Expirable() { - long myOwnStamp = trackOnlyForcedCommands ? myForcedCmdTimestamp : myCmdTimestamp; - - public boolean isExpired() { - return myOwnStamp < (trackOnlyForcedCommands ? myForcedCmdTimestamp : myCmdTimestamp); - } - }; - } - - @Override - public FocusRequestor getFurtherRequestor() { - return new FurtherRequestor(this, getTimestamp(true)); - } - - @Override - public Component getFocusOwner() { - Component result = null; - if (myRunContext != null) { - result = (Component)myRunContext.getData(PlatformDataKeys.CONTEXT_COMPONENT.getName()); - } - - if (result == null) { - result = isFocusBeingTransferred() ? null : KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - } - - final boolean meaninglessOwner = result instanceof JFrame || result instanceof JDialog || result instanceof JWindow || result instanceof JRootPane; - if ((result == null && !isFocusBeingTransferred()) || meaninglessOwner) { - final Component permOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner(); - if (permOwner != null) { - result = permOwner; - } - } - - return result; - } - - @Override - public void runOnOwnContext(DataContext context, Runnable runnable) { - myRunContext = context; - try { - runnable.run(); - } finally { - myRunContext = null; - } - } - - private static class FurtherRequestor implements FocusRequestor { - private final IdeFocusManager myManager; - private final Expirable myExpirable; - - private FurtherRequestor(IdeFocusManager manager, Expirable expirable) { - myManager = manager; - myExpirable = expirable; - } - - @NotNull - @Override - public ActionCallback requestFocus(@NotNull Component c, boolean forced) { - return myExpirable.isExpired() ? new ActionCallback.Rejected() : myManager.requestFocus(c, forced); - } - - @NotNull - @Override - public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) { - return myExpirable.isExpired() ? new ActionCallback.Rejected() : myManager.requestFocus(command, forced); - } - } - - - static class EdtAlarm { - private final Alarm myAlarm; - - private EdtAlarm(Disposable parent) { - myAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD, parent); - } - - public void cancelAllRequests() { - myAlarm.cancelAllRequests(); - } - - public void addRequest(EdtRunnable runnable, int delay) { - myAlarm.addRequest(runnable, delay); - } - } - - private void forceFinishFocusSettledown(FocusCommand cmd, ActionCallback cmdCallback) { - rejectCommand(cmd, cmdCallback); - } - - - private void rejectCommand(FocusCommand cmd, ActionCallback callback) { - resetCommand(cmd, true); - resetUnforcedCommand(cmd); - - callback.setRejected(); - } - - private class AppListener extends ApplicationAdapter { - - @Override - public void applicationDeactivated(IdeFrame ideFrame) { - final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - Component parent = UIUtil.findUltimateParent(owner); - - if (parent == ideFrame) { - myLastFocusedAtDeactivation.put(ideFrame, new WeakReference(owner)); - } - } - - @Override - public void applicationActivated(final IdeFrame ideFrame) { - final FocusCommand cmd = myFocusCommandOnAppActivation; - ActionCallback callback = myCallbackOnActivation; - myFocusCommandOnAppActivation = null; - myCallbackOnActivation = null; - - if (cmd != null) { - requestFocus(cmd, true).notify(callback).doWhenRejected(new Runnable() { - public void run() { - focusLastFocusedComponent(ideFrame); - } - }); - } - else { - focusLastFocusedComponent(ideFrame); - } - } - - private void focusLastFocusedComponent(IdeFrame ideFrame) { - final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - if (mgr.getFocusOwner() == null) { - Component c = getComponent(myLastFocusedAtDeactivation, ideFrame); - if (c == null || !c.isShowing()) { - c = getComponent(myLastFocused, ideFrame); - } - - if (c != null && c.isShowing()) { - requestFocus(c, false); - } - } - - myLastFocusedAtDeactivation.remove(ideFrame); - } - } - - @Nullable - private static Component getComponent(Map> map, IdeFrame frame) { - WeakReference ref = map.get(frame); - if (ref == null) return null; - return ref.get(); - } - - @Override - public JComponent getFocusTargetFor(@NotNull JComponent comp) { - return IdeFocusTraversalPolicy.getPreferredFocusedComponent(comp); - } - - @Override - public Component getFocusedDescendantFor(Component comp) { - final Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (focused == null) return null; - - if (focused == comp || SwingUtilities.isDescendingFrom(focused, comp)) return focused; - - List popups = FocusTrackback.getChildPopups(comp); - for (JBPopup each : popups) { - if (each.isFocused()) return focused; - } - - return null; - } - - @Override - public boolean isFocusBeingTransferred() { - return !isFocusTransferReady(); - } - - @Override - public ActionCallback requestDefaultFocus(boolean forced) { - return new ActionCallback.Done(); - } - - @Override - public boolean isFocusTransferEnabled() { - return myApp.isActive() || !Registry.is("actionSystem.suspendFocusTransferIfApplicationInactive"); - } -} +/* + * 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.wm.impl; + +import com.intellij.ide.IdeEventQueue; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.EdtRunnable; +import com.intellij.openapi.util.Expirable; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.*; +import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; +import com.intellij.ui.FocusTrackback; +import com.intellij.util.Alarm; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.List; + +public class FocusManagerImpl extends IdeFocusManager implements Disposable { + + private final Application myApp; + + private FocusCommand myRequestFocusCmd; + private final ArrayList myFocusRequests = new ArrayList(); + + private final ArrayList myToDispatchOnDone = new ArrayList(); + private int myFlushingIdleRequestsEntryCount = 0; + + private WeakReference myLastForcedRequest = new WeakReference(null); + + private FocusCommand myFocusCommandOnAppActivation; + private ActionCallback myCallbackOnActivation; + + private final IdeEventQueue myQueue; + private final KeyProcessorConext myKeyProcessorContext = new KeyProcessorConext(); + + private long myCmdTimestamp; + private long myForcedCmdTimestamp; + + final EdtAlarm myFocusedComponentAlaram; + private final EdtAlarm myForcedFocusRequestsAlarm; + + private final EdtAlarm myIdleAlarm; + private final Set myIdleRequests = new LinkedHashSet(); + private final EdtRunnable myIdleRunnable = new EdtRunnable() { + public void runEdt() { + if (canFlushIdleRequests()) { + flushIdleRequests(); + } + else { + restartIdleAlarm(); + } + } + }; + private boolean myFlushWasDelayedToFixFocus; + + private boolean canFlushIdleRequests() { + return isFocusTransferReady() && !isIdleQueueEmpty(); + } + + private final Map> myLastFocused = new HashMap>(); + private final Map> myLastFocusedAtDeactivation = new HashMap>(); + + private DataContext myRunContext; + + public FocusManagerImpl(WindowManager wm) { + myApp = ApplicationManager.getApplication(); + myQueue = IdeEventQueue.getInstance(); + + myFocusedComponentAlaram = new EdtAlarm(this); + myForcedFocusRequestsAlarm = new EdtAlarm(this); + myIdleAlarm = new EdtAlarm(this); + + final AppListener myAppListener = new AppListener(); + myApp.addApplicationListener(myAppListener); + + IdeEventQueue.getInstance().addDispatcher(new IdeEventQueue.EventDispatcher() { + public boolean dispatch(AWTEvent e) { + if (e instanceof FocusEvent) { + final FocusEvent fe = (FocusEvent)e; + final Component c = fe.getComponent(); + if (c instanceof Window || c == null) return false; + + Component parent = UIUtil.findUltimateParent(c); + if (parent instanceof IdeFrame) { + myLastFocused.put((IdeFrame)parent, new WeakReference(c)); + } + } else if (e instanceof WindowEvent) { + if (e.getID() == WindowEvent.WINDOW_CLOSED) { + Window wnd = ((WindowEvent)e).getWindow(); + if (wnd instanceof IdeFrame) { + myLastFocused.remove((IdeFrame)wnd); + myLastFocusedAtDeactivation.remove((IdeFrame)wnd); + } + } + } + + return false; + } + }, this); + + } + + @NotNull + public ActionCallback requestFocus(@NotNull final Component c, final boolean forced) { + return requestFocus(new FocusCommand.ByComponent(c), forced); + } + + @NotNull + public ActionCallback requestFocus(@NotNull final FocusCommand command, final boolean forced) { + final ActionCallback result = new ActionCallback(); + + if (!forced) { + if (!myFocusRequests.contains(command)) { + myFocusRequests.add(command); + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + resetUnforcedCommand(command); + _requestFocus(command, forced, result); + } + }); + } + else { + _requestFocus(command, forced, result); + } + + result.doWhenProcessed(new Runnable() { + public void run() { + restartIdleAlarm(); + } + }); + + return result; + } + + private void _requestFocus(final FocusCommand command, final boolean forced, final ActionCallback result) { + if (checkForRejectOrByPass(command, forced, result)) return; + + setCommand(command); + command.setCallback(result); + + if (forced) { + myForcedFocusRequestsAlarm.cancelAllRequests(); + setLastEffectiveForcedRequest(command); + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (checkForRejectOrByPass(command, forced, result)) return; + + if (myRequestFocusCmd == command) { + final ActionCallback.TimedOut focusTimeout = + new ActionCallback.TimedOut(Registry.intValue("actionSystem.commandProcessingTimeout"), + "Focus command timed out, cmd=" + command, command.getAllocation(), true) { + @Override + protected void onTimeout() { + forceFinishFocusSettledown(command, result); + } + }; + + myCmdTimestamp++; + if (forced) { + myForcedCmdTimestamp++; + } + + command.run().doWhenDone(new Runnable() { + public void run() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + result.setDone(); + } + }); + } + }).doWhenRejected(new Runnable() { + public void run() { + result.setRejected(); + } + }).doWhenProcessed(new Runnable() { + public void run() { + resetCommand(command, false); + + if (forced) { + myForcedFocusRequestsAlarm.addRequest(new EdtRunnable() { + public void runEdt() { + setLastEffectiveForcedRequest(null); + } + }, 250); + } + } + }).notify(focusTimeout); + } + else { + rejectCommand(command, result); + } + } + }); + } + + private boolean checkForRejectOrByPass(final FocusCommand cmd, final boolean forced, final ActionCallback result) { + if (cmd.isExpired()) { + rejectCommand(cmd, result); + return true; + } + + final FocusCommand lastRequest = getLastEffectiveForcedRequest(); + + if (!forced && !isUnforcedRequestAllowed()) { + if (cmd.equals(lastRequest)) { + resetCommand(cmd, false); + result.setDone(); + } + else { + rejectCommand(cmd, result); + } + return true; + } + + + if (lastRequest != null && lastRequest.dominatesOver(cmd)) { + rejectCommand(cmd, result); + return true; + } + + boolean doNotExecuteBecauseAppIsInactive = + !myApp.isActive() && !canExecuteOnInactiveApplication(cmd) && Registry.is("actionSystem.suspendFocusTransferIfApplicationInactive"); + + if (doNotExecuteBecauseAppIsInactive) { + if (myCallbackOnActivation != null) { + myCallbackOnActivation.setRejected(); + if (myFocusCommandOnAppActivation != null) { + resetCommand(myFocusCommandOnAppActivation, true); + } + } + + myFocusCommandOnAppActivation = cmd; + myCallbackOnActivation = result; + + return true; + } + + return false; + } + + private void setCommand(FocusCommand command) { + myRequestFocusCmd = command; + + if (!myFocusRequests.contains(command)) { + myFocusRequests.add(command); + } + } + + private void resetCommand(FocusCommand cmd, boolean reject) { + if (cmd == myRequestFocusCmd) { + myRequestFocusCmd = null; + } + + final KeyEventProcessor processor = cmd.getProcessor(); + if (processor != null) { + processor.finish(myKeyProcessorContext); + } + + myFocusRequests.remove(cmd); + + if (reject) { + ActionCallback cb = cmd.getCallback(); + if (cb != null && !cb.isProcessed()) { + cmd.getCallback().setRejected(); + } + } + } + + private void resetUnforcedCommand(FocusCommand cmd) { + myFocusRequests.remove(cmd); + } + + private static boolean canExecuteOnInactiveApplication(FocusCommand cmd) { + return cmd.canExecuteOnInactiveApp(); + } + + private void setLastEffectiveForcedRequest(FocusCommand command) { + myLastForcedRequest = new WeakReference(command); + } + + @Nullable + private FocusCommand getLastEffectiveForcedRequest() { + if (myLastForcedRequest == null) return null; + final FocusCommand request = myLastForcedRequest.get(); + return request != null && !request.isExpired() ? request : null; + } + + boolean isUnforcedRequestAllowed() { + return getLastEffectiveForcedRequest() == null; + } + + public static FocusManagerImpl getInstance() { + return (FocusManagerImpl)ApplicationManager.getApplication().getComponent(IdeFocusManager.class); + } + + public void dispose() { + } + + private class KeyProcessorConext implements KeyEventProcessor.Context { + public List getQueue() { + return myToDispatchOnDone; + } + + public void dispatch(final List events) { + doWhenFocusSettlesDown(new Runnable() { + public void run() { + myToDispatchOnDone.addAll(events); + restartIdleAlarm(); + } + }); + } + } + + public void doWhenFocusSettlesDown(@NotNull final Runnable runnable) { + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + if (isFlushingIdleRequests()) { + SwingUtilities.invokeLater(this); + return; + } + + if (myRunContext != null) { + runnable.run(); + return; + } + + final boolean needsRestart = isIdleQueueEmpty(); + myIdleRequests.add(runnable); + + if (canFlushIdleRequests()) { + flushIdleRequests(); + } else { + if (needsRestart) { + restartIdleAlarm(); + } + } + } + }); + } + + private void restartIdleAlarm() { + myIdleAlarm.cancelAllRequests(); + myIdleAlarm.addRequest(myIdleRunnable, Registry.intValue("actionSystem.focusIdleTimeout")); + } + + private void flushIdleRequests() { + try { + myFlushingIdleRequestsEntryCount++; + + final KeyEvent[] events = myToDispatchOnDone.toArray(new KeyEvent[myToDispatchOnDone.size()]); + if (events.length > 0) { + IdeEventQueue.getInstance().getKeyEventDispatcher().resetState(); + } + + boolean keyWasPressed = false; + + for (KeyEvent each : events) { + if (!isFocusTransferReady()) break; + + if (!keyWasPressed) { + if (each.getID() == KeyEvent.KEY_PRESSED) { + keyWasPressed = true; + } + else { + myToDispatchOnDone.remove(each); + continue; + } + } + + Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (owner == null) { + owner = JOptionPane.getRootFrame(); + } + + KeyEvent keyEvent = new KeyEvent(owner, each.getID(), each.getWhen(), each.getModifiersEx(), each.getKeyCode(), each.getKeyChar(), + each.getKeyLocation()); + + myToDispatchOnDone.remove(each); + + if (owner != null && SwingUtilities.getWindowAncestor(owner) != null) { + IdeEventQueue.getInstance().dispatchEvent(keyEvent); + } + else { + myQueue._dispatchEvent(keyEvent, true); + } + } + + + if (isPendingKeyEventsRedispatched()) { + boolean focusOk = getFocusOwner() != null || myFlushWasDelayedToFixFocus; + if (!focusOk) { + IdeEventQueue.getInstance().fixStickyFocusedComponents(null); + myFlushWasDelayedToFixFocus = true; + } + + if (canFlushIdleRequests() && myFlushingIdleRequestsEntryCount <= 1 && (focusOk || !myFlushWasDelayedToFixFocus)) { + myFlushWasDelayedToFixFocus = false; + flushNow(); + } + } + } + finally { + myFlushingIdleRequestsEntryCount--; + if (!isIdleQueueEmpty()) { + restartIdleAlarm(); + } + } + } + + private void flushNow() { + final Runnable[] all = myIdleRequests.toArray(new Runnable[myIdleRequests.size()]); + myIdleRequests.clear(); + for (Runnable each : all) { + if (each != null) { + each.run(); + } + } + } + + public boolean isFocusTransferReady() { + if (myRunContext != null) return true; + + invalidateFocusRequestsQueue(); + + if (!myFocusRequests.isEmpty()) return false; + if (myQueue == null) return true; + + return !myQueue.isSuspendMode() && !myQueue.hasFocusEventsPending(); + } + + private void invalidateFocusRequestsQueue() { + if (myFocusRequests.isEmpty()) return; + + FocusCommand[] requests = myFocusRequests.toArray(new FocusCommand[myFocusRequests.size()]); + boolean wasChanged = false; + for (FocusCommand each : requests) { + if (each.isExpired()) { + resetCommand(each, true); + wasChanged = true; + } + } + + if (wasChanged && myFocusRequests.isEmpty()) { + restartIdleAlarm(); + } + } + + private boolean isIdleQueueEmpty() { + return isPendingKeyEventsRedispatched() && myIdleRequests.isEmpty(); + } + + private boolean isPendingKeyEventsRedispatched() { + return myToDispatchOnDone.isEmpty(); + } + + public boolean dispatch(KeyEvent e) { + if (!Registry.is("actionSystem.fixLostTyping")) return false; + + if (isFlushingIdleRequests()) return false; + + if (!isFocusTransferReady() || !isPendingKeyEventsRedispatched()) { + for (FocusCommand each : myFocusRequests) { + final KeyEventProcessor processor = each.getProcessor(); + if (processor != null) { + final Boolean result = processor.dispatch(e, myKeyProcessorContext); + if (result != null) { + return result.booleanValue(); + } + } + } + + myToDispatchOnDone.add(e); + restartIdleAlarm(); + + return true; + } + else { + return false; + } + } + + private boolean isFlushingIdleRequests() { + return myFlushingIdleRequestsEntryCount > 0; + } + + public void suspendKeyProcessingUntil(@NotNull final ActionCallback done) { + requestFocus(new FocusCommand(done) { + public ActionCallback run() { + return done; + } + }.saveAllocation(), true); + } + + public Expirable getTimestamp(final boolean trackOnlyForcedCommands) { + return new Expirable() { + long myOwnStamp = trackOnlyForcedCommands ? myForcedCmdTimestamp : myCmdTimestamp; + + public boolean isExpired() { + return myOwnStamp < (trackOnlyForcedCommands ? myForcedCmdTimestamp : myCmdTimestamp); + } + }; + } + + @Override + public FocusRequestor getFurtherRequestor() { + return new FurtherRequestor(this, getTimestamp(true)); + } + + @Override + public Component getFocusOwner() { + Component result = null; + if (myRunContext != null) { + result = (Component)myRunContext.getData(PlatformDataKeys.CONTEXT_COMPONENT.getName()); + } + + if (result == null) { + result = isFocusBeingTransferred() ? null : KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + } + + final boolean meaninglessOwner = result instanceof JFrame || result instanceof JDialog || result instanceof JWindow || result instanceof JRootPane; + if ((result == null && !isFocusBeingTransferred()) || meaninglessOwner) { + final Component permOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner(); + if (permOwner != null) { + result = permOwner; + } + } + + return result; + } + + @Override + public void runOnOwnContext(DataContext context, Runnable runnable) { + myRunContext = context; + try { + runnable.run(); + } finally { + myRunContext = null; + } + } + + private static class FurtherRequestor implements FocusRequestor { + private final IdeFocusManager myManager; + private final Expirable myExpirable; + + private FurtherRequestor(IdeFocusManager manager, Expirable expirable) { + myManager = manager; + myExpirable = expirable; + } + + @NotNull + @Override + public ActionCallback requestFocus(@NotNull Component c, boolean forced) { + return myExpirable.isExpired() ? new ActionCallback.Rejected() : myManager.requestFocus(c, forced); + } + + @NotNull + @Override + public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) { + return myExpirable.isExpired() ? new ActionCallback.Rejected() : myManager.requestFocus(command, forced); + } + } + + + static class EdtAlarm { + private final Alarm myAlarm; + + private EdtAlarm(Disposable parent) { + myAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD, parent); + } + + public void cancelAllRequests() { + myAlarm.cancelAllRequests(); + } + + public void addRequest(EdtRunnable runnable, int delay) { + myAlarm.addRequest(runnable, delay); + } + } + + private void forceFinishFocusSettledown(FocusCommand cmd, ActionCallback cmdCallback) { + rejectCommand(cmd, cmdCallback); + } + + + private void rejectCommand(FocusCommand cmd, ActionCallback callback) { + resetCommand(cmd, true); + resetUnforcedCommand(cmd); + + callback.setRejected(); + } + + private class AppListener extends ApplicationAdapter { + + @Override + public void applicationDeactivated(IdeFrame ideFrame) { + final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + Component parent = UIUtil.findUltimateParent(owner); + + if (parent == ideFrame) { + myLastFocusedAtDeactivation.put(ideFrame, new WeakReference(owner)); + } + } + + @Override + public void applicationActivated(final IdeFrame ideFrame) { + final FocusCommand cmd = myFocusCommandOnAppActivation; + ActionCallback callback = myCallbackOnActivation; + myFocusCommandOnAppActivation = null; + myCallbackOnActivation = null; + + if (cmd != null) { + requestFocus(cmd, true).notify(callback).doWhenRejected(new Runnable() { + public void run() { + focusLastFocusedComponent(ideFrame); + } + }); + } + else { + focusLastFocusedComponent(ideFrame); + } + } + + private void focusLastFocusedComponent(IdeFrame ideFrame) { + final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + if (mgr.getFocusOwner() == null) { + Component c = getComponent(myLastFocusedAtDeactivation, ideFrame); + if (c == null || !c.isShowing()) { + c = getComponent(myLastFocused, ideFrame); + } + + if (c != null && c.isShowing()) { + requestFocus(c, false); + } + } + + myLastFocusedAtDeactivation.remove(ideFrame); + } + } + + @Nullable + private static Component getComponent(Map> map, IdeFrame frame) { + WeakReference ref = map.get(frame); + if (ref == null) return null; + return ref.get(); + } + + @Override + public JComponent getFocusTargetFor(@NotNull JComponent comp) { + return IdeFocusTraversalPolicy.getPreferredFocusedComponent(comp); + } + + @Override + public Component getFocusedDescendantFor(Component comp) { + final Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (focused == null) return null; + + if (focused == comp || SwingUtilities.isDescendingFrom(focused, comp)) return focused; + + List popups = FocusTrackback.getChildPopups(comp); + for (JBPopup each : popups) { + if (each.isFocused()) return focused; + } + + return null; + } + + @Override + public boolean isFocusBeingTransferred() { + return !isFocusTransferReady(); + } + + @Override + public ActionCallback requestDefaultFocus(boolean forced) { + return new ActionCallback.Done(); + } + + @Override + public boolean isFocusTransferEnabled() { + return myApp.isActive() || !Registry.is("actionSystem.suspendFocusTransferIfApplicationInactive"); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java index 3cdc003a6aca..0a5a37174de4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java @@ -1,352 +1,352 @@ -/* - * 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.wm.impl; - -import com.intellij.ide.AppLifecycleListener; -import com.intellij.ide.DataManager; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.MnemonicHelper; -import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ex.ApplicationInfoEx; -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.project.DumbAwareRunnable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt; -import com.intellij.openapi.wm.ex.StatusBarEx; -import com.intellij.openapi.wm.impl.status.EncodingPanel; -import com.intellij.openapi.wm.impl.status.InsertOverwritePanel; -import com.intellij.openapi.wm.impl.status.PositionPanel; -import com.intellij.openapi.wm.impl.status.ToggleReadOnlyAttributePanel; -import com.intellij.ui.AppUIUtil; -import com.intellij.ui.BalloonLayout; -import com.intellij.ui.FocusTrackback; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; - -/** - * @author Anton Katilin - * @author Vladimir Kondratyev - */ - -// Made non-final for Fabrique -public class IdeFrameImpl extends JFrame implements IdeFrame, DataProvider { - private String myTitle; - - private String myFileTitle; - private File myCurrentFile; - - private Project myProject; - private final LayoutFocusTraversalPolicyExt myLayoutFocusTraversalPolicy; - - private IdeRootPane myRootPane; - private final BalloonLayout myBalloonLayout; - private static boolean myUpdatingTitle; - - public IdeFrameImpl(ApplicationInfoEx applicationInfoEx, ActionManagerEx actionManager, UISettings uiSettings, DataManager dataManager, - final Application application, final String[] commandLineArgs) { - super(applicationInfoEx.getFullApplicationName()); - myRootPane = new IdeRootPane(actionManager, uiSettings, dataManager, application, commandLineArgs, this); - setRootPane(myRootPane); - - AppUIUtil.updateFrameIcon(this); - final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(10, 10, screenSize.width - 20, screenSize.height - 40); - - myLayoutFocusTraversalPolicy = new LayoutFocusTraversalPolicyExt(); - setFocusTraversalPolicy(myLayoutFocusTraversalPolicy); - - setupCloseAction(); - new MnemonicHelper().register(this); - - myBalloonLayout = new BalloonLayout(myRootPane.getLayeredPane(), new Insets(8, 8, 8, 8)); - - if (!Registry.is("ide.windowSystem.focusAppOnStartup") && !isThereActiveFrame()) { - setFocusableWindowState(false); - } - - } - - private static boolean isThereActiveFrame() { - Frame[] all = Frame.getFrames(); - for (Frame each : all) { - if (each.isActive()) { - return true; - } - } - - return false; - } - - @Override - public void show() { - super.show(); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - setFocusableWindowState(true); - } - }); - } - - /** - * !!!!! CAUTION !!!!! - * !!!!! CAUTION !!!!! - * !!!!! CAUTION !!!!! - * - * THIS IS AN "ABSOLUTELY-GURU METHOD". - * NOBODY SHOULD ADD OTHER USAGES OF IT :) - * ONLY ANTON AND VOVA ARE PERMITTED TO USE THIS METHOD!!! - * - * !!!!! CAUTION !!!!! - * !!!!! CAUTION !!!!! - * !!!!! CAUTION !!!!! - */ - public final void setDefaultFocusableComponent(final JComponent component) { - LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(component); - } - - /** - * This is overriden to get rid of strange Alloy LaF customization of frames. For unknown reason it sets the maxBounds rectangle - * and it does it plain wrong. Setting bounds to null means default value should be taken from the underlying OS. - */ - public synchronized void setMaximizedBounds(Rectangle bounds) { - super.setMaximizedBounds(null); - } - - private void setupCloseAction() { - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - addWindowListener( - new WindowAdapter() { - public void windowClosing(final WindowEvent e) { - final Application app = ApplicationManager.getApplication(); - app.invokeLater(new DumbAwareRunnable() { - public void run() { - if (app.isDisposed()) { - ApplicationManagerEx.getApplicationEx().exit(); - return; - } - - final Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); - if (openProjects.length > 1) { - if (myProject != null && myProject.isOpen()) { - ProjectUtil.closeAndDispose(myProject); - } - app.getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).projectFrameClosed(); - } - else { - ApplicationManagerEx.getApplicationEx().exit(); - } - } - }, ModalityState.NON_MODAL); - } - } - ); - } - - public StatusBar getStatusBar() { - return ((IdeRootPane)getRootPane()).getStatusBar(); - } - - public void updateToolbar() { - ((IdeRootPane)getRootPane()).updateToolbar(); - } - - public void updateMenuBar(){ - ((IdeRootPane)getRootPane()).updateMainMenuActions(); - } - - public void setTitle(final String title) { - if (myUpdatingTitle) { - super.setTitle(title); - } else { - myTitle = title; - } - - updateTitle(); - } - - public void setFrameTitle(final String text) { - super.setTitle(text); - } - - public void setFileTitle(final String fileTitle) { - setFileTitle(fileTitle, null); - } - - public void setFileTitle(@Nullable final String fileTitle, @Nullable File file) { - myFileTitle = fileTitle; - myCurrentFile = file; - updateTitle(); - } - - private void updateTitle() { - updateTitle(this, myTitle, myFileTitle, myCurrentFile); - } - - public static void updateTitle(JFrame frame, final String title, final String fileTitle, final File currentFile) { - if (myUpdatingTitle) return; - - try { - myUpdatingTitle = true; - - final StringBuilder sb = new StringBuilder(); - if (title != null && title.length() > 0) { - sb.append(title); - sb.append(" - "); - } - if (fileTitle != null && fileTitle.length() > 0) { - sb.append(fileTitle); - sb.append(" - "); - } - - frame.getRootPane().putClientProperty("Window.documentFile", currentFile); - - sb.append(((ApplicationInfoEx)ApplicationInfo.getInstance()).getFullApplicationName()); - frame.setTitle(sb.toString()); - } - finally { - myUpdatingTitle = false; - } - } - - public Object getData(final String dataId) { - if (PlatformDataKeys.PROJECT.is(dataId)) { - if (myProject != null) { - return myProject.isInitialized() ? myProject : null; - } - } - return null; - } - - public void setProject(final Project project) { - myProject = project; - if (project != null) { - if (myRootPane != null) { - myRootPane.installNorthComponents(project); - } - - project.getMessageBus().connect().subscribe(StatusBar.Info.TOPIC, myRootPane.getStatusBar()); - installDefaultProjectStatusBarWidgets(myProject); - } - else { - if (myRootPane != null) { //already disposed - myRootPane.deinstallNorthComponents(); - } - } - - if (project == null) { - FocusTrackback.release(this); - } - } - - private void installDefaultProjectStatusBarWidgets(@NotNull final Project project) { - final StatusBar statusBar = getStatusBar(); - - final PositionPanel positionPanel = new PositionPanel(project); - statusBar.addWidget(positionPanel, "before Notifications"); - - final EncodingPanel encodingPanel = new EncodingPanel(project); - statusBar.addWidget(encodingPanel, "after Position"); - - final ToggleReadOnlyAttributePanel readOnlyAttributePanel = new ToggleReadOnlyAttributePanel(); - - InsertOverwritePanel insertOverwritePanel = null; - if (!SystemInfo.isMac) { - insertOverwritePanel = new InsertOverwritePanel(); - statusBar.addWidget(insertOverwritePanel, "after Encoding"); - statusBar.addWidget(readOnlyAttributePanel, "after InsertOverwrite"); - } else { - statusBar.addWidget(readOnlyAttributePanel, "after Encoding"); - } - - final InsertOverwritePanel finalInsertOverwritePanel = insertOverwritePanel; - Disposer.register(project, new Disposable() { - public void dispose() { - statusBar.removeWidget(encodingPanel.ID()); - statusBar.removeWidget(positionPanel.ID()); - statusBar.removeWidget(readOnlyAttributePanel.ID()); - if (finalInsertOverwritePanel != null) statusBar.removeWidget(finalInsertOverwritePanel.ID()); - - ((StatusBarEx)statusBar).removeCustomIndicationComponents(); - } - }); - } - - public Project getProject() { - return myProject; - } - - public void dispose() { - if (myRootPane != null) { - myRootPane = null; - } - super.dispose(); - } - - @Override - public void paint(Graphics g) { - UIUtil.applyRenderingHints(g); - super.paint(g); - } - - public Rectangle suggestChildFrameBounds() { -//todo [kirillk] a dummy implementation - final Rectangle b = getBounds(); - b.x += 100; - b.width -= 200; - b.y += 100; - b.height -= 200; - return b; - } - - @Nullable - public static Component findNearestModalComponent(@NotNull Component c) { - Component eachParent = c; - while (eachParent != null) { - if (eachParent instanceof IdeFrameImpl) return eachParent; - if (eachParent instanceof JDialog) { - if (((JDialog)eachParent).isModal()) return eachParent; - } - eachParent = eachParent.getParent(); - } - - return null; - } - - public final BalloonLayout getBalloonLayout() { - return myBalloonLayout; - } -} +/* + * 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.wm.impl; + +import com.intellij.ide.AppLifecycleListener; +import com.intellij.ide.DataManager; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.MnemonicHelper; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationInfo; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ex.ApplicationInfoEx; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt; +import com.intellij.openapi.wm.ex.StatusBarEx; +import com.intellij.openapi.wm.impl.status.EncodingPanel; +import com.intellij.openapi.wm.impl.status.InsertOverwritePanel; +import com.intellij.openapi.wm.impl.status.PositionPanel; +import com.intellij.openapi.wm.impl.status.ToggleReadOnlyAttributePanel; +import com.intellij.ui.AppUIUtil; +import com.intellij.ui.BalloonLayout; +import com.intellij.ui.FocusTrackback; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; + +/** + * @author Anton Katilin + * @author Vladimir Kondratyev + */ + +// Made non-final for Fabrique +public class IdeFrameImpl extends JFrame implements IdeFrame, DataProvider { + private String myTitle; + + private String myFileTitle; + private File myCurrentFile; + + private Project myProject; + private final LayoutFocusTraversalPolicyExt myLayoutFocusTraversalPolicy; + + private IdeRootPane myRootPane; + private final BalloonLayout myBalloonLayout; + private static boolean myUpdatingTitle; + + public IdeFrameImpl(ApplicationInfoEx applicationInfoEx, ActionManagerEx actionManager, UISettings uiSettings, DataManager dataManager, + final Application application, final String[] commandLineArgs) { + super(applicationInfoEx.getFullApplicationName()); + myRootPane = new IdeRootPane(actionManager, uiSettings, dataManager, application, commandLineArgs, this); + setRootPane(myRootPane); + + AppUIUtil.updateFrameIcon(this); + final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(10, 10, screenSize.width - 20, screenSize.height - 40); + + myLayoutFocusTraversalPolicy = new LayoutFocusTraversalPolicyExt(); + setFocusTraversalPolicy(myLayoutFocusTraversalPolicy); + + setupCloseAction(); + new MnemonicHelper().register(this); + + myBalloonLayout = new BalloonLayout(myRootPane.getLayeredPane(), new Insets(8, 8, 8, 8)); + + if (!Registry.is("ide.windowSystem.focusAppOnStartup") && !isThereActiveFrame()) { + setFocusableWindowState(false); + } + + } + + private static boolean isThereActiveFrame() { + Frame[] all = Frame.getFrames(); + for (Frame each : all) { + if (each.isActive()) { + return true; + } + } + + return false; + } + + @Override + public void show() { + super.show(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setFocusableWindowState(true); + } + }); + } + + /** + * !!!!! CAUTION !!!!! + * !!!!! CAUTION !!!!! + * !!!!! CAUTION !!!!! + * + * THIS IS AN "ABSOLUTELY-GURU METHOD". + * NOBODY SHOULD ADD OTHER USAGES OF IT :) + * ONLY ANTON AND VOVA ARE PERMITTED TO USE THIS METHOD!!! + * + * !!!!! CAUTION !!!!! + * !!!!! CAUTION !!!!! + * !!!!! CAUTION !!!!! + */ + public final void setDefaultFocusableComponent(final JComponent component) { + LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(component); + } + + /** + * This is overriden to get rid of strange Alloy LaF customization of frames. For unknown reason it sets the maxBounds rectangle + * and it does it plain wrong. Setting bounds to null means default value should be taken from the underlying OS. + */ + public synchronized void setMaximizedBounds(Rectangle bounds) { + super.setMaximizedBounds(null); + } + + private void setupCloseAction() { + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + addWindowListener( + new WindowAdapter() { + public void windowClosing(final WindowEvent e) { + final Application app = ApplicationManager.getApplication(); + app.invokeLater(new DumbAwareRunnable() { + public void run() { + if (app.isDisposed()) { + ApplicationManagerEx.getApplicationEx().exit(); + return; + } + + final Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + if (openProjects.length > 1) { + if (myProject != null && myProject.isOpen()) { + ProjectUtil.closeAndDispose(myProject); + } + app.getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).projectFrameClosed(); + } + else { + ApplicationManagerEx.getApplicationEx().exit(); + } + } + }, ModalityState.NON_MODAL); + } + } + ); + } + + public StatusBar getStatusBar() { + return ((IdeRootPane)getRootPane()).getStatusBar(); + } + + public void updateToolbar() { + ((IdeRootPane)getRootPane()).updateToolbar(); + } + + public void updateMenuBar(){ + ((IdeRootPane)getRootPane()).updateMainMenuActions(); + } + + public void setTitle(final String title) { + if (myUpdatingTitle) { + super.setTitle(title); + } else { + myTitle = title; + } + + updateTitle(); + } + + public void setFrameTitle(final String text) { + super.setTitle(text); + } + + public void setFileTitle(final String fileTitle) { + setFileTitle(fileTitle, null); + } + + public void setFileTitle(@Nullable final String fileTitle, @Nullable File file) { + myFileTitle = fileTitle; + myCurrentFile = file; + updateTitle(); + } + + private void updateTitle() { + updateTitle(this, myTitle, myFileTitle, myCurrentFile); + } + + public static void updateTitle(JFrame frame, final String title, final String fileTitle, final File currentFile) { + if (myUpdatingTitle) return; + + try { + myUpdatingTitle = true; + + final StringBuilder sb = new StringBuilder(); + if (title != null && title.length() > 0) { + sb.append(title); + sb.append(" - "); + } + if (fileTitle != null && fileTitle.length() > 0) { + sb.append(fileTitle); + sb.append(" - "); + } + + frame.getRootPane().putClientProperty("Window.documentFile", currentFile); + + sb.append(((ApplicationInfoEx)ApplicationInfo.getInstance()).getFullApplicationName()); + frame.setTitle(sb.toString()); + } + finally { + myUpdatingTitle = false; + } + } + + public Object getData(final String dataId) { + if (PlatformDataKeys.PROJECT.is(dataId)) { + if (myProject != null) { + return myProject.isInitialized() ? myProject : null; + } + } + return null; + } + + public void setProject(final Project project) { + myProject = project; + if (project != null) { + if (myRootPane != null) { + myRootPane.installNorthComponents(project); + } + + project.getMessageBus().connect().subscribe(StatusBar.Info.TOPIC, myRootPane.getStatusBar()); + installDefaultProjectStatusBarWidgets(myProject); + } + else { + if (myRootPane != null) { //already disposed + myRootPane.deinstallNorthComponents(); + } + } + + if (project == null) { + FocusTrackback.release(this); + } + } + + private void installDefaultProjectStatusBarWidgets(@NotNull final Project project) { + final StatusBar statusBar = getStatusBar(); + + final PositionPanel positionPanel = new PositionPanel(project); + statusBar.addWidget(positionPanel, "before Notifications"); + + final EncodingPanel encodingPanel = new EncodingPanel(project); + statusBar.addWidget(encodingPanel, "after Position"); + + final ToggleReadOnlyAttributePanel readOnlyAttributePanel = new ToggleReadOnlyAttributePanel(); + + InsertOverwritePanel insertOverwritePanel = null; + if (!SystemInfo.isMac) { + insertOverwritePanel = new InsertOverwritePanel(); + statusBar.addWidget(insertOverwritePanel, "after Encoding"); + statusBar.addWidget(readOnlyAttributePanel, "after InsertOverwrite"); + } else { + statusBar.addWidget(readOnlyAttributePanel, "after Encoding"); + } + + final InsertOverwritePanel finalInsertOverwritePanel = insertOverwritePanel; + Disposer.register(project, new Disposable() { + public void dispose() { + statusBar.removeWidget(encodingPanel.ID()); + statusBar.removeWidget(positionPanel.ID()); + statusBar.removeWidget(readOnlyAttributePanel.ID()); + if (finalInsertOverwritePanel != null) statusBar.removeWidget(finalInsertOverwritePanel.ID()); + + ((StatusBarEx)statusBar).removeCustomIndicationComponents(); + } + }); + } + + public Project getProject() { + return myProject; + } + + public void dispose() { + if (myRootPane != null) { + myRootPane = null; + } + super.dispose(); + } + + @Override + public void paint(Graphics g) { + UIUtil.applyRenderingHints(g); + super.paint(g); + } + + public Rectangle suggestChildFrameBounds() { +//todo [kirillk] a dummy implementation + final Rectangle b = getBounds(); + b.x += 100; + b.width -= 200; + b.y += 100; + b.height -= 200; + return b; + } + + @Nullable + public static Component findNearestModalComponent(@NotNull Component c) { + Component eachParent = c; + while (eachParent != null) { + if (eachParent instanceof IdeFrameImpl) return eachParent; + if (eachParent instanceof JDialog) { + if (((JDialog)eachParent).isModal()) return eachParent; + } + eachParent = eachParent.getParent(); + } + + return null; + } + + public final BalloonLayout getBalloonLayout() { + return myBalloonLayout; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeRootPane.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeRootPane.java index 60804ae3aaa6..b474f9d0b6f0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeRootPane.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeRootPane.java @@ -1,326 +1,326 @@ -/* - * 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.wm.impl; - -import com.intellij.diagnostic.IdeMessagePanel; -import com.intellij.diagnostic.MessagePool; -import com.intellij.ide.AppLifecycleListener; -import com.intellij.ide.DataManager; -import com.intellij.ide.actions.CustomizeUIAction; -import com.intellij.ide.actions.ViewToolbarAction; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.ide.ui.UISettings; -import com.intellij.ide.ui.UISettingsListener; -import com.intellij.ide.ui.customization.CustomActionsSchema; -import com.intellij.notification.impl.IdeNotificationArea; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.wm.CustomStatusBarWidget; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarCustomComponentFactory; -import com.intellij.openapi.wm.impl.status.IdeStatusBarImpl; -import com.intellij.openapi.wm.impl.status.MemoryUsagePanel; -import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreen; -import com.intellij.ui.PopupHandler; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Anton Katilin - * @author Vladimir Kondratyev - */ - -// Made public and non-final for Fabrique -public class IdeRootPane extends JRootPane implements UISettingsListener { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.IdeRootPane"); - - /** - * Toolbar and status bar. - */ - private JComponent myToolbar; - private IdeStatusBarImpl myStatusBar; - - private final Box myNorthPanel = Box.createVerticalBox(); - private final List myNorthComponents = new ArrayList(); - - /** - * Current ToolWindowsPane. If there is no such pane then this field is null. - */ - private ToolWindowsPane myToolWindowsPane; - private final MyUISettingsListenerImpl myUISettingsListener; - private JPanel myContentPane; - private final ActionManager myActionManager; - private final UISettings myUISettings; - - private static Component myWelcomePane; - private final boolean myGlassPaneInitialized; - private final IdeGlassPaneImpl myGlassPane; - - private final Application myApplication; - private MemoryUsagePanel myMemoryWidget; - private final StatusBarCustomComponentFactory[] myStatusBarCustomComponentFactories; - private final Disposable myDisposable= Disposer.newDisposable(); - - IdeRootPane(ActionManagerEx actionManager, UISettings uiSettings, DataManager dataManager, - final Application application, final String[] commandLineArgs, IdeFrame frame){ - myActionManager = actionManager; - myUISettings = uiSettings; - - updateToolbar(); - myContentPane.add(myNorthPanel, BorderLayout.NORTH); - - myStatusBarCustomComponentFactories = application.getExtensions(StatusBarCustomComponentFactory.EP_NAME); - myApplication = application; - - createStatusBar(frame); - - updateStatusBarVisibility(); - - myContentPane.add(myStatusBar, BorderLayout.SOUTH); - - myUISettingsListener=new MyUISettingsListenerImpl(); - setJMenuBar(new IdeMenuBar(actionManager, dataManager)); - - final Ref willOpenProject = new Ref(Boolean.FALSE); - final AppLifecycleListener lifecyclePublisher = application.getMessageBus().syncPublisher(AppLifecycleListener.TOPIC); - lifecyclePublisher.appFrameCreated(commandLineArgs, willOpenProject); - LOG.info("App initialization took " + (System.nanoTime() - PluginManager.startupStart) / 1000000 + " ms"); - PluginManager.dumpPluginClassStatistics(); - if (!willOpenProject.get()) { - myWelcomePane = WelcomeScreen.createWelcomePanel(); - myContentPane.add(myWelcomePane); - lifecyclePublisher.welcomeScreenDisplayed(); - } - - myGlassPane = new IdeGlassPaneImpl(this); - setGlassPane(myGlassPane); - myGlassPaneInitialized = true; - - myGlassPane.setVisible(false); - Disposer.register(application, myDisposable); - } - - - public void setGlassPane(final Component glass) { - if (myGlassPaneInitialized) throw new IllegalStateException("Setting of glass pane for IdeFrame is prohibited"); - super.setGlassPane(glass); - } - - /** - * Invoked when enclosed frame is being shown. - */ - public final void addNotify(){ - super.addNotify(); - myUISettings.addUISettingsListener(myUISettingsListener, myDisposable); - } - - /** - * Invoked when enclosed frame is being disposed. - */ - public final void removeNotify(){ - Disposer.dispose(myDisposable); - super.removeNotify(); - } - - /** - * Sets current tool windows pane (panel where all tool windows are located). - * If toolWindowsPane is null then the method just removes - * the current tool windows pane. - */ - final void setToolWindowsPane(final ToolWindowsPane toolWindowsPane) { - final JComponent contentPane = (JComponent)getContentPane(); - if(myToolWindowsPane != null){ - contentPane.remove(myToolWindowsPane); - } - - if (myWelcomePane != null) { - contentPane.remove(myWelcomePane); - myWelcomePane = null; - } - - myToolWindowsPane = toolWindowsPane; - if(myToolWindowsPane != null) { - contentPane.add(myToolWindowsPane,BorderLayout.CENTER); - } - else if (!myApplication.isDisposeInProgress()) { - myWelcomePane = WelcomeScreen.createWelcomePanel(); - contentPane.add(myWelcomePane); - } - - contentPane.revalidate(); - } - - protected final Container createContentPane(){ - myContentPane = new JPanel(new BorderLayout()); - myContentPane.setBackground(Color.GRAY); - - return myContentPane; - } - - void updateToolbar() { - if (myToolbar != null) { - myNorthPanel.remove(myToolbar); - } - myToolbar = createToolbar(); - myNorthPanel.add(myToolbar); - updateToolbarVisibility(); - myContentPane.revalidate(); - } - - void updateMainMenuActions(){ - ((IdeMenuBar)menuBar).updateMenuActions(); - menuBar.repaint(); - } - - private JComponent createToolbar() { - ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_MAIN_TOOLBAR); - final ActionToolbar toolBar= myActionManager.createActionToolbar( - ActionPlaces.MAIN_TOOLBAR, - group, - true - ); - toolBar.setLayoutPolicy(ActionToolbar.WRAP_LAYOUT_POLICY); - - DefaultActionGroup menuGroup = new DefaultActionGroup(); - menuGroup.add(new ViewToolbarAction()); - menuGroup.add(new CustomizeUIAction()); - PopupHandler.installUnknownPopupHandler(toolBar.getComponent(), menuGroup, myActionManager); - - return toolBar.getComponent(); - } - - private void createStatusBar(IdeFrame frame) { - myUISettings.addUISettingsListener(this, myApplication); - - myStatusBar = new IdeStatusBarImpl(); - myStatusBar.install(frame); - - myMemoryWidget = new MemoryUsagePanel(); - myStatusBar.addWidget(myMemoryWidget); - myStatusBar.addWidget(new IdeNotificationArea(), "before Memory"); - myStatusBar.addWidget(new IdeMessagePanel(MessagePool.getInstance()), "before Memory"); - - if (myStatusBarCustomComponentFactories != null) { - for (final StatusBarCustomComponentFactory componentFactory : myStatusBarCustomComponentFactories) { - final JComponent c = componentFactory.createComponent(myStatusBar); - myStatusBar.addWidget(new CustomStatusBarWidget() { - public JComponent getComponent() { - return c; - } - - @NotNull - public String ID() { - return c.getClass().getSimpleName(); - } - - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return null; - } - - public void install(@NotNull StatusBar statusBar) { - } - - public void dispose() { - componentFactory.disposeComponent(myStatusBar, c); - } - }, "before Memory"); - } - } - - setMemoryIndicatorVisible(myUISettings.SHOW_MEMORY_INDICATOR); - } - - void setMemoryIndicatorVisible(final boolean visible) { - if (myMemoryWidget != null) { - myMemoryWidget.setShowing(visible); - if (!SystemInfo.isMac) { - myStatusBar.setBorder(BorderFactory.createEmptyBorder(1, 4, 0, visible ? 0 : 2)); - } - } - } - - @Nullable - final StatusBar getStatusBar() { - return myStatusBar; - } - - private void updateToolbarVisibility(){ - myToolbar.setVisible(myUISettings.SHOW_MAIN_TOOLBAR); - } - - private void updateStatusBarVisibility(){ - myStatusBar.setVisible(myUISettings.SHOW_STATUS_BAR); - } - - public void installNorthComponents(final Project project) { - ContainerUtil.addAll(myNorthComponents, Extensions.getExtensions(IdeRootPaneNorthExtension.EP_NAME, project)); - for (IdeRootPaneNorthExtension northComponent : myNorthComponents) { - myNorthPanel.add(northComponent.getComponent()); - northComponent.uiSettingsChanged(myUISettings); - } - } - - public void deinstallNorthComponents(){ - for (IdeRootPaneNorthExtension northComponent : myNorthComponents) { - myNorthPanel.remove(northComponent.getComponent()); - Disposer.dispose(northComponent); - } - myNorthComponents.clear(); - } - - public IdeRootPaneNorthExtension findByName(String name) { - for (IdeRootPaneNorthExtension northComponent : myNorthComponents) { - if (Comparing.strEqual(name, northComponent.getKey())) { - return northComponent; - } - } - return null; - } - - public void uiSettingsChanged(UISettings source) { - setMemoryIndicatorVisible(source.SHOW_MEMORY_INDICATOR); - } - - private final class MyUISettingsListenerImpl implements UISettingsListener{ - public final void uiSettingsChanged(final UISettings source){ - updateToolbarVisibility(); - updateStatusBarVisibility(); - for (IdeRootPaneNorthExtension component : myNorthComponents) { - component.uiSettingsChanged(source); - } - } - } - - public boolean isOptimizedDrawingEnabled() { - return !myGlassPane.hasPainters() && myGlassPane.getComponentCount() == 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.wm.impl; + +import com.intellij.diagnostic.IdeMessagePanel; +import com.intellij.diagnostic.MessagePool; +import com.intellij.ide.AppLifecycleListener; +import com.intellij.ide.DataManager; +import com.intellij.ide.actions.CustomizeUIAction; +import com.intellij.ide.actions.ViewToolbarAction; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.ide.ui.UISettings; +import com.intellij.ide.ui.UISettingsListener; +import com.intellij.ide.ui.customization.CustomActionsSchema; +import com.intellij.notification.impl.IdeNotificationArea; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.wm.CustomStatusBarWidget; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarCustomComponentFactory; +import com.intellij.openapi.wm.impl.status.IdeStatusBarImpl; +import com.intellij.openapi.wm.impl.status.MemoryUsagePanel; +import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreen; +import com.intellij.ui.PopupHandler; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Anton Katilin + * @author Vladimir Kondratyev + */ + +// Made public and non-final for Fabrique +public class IdeRootPane extends JRootPane implements UISettingsListener { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.IdeRootPane"); + + /** + * Toolbar and status bar. + */ + private JComponent myToolbar; + private IdeStatusBarImpl myStatusBar; + + private final Box myNorthPanel = Box.createVerticalBox(); + private final List myNorthComponents = new ArrayList(); + + /** + * Current ToolWindowsPane. If there is no such pane then this field is null. + */ + private ToolWindowsPane myToolWindowsPane; + private final MyUISettingsListenerImpl myUISettingsListener; + private JPanel myContentPane; + private final ActionManager myActionManager; + private final UISettings myUISettings; + + private static Component myWelcomePane; + private final boolean myGlassPaneInitialized; + private final IdeGlassPaneImpl myGlassPane; + + private final Application myApplication; + private MemoryUsagePanel myMemoryWidget; + private final StatusBarCustomComponentFactory[] myStatusBarCustomComponentFactories; + private final Disposable myDisposable= Disposer.newDisposable(); + + IdeRootPane(ActionManagerEx actionManager, UISettings uiSettings, DataManager dataManager, + final Application application, final String[] commandLineArgs, IdeFrame frame){ + myActionManager = actionManager; + myUISettings = uiSettings; + + updateToolbar(); + myContentPane.add(myNorthPanel, BorderLayout.NORTH); + + myStatusBarCustomComponentFactories = application.getExtensions(StatusBarCustomComponentFactory.EP_NAME); + myApplication = application; + + createStatusBar(frame); + + updateStatusBarVisibility(); + + myContentPane.add(myStatusBar, BorderLayout.SOUTH); + + myUISettingsListener=new MyUISettingsListenerImpl(); + setJMenuBar(new IdeMenuBar(actionManager, dataManager)); + + final Ref willOpenProject = new Ref(Boolean.FALSE); + final AppLifecycleListener lifecyclePublisher = application.getMessageBus().syncPublisher(AppLifecycleListener.TOPIC); + lifecyclePublisher.appFrameCreated(commandLineArgs, willOpenProject); + LOG.info("App initialization took " + (System.nanoTime() - PluginManager.startupStart) / 1000000 + " ms"); + PluginManager.dumpPluginClassStatistics(); + if (!willOpenProject.get()) { + myWelcomePane = WelcomeScreen.createWelcomePanel(); + myContentPane.add(myWelcomePane); + lifecyclePublisher.welcomeScreenDisplayed(); + } + + myGlassPane = new IdeGlassPaneImpl(this); + setGlassPane(myGlassPane); + myGlassPaneInitialized = true; + + myGlassPane.setVisible(false); + Disposer.register(application, myDisposable); + } + + + public void setGlassPane(final Component glass) { + if (myGlassPaneInitialized) throw new IllegalStateException("Setting of glass pane for IdeFrame is prohibited"); + super.setGlassPane(glass); + } + + /** + * Invoked when enclosed frame is being shown. + */ + public final void addNotify(){ + super.addNotify(); + myUISettings.addUISettingsListener(myUISettingsListener, myDisposable); + } + + /** + * Invoked when enclosed frame is being disposed. + */ + public final void removeNotify(){ + Disposer.dispose(myDisposable); + super.removeNotify(); + } + + /** + * Sets current tool windows pane (panel where all tool windows are located). + * If toolWindowsPane is null then the method just removes + * the current tool windows pane. + */ + final void setToolWindowsPane(final ToolWindowsPane toolWindowsPane) { + final JComponent contentPane = (JComponent)getContentPane(); + if(myToolWindowsPane != null){ + contentPane.remove(myToolWindowsPane); + } + + if (myWelcomePane != null) { + contentPane.remove(myWelcomePane); + myWelcomePane = null; + } + + myToolWindowsPane = toolWindowsPane; + if(myToolWindowsPane != null) { + contentPane.add(myToolWindowsPane,BorderLayout.CENTER); + } + else if (!myApplication.isDisposeInProgress()) { + myWelcomePane = WelcomeScreen.createWelcomePanel(); + contentPane.add(myWelcomePane); + } + + contentPane.revalidate(); + } + + protected final Container createContentPane(){ + myContentPane = new JPanel(new BorderLayout()); + myContentPane.setBackground(Color.GRAY); + + return myContentPane; + } + + void updateToolbar() { + if (myToolbar != null) { + myNorthPanel.remove(myToolbar); + } + myToolbar = createToolbar(); + myNorthPanel.add(myToolbar); + updateToolbarVisibility(); + myContentPane.revalidate(); + } + + void updateMainMenuActions(){ + ((IdeMenuBar)menuBar).updateMenuActions(); + menuBar.repaint(); + } + + private JComponent createToolbar() { + ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_MAIN_TOOLBAR); + final ActionToolbar toolBar= myActionManager.createActionToolbar( + ActionPlaces.MAIN_TOOLBAR, + group, + true + ); + toolBar.setLayoutPolicy(ActionToolbar.WRAP_LAYOUT_POLICY); + + DefaultActionGroup menuGroup = new DefaultActionGroup(); + menuGroup.add(new ViewToolbarAction()); + menuGroup.add(new CustomizeUIAction()); + PopupHandler.installUnknownPopupHandler(toolBar.getComponent(), menuGroup, myActionManager); + + return toolBar.getComponent(); + } + + private void createStatusBar(IdeFrame frame) { + myUISettings.addUISettingsListener(this, myApplication); + + myStatusBar = new IdeStatusBarImpl(); + myStatusBar.install(frame); + + myMemoryWidget = new MemoryUsagePanel(); + myStatusBar.addWidget(myMemoryWidget); + myStatusBar.addWidget(new IdeNotificationArea(), "before Memory"); + myStatusBar.addWidget(new IdeMessagePanel(MessagePool.getInstance()), "before Memory"); + + if (myStatusBarCustomComponentFactories != null) { + for (final StatusBarCustomComponentFactory componentFactory : myStatusBarCustomComponentFactories) { + final JComponent c = componentFactory.createComponent(myStatusBar); + myStatusBar.addWidget(new CustomStatusBarWidget() { + public JComponent getComponent() { + return c; + } + + @NotNull + public String ID() { + return c.getClass().getSimpleName(); + } + + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return null; + } + + public void install(@NotNull StatusBar statusBar) { + } + + public void dispose() { + componentFactory.disposeComponent(myStatusBar, c); + } + }, "before Memory"); + } + } + + setMemoryIndicatorVisible(myUISettings.SHOW_MEMORY_INDICATOR); + } + + void setMemoryIndicatorVisible(final boolean visible) { + if (myMemoryWidget != null) { + myMemoryWidget.setShowing(visible); + if (!SystemInfo.isMac) { + myStatusBar.setBorder(BorderFactory.createEmptyBorder(1, 4, 0, visible ? 0 : 2)); + } + } + } + + @Nullable + final StatusBar getStatusBar() { + return myStatusBar; + } + + private void updateToolbarVisibility(){ + myToolbar.setVisible(myUISettings.SHOW_MAIN_TOOLBAR); + } + + private void updateStatusBarVisibility(){ + myStatusBar.setVisible(myUISettings.SHOW_STATUS_BAR); + } + + public void installNorthComponents(final Project project) { + ContainerUtil.addAll(myNorthComponents, Extensions.getExtensions(IdeRootPaneNorthExtension.EP_NAME, project)); + for (IdeRootPaneNorthExtension northComponent : myNorthComponents) { + myNorthPanel.add(northComponent.getComponent()); + northComponent.uiSettingsChanged(myUISettings); + } + } + + public void deinstallNorthComponents(){ + for (IdeRootPaneNorthExtension northComponent : myNorthComponents) { + myNorthPanel.remove(northComponent.getComponent()); + Disposer.dispose(northComponent); + } + myNorthComponents.clear(); + } + + public IdeRootPaneNorthExtension findByName(String name) { + for (IdeRootPaneNorthExtension northComponent : myNorthComponents) { + if (Comparing.strEqual(name, northComponent.getKey())) { + return northComponent; + } + } + return null; + } + + public void uiSettingsChanged(UISettings source) { + setMemoryIndicatorVisible(source.SHOW_MEMORY_INDICATOR); + } + + private final class MyUISettingsListenerImpl implements UISettingsListener{ + public final void uiSettingsChanged(final UISettings source){ + updateToolbarVisibility(); + updateStatusBarVisibility(); + for (IdeRootPaneNorthExtension component : myNorthComponents) { + component.uiSettingsChanged(source); + } + } + } + + public boolean isOptimizedDrawingEnabled() { + return !myGlassPane.hasPainters() && myGlassPane.getComponentCount() == 0; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/Stripe.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/Stripe.java index 30cbe81bedf0..b0ac04cde7e9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/Stripe.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/Stripe.java @@ -1,486 +1,486 @@ -/* - * 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.wm.impl; - -import com.intellij.ide.ui.UISettings; -import com.intellij.ide.ui.UISettingsListener; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManagerListener; -import com.intellij.openapi.keymap.ex.KeymapManagerEx; -import com.intellij.openapi.keymap.ex.WeakKeymapManagerListener; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.wm.ToolWindowAnchor; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * @author Eugene Belyaev - */ -final class Stripe extends JPanel{ - private final int myAnchor; - private final ArrayList myButtons = new ArrayList(); - private final WeakKeymapManagerListener myWeakKeymapManagerListener; - private final MyUISettingsListener myUISettingsListener; - - private Dimension myPrefSize; - private StripeButton myDragButton; - private Rectangle myDropRectangle; - private final ToolWindowManagerImpl myManager; - private JComponent myDragButtonImage; - private LayoutData myLastLayoutData; - private boolean myFinishingDrop; - static final int DROP_DISTANCE_SENSIVITY = 20; - private final Disposable myDisposable = Disposer.newDisposable(); - - Stripe(final int anchor, ToolWindowManagerImpl manager){ - super(new GridBagLayout()); - //setBackground(new Color(247, 243, 239)); - myManager = manager; - myAnchor = anchor; - myWeakKeymapManagerListener=new WeakKeymapManagerListener(KeymapManagerEx.getInstanceEx(), new MyKeymapManagerListener()); - myUISettingsListener=new MyUISettingsListener(); - } - - /** - * Invoked when enclosed frame is being shown. - */ - public void addNotify(){ - super.addNotify(); - updateText(); - updateState(); - KeymapManagerEx.getInstanceEx().addKeymapManagerListener(myWeakKeymapManagerListener); - UISettings.getInstance().addUISettingsListener(myUISettingsListener,myDisposable); - } - - /** - * Invoked when enclosed frame is being disposed. - */ - public void removeNotify(){ - KeymapManagerEx.getInstanceEx().removeKeymapManagerListener(myWeakKeymapManagerListener); - Disposer.dispose(myDisposable); - super.removeNotify(); - } - - void addButton(final StripeButton button,final Comparator comparator){ - myPrefSize = null; - myButtons.add(button); - Collections.sort(myButtons,comparator); - add(button); - revalidate(); - } - - void removeButton(final StripeButton button) { - myPrefSize = null; - myButtons.remove(button); - remove(button); - revalidate(); - } - - public List getButtons() { - return Collections.unmodifiableList(myButtons); - } - - public void invalidate() { - myPrefSize = null; - super.invalidate(); - } - - public void doLayout() { - if (!myFinishingDrop) { - myLastLayoutData = recomputeBounds(true, getSize()); - } - } - - private LayoutData recomputeBounds(boolean setBounds, Dimension toFitWith) { - return recomputeBounds(setBounds, toFitWith, false); - } - - private LayoutData recomputeBounds(boolean setBounds, Dimension toFitWith, boolean noDrop) { - final LayoutData data = new LayoutData(); - final int horizontaloffset = getHeight(); - - data.eachY = 0; - data.size = new Dimension(); - data.gap = 1; - data.horizontal = isHorizontal(); - data.dragInsertPosition = -1; - if (data.horizontal) { - data.eachX = horizontaloffset; - } else { - data.eachX = 0; - } - - data.fitSize = toFitWith != null ? toFitWith : new Dimension(); - - final Rectangle stripeSensetiveRec = new Rectangle(-DROP_DISTANCE_SENSIVITY, -DROP_DISTANCE_SENSIVITY, getWidth() + DROP_DISTANCE_SENSIVITY * 2, getHeight() + DROP_DISTANCE_SENSIVITY * 2); - boolean processDrop = isDroppingButton() && stripeSensetiveRec.intersects(myDropRectangle) && !noDrop; - - if (toFitWith == null) { - for (StripeButton eachButton : myButtons) { - if (!isConsideredInLayout(eachButton)) continue; - final Dimension eachSize = eachButton.getPreferredSize(); - data.fitSize.width = Math.max(eachSize.width, data.fitSize.width); - data.fitSize.height = Math.max(eachSize.height, data.fitSize.height); - } - } - - int gap = 0; - if (toFitWith != null) { - LayoutData layoutData = recomputeBounds(false, null, true); - if (data.horizontal) { - gap = toFitWith.width - horizontaloffset - layoutData.size.width - data.eachX ; - } - else { - gap = toFitWith.height - layoutData.size.height - data.eachY; - } - - if (processDrop) { - if (data.horizontal) { - gap -= myDropRectangle.width + data.gap; - } - else { - gap -= myDropRectangle.height + data.gap; - } - } - gap = Math.max(gap, 0); - } - - int insertOrder = -1; - boolean sidesStarted = false; - - for (StripeButton eachButton : getButtonsToLayOut()) { - insertOrder = eachButton.getDecorator().getWindowInfo().getOrder(); - final Dimension eachSize = eachButton.getPreferredSize(); - - if (!sidesStarted && eachButton.getWindowInfo().isSplit()) { - if (processDrop) { - tryDroppingOnGap(data, gap, eachButton.getWindowInfo().getOrder()); - } - if (data.horizontal) { - data.eachX += gap; - data.size.width += gap; - } else { - data.eachY += gap; - data.size.height += gap; - } - sidesStarted = true; - } - - if (processDrop && !data.dragTargetChoosen) { - if (data.horizontal) { - int distance = myDropRectangle.x - data.eachX; - if (distance < eachSize.width / 2 || (myDropRectangle.x + myDropRectangle.width) < eachSize.width / 2) { - layoutButton(data, myDragButtonImage, false); - data.dragInsertPosition = insertOrder; - data.dragToSide = sidesStarted; - data.dragTargetChoosen = true; - } - } - else { - int distance = myDropRectangle.y - data.eachY; - if (distance < eachSize.height / 2 || (myDropRectangle.y + myDropRectangle.height) < eachSize.height / 2) { - layoutButton(data, myDragButtonImage, false); - data.dragInsertPosition = insertOrder; - data.dragToSide = sidesStarted; - data.dragTargetChoosen = true; - } - } - } - - layoutButton(data, eachButton, setBounds); - } - - if (!sidesStarted && processDrop) { - tryDroppingOnGap(data, gap, -1); - } - - - if (isDroppingButton()) { - final Dimension dragSize = myDragButton.getPreferredSize(); - if (getAnchor().isHorizontal() == myDragButton.getWindowInfo().getAnchor().isHorizontal()) { - data.size.width = Math.max(data.size.width, dragSize.width); - data.size.height = Math.max(data.size.height, dragSize.height); - } else { - data.size.width = Math.max(data.size.width, dragSize.height); - data.size.height = Math.max(data.size.height, dragSize.width); - } - } - - if (processDrop && !data.dragTargetChoosen) { - data.dragInsertPosition = -1; - data.dragToSide = true; - data.dragTargetChoosen = true; - } - - return data; - } - - private void tryDroppingOnGap(final LayoutData data, final int gap, final int insertOrder) { - if (data.dragTargetChoosen) return; - - int nonSideDistance; - int sideDistance; - if (data.horizontal) { - nonSideDistance = myDropRectangle.x - data.eachX; - sideDistance = data.eachX + gap - myDropRectangle.x; - } - else { - nonSideDistance = myDropRectangle.y - data.eachY; - sideDistance = data.eachY + gap - myDropRectangle.y; - } - nonSideDistance = Math.max(0, nonSideDistance); - - if (sideDistance > 0) { - if (nonSideDistance > sideDistance) { - data.dragInsertPosition = insertOrder; - data.dragToSide = true; - data.dragTargetChoosen = true; - } - else { - data.dragInsertPosition = -1; - data.dragToSide = false; - data.dragTargetChoosen = true; - } - - layoutButton(data, myDragButtonImage, false); - } - } - - private List getButtonsToLayOut() { - List result = new ArrayList(); - - List tools = new ArrayList(); - List sideTools = new ArrayList(); - - for (StripeButton b : myButtons) { - if (!isConsideredInLayout(b)) continue; - - if (b.getWindowInfo().isSplit()) { - sideTools.add(b); - } - else { - tools.add(b); - } - } - - result.addAll(tools); - result.addAll(sideTools); - - return result; - } - - - public ToolWindowAnchor getAnchor() { - return ToolWindowAnchor.get(myAnchor); - } - - private static void layoutButton(final LayoutData data, final JComponent eachButton, boolean setBounds) { - final Dimension eachSize = eachButton.getPreferredSize(); - if (setBounds) { - final int width = data.horizontal ? eachSize.width : data.fitSize.width; - final int height = data.horizontal ? data.fitSize.height : eachSize.height; - eachButton.setBounds(data.eachX, data.eachY, width, height); - } - if (data.horizontal) { - final int deltaX = eachSize.width + data.gap; - data.eachX += deltaX; - data.size.width += deltaX; - data.size.height = eachSize.height; - } else { - final int deltaY = eachSize.height + data.gap; - data.eachY += deltaY; - data.size.width = eachSize.width; - data.size.height += deltaY; - } - data.processedComponents++; - } - - public void startDrag() { - revalidate(); - repaint(); - } - - public void stopDrag() { - revalidate(); - repaint(); - } - - public StripeButton getButtonFor(final String toolWindowId) { - for (StripeButton each : myButtons) { - if (each.getWindowInfo().getId().equals(toolWindowId)) return each; - } - return null; - } - - public void setOverlayed(boolean overlayed) { - Color bg = UIUtil.getPanelBackground(); - if (overlayed) { - setBackground(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 190)); - } else { - setBackground(bg); - } - } - - private static class LayoutData { - int eachX; - int eachY; - int gap; - Dimension size; - Dimension fitSize; - boolean horizontal; - int processedComponents; - - boolean dragTargetChoosen; - boolean dragToSide; - int dragInsertPosition; - } - - private boolean isHorizontal() { - return myAnchor == SwingConstants.TOP || myAnchor == SwingConstants.BOTTOM; - } - - public Dimension getPreferredSize() { - if (myPrefSize == null) { - myPrefSize = recomputeBounds(false, null).size; - } - - return myPrefSize; - } - - - private void updateText(){ - final int size=myButtons.size(); - for(int i=0;i myButtons = new ArrayList(); + private final WeakKeymapManagerListener myWeakKeymapManagerListener; + private final MyUISettingsListener myUISettingsListener; + + private Dimension myPrefSize; + private StripeButton myDragButton; + private Rectangle myDropRectangle; + private final ToolWindowManagerImpl myManager; + private JComponent myDragButtonImage; + private LayoutData myLastLayoutData; + private boolean myFinishingDrop; + static final int DROP_DISTANCE_SENSIVITY = 20; + private final Disposable myDisposable = Disposer.newDisposable(); + + Stripe(final int anchor, ToolWindowManagerImpl manager){ + super(new GridBagLayout()); + //setBackground(new Color(247, 243, 239)); + myManager = manager; + myAnchor = anchor; + myWeakKeymapManagerListener=new WeakKeymapManagerListener(KeymapManagerEx.getInstanceEx(), new MyKeymapManagerListener()); + myUISettingsListener=new MyUISettingsListener(); + } + + /** + * Invoked when enclosed frame is being shown. + */ + public void addNotify(){ + super.addNotify(); + updateText(); + updateState(); + KeymapManagerEx.getInstanceEx().addKeymapManagerListener(myWeakKeymapManagerListener); + UISettings.getInstance().addUISettingsListener(myUISettingsListener,myDisposable); + } + + /** + * Invoked when enclosed frame is being disposed. + */ + public void removeNotify(){ + KeymapManagerEx.getInstanceEx().removeKeymapManagerListener(myWeakKeymapManagerListener); + Disposer.dispose(myDisposable); + super.removeNotify(); + } + + void addButton(final StripeButton button,final Comparator comparator){ + myPrefSize = null; + myButtons.add(button); + Collections.sort(myButtons,comparator); + add(button); + revalidate(); + } + + void removeButton(final StripeButton button) { + myPrefSize = null; + myButtons.remove(button); + remove(button); + revalidate(); + } + + public List getButtons() { + return Collections.unmodifiableList(myButtons); + } + + public void invalidate() { + myPrefSize = null; + super.invalidate(); + } + + public void doLayout() { + if (!myFinishingDrop) { + myLastLayoutData = recomputeBounds(true, getSize()); + } + } + + private LayoutData recomputeBounds(boolean setBounds, Dimension toFitWith) { + return recomputeBounds(setBounds, toFitWith, false); + } + + private LayoutData recomputeBounds(boolean setBounds, Dimension toFitWith, boolean noDrop) { + final LayoutData data = new LayoutData(); + final int horizontaloffset = getHeight(); + + data.eachY = 0; + data.size = new Dimension(); + data.gap = 1; + data.horizontal = isHorizontal(); + data.dragInsertPosition = -1; + if (data.horizontal) { + data.eachX = horizontaloffset; + } else { + data.eachX = 0; + } + + data.fitSize = toFitWith != null ? toFitWith : new Dimension(); + + final Rectangle stripeSensetiveRec = new Rectangle(-DROP_DISTANCE_SENSIVITY, -DROP_DISTANCE_SENSIVITY, getWidth() + DROP_DISTANCE_SENSIVITY * 2, getHeight() + DROP_DISTANCE_SENSIVITY * 2); + boolean processDrop = isDroppingButton() && stripeSensetiveRec.intersects(myDropRectangle) && !noDrop; + + if (toFitWith == null) { + for (StripeButton eachButton : myButtons) { + if (!isConsideredInLayout(eachButton)) continue; + final Dimension eachSize = eachButton.getPreferredSize(); + data.fitSize.width = Math.max(eachSize.width, data.fitSize.width); + data.fitSize.height = Math.max(eachSize.height, data.fitSize.height); + } + } + + int gap = 0; + if (toFitWith != null) { + LayoutData layoutData = recomputeBounds(false, null, true); + if (data.horizontal) { + gap = toFitWith.width - horizontaloffset - layoutData.size.width - data.eachX ; + } + else { + gap = toFitWith.height - layoutData.size.height - data.eachY; + } + + if (processDrop) { + if (data.horizontal) { + gap -= myDropRectangle.width + data.gap; + } + else { + gap -= myDropRectangle.height + data.gap; + } + } + gap = Math.max(gap, 0); + } + + int insertOrder = -1; + boolean sidesStarted = false; + + for (StripeButton eachButton : getButtonsToLayOut()) { + insertOrder = eachButton.getDecorator().getWindowInfo().getOrder(); + final Dimension eachSize = eachButton.getPreferredSize(); + + if (!sidesStarted && eachButton.getWindowInfo().isSplit()) { + if (processDrop) { + tryDroppingOnGap(data, gap, eachButton.getWindowInfo().getOrder()); + } + if (data.horizontal) { + data.eachX += gap; + data.size.width += gap; + } else { + data.eachY += gap; + data.size.height += gap; + } + sidesStarted = true; + } + + if (processDrop && !data.dragTargetChoosen) { + if (data.horizontal) { + int distance = myDropRectangle.x - data.eachX; + if (distance < eachSize.width / 2 || (myDropRectangle.x + myDropRectangle.width) < eachSize.width / 2) { + layoutButton(data, myDragButtonImage, false); + data.dragInsertPosition = insertOrder; + data.dragToSide = sidesStarted; + data.dragTargetChoosen = true; + } + } + else { + int distance = myDropRectangle.y - data.eachY; + if (distance < eachSize.height / 2 || (myDropRectangle.y + myDropRectangle.height) < eachSize.height / 2) { + layoutButton(data, myDragButtonImage, false); + data.dragInsertPosition = insertOrder; + data.dragToSide = sidesStarted; + data.dragTargetChoosen = true; + } + } + } + + layoutButton(data, eachButton, setBounds); + } + + if (!sidesStarted && processDrop) { + tryDroppingOnGap(data, gap, -1); + } + + + if (isDroppingButton()) { + final Dimension dragSize = myDragButton.getPreferredSize(); + if (getAnchor().isHorizontal() == myDragButton.getWindowInfo().getAnchor().isHorizontal()) { + data.size.width = Math.max(data.size.width, dragSize.width); + data.size.height = Math.max(data.size.height, dragSize.height); + } else { + data.size.width = Math.max(data.size.width, dragSize.height); + data.size.height = Math.max(data.size.height, dragSize.width); + } + } + + if (processDrop && !data.dragTargetChoosen) { + data.dragInsertPosition = -1; + data.dragToSide = true; + data.dragTargetChoosen = true; + } + + return data; + } + + private void tryDroppingOnGap(final LayoutData data, final int gap, final int insertOrder) { + if (data.dragTargetChoosen) return; + + int nonSideDistance; + int sideDistance; + if (data.horizontal) { + nonSideDistance = myDropRectangle.x - data.eachX; + sideDistance = data.eachX + gap - myDropRectangle.x; + } + else { + nonSideDistance = myDropRectangle.y - data.eachY; + sideDistance = data.eachY + gap - myDropRectangle.y; + } + nonSideDistance = Math.max(0, nonSideDistance); + + if (sideDistance > 0) { + if (nonSideDistance > sideDistance) { + data.dragInsertPosition = insertOrder; + data.dragToSide = true; + data.dragTargetChoosen = true; + } + else { + data.dragInsertPosition = -1; + data.dragToSide = false; + data.dragTargetChoosen = true; + } + + layoutButton(data, myDragButtonImage, false); + } + } + + private List getButtonsToLayOut() { + List result = new ArrayList(); + + List tools = new ArrayList(); + List sideTools = new ArrayList(); + + for (StripeButton b : myButtons) { + if (!isConsideredInLayout(b)) continue; + + if (b.getWindowInfo().isSplit()) { + sideTools.add(b); + } + else { + tools.add(b); + } + } + + result.addAll(tools); + result.addAll(sideTools); + + return result; + } + + + public ToolWindowAnchor getAnchor() { + return ToolWindowAnchor.get(myAnchor); + } + + private static void layoutButton(final LayoutData data, final JComponent eachButton, boolean setBounds) { + final Dimension eachSize = eachButton.getPreferredSize(); + if (setBounds) { + final int width = data.horizontal ? eachSize.width : data.fitSize.width; + final int height = data.horizontal ? data.fitSize.height : eachSize.height; + eachButton.setBounds(data.eachX, data.eachY, width, height); + } + if (data.horizontal) { + final int deltaX = eachSize.width + data.gap; + data.eachX += deltaX; + data.size.width += deltaX; + data.size.height = eachSize.height; + } else { + final int deltaY = eachSize.height + data.gap; + data.eachY += deltaY; + data.size.width = eachSize.width; + data.size.height += deltaY; + } + data.processedComponents++; + } + + public void startDrag() { + revalidate(); + repaint(); + } + + public void stopDrag() { + revalidate(); + repaint(); + } + + public StripeButton getButtonFor(final String toolWindowId) { + for (StripeButton each : myButtons) { + if (each.getWindowInfo().getId().equals(toolWindowId)) return each; + } + return null; + } + + public void setOverlayed(boolean overlayed) { + Color bg = UIUtil.getPanelBackground(); + if (overlayed) { + setBackground(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 190)); + } else { + setBackground(bg); + } + } + + private static class LayoutData { + int eachX; + int eachY; + int gap; + Dimension size; + Dimension fitSize; + boolean horizontal; + int processedComponents; + + boolean dragTargetChoosen; + boolean dragToSide; + int dragInsertPosition; + } + + private boolean isHorizontal() { + return myAnchor == SwingConstants.TOP || myAnchor == SwingConstants.BOTTOM; + } + + public Dimension getPreferredSize() { + if (myPrefSize == null) { + myPrefSize = recomputeBounds(false, null).size; + } + + return myPrefSize; + } + + + private void updateText(){ + final int size=myButtons.size(); + for(int i=0;i myId2InternalDecorator; - private final HashMap myId2FloatingDecorator; - private final HashMap myId2StripeButton; - private final HashMap myId2FocusWatcher; - private final Set myDumbAwareIds = Collections.synchronizedSet(CollectionFactory.newTroveSet()); - - private final EditorComponentFocusWatcher myEditorComponentFocusWatcher; - private final MyToolWindowPropertyChangeListener myToolWindowPropertyChangeListener; - private final InternalDecoratorListener myInternalDecoratorListener; - - private boolean myEditorComponentActive; - private final ActiveStack myActiveStack; - private final SideStack mySideStack; - - private ToolWindowsPane myToolWindowsPane; - private IdeFrameImpl myFrame; - private DesktopLayout myLayoutToRestoreLater = null; - @NonNls private static final String EDITOR_ELEMENT = "editor"; - @NonNls private static final String ACTIVE_ATTR_VALUE = "active"; - @NonNls private static final String FRAME_ELEMENT = "frame"; - @NonNls private static final String X_ATTR = "x"; - @NonNls private static final String Y_ATTR = "y"; - @NonNls private static final String WIDTH_ATTR = "width"; - @NonNls private static final String HEIGHT_ATTR = "height"; - @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state"; - - - private final Set myRestoredToolWindowIds = new HashSet(); - private final FileEditorManager myFileEditorManager; - - private final Map myWindow2Balloon = new HashMap(); - - private KeyState myCurrentState = KeyState.waiting; - private final Alarm myWaiterForSecondPress = new Alarm(); - private final Runnable mySecondPressRunnable = new Runnable() { - public void run() { - if (myCurrentState != KeyState.hold) { - resetHoldState(); - } - } - }; - - private enum KeyState { - waiting, pressed, released, hold - } - - /** - * invoked by reflection - */ - public ToolWindowManagerImpl(final Project project, - WindowManagerEx windowManagerEx, - final FileEditorManager fem, - ActionManager actionManager) { - myProject = project; - myWindowManager = windowManagerEx; - myFileEditorManager = fem; - myListenerList = new EventListenerList(); - - if (!project.isDefault()) { - actionManager.addAnActionListener(new AnActionListener() { - @Override - public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { - if (myCurrentState != KeyState.hold) { - resetHoldState(); - } - } - - @Override - public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { - } - - @Override - public void beforeEditorTyping(char c, DataContext dataContext) { - } - }, project); - } - - myLayout = new DesktopLayout(); - myLayout.copyFrom(windowManagerEx.getLayout()); - - myId2InternalDecorator = new HashMap(); - myId2FloatingDecorator = new HashMap(); - myId2StripeButton = new HashMap(); - myId2FocusWatcher = new HashMap(); - - myEditorComponentFocusWatcher = new EditorComponentFocusWatcher(); - myToolWindowPropertyChangeListener = new MyToolWindowPropertyChangeListener(); - myInternalDecoratorListener = new MyInternalDecoratorListener(); - - myEditorComponentActive = false; - myActiveStack = new ActiveStack(); - mySideStack = new SideStack(); - - project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { - public void fileOpened(FileEditorManager source, VirtualFile file) { - } - - public void fileClosed(FileEditorManager source, VirtualFile file) { - getFocusManagerImpl().doWhenFocusSettlesDown(new Runnable() { - public void run() { - if (!hasOpenEditorFiles()) { - focusToolWinowByDefault(null); - } - } - }); - } - - public void selectionChanged(FileEditorManagerEvent event) { - } - }); - } - - public boolean dispatchKeyEvent(KeyEvent e) { - if (e.getKeyCode() != KeyEvent.VK_CONTROL && - e.getKeyCode() != KeyEvent.VK_ALT && - e.getKeyCode() != KeyEvent.VK_SHIFT && - e.getKeyCode() != KeyEvent.VK_META) { - if (e.getModifiers() == 0) { - resetHoldState(); - } - return false; - } - if (e.getID() != KeyEvent.KEY_PRESSED && e.getID() != KeyEvent.KEY_RELEASED) return false; - - Component parent = UIUtil.findUltimateParent(e.getComponent()); - if (parent instanceof IdeFrame) { - if (((IdeFrame)parent).getProject() != myProject) { - resetHoldState(); - return false; - } - } - - Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); - Shortcut[] baseShortcut = keymap.getShortcuts("ActivateProjectToolWindow"); - int baseModifiers = 0; - for (Shortcut each : baseShortcut) { - if (each instanceof KeyboardShortcut) { - KeyStroke keyStroke = ((KeyboardShortcut)each).getFirstKeyStroke(); - baseModifiers = keyStroke.getModifiers(); - if (baseModifiers > 0) { - break; - } - } - } - - if (baseModifiers == 0) { - resetHoldState(); - return false; - } - - Set vks = QuickAccessSettings.getModifiersVKs(baseModifiers); - if (vks.contains(e.getKeyCode())) { - boolean pressed = e.getID() == KeyEvent.KEY_PRESSED; - int modifiers = e.getModifiers(); - - int mouseMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK; - if ((e.getModifiersEx() & mouseMask) == 0) { - if (SwitchManager.areAllModifiersPressed(modifiers, vks) || !pressed) { - processState(pressed); - } - else { - resetHoldState(); - } - } - } - - - return false; - } - - private void resetHoldState() { - myCurrentState = KeyState.waiting; - processHoldState(); - } - - private void resetState() { - myCurrentState = KeyState.waiting; - } - - private void processState(boolean pressed) { - if (pressed) { - if (myCurrentState == KeyState.waiting) { - myCurrentState = KeyState.pressed; - } - else if (myCurrentState == KeyState.released) { - myCurrentState = KeyState.hold; - processHoldState(); - } - } - else { - if (myCurrentState == KeyState.pressed) { - myCurrentState = KeyState.released; - restartWaitingForSecondPressAlarm(); - } - else if (myCurrentState == KeyState.hold) { - resetHoldState(); - } - else { - resetHoldState(); - } - } - } - - private void processHoldState() { - myToolWindowsPane.setStripesOverlayed(myCurrentState == KeyState.hold); - } - - private void restartWaitingForSecondPressAlarm() { - myWaiterForSecondPress.cancelAllRequests(); - myWaiterForSecondPress.addRequest(mySecondPressRunnable, Registry.intValue("actionSystem.keyGestureDblClickTime")); - } - - private boolean hasOpenEditorFiles() { - return myFileEditorManager.getOpenFiles().length > 0; - } - - FocusManagerImpl getFocusManagerImpl() { - return FocusManagerImpl.getInstance(); - } - - public Project getProject() { - return myProject; - } - - public void initComponent() { - } - - public void disposeComponent() { - } - - public void projectOpened() { - final MyUIManagerPropertyChangeListener myUIManagerPropertyChangeListener = new MyUIManagerPropertyChangeListener(); - final MyLafManagerListener myLafManagerListener = new MyLafManagerListener(); - - UIManager.addPropertyChangeListener(myUIManagerPropertyChangeListener); - LafManager.getInstance().addLafManagerListener(myLafManagerListener); - - Disposer.register(myProject, new Disposable() { - public void dispose() { - UIManager.removePropertyChangeListener(myUIManagerPropertyChangeListener); - LafManager.getInstance().removeLafManagerListener(myLafManagerListener); - } - }); - myFrame = myWindowManager.allocateFrame(myProject); - LOG.assertTrue(myFrame != null); - - final ArrayList commandsList = new ArrayList(); - - myToolWindowsPane = new ToolWindowsPane(myFrame, this); - Disposer.register(myProject, myToolWindowsPane); - ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(myToolWindowsPane); - appendUpdateToolWindowsPaneCmd(commandsList); - - myFrame.setTitle(FrameTitleBuilder.getInstance().getProjectTitle(myProject)); - - FileEditorManagerEx editorManager = FileEditorManagerEx.getInstanceEx(myProject); - final JComponent editorComponent = editorManager.getComponent(); - myEditorComponentFocusWatcher.install(editorComponent); - - appendSetEditorComponentCmd(editorComponent, commandsList); - if (myEditorComponentActive) { - activateEditorComponentImpl(editorManager.getSplitters(), commandsList, true); - } - execute(commandsList); - - final DumbService.DumbModeListener dumbModeListener = new DumbService.DumbModeListener() { - public void enteredDumbMode() { - for (final String id : getToolWindowIds()) { - if (!myDumbAwareIds.contains(id)) { - if (isToolWindowVisible(id)) { - hideToolWindow(id, true); - } - getStripeButton(id).setEnabled(false); - } - } - } - - public void exitDumbMode() { - for (final String id : getToolWindowIds()) { - getStripeButton(id).setEnabled(true); - } - } - }; - myProject.getMessageBus().connect().subscribe(DumbService.DUMB_MODE, dumbModeListener); - - StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { - public void run() { - registerToolWindowsFromBeans(); - if (DumbService.getInstance(myProject).isDumb()) { - dumbModeListener.enteredDumbMode(); - } - } - }); - - KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); - } - - private void registerToolWindowsFromBeans() { - ToolWindowEP[] beans = Extensions.getExtensions(ToolWindowEP.EP_NAME); - for (final ToolWindowEP bean : beans) { - final Condition condition = bean.getCondition(); - if (condition != null && !condition.value(myProject)) { - continue; - } - ToolWindowAnchor toolWindowAnchor; - try { - toolWindowAnchor = ToolWindowAnchor.fromText(bean.anchor); - } - catch (Exception e) { - LOG.error(e); - continue; - } - JLabel label = new JLabel("Initializing...", JLabel.CENTER); - label.setOpaque(true); - final Color treeBg = UIManager.getColor("Tree.background"); - label.setBackground(new Color(treeBg.getRed(), treeBg.getGreen(), treeBg.getBlue(), 180)); - final Color treeFg = UIUtil.getTreeForeground(); - label.setForeground(new Color(treeFg.getRed(), treeFg.getGreen(), treeFg.getBlue(), 180)); - final ToolWindowFactory factory = bean.getToolWindowFactory(); - final ToolWindowImpl toolWindow = - (ToolWindowImpl)registerToolWindow(bean.id, label, toolWindowAnchor, myProject, DumbService.isDumbAware(factory)); - toolWindow.setContentFactory(factory); - if (bean.icon != null) { - Icon icon = IconLoader.findIcon(bean.icon, factory.getClass()); - if (icon == null) { - try { - icon = IconLoader.getIcon(bean.icon); - } - catch (Exception ignored) { - } - } - toolWindow.setIcon(icon); - } - - if (!getInfo(bean.id).isSplit() && bean.secondary && !myRestoredToolWindowIds.contains(bean.id)) { - toolWindow.setSplitMode(bean.secondary, null); - } - - final ActionCallback activation = toolWindow.setActivation(new ActionCallback()); - - UiNotifyConnector.doWhenFirstShown(label, new Runnable() { - public void run() { - ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { - public void run() { - toolWindow.ensureContentInitialized(); - activation.setDone(); - } - }); - } - }); - } - } - - public void projectClosed() { - final ArrayList commandsList = new ArrayList(); - final String[] ids = getToolWindowIds(); - - // Remove ToolWindowsPane - - ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(null); - myWindowManager.releaseFrame(myFrame); - appendUpdateToolWindowsPaneCmd(commandsList); - - // Hide all tool windows - - for (final String id : ids) { - deactivateToolWindowImpl(id, true, commandsList); - } - - // Remove editor component - - final JComponent editorComponent = FileEditorManagerEx.getInstanceEx(myProject).getComponent(); - myEditorComponentFocusWatcher.deinstall(editorComponent); - appendSetEditorComponentCmd(null, commandsList); - execute(commandsList); - - KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this); - } - - public void addToolWindowManagerListener(@NotNull final ToolWindowManagerListener l) { - myListenerList.add(ToolWindowManagerListener.class, l); - } - - public void removeToolWindowManagerListener(@NotNull final ToolWindowManagerListener l) { - myListenerList.remove(ToolWindowManagerListener.class, l); - } - - /** - * This is helper method. It delegated its fuctionality to the WindowManager. - * Before delegating it fires state changed. - */ - private void execute(final ArrayList commandList) { - for (FinalizableCommand each : commandList) { - if (each.willChangeState()) { - fireStateChanged(); - break; - } - } - - for (FinalizableCommand each : commandList) { - each.beforeExecute(this); - } - myWindowManager.getCommandProcessor().execute(commandList, myProject.getDisposed()); - } - - public void activateEditorComponent() { - activateEditorComponent(true); - } - - private void activateEditorComponent(final boolean forced) { - activateEditorComponent(forced, false); - } - private void activateEditorComponent(final boolean forced, boolean now) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: activateEditorComponent()"); - } - ApplicationManager.getApplication().assertIsDispatchThread(); - - Runnable runnable = new Runnable() { - @Override - public void run() { - final ArrayList commandList = new ArrayList(); - activateEditorComponentImpl(getSplittersFromFocus(), commandList, forced); - execute(commandList); - } - }; - - if (now) { - runnable.run(); - } else { - getFocusManager().doWhenFocusSettlesDown(runnable); - - } - } - - private EditorsSplitters getSplittersFromFocus() { - return FileEditorManagerEx.getInstanceEx(myProject).getSplitters(); - } - - private void activateEditorComponentImpl(EditorsSplitters splitters, final ArrayList commandList, final boolean forced) { - final String active = getActiveToolWindowId(); - // Now we have to request focus into most recent focused editor - appendRequestFocusInEditorComponentCmd(splitters, commandList, forced).doWhenDone(new Runnable() { - public void run() { - final ArrayList postExecute = new ArrayList(); - - if (LOG.isDebugEnabled()) { - LOG.debug("editor activated"); - } - deactivateWindows(postExecute, null); - myActiveStack.clear(); - myEditorComponentActive = true; - - execute(postExecute); - } - }).doWhenRejected(new Runnable() { - public void run() { - if (forced) { - getFocusManagerImpl().requestFocus(new FocusCommand() { - public ActionCallback run() { - final ArrayList cmds = new ArrayList(); - - final WindowInfoImpl toReactivate = getInfo(active); - final boolean reactivateLastActive = toReactivate != null && !isToHide(toReactivate); - deactivateWindows(cmds, reactivateLastActive ? active : null); - execute(cmds); - - if (reactivateLastActive) { - activateToolWindow(active, false, true); - } - else { - if (active != null) { - myActiveStack.remove(active, false); - } - - if (!myActiveStack.isEmpty()) { - activateToolWindow(myActiveStack.peek(), false, true); - } - } - return new ActionCallback.Done(); - } - }, false); - } - } - }); - } - - private void deactivateWindows(final ArrayList postExecute, String idToIgnore) { - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (final WindowInfoImpl info : infos) { - final boolean shouldHide = isToHide(info); - if (idToIgnore != null && idToIgnore.equals(info.getId())) { - continue; - } - deactivateToolWindowImpl(info.getId(), shouldHide, postExecute); - } - } - - private boolean isToHide(final WindowInfoImpl info) { - return (info.isAutoHide() || info.isSliding()) && !(info.isFloating() && hasModalChild(info)); - } - - /** - * Helper method. It makes window visible, activates it and request focus into the tool window. - * But it doesn't deactivate other tool windows. Use prepareForActivation method to - * deactivates other tool windows. - * - * @param dirtyMode if true then all UI operations are performed in "dirty" mode. - * It means that UI isn't validated and repainted just after each add/remove operation. - * @see ToolWindowManagerImpl#prepareForActivation - */ - private void showAndActivate(final String id, - final boolean dirtyMode, - final ArrayList commandsList, - boolean autoFocusContents) { - if (!getToolWindow(id).isAvailable()) { - return; - } - // show activated - final WindowInfoImpl info = getInfo(id); - boolean toApplyInfo = false; - if (!info.isActive()) { - info.setActive(true); - toApplyInfo = true; - } - showToolWindowImpl(id, dirtyMode, commandsList); - - // activate - if (toApplyInfo) { - appendApplyWindowInfoCmd(info, commandsList); - myActiveStack.push(id); - myEditorComponentActive = false; - } - - if (autoFocusContents) { - appendRequestFocusInToolWindowCmd(id, commandsList, true); - } - } - - void activateToolWindow(final String id, boolean forced, boolean autoFocusContents) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: activateToolWindow(" + id + ")"); - } - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - if (DumbService.getInstance(myProject).isDumb() && !myDumbAwareIds.contains(id)) { - return; - } - - final ArrayList commandList = new ArrayList(); - activateToolWindowImpl(id, commandList, forced, autoFocusContents); - execute(commandList); - } - - private void activateToolWindowImpl(final String id, - final ArrayList commandList, - boolean forced, - boolean autoFocusContents) { - if (!getFocusManagerImpl().isUnforcedRequestAllowed() && !forced) return; - - if (LOG.isDebugEnabled()) { - LOG.debug("enter: activateToolWindowImpl(" + id + ")"); - } - if (!getToolWindow(id).isAvailable()) { - // Tool window can be "logically" active but not focused. For example, - // when the user switched to another application. So we just need to bring - // tool window's window to front. - final InternalDecorator decorator = getInternalDecorator(id); - if (!decorator.hasFocus() && autoFocusContents) { - appendRequestFocusInToolWindowCmd(id, commandList, forced); - } - return; - } - prepareForActivation(id, commandList); - showAndActivate(id, false, commandList, autoFocusContents); - } - - /** - * Checkes whether the specified id defines installed tool - * window. If it's not then throws IllegalStateException. - * - * @throws IllegalStateException if tool window isn't installed. - */ - private void checkId(final String id) { - if (!myLayout.isToolWindowRegistered(id)) { - throw new IllegalStateException("window with id=\"" + id + "\" isn't registered"); - } - } - - /** - * Helper method. It deactivates (and hides) window with specified id. - * - * @param id id of the tool window to be deactivated. - * @param shouldHide if true then also hides specified tool window. - */ - private void deactivateToolWindowImpl(final String id, final boolean shouldHide, final List commandsList) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: deactivateToolWindowImpl(" + id + "," + shouldHide + ")"); - } - final WindowInfoImpl info = getInfo(id); - if (shouldHide && info.isVisible()) { - info.setVisible(false); - if (info.isFloating()) { - appendRemoveFloatingDecoratorCmd(info, commandsList); - } - else { // docked and sliding windows - appendRemoveDecoratorCmd(id, false, commandsList); - } - } - info.setActive(false); - appendApplyWindowInfoCmd(info, commandsList); - } - - public String[] getToolWindowIds() { - ApplicationManager.getApplication().assertIsDispatchThread(); - final WindowInfoImpl[] infos = myLayout.getInfos(); - final String[] ids = ArrayUtil.newStringArray(infos.length); - for (int i = 0; i < infos.length; i++) { - ids[i] = infos[i].getId(); - } - return ids; - } - - public String getActiveToolWindowId() { - ApplicationManager.getApplication().assertIsDispatchThread(); - return myLayout.getActiveId(); - } - - public String getLastActiveToolWindowId() { - return getLastActiveToolWindowId(null); - } - - public String getLastActiveToolWindowId(Condition condition) { - ApplicationManager.getApplication().assertIsDispatchThread(); - String lastActiveToolWindowId = null; - for (int i = 0; i < myActiveStack.getPersistentSize(); i++) { - final String id = myActiveStack.peekPersistent(i); - final ToolWindow toolWindow = getToolWindow(id); - LOG.assertTrue(toolWindow != null); - if (toolWindow.isAvailable()) { - if (condition == null || condition.value(toolWindow.getComponent())) { - lastActiveToolWindowId = id; - break; - } - } - } - return lastActiveToolWindowId; - } - - /** - * @return floating decorator for the tool window with specified ID. - */ - private FloatingDecorator getFloatingDecorator(final String id) { - return myId2FloatingDecorator.get(id); - } - - /** - * @return internal decorator for the tool window with specified ID. - */ - private InternalDecorator getInternalDecorator(final String id) { - return myId2InternalDecorator.get(id); - } - - /** - * @return tool button for the window with specified ID. - */ - private StripeButton getStripeButton(final String id) { - return myId2StripeButton.get(id); - } - - /** - * @return info for the tool window with specified ID. - */ - private WindowInfoImpl getInfo(final String id) { - return myLayout.getInfo(id, true); - } - - public List getIdsOn(@NotNull final ToolWindowAnchor anchor) { - return myLayout.getVisibleIdsOn(anchor, this); - } - - public ToolWindow getToolWindow(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (!myLayout.isToolWindowRegistered(id)) { - return null; - } - InternalDecorator decorator = getInternalDecorator(id); - return decorator != null ? decorator.getToolWindow() : null; - } - - void showToolWindow(final String id) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: showToolWindow(" + id + ")"); - } - ApplicationManager.getApplication().assertIsDispatchThread(); - final ArrayList commandList = new ArrayList(); - showToolWindowImpl(id, false, commandList); - execute(commandList); - } - - public void hideToolWindow(@NotNull final String id, final boolean hideSide) { - hideToolWindow(id, hideSide, true); - } - - public void hideToolWindow(final String id, final boolean hideSide, final boolean moveFocus) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - final WindowInfoImpl info = getInfo(id); - if (!info.isVisible()) return; - final ArrayList commandList = new ArrayList(); - final boolean wasActive = info.isActive(); - - // hide and deactivate - - deactivateToolWindowImpl(id, true, commandList); - - if (hideSide || info.isFloating()) { - final List ids = myLayout.getVisibleIdsOn(info.getAnchor(), this); - for (String each : ids) { - myActiveStack.remove(each, true); - } - - - while (!mySideStack.isEmpty(info.getAnchor())) { - mySideStack.pop(info.getAnchor()); - } - - final String[] all = getToolWindowIds(); - for (String eachId : all) { - final WindowInfoImpl eachInfo = getInfo(eachId); - if (eachInfo.isVisible() && eachInfo.getAnchor() == info.getAnchor()) { - deactivateToolWindowImpl(eachId, true, commandList); - } - } - - activateEditorComponentImpl(getSplittersFromFocus(), commandList, true); - } - else { - - // first of all we have to find tool window that was located at the same side and - // was hidden. - - WindowInfoImpl info2 = null; - while (!mySideStack.isEmpty(info.getAnchor())) { - final WindowInfoImpl storedInfo = mySideStack.pop(info.getAnchor()); - final WindowInfoImpl currentInfo = getInfo(storedInfo.getId()); - LOG.assertTrue(currentInfo != null); - // SideStack contains copies of real WindowInfos. It means that - // these stored infos can be invalid. The following loop removes invalid WindowInfos. - if (storedInfo.getAnchor() == currentInfo.getAnchor() && - storedInfo.getType() == currentInfo.getType() && - storedInfo.isAutoHide() == currentInfo.isAutoHide()) { - info2 = storedInfo; - break; - } - } - if (info2 != null) { - showToolWindowImpl(info2.getId(), false, commandList); - } - - // If we hide currently active tool window then we should activate the previous - // one which is located in the tool window stack. - // Activate another tool window if no active tool window exists and - // window stack is enabled. - - myActiveStack.remove(id, false); // hidden window should be at the top of stack - - if (wasActive && moveFocus) { - if (myActiveStack.isEmpty()) { - if (hasOpenEditorFiles()) { - activateEditorComponentImpl(getSplittersFromFocus(), commandList, false); - } - else { - focusToolWinowByDefault(id); - } - } - else { - final String toBeActivatedId = myActiveStack.pop(); - if (toBeActivatedId != null) { - activateToolWindowImpl(toBeActivatedId, commandList, false, true); - } - else { - focusToolWinowByDefault(id); - } - } - } - } - - execute(commandList); - } - - /** - * @param dirtyMode if true then all UI operations are performed in dirty mode. - */ - private void showToolWindowImpl(final String id, final boolean dirtyMode, final List commandsList) { - final WindowInfoImpl toBeShownInfo = getInfo(id); - if (toBeShownInfo.isVisible() || !getToolWindow(id).isAvailable()) { - return; - } - - if (DumbService.getInstance(myProject).isDumb() && !myDumbAwareIds.contains(id)) { - return; - } - - toBeShownInfo.setVisible(true); - final InternalDecorator decorator = getInternalDecorator(id); - - if (toBeShownInfo.isFloating()) { - commandsList.add(new AddFloatingDecoratorCmd(decorator, toBeShownInfo)); - } - else { // docked and sliding windows - - // If there is tool window on the same side then we have to hide it, i.e. - // clear place for tool window to be shown. - // - // We store WindowInfo of hidden tool window in the SideStack (if the tool window - // is docked and not auto-hide one). Therefore it's possible to restore the - // hidden tool window when showing tool window will be closed. - - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (final WindowInfoImpl info : infos) { - if (id.equals(info.getId())) { - continue; - } - if (info.isVisible() && - info.getType() == toBeShownInfo.getType() && - info.getAnchor() == toBeShownInfo.getAnchor() && - info.isSplit() == toBeShownInfo.isSplit()) { - // hide and deactivate tool window - info.setVisible(false); - appendRemoveDecoratorCmd(info.getId(), false, commandsList); - if (info.isActive()) { - info.setActive(false); - } - appendApplyWindowInfoCmd(info, commandsList); - // store WindowInfo into the SideStack - if (info.isDocked() && !info.isAutoHide()) { - mySideStack.push(info); - } - } - } - appendAddDecoratorCmd(decorator, toBeShownInfo, dirtyMode, commandsList); - - // Remove tool window from the SideStack. - - mySideStack.remove(id); - } - - appendApplyWindowInfoCmd(toBeShownInfo, commandsList); - } - - public ToolWindow registerToolWindow(@NotNull final String id, - @NotNull final JComponent component, - @NotNull final ToolWindowAnchor anchor) { - return registerToolWindow(id, component, anchor, false); - } - - public ToolWindow registerToolWindow(@NotNull final String id, - @NotNull JComponent component, - @NotNull ToolWindowAnchor anchor, - Disposable parentDisposable) { - return registerToolWindow(id, component, anchor, parentDisposable, false); - } - - public ToolWindow registerToolWindow(@NotNull final String id, - @NotNull JComponent component, - @NotNull ToolWindowAnchor anchor, - Disposable parentDisposable, - boolean canWorkInDumbMode) { - return registerDisposable(id, parentDisposable, registerToolWindow(id, component, anchor, canWorkInDumbMode)); - } - - private ToolWindow registerToolWindow(@NotNull final String id, - @NotNull final JComponent component, - @NotNull final ToolWindowAnchor anchor, - boolean canWorkInDumbMode) { - return registerToolWindow(id, component, anchor, false, false, canWorkInDumbMode); - } - - public ToolWindow registerToolWindow(@NotNull final String id, final boolean canCloseContent, @NotNull final ToolWindowAnchor anchor) { - return registerToolWindow(id, null, anchor, false, canCloseContent, false); - } - - public ToolWindow registerToolWindow(@NotNull final String id, - final boolean canCloseContent, - @NotNull final ToolWindowAnchor anchor, - final boolean sideTool) { - return registerToolWindow(id, null, anchor, sideTool, canCloseContent, false); - } - - - public ToolWindow registerToolWindow(@NotNull final String id, - final boolean canCloseContent, - @NotNull final ToolWindowAnchor anchor, - final Disposable parentDisposable, - final boolean canWorkInDumbMode) { - return registerDisposable(id, parentDisposable, registerToolWindow(id, null, anchor, false, canCloseContent, canWorkInDumbMode)); - } - - private ToolWindow registerToolWindow(@NotNull final String id, - @Nullable final JComponent component, - @NotNull final ToolWindowAnchor anchor, - boolean sideTool, - boolean canCloseContent, - final boolean canWorkInDumbMode) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: installToolWindow(" + id + "," + component + "," + anchor + "\")"); - } - ApplicationManager.getApplication().assertIsDispatchThread(); - if (myLayout.isToolWindowRegistered(id)) { - throw new IllegalArgumentException("window with id=\"" + id + "\" is already registered"); - } - - final WindowInfoImpl info = myLayout.register(id, anchor, sideTool); - final boolean wasActive = info.isActive(); - final boolean wasVisible = info.isVisible(); - info.setActive(false); - info.setVisible(false); - - // Create decorator - - final ToolWindowImpl toolWindow = new ToolWindowImpl(this, id, canCloseContent, component); - final InternalDecorator decorator = new InternalDecorator(myProject, info.copy(), toolWindow); - myId2InternalDecorator.put(id, decorator); - decorator.addInternalDecoratorListener(myInternalDecoratorListener); - toolWindow.addPropertyChangeListener(myToolWindowPropertyChangeListener); - myId2FocusWatcher.put(id, new ToolWindowFocusWatcher(toolWindow)); - - // Create and show tool button - - final StripeButton button = new StripeButton(decorator, myToolWindowsPane); - myId2StripeButton.put(id, button); - final ArrayList commandsList = new ArrayList(); - appendAddButtonCmd(button, info, commandsList); - - if (canWorkInDumbMode) { - myDumbAwareIds.add(id); - } - else if (DumbService.getInstance(getProject()).isDumb()) { - button.setEnabled(false); - } - - // If preloaded info is visible or active then we have to show/activate the installed - // tool window. This step has sense only for windows which are not in the autohide - // mode. But if tool window was active but its mode doen't allow to activate it again - // (for example, tool window is in autohide mode) then we just activate editor component. - - if (!info.isAutoHide() && (info.isDocked() || info.isFloating())) { - if (wasActive) { - activateToolWindowImpl(info.getId(), commandsList, true, true); - } - else if (wasVisible) { - showToolWindowImpl(info.getId(), false, commandsList); - } - } - else if (wasActive) { // tool window was active but it cannot be activate again - activateEditorComponentImpl(getSplittersFromFocus(), commandsList, true); - } - - execute(commandsList); - fireToolWindowRegistered(id); - return toolWindow; - } - - private ToolWindow registerDisposable(final String id, final Disposable parentDisposable, final ToolWindow window) { - Disposer.register(parentDisposable, new Disposable() { - public void dispose() { - unregisterToolWindow(id); - } - }); - return window; - } - - public void unregisterToolWindow(@NotNull final String id) { - if (LOG.isDebugEnabled()) { - LOG.debug("enter: unregisterToolWindow(" + id + ")"); - } - ApplicationManager.getApplication().assertIsDispatchThread(); - if (!myLayout.isToolWindowRegistered(id)) { - return; - } - - final WindowInfoImpl info = getInfo(id); - final ToolWindowEx toolWindow = (ToolWindowEx)getToolWindow(id); - // Save recent appearance of tool window - myLayout.unregister(id); - // Remove decorator and tool button from the screen - final ArrayList commandsList = new ArrayList(); - if (info.isVisible()) { - info.setVisible(false); - if (info.isFloating()) { - appendRemoveFloatingDecoratorCmd(info, commandsList); - } - else { // floating and sliding windows - appendRemoveDecoratorCmd(id, false, commandsList); - } - } - appendRemoveButtonCmd(id, commandsList); - appendApplyWindowInfoCmd(info, commandsList); - execute(commandsList); - // Remove all references on tool window and save its last properties - toolWindow.removePropertyChangeListener(myToolWindowPropertyChangeListener); - myActiveStack.remove(id, true); - mySideStack.remove(id); - // Destroy stripe button - final StripeButton button = getStripeButton(id); - button.dispose(); - myId2StripeButton.remove(id); - // - myId2FocusWatcher.remove(id); - // Destroy decorator - final InternalDecorator decorator = getInternalDecorator(id); - decorator.dispose(); - decorator.removeInternalDecoratorListener(myInternalDecoratorListener); - myId2InternalDecorator.remove(id); - } - - public DesktopLayout getLayout() { - ApplicationManager.getApplication().assertIsDispatchThread(); - return myLayout; - } - - public void setLayoutToRestoreLater(DesktopLayout layout) { - myLayoutToRestoreLater = layout; - } - - public DesktopLayout getLayoutToRestoreLater() { - return myLayoutToRestoreLater; - } - - public void setLayout(@NotNull final DesktopLayout layout) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final ArrayList commandList = new ArrayList(); - // hide tool window that are invisible in new layout - final WindowInfoImpl[] currentInfos = myLayout.getInfos(); - for (final WindowInfoImpl currentInfo : currentInfos) { - final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); - if (info == null) { - continue; - } - if (currentInfo.isVisible() && !info.isVisible()) { - deactivateToolWindowImpl(currentInfo.getId(), true, commandList); - } - } - // change anchor of tool windows - for (final WindowInfoImpl currentInfo : currentInfos) { - final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); - if (info == null) { - continue; - } - if (currentInfo.getAnchor() != info.getAnchor() || currentInfo.getOrder() != info.getOrder()) { - setToolWindowAnchorImpl(currentInfo.getId(), info.getAnchor(), info.getOrder(), commandList); - } - } - // change types of tool windows - for (final WindowInfoImpl currentInfo : currentInfos) { - final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); - if (info == null) { - continue; - } - if (currentInfo.getType() != info.getType()) { - setToolWindowTypeImpl(currentInfo.getId(), info.getType(), commandList); - } - } - // change auto-hide state - for (final WindowInfoImpl currentInfo : currentInfos) { - final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); - if (info == null) { - continue; - } - if (currentInfo.isAutoHide() != info.isAutoHide()) { - setToolWindowAutoHideImpl(currentInfo.getId(), info.isAutoHide(), commandList); - } - } - // restore visibility - for (final WindowInfoImpl currentInfo : currentInfos) { - final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); - if (info == null) { - continue; - } - if (info.isVisible()) { - showToolWindowImpl(currentInfo.getId(), false, commandList); - } - } - // if there is no any active tool window and editor is also inactive - // then activate editor - if (!myEditorComponentActive && getActiveToolWindowId() == null) { - activateEditorComponentImpl(getSplittersFromFocus(), commandList, true); - } - execute(commandList); - } - - public void invokeLater(final Runnable runnable) { - final ArrayList commandList = new ArrayList(); - commandList.add(new InvokeLaterCmd(runnable, myWindowManager.getCommandProcessor())); - execute(commandList); - } - - public IdeFocusManager getFocusManager() { - return IdeFocusManager.getInstance(myProject); - } - - @Override - public void notifyByBalloon(@NotNull final String toolWindowId, @NotNull final MessageType type, @NotNull final String htmlBody) { - notifyByBalloon(toolWindowId, type, htmlBody, null, null); - } - - public void notifyByBalloon(@NotNull final String toolWindowId, - @NotNull final MessageType type, - @NotNull final String text, - @Nullable final Icon icon, - @Nullable HyperlinkListener listener) { - checkId(toolWindowId); - - - Balloon existing = myWindow2Balloon.get(toolWindowId); - if (existing != null) { - existing.hide(); - } - - final Stripe stripe = myToolWindowsPane.getStripeFor(toolWindowId); - final ToolWindowImpl window = getInternalDecorator(toolWindowId).getToolWindow(); - if (!window.isAvailable()) { - window.setPlaceholderMode(true); - stripe.updateState(); - stripe.revalidate(); - stripe.repaint(); - } - - final ToolWindowAnchor anchor = getInfo(toolWindowId).getAnchor(); - final Ref position = Ref.create(Balloon.Position.below); - if (ToolWindowAnchor.TOP == anchor) { - position.set(Balloon.Position.below); - } - else if (ToolWindowAnchor.BOTTOM == anchor) { - position.set(Balloon.Position.above); - } - else if (ToolWindowAnchor.LEFT == anchor) { - position.set(Balloon.Position.atRight); - } - else if (ToolWindowAnchor.RIGHT == anchor) { - position.set(Balloon.Position.atLeft); - } - - Icon actualIcon = icon != null ? icon : type.getDefaultIcon(); - - final Balloon balloon = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text.replace("\n", "
"), actualIcon, type.getPopupBackground(), listener) - .setHideOnClickOutside(true).setHideOnFrameResize(false).createBalloon(); - myWindow2Balloon.put(toolWindowId, balloon); - Disposer.register(balloon, new Disposable() { - public void dispose() { - window.setPlaceholderMode(false); - stripe.updateState(); - stripe.revalidate(); - stripe.repaint(); - myWindow2Balloon.remove(toolWindowId); - } - }); - Disposer.register(getProject(), balloon); - - final StripeButton button = stripe.getButtonFor(toolWindowId); - if (button == null) return; - - final Runnable show = new Runnable() { - public void run() { - if (button.isShowing()) { - PositionTracker tracker = new PositionTracker(button) { - @Override - public RelativePoint recalculateLocation(Balloon object) { - Stripe twStripe = myToolWindowsPane.getStripeFor(toolWindowId); - StripeButton twButton = twStripe != null ? twStripe.getButtonFor(toolWindowId) : null; - - if (twButton == null) return null; - - if (getToolWindow(toolWindowId).getAnchor() != anchor) { - object.hide(); - return null; - } - - final Point point = new Point(twButton.getBounds().width / 2, twButton.getHeight() / 2 - 2); - return new RelativePoint(twButton, point); - } - }; - balloon.show(tracker, position.get()); - } - else { - final Rectangle bounds = myToolWindowsPane.getBounds(); - final Point target = UIUtil.getCenterPoint(bounds, new Dimension(1, 1)); - if (ToolWindowAnchor.TOP == anchor) { - target.y = 0; - } - else if (ToolWindowAnchor.BOTTOM == anchor) { - target.y = bounds.height - 3; - } - else if (ToolWindowAnchor.LEFT == anchor) { - target.x = 0; - } - else if (ToolWindowAnchor.RIGHT == anchor) { - target.x = bounds.width; - } - - balloon.show(new RelativePoint(myToolWindowsPane, target), position.get()); - } - } - }; - - if (!button.isValid()) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - show.run(); - } - }); - } - else { - show.run(); - } - - } - - public boolean isEditorComponentActive() { - ApplicationManager.getApplication().assertIsDispatchThread(); - return myEditorComponentActive; - } - - ToolWindowAnchor getToolWindowAnchor(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).getAnchor(); - } - - void setToolWindowAnchor(final String id, final ToolWindowAnchor anchor) { - ApplicationManager.getApplication().assertIsDispatchThread(); - setToolWindowAnchor(id, anchor, -1); - } - - void setToolWindowAnchor(final String id, final ToolWindowAnchor anchor, final int order) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final ArrayList commandList = new ArrayList(); - setToolWindowAnchorImpl(id, anchor, order, commandList); - execute(commandList); - } - - private void setToolWindowAnchorImpl(final String id, - final ToolWindowAnchor anchor, - final int order, - final ArrayList commandsList) { - checkId(id); - final WindowInfoImpl info = getInfo(id); - if (anchor == info.getAnchor() && order == info.getOrder()) { - return; - } - // if tool window isn't visible or only order number is changed then just remove/add stripe button - if (!info.isVisible() || anchor == info.getAnchor() || info.isFloating()) { - appendRemoveButtonCmd(id, commandsList); - myLayout.setAnchor(id, anchor, order); - // update infos for all window. Actually we have to update only infos affected by - // setAnchor method - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (WindowInfoImpl info1 : infos) { - appendApplyWindowInfoCmd(info1, commandsList); - } - appendAddButtonCmd(getStripeButton(id), info, commandsList); - } - else { // for docked and sliding windows we have to move buttons and window's decorators - info.setVisible(false); - appendRemoveDecoratorCmd(id, false, commandsList); - appendRemoveButtonCmd(id, commandsList); - myLayout.setAnchor(id, anchor, order); - // update infos for all window. Actually we have to update only infos affected by - // setAnchor method - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (WindowInfoImpl info1 : infos) { - appendApplyWindowInfoCmd(info1, commandsList); - } - appendAddButtonCmd(getStripeButton(id), info, commandsList); - showToolWindowImpl(id, false, commandsList); - if (info.isActive()) { - appendRequestFocusInToolWindowCmd(id, commandsList, true); - } - } - } - - boolean isSplitMode(String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).isSplit(); - } - - ToolWindowContentUiType getContentUiType(String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).getContentUiType(); - } - - void setSideTool(String id, boolean isSide) { - final ArrayList commandList = new ArrayList(); - setSplitModeImpl(id, isSide, commandList); - execute(commandList); - } - - public void setContentUiType(String id, ToolWindowContentUiType type) { - final ArrayList commandList = new ArrayList(); - checkId(id); - WindowInfoImpl info = getInfo(id); - info.setContentUiType(type); - appendApplyWindowInfoCmd(info, commandList); - execute(commandList); - } - - void setSideToolAndAnchor(String id, ToolWindowAnchor anchor, int order, boolean isSide) { - final ArrayList commandList = new ArrayList(); - setToolWindowAnchor(id, anchor, order); - setSplitModeImpl(id, isSide, commandList); - execute(commandList); - } - - private void setSplitModeImpl(final String id, final boolean isSplit, final ArrayList commandList) { - checkId(id); - final WindowInfoImpl info = getInfo(id); - if (isSplit == info.isSplit()) { - return; - } - - myLayout.setSplitMode(id, isSplit); - - boolean wasActive = info.isActive(); - if (wasActive) { - deactivateToolWindowImpl(id, true, commandList); - } - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (WindowInfoImpl info1 : infos) { - appendApplyWindowInfoCmd(info1, commandList); - } - if (wasActive) { - activateToolWindowImpl(id, commandList, true, true); - } - commandList.add(myToolWindowsPane.createUpdateButtonPositionCmd(id, myWindowManager.getCommandProcessor())); - } - - ToolWindowType getToolWindowInternalType(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).getInternalType(); - } - - ToolWindowType getToolWindowType(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).getType(); - } - - private void fireToolWindowRegistered(final String id) { - final ToolWindowManagerListener[] listeners = myListenerList.getListeners(ToolWindowManagerListener.class); - for (ToolWindowManagerListener listener : listeners) { - listener.toolWindowRegistered(id); - } - } - - private void fireStateChanged() { - final ToolWindowManagerListener[] listeners = myListenerList.getListeners(ToolWindowManagerListener.class); - for (ToolWindowManagerListener listener : listeners) { - listener.stateChanged(); - } - } - - boolean isToolWindowActive(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).isActive(); - } - - boolean isToolWindowAutoHide(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).isAutoHide(); - } - - public boolean isToolWindowFloating(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).isFloating(); - } - - boolean isToolWindowVisible(final String id) { - ApplicationManager.getApplication().assertIsDispatchThread(); - checkId(id); - return getInfo(id).isVisible(); - } - - void setToolWindowAutoHide(final String id, final boolean autoHide) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final ArrayList commandList = new ArrayList(); - setToolWindowAutoHideImpl(id, autoHide, commandList); - execute(commandList); - } - - private void setToolWindowAutoHideImpl(final String id, final boolean autoHide, final ArrayList commandsList) { - checkId(id); - final WindowInfoImpl info = getInfo(id); - if (info.isAutoHide() == autoHide) { - return; - } - info.setAutoHide(autoHide); - appendApplyWindowInfoCmd(info, commandsList); - if (info.isVisible()) { - prepareForActivation(id, commandsList); - showAndActivate(id, false, commandsList, true); - } - } - - void setToolWindowType(final String id, final ToolWindowType type) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final ArrayList commandList = new ArrayList(); - setToolWindowTypeImpl(id, type, commandList); - execute(commandList); - } - - private void setToolWindowTypeImpl(final String id, final ToolWindowType type, final ArrayList commandsList) { - checkId(id); - final WindowInfoImpl info = getInfo(id); - if (info.getType() == type) { - return; - } - if (info.isVisible()) { - final boolean dirtyMode = info.isDocked() || info.isSliding(); - info.setVisible(false); - if (info.isFloating()) { - appendRemoveFloatingDecoratorCmd(info, commandsList); - } - else { // docked and sliding windows - appendRemoveDecoratorCmd(id, dirtyMode, commandsList); - } - info.setType(type); - appendApplyWindowInfoCmd(info, commandsList); - prepareForActivation(id, commandsList); - showAndActivate(id, dirtyMode, commandsList, true); - appendUpdateToolWindowsPaneCmd(commandsList); - } - else { - info.setType(type); - appendApplyWindowInfoCmd(info, commandsList); - } - } - - private void appendApplyWindowInfoCmd(final WindowInfoImpl info, final List commandsList) { - final StripeButton button = getStripeButton(info.getId()); - final InternalDecorator decorator = getInternalDecorator(info.getId()); - commandsList.add(new ApplyWindowInfoCmd(info, button, decorator, myWindowManager.getCommandProcessor())); - } - - /** - * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddDecoratorCmd - */ - private void appendAddDecoratorCmd(final InternalDecorator decorator, - final WindowInfoImpl info, - final boolean dirtyMode, - final List commandsList) { - final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); - final FinalizableCommand command = myToolWindowsPane.createAddDecoratorCmd(decorator, info, dirtyMode, commandProcessor); - commandsList.add(command); - } - - /** - * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createRemoveDecoratorCmd - */ - private void appendRemoveDecoratorCmd(final String id, final boolean dirtyMode, final List commandsList) { - final FinalizableCommand command = myToolWindowsPane.createRemoveDecoratorCmd(id, dirtyMode, myWindowManager.getCommandProcessor()); - commandsList.add(command); - } - - private void appendRemoveFloatingDecoratorCmd(final WindowInfoImpl info, final List commandsList) { - final RemoveFloatingDecoratorCmd command = new RemoveFloatingDecoratorCmd(info); - commandsList.add(command); - } - - /** - * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddButtonCmd - */ - private void appendAddButtonCmd(final StripeButton button, final WindowInfoImpl info, final List commandsList) { - final Comparator comparator = myLayout.comparator(info.getAnchor()); - final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); - final FinalizableCommand command = myToolWindowsPane.createAddButtonCmd(button, info, comparator, commandProcessor); - commandsList.add(command); - } - - /** - * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddButtonCmd - */ - private void appendRemoveButtonCmd(final String id, final List commandsList) { - final FinalizableCommand command = myToolWindowsPane.createRemoveButtonCmd(id, myWindowManager.getCommandProcessor()); - commandsList.add(command); - } - - private ActionCallback appendRequestFocusInEditorComponentCmd(EditorsSplitters splitters, final ArrayList commandList, boolean forced) { - if (myProject.isDisposed()) return new ActionCallback.Done(); - final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); - final RequestFocusInEditorComponentCmd command = - new RequestFocusInEditorComponentCmd(splitters, getFocusManager(), commandProcessor, forced); - commandList.add(command); - return command.getDoneCallback(); - } - - private void appendRequestFocusInToolWindowCmd(final String id, final ArrayList commandList, boolean forced) { - final ToolWindowImpl toolWindow = (ToolWindowImpl)getToolWindow(id); - final FocusWatcher focusWatcher = myId2FocusWatcher.get(id); - commandList - .add(new RequestFocusInToolWindowCmd(getFocusManager(), toolWindow, focusWatcher, myWindowManager.getCommandProcessor(), forced)); - } - - /** - * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createSetEditorComponentCmd - */ - private void appendSetEditorComponentCmd(final JComponent component, final List commandsList) { - final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); - final FinalizableCommand command = myToolWindowsPane.createSetEditorComponentCmd(component, commandProcessor); - commandsList.add(command); - } - - private void appendUpdateToolWindowsPaneCmd(final List commandsList) { - final JRootPane rootPane = myFrame.getRootPane(); - final FinalizableCommand command = new UpdateRootPaneCmd(rootPane, myWindowManager.getCommandProcessor()); - commandsList.add(command); - } - - /** - * @return true if tool window with the specified id - * is floating and has modal showing child dialog. Such windows should not be closed - * when auto-hide windows are gone. - */ - private boolean hasModalChild(final WindowInfoImpl info) { - if (!info.isVisible() || !info.isFloating()) { - return false; - } - final FloatingDecorator decorator = getFloatingDecorator(info.getId()); - LOG.assertTrue(decorator != null); - return isModalOrHasModalChild(decorator); - } - - private static boolean isModalOrHasModalChild(final Window window) { - if (window instanceof Dialog) { - final Dialog dialog = (Dialog)window; - if (dialog.isModal() && dialog.isShowing()) { - return true; - } - final Window[] ownedWindows = dialog.getOwnedWindows(); - for (int i = ownedWindows.length - 1; i >= 0; i--) { - if (isModalOrHasModalChild(ownedWindows[i])) { - return true; - } - } - } - return false; - } - - /** - * Helper method. It deactivates all tool windows excepting the tool window - * which should be activated. - */ - private void prepareForActivation(final String id, final List commandList) { - final WindowInfoImpl toBeActivatedInfo = getInfo(id); - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (final WindowInfoImpl info : infos) { - if (id.equals(info.getId())) { - continue; - } - if (toBeActivatedInfo.isDocked() || toBeActivatedInfo.isSliding()) { - deactivateToolWindowImpl(info.getId(), info.isAutoHide() || info.isSliding(), commandList); - } - else { // floating window is being activated - deactivateToolWindowImpl(info.getId(), info.isAutoHide() && info.isFloating() && !hasModalChild(info), commandList); - } - } - } - - public void clearSideStack() { - mySideStack.clear(); - } - - public void readExternal(final Element element) { - for (final Object o : element.getChildren()) { - final Element e = (Element)o; - if (EDITOR_ELEMENT.equals(e.getName())) { - myEditorComponentActive = Boolean.valueOf(e.getAttributeValue(ACTIVE_ATTR_VALUE)).booleanValue(); - } - else if (DesktopLayout.TAG.equals(e.getName())) { // read layout of tool windows - myLayout.readExternal(e); - final WindowInfoImpl[] windowInfos = myLayout.getAllInfos(); - for (WindowInfoImpl windowInfo : windowInfos) { - myRestoredToolWindowIds.add(windowInfo.getId()); - } - } - } - } - - public void writeExternal(final Element element) { - if (myFrame == null) { - // do nothing if the project was not opened - return; - } - final String[] ids = getToolWindowIds(); - - // Update size of all open floating windows. See SCR #18439 - for (final String id : ids) { - final WindowInfoImpl info = getInfo(id); - if (info.isVisible()) { - final InternalDecorator decorator = getInternalDecorator(id); - LOG.assertTrue(decorator != null); - decorator.fireResized(); - } - } - - // Save frame's bounds - final Rectangle frameBounds = myFrame.getBounds(); - final Element frameElement = new Element(FRAME_ELEMENT); - element.addContent(frameElement); - frameElement.setAttribute(X_ATTR, Integer.toString(frameBounds.x)); - frameElement.setAttribute(Y_ATTR, Integer.toString(frameBounds.y)); - frameElement.setAttribute(WIDTH_ATTR, Integer.toString(frameBounds.width)); - frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(frameBounds.height)); - frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(myFrame.getExtendedState())); - // Save whether editor is active or not - final Element editorElement = new Element(EDITOR_ELEMENT); - editorElement.setAttribute(ACTIVE_ATTR_VALUE, myEditorComponentActive ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); - element.addContent(editorElement); - // Save layout of tool windows - final Element layoutElement = new Element(DesktopLayout.TAG); - element.addContent(layoutElement); - myLayout.writeExternal(layoutElement); - } - - public void setDefaultState(@NotNull final ToolWindowImpl toolWindow, - @Nullable final ToolWindowAnchor anchor, - @Nullable final ToolWindowType type, - @Nullable final Rectangle floatingBounds) { - - final WindowInfoImpl info = getInfo(toolWindow.getId()); - if (info.wasRead()) return; - - if (floatingBounds != null) { - info.setFloatingBounds(floatingBounds); - } - - if (anchor != null) { - toolWindow.setAnchor(anchor, null); - } - - if (type != null) { - toolWindow.setType(type, null); - } - - } - - public void setDefaultContentUiType(ToolWindowImpl toolWindow, ToolWindowContentUiType type) { - final WindowInfoImpl info = getInfo(toolWindow.getId()); - if (info.wasRead()) return; - toolWindow.setContentUiType(type, null); - } - - - public void stretchWidth(ToolWindowImpl toolWindow, int value) { - myToolWindowsPane.stretchWidth(toolWindow, value); - } - - public void stretchHeight(ToolWindowImpl toolWindow, int value) { - myToolWindowsPane.stretchHeight(toolWindow, value); - } - - - /** - * This command creates and shows FloatingDecorator. - */ - private final class AddFloatingDecoratorCmd extends FinalizableCommand { - private final FloatingDecorator myFloatingDecorator; - - /** - * Creates floating decorator for specified floating decorator. - */ - private AddFloatingDecoratorCmd(final InternalDecorator decorator, final WindowInfoImpl info) { - super(myWindowManager.getCommandProcessor()); - myFloatingDecorator = new FloatingDecorator(myFrame, info.copy(), decorator); - myId2FloatingDecorator.put(info.getId(), myFloatingDecorator); - final Rectangle bounds = info.getFloatingBounds(); - if (bounds != null && - bounds.width > 0 && - bounds.height > 0 && - myWindowManager.isInsideScreenBounds(bounds.x, bounds.y, bounds.width)) { - myFloatingDecorator.setBounds(bounds); - } - else { // place new frame at the center of main frame if there are no floating bounds - Dimension size = decorator.getSize(); - if (size.width == 0 || size.height == 0) { - size = decorator.getPreferredSize(); - } - myFloatingDecorator.setSize(size); - myFloatingDecorator.setLocationRelativeTo(myFrame); - } - } - - public void run() { - try { - myFloatingDecorator.show(); - } - finally { - finish(); - } - } - } - - /** - * This command hides and destroys floating decorator for tool window - * with specified ID. - */ - private final class RemoveFloatingDecoratorCmd extends FinalizableCommand { - private final FloatingDecorator myFloatingDecorator; - - private RemoveFloatingDecoratorCmd(final WindowInfoImpl info) { - super(myWindowManager.getCommandProcessor()); - myFloatingDecorator = getFloatingDecorator(info.getId()); - myId2FloatingDecorator.remove(info.getId()); - info.setFloatingBounds(myFloatingDecorator.getBounds()); - } - - public void run() { - try { - if (Patches.SPECIAL_WINPUT_METHOD_PROCESSING) { - myFloatingDecorator.remove(myFloatingDecorator.getRootPane()); - } - myFloatingDecorator.dispose(); - } - finally { - finish(); - } - } - - @Nullable - public Condition getExpireCondition() { - return ApplicationManager.getApplication().getDisposed(); - } - } - - private final class EditorComponentFocusWatcher extends FocusWatcher { - protected void focusedComponentChanged(final Component component, final AWTEvent cause) { - if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) { - return; - } - - // Sometimes focus gained comes when editor is active. For example it can happen when - // user switches between menus or closes some dialog. In that case we just ignore this event, - // i.e. don't initiate deactivation of tool windows and requesting focus in editor. - if (myEditorComponentActive) { - return; - } - - - final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - final Component owner = mgr.getFocusOwner(); - - IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { - public void run() { - if (mgr.getFocusOwner() == owner) { - activateEditorComponent(false); - } - } - }); - } - } - - - /** - * Notifies window manager about focus traversal in tool window - */ - private final class ToolWindowFocusWatcher extends FocusWatcher { - private final String myId; - - - private ToolWindowFocusWatcher(final ToolWindowImpl toolWindow) { - myId = toolWindow.getId(); - install(toolWindow.getComponent()); - } - - protected boolean isFocusedComponentChangeValid(final Component comp, final AWTEvent cause) { - return myWindowManager.getCommandProcessor().getCommandCount() == 0 && comp != null; - } - - protected void focusedComponentChanged(final Component component, final AWTEvent cause) { - if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) { - return; - } - final WindowInfoImpl info = getInfo(myId); - getFocusManagerImpl().myFocusedComponentAlaram.cancelAllRequests(); - - if (!info.isActive()) { - getFocusManagerImpl().myFocusedComponentAlaram.addRequest(new EdtRunnable() { - public void runEdt() { - if (!myLayout.isToolWindowRegistered(myId)) return; - activateToolWindow(myId, false, false); - } - }, 100); - } - } - } - - /** - * Spies on IdeToolWindow properties and applies them to the window - * state. - */ - private final class MyToolWindowPropertyChangeListener implements PropertyChangeListener { - public void propertyChange(final PropertyChangeEvent e) { - final ToolWindowImpl toolWindow = (ToolWindowImpl)e.getSource(); - if (ToolWindowEx.PROP_AVAILABLE.equals(e.getPropertyName())) { - final WindowInfoImpl info = getInfo(toolWindow.getId()); - if (!toolWindow.isAvailable() && info.isVisible()) { - hideToolWindow(toolWindow.getId(), false); - } - } - } - } - - /** - * Translates events from InternalDecorator into ToolWindowManager method invocations. - */ - private final class MyInternalDecoratorListener implements InternalDecoratorListener { - public void anchorChanged(final InternalDecorator source, final ToolWindowAnchor anchor) { - setToolWindowAnchor(source.getToolWindow().getId(), anchor); - } - - public void autoHideChanged(final InternalDecorator source, final boolean autoHide) { - setToolWindowAutoHide(source.getToolWindow().getId(), autoHide); - } - - public void hidden(final InternalDecorator source) { - hideToolWindow(source.getToolWindow().getId(), false); - } - - public void hiddenSide(final InternalDecorator source) { - hideToolWindow(source.getToolWindow().getId(), true); - } - - public void contentUiTypeChanges(InternalDecorator source, ToolWindowContentUiType type) { - setContentUiType(source.getToolWindow().getId(), type); - } - - /** - * Handles event from decorator and modify weight/floating bounds of the - * tool window depending on decoration type. - */ - public void resized(final InternalDecorator source) { - final WindowInfoImpl info = getInfo(source.getToolWindow().getId()); - if (info.isFloating()) { - final Window owner = SwingUtilities.getWindowAncestor(source); - if (owner != null) { - info.setFloatingBounds(owner.getBounds()); - } - } - else { // docked and sliding windows - if (ToolWindowAnchor.TOP == info.getAnchor() || ToolWindowAnchor.BOTTOM == info.getAnchor()) { - info.setWeight((float)source.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight()); - float newSideWeight = (float)source.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth(); - if (newSideWeight < 1.0f) { - info.setSideWeight(newSideWeight); - } - } - else { - info.setWeight((float)source.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth()); - float newSideWeight = (float)source.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight(); - if (newSideWeight < 1.0f) { - info.setSideWeight(newSideWeight); - } - } - } - } - - public void activated(final InternalDecorator source) { - activateToolWindow(source.getToolWindow().getId(), true, true); - } - - public void typeChanged(final InternalDecorator source, final ToolWindowType type) { - setToolWindowType(source.getToolWindow().getId(), type); - } - - public void sideStatusChanged(final InternalDecorator source, final boolean isSideTool) { - setSideTool(source.getToolWindow().getId(), isSideTool); - } - } - - private void updateComponentTreeUI() { - ApplicationManager.getApplication().assertIsDispatchThread(); - final WindowInfoImpl[] infos = myLayout.getInfos(); - for (final WindowInfoImpl info : infos) { - if (info.isVisible()) { // skip visible tool windows (optimization) - continue; - } - SwingUtilities.updateComponentTreeUI(getInternalDecorator(info.getId())); - } - } - - private final class MyUIManagerPropertyChangeListener implements PropertyChangeListener { - public void propertyChange(final PropertyChangeEvent e) { - updateComponentTreeUI(); - } - } - - private final class MyLafManagerListener implements LafManagerListener { - public void lookAndFeelChanged(final LafManager source) { - updateComponentTreeUI(); - } - } - - - @NotNull - public String getComponentName() { - return "ToolWindowManager"; - } - - public ToolWindowsPane getToolWindowsPane() { - return myToolWindowsPane; - } - - public ActionCallback requestDefaultFocus(final boolean forced) { - return getFocusManagerImpl().requestFocus(new FocusCommand() { - public ActionCallback run() { - return processDefaultFocusRequest(forced); - } - }, forced); - } - - private void focusToolWinowByDefault(String idToIngore) { - String toFocus = null; - - for (String each : myActiveStack.getStack()) { - if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue; - - if (getInfo(each).isVisible()) { - toFocus = each; - break; - } - } - - if (toFocus == null) { - for (String each : myActiveStack.getPersistentStack()) { - if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue; - - if (getInfo(each).isVisible()) { - toFocus = each; - break; - } - } - } - - if (toFocus != null) { - activateToolWindow(toFocus, false, true); - } - } - - private ActionCallback processDefaultFocusRequest(boolean forced) { - if (ModalityState.NON_MODAL.equals(ModalityState.current())) { - final String activeId = getActiveToolWindowId(); - if (myEditorComponentActive || activeId == null || getToolWindow(activeId) == null) { - activateEditorComponent(forced, true); - } - else { - activateToolWindow(activeId, forced, true); - } - } - return new ActionCallback.Done(); - } - - - private boolean isProjectComponent(Component c) { - final Component frame = UIUtil.findUltimateParent(c); - if (frame instanceof IdeFrame) { - return frame == myWindowManager.getFrame(myProject); - } - else { - return false; - } - } - - /** - * Delegate method for compatibility with older versions of IDEA - */ - @NotNull - public ActionCallback requestFocus(@NotNull Component c, boolean forced) { - return IdeFocusManager.getInstance(myProject).requestFocus(c, forced); - } - - @NotNull - public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) { - return IdeFocusManager.getInstance(myProject).requestFocus(command, forced); - } - - public JComponent getFocusTargetFor(@NotNull JComponent comp) { - return IdeFocusManager.getInstance(myProject).getFocusTargetFor(comp); - } - - public void doWhenFocusSettlesDown(@NotNull Runnable runnable) { - IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(runnable); - } - - public Component getFocusedDescendantFor(Component comp) { - return IdeFocusManager.getInstance(myProject).getFocusedDescendantFor(comp); - } - - public boolean dispatch(KeyEvent e) { - return IdeFocusManager.getInstance(myProject).dispatch(e); - } - - public void suspendKeyProcessingUntil(@NotNull ActionCallback done) { - IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(done); - } - - public boolean isFocusBeingTransferred() { - return IdeFocusManager.getInstance(myProject).isFocusBeingTransferred(); - } - - public Expirable getTimestamp(boolean trackOnlyForcedCommands) { - return IdeFocusManager.getInstance(myProject).getTimestamp(trackOnlyForcedCommands); - } -} +/* + * 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.wm.impl; + +import com.intellij.Patches; +import com.intellij.ide.ui.LafManager; +import com.intellij.ide.ui.LafManagerListener; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.AnActionListener; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.fileEditor.impl.EditorsSplitters; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.*; +import com.intellij.openapi.wm.ex.ToolWindowEx; +import com.intellij.openapi.wm.ex.ToolWindowManagerEx; +import com.intellij.openapi.wm.ex.ToolWindowManagerListener; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.openapi.wm.impl.commands.*; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.switcher.QuickAccessSettings; +import com.intellij.ui.switcher.SwitchManager; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.PositionTracker; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.UiNotifyConnector; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.EventListenerList; +import javax.swing.event.HyperlinkListener; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.*; +import java.util.List; + +/** + * @author Anton Katilin + * @author Vladimir Kondratyev + */ +public final class ToolWindowManagerImpl extends ToolWindowManagerEx implements ProjectComponent, JDOMExternalizable, KeyEventDispatcher { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.ToolWindowManagerImpl"); + + private final Project myProject; + private final WindowManagerEx myWindowManager; + private final EventListenerList myListenerList; + private final DesktopLayout myLayout; + private final HashMap myId2InternalDecorator; + private final HashMap myId2FloatingDecorator; + private final HashMap myId2StripeButton; + private final HashMap myId2FocusWatcher; + private final Set myDumbAwareIds = Collections.synchronizedSet(CollectionFactory.newTroveSet()); + + private final EditorComponentFocusWatcher myEditorComponentFocusWatcher; + private final MyToolWindowPropertyChangeListener myToolWindowPropertyChangeListener; + private final InternalDecoratorListener myInternalDecoratorListener; + + private boolean myEditorComponentActive; + private final ActiveStack myActiveStack; + private final SideStack mySideStack; + + private ToolWindowsPane myToolWindowsPane; + private IdeFrameImpl myFrame; + private DesktopLayout myLayoutToRestoreLater = null; + @NonNls private static final String EDITOR_ELEMENT = "editor"; + @NonNls private static final String ACTIVE_ATTR_VALUE = "active"; + @NonNls private static final String FRAME_ELEMENT = "frame"; + @NonNls private static final String X_ATTR = "x"; + @NonNls private static final String Y_ATTR = "y"; + @NonNls private static final String WIDTH_ATTR = "width"; + @NonNls private static final String HEIGHT_ATTR = "height"; + @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state"; + + + private final Set myRestoredToolWindowIds = new HashSet(); + private final FileEditorManager myFileEditorManager; + + private final Map myWindow2Balloon = new HashMap(); + + private KeyState myCurrentState = KeyState.waiting; + private final Alarm myWaiterForSecondPress = new Alarm(); + private final Runnable mySecondPressRunnable = new Runnable() { + public void run() { + if (myCurrentState != KeyState.hold) { + resetHoldState(); + } + } + }; + + private enum KeyState { + waiting, pressed, released, hold + } + + /** + * invoked by reflection + */ + public ToolWindowManagerImpl(final Project project, + WindowManagerEx windowManagerEx, + final FileEditorManager fem, + ActionManager actionManager) { + myProject = project; + myWindowManager = windowManagerEx; + myFileEditorManager = fem; + myListenerList = new EventListenerList(); + + if (!project.isDefault()) { + actionManager.addAnActionListener(new AnActionListener() { + @Override + public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { + if (myCurrentState != KeyState.hold) { + resetHoldState(); + } + } + + @Override + public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { + } + + @Override + public void beforeEditorTyping(char c, DataContext dataContext) { + } + }, project); + } + + myLayout = new DesktopLayout(); + myLayout.copyFrom(windowManagerEx.getLayout()); + + myId2InternalDecorator = new HashMap(); + myId2FloatingDecorator = new HashMap(); + myId2StripeButton = new HashMap(); + myId2FocusWatcher = new HashMap(); + + myEditorComponentFocusWatcher = new EditorComponentFocusWatcher(); + myToolWindowPropertyChangeListener = new MyToolWindowPropertyChangeListener(); + myInternalDecoratorListener = new MyInternalDecoratorListener(); + + myEditorComponentActive = false; + myActiveStack = new ActiveStack(); + mySideStack = new SideStack(); + + project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { + public void fileOpened(FileEditorManager source, VirtualFile file) { + } + + public void fileClosed(FileEditorManager source, VirtualFile file) { + getFocusManagerImpl().doWhenFocusSettlesDown(new Runnable() { + public void run() { + if (!hasOpenEditorFiles()) { + focusToolWinowByDefault(null); + } + } + }); + } + + public void selectionChanged(FileEditorManagerEvent event) { + } + }); + } + + public boolean dispatchKeyEvent(KeyEvent e) { + if (e.getKeyCode() != KeyEvent.VK_CONTROL && + e.getKeyCode() != KeyEvent.VK_ALT && + e.getKeyCode() != KeyEvent.VK_SHIFT && + e.getKeyCode() != KeyEvent.VK_META) { + if (e.getModifiers() == 0) { + resetHoldState(); + } + return false; + } + if (e.getID() != KeyEvent.KEY_PRESSED && e.getID() != KeyEvent.KEY_RELEASED) return false; + + Component parent = UIUtil.findUltimateParent(e.getComponent()); + if (parent instanceof IdeFrame) { + if (((IdeFrame)parent).getProject() != myProject) { + resetHoldState(); + return false; + } + } + + Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); + Shortcut[] baseShortcut = keymap.getShortcuts("ActivateProjectToolWindow"); + int baseModifiers = 0; + for (Shortcut each : baseShortcut) { + if (each instanceof KeyboardShortcut) { + KeyStroke keyStroke = ((KeyboardShortcut)each).getFirstKeyStroke(); + baseModifiers = keyStroke.getModifiers(); + if (baseModifiers > 0) { + break; + } + } + } + + if (baseModifiers == 0) { + resetHoldState(); + return false; + } + + Set vks = QuickAccessSettings.getModifiersVKs(baseModifiers); + if (vks.contains(e.getKeyCode())) { + boolean pressed = e.getID() == KeyEvent.KEY_PRESSED; + int modifiers = e.getModifiers(); + + int mouseMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK; + if ((e.getModifiersEx() & mouseMask) == 0) { + if (SwitchManager.areAllModifiersPressed(modifiers, vks) || !pressed) { + processState(pressed); + } + else { + resetHoldState(); + } + } + } + + + return false; + } + + private void resetHoldState() { + myCurrentState = KeyState.waiting; + processHoldState(); + } + + private void resetState() { + myCurrentState = KeyState.waiting; + } + + private void processState(boolean pressed) { + if (pressed) { + if (myCurrentState == KeyState.waiting) { + myCurrentState = KeyState.pressed; + } + else if (myCurrentState == KeyState.released) { + myCurrentState = KeyState.hold; + processHoldState(); + } + } + else { + if (myCurrentState == KeyState.pressed) { + myCurrentState = KeyState.released; + restartWaitingForSecondPressAlarm(); + } + else if (myCurrentState == KeyState.hold) { + resetHoldState(); + } + else { + resetHoldState(); + } + } + } + + private void processHoldState() { + myToolWindowsPane.setStripesOverlayed(myCurrentState == KeyState.hold); + } + + private void restartWaitingForSecondPressAlarm() { + myWaiterForSecondPress.cancelAllRequests(); + myWaiterForSecondPress.addRequest(mySecondPressRunnable, Registry.intValue("actionSystem.keyGestureDblClickTime")); + } + + private boolean hasOpenEditorFiles() { + return myFileEditorManager.getOpenFiles().length > 0; + } + + FocusManagerImpl getFocusManagerImpl() { + return FocusManagerImpl.getInstance(); + } + + public Project getProject() { + return myProject; + } + + public void initComponent() { + } + + public void disposeComponent() { + } + + public void projectOpened() { + final MyUIManagerPropertyChangeListener myUIManagerPropertyChangeListener = new MyUIManagerPropertyChangeListener(); + final MyLafManagerListener myLafManagerListener = new MyLafManagerListener(); + + UIManager.addPropertyChangeListener(myUIManagerPropertyChangeListener); + LafManager.getInstance().addLafManagerListener(myLafManagerListener); + + Disposer.register(myProject, new Disposable() { + public void dispose() { + UIManager.removePropertyChangeListener(myUIManagerPropertyChangeListener); + LafManager.getInstance().removeLafManagerListener(myLafManagerListener); + } + }); + myFrame = myWindowManager.allocateFrame(myProject); + LOG.assertTrue(myFrame != null); + + final ArrayList commandsList = new ArrayList(); + + myToolWindowsPane = new ToolWindowsPane(myFrame, this); + Disposer.register(myProject, myToolWindowsPane); + ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(myToolWindowsPane); + appendUpdateToolWindowsPaneCmd(commandsList); + + myFrame.setTitle(FrameTitleBuilder.getInstance().getProjectTitle(myProject)); + + FileEditorManagerEx editorManager = FileEditorManagerEx.getInstanceEx(myProject); + final JComponent editorComponent = editorManager.getComponent(); + myEditorComponentFocusWatcher.install(editorComponent); + + appendSetEditorComponentCmd(editorComponent, commandsList); + if (myEditorComponentActive) { + activateEditorComponentImpl(editorManager.getSplitters(), commandsList, true); + } + execute(commandsList); + + final DumbService.DumbModeListener dumbModeListener = new DumbService.DumbModeListener() { + public void enteredDumbMode() { + for (final String id : getToolWindowIds()) { + if (!myDumbAwareIds.contains(id)) { + if (isToolWindowVisible(id)) { + hideToolWindow(id, true); + } + getStripeButton(id).setEnabled(false); + } + } + } + + public void exitDumbMode() { + for (final String id : getToolWindowIds()) { + getStripeButton(id).setEnabled(true); + } + } + }; + myProject.getMessageBus().connect().subscribe(DumbService.DUMB_MODE, dumbModeListener); + + StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { + public void run() { + registerToolWindowsFromBeans(); + if (DumbService.getInstance(myProject).isDumb()) { + dumbModeListener.enteredDumbMode(); + } + } + }); + + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); + } + + private void registerToolWindowsFromBeans() { + ToolWindowEP[] beans = Extensions.getExtensions(ToolWindowEP.EP_NAME); + for (final ToolWindowEP bean : beans) { + final Condition condition = bean.getCondition(); + if (condition != null && !condition.value(myProject)) { + continue; + } + ToolWindowAnchor toolWindowAnchor; + try { + toolWindowAnchor = ToolWindowAnchor.fromText(bean.anchor); + } + catch (Exception e) { + LOG.error(e); + continue; + } + JLabel label = new JLabel("Initializing...", JLabel.CENTER); + label.setOpaque(true); + final Color treeBg = UIManager.getColor("Tree.background"); + label.setBackground(new Color(treeBg.getRed(), treeBg.getGreen(), treeBg.getBlue(), 180)); + final Color treeFg = UIUtil.getTreeForeground(); + label.setForeground(new Color(treeFg.getRed(), treeFg.getGreen(), treeFg.getBlue(), 180)); + final ToolWindowFactory factory = bean.getToolWindowFactory(); + final ToolWindowImpl toolWindow = + (ToolWindowImpl)registerToolWindow(bean.id, label, toolWindowAnchor, myProject, DumbService.isDumbAware(factory)); + toolWindow.setContentFactory(factory); + if (bean.icon != null) { + Icon icon = IconLoader.findIcon(bean.icon, factory.getClass()); + if (icon == null) { + try { + icon = IconLoader.getIcon(bean.icon); + } + catch (Exception ignored) { + } + } + toolWindow.setIcon(icon); + } + + if (!getInfo(bean.id).isSplit() && bean.secondary && !myRestoredToolWindowIds.contains(bean.id)) { + toolWindow.setSplitMode(bean.secondary, null); + } + + final ActionCallback activation = toolWindow.setActivation(new ActionCallback()); + + UiNotifyConnector.doWhenFirstShown(label, new Runnable() { + public void run() { + ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { + public void run() { + toolWindow.ensureContentInitialized(); + activation.setDone(); + } + }); + } + }); + } + } + + public void projectClosed() { + final ArrayList commandsList = new ArrayList(); + final String[] ids = getToolWindowIds(); + + // Remove ToolWindowsPane + + ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(null); + myWindowManager.releaseFrame(myFrame); + appendUpdateToolWindowsPaneCmd(commandsList); + + // Hide all tool windows + + for (final String id : ids) { + deactivateToolWindowImpl(id, true, commandsList); + } + + // Remove editor component + + final JComponent editorComponent = FileEditorManagerEx.getInstanceEx(myProject).getComponent(); + myEditorComponentFocusWatcher.deinstall(editorComponent); + appendSetEditorComponentCmd(null, commandsList); + execute(commandsList); + + KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this); + } + + public void addToolWindowManagerListener(@NotNull final ToolWindowManagerListener l) { + myListenerList.add(ToolWindowManagerListener.class, l); + } + + public void removeToolWindowManagerListener(@NotNull final ToolWindowManagerListener l) { + myListenerList.remove(ToolWindowManagerListener.class, l); + } + + /** + * This is helper method. It delegated its fuctionality to the WindowManager. + * Before delegating it fires state changed. + */ + private void execute(final ArrayList commandList) { + for (FinalizableCommand each : commandList) { + if (each.willChangeState()) { + fireStateChanged(); + break; + } + } + + for (FinalizableCommand each : commandList) { + each.beforeExecute(this); + } + myWindowManager.getCommandProcessor().execute(commandList, myProject.getDisposed()); + } + + public void activateEditorComponent() { + activateEditorComponent(true); + } + + private void activateEditorComponent(final boolean forced) { + activateEditorComponent(forced, false); + } + private void activateEditorComponent(final boolean forced, boolean now) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: activateEditorComponent()"); + } + ApplicationManager.getApplication().assertIsDispatchThread(); + + Runnable runnable = new Runnable() { + @Override + public void run() { + final ArrayList commandList = new ArrayList(); + activateEditorComponentImpl(getSplittersFromFocus(), commandList, forced); + execute(commandList); + } + }; + + if (now) { + runnable.run(); + } else { + getFocusManager().doWhenFocusSettlesDown(runnable); + + } + } + + private EditorsSplitters getSplittersFromFocus() { + return FileEditorManagerEx.getInstanceEx(myProject).getSplitters(); + } + + private void activateEditorComponentImpl(EditorsSplitters splitters, final ArrayList commandList, final boolean forced) { + final String active = getActiveToolWindowId(); + // Now we have to request focus into most recent focused editor + appendRequestFocusInEditorComponentCmd(splitters, commandList, forced).doWhenDone(new Runnable() { + public void run() { + final ArrayList postExecute = new ArrayList(); + + if (LOG.isDebugEnabled()) { + LOG.debug("editor activated"); + } + deactivateWindows(postExecute, null); + myActiveStack.clear(); + myEditorComponentActive = true; + + execute(postExecute); + } + }).doWhenRejected(new Runnable() { + public void run() { + if (forced) { + getFocusManagerImpl().requestFocus(new FocusCommand() { + public ActionCallback run() { + final ArrayList cmds = new ArrayList(); + + final WindowInfoImpl toReactivate = getInfo(active); + final boolean reactivateLastActive = toReactivate != null && !isToHide(toReactivate); + deactivateWindows(cmds, reactivateLastActive ? active : null); + execute(cmds); + + if (reactivateLastActive) { + activateToolWindow(active, false, true); + } + else { + if (active != null) { + myActiveStack.remove(active, false); + } + + if (!myActiveStack.isEmpty()) { + activateToolWindow(myActiveStack.peek(), false, true); + } + } + return new ActionCallback.Done(); + } + }, false); + } + } + }); + } + + private void deactivateWindows(final ArrayList postExecute, String idToIgnore) { + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (final WindowInfoImpl info : infos) { + final boolean shouldHide = isToHide(info); + if (idToIgnore != null && idToIgnore.equals(info.getId())) { + continue; + } + deactivateToolWindowImpl(info.getId(), shouldHide, postExecute); + } + } + + private boolean isToHide(final WindowInfoImpl info) { + return (info.isAutoHide() || info.isSliding()) && !(info.isFloating() && hasModalChild(info)); + } + + /** + * Helper method. It makes window visible, activates it and request focus into the tool window. + * But it doesn't deactivate other tool windows. Use prepareForActivation method to + * deactivates other tool windows. + * + * @param dirtyMode if true then all UI operations are performed in "dirty" mode. + * It means that UI isn't validated and repainted just after each add/remove operation. + * @see ToolWindowManagerImpl#prepareForActivation + */ + private void showAndActivate(final String id, + final boolean dirtyMode, + final ArrayList commandsList, + boolean autoFocusContents) { + if (!getToolWindow(id).isAvailable()) { + return; + } + // show activated + final WindowInfoImpl info = getInfo(id); + boolean toApplyInfo = false; + if (!info.isActive()) { + info.setActive(true); + toApplyInfo = true; + } + showToolWindowImpl(id, dirtyMode, commandsList); + + // activate + if (toApplyInfo) { + appendApplyWindowInfoCmd(info, commandsList); + myActiveStack.push(id); + myEditorComponentActive = false; + } + + if (autoFocusContents) { + appendRequestFocusInToolWindowCmd(id, commandsList, true); + } + } + + void activateToolWindow(final String id, boolean forced, boolean autoFocusContents) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: activateToolWindow(" + id + ")"); + } + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + if (DumbService.getInstance(myProject).isDumb() && !myDumbAwareIds.contains(id)) { + return; + } + + final ArrayList commandList = new ArrayList(); + activateToolWindowImpl(id, commandList, forced, autoFocusContents); + execute(commandList); + } + + private void activateToolWindowImpl(final String id, + final ArrayList commandList, + boolean forced, + boolean autoFocusContents) { + if (!getFocusManagerImpl().isUnforcedRequestAllowed() && !forced) return; + + if (LOG.isDebugEnabled()) { + LOG.debug("enter: activateToolWindowImpl(" + id + ")"); + } + if (!getToolWindow(id).isAvailable()) { + // Tool window can be "logically" active but not focused. For example, + // when the user switched to another application. So we just need to bring + // tool window's window to front. + final InternalDecorator decorator = getInternalDecorator(id); + if (!decorator.hasFocus() && autoFocusContents) { + appendRequestFocusInToolWindowCmd(id, commandList, forced); + } + return; + } + prepareForActivation(id, commandList); + showAndActivate(id, false, commandList, autoFocusContents); + } + + /** + * Checkes whether the specified id defines installed tool + * window. If it's not then throws IllegalStateException. + * + * @throws IllegalStateException if tool window isn't installed. + */ + private void checkId(final String id) { + if (!myLayout.isToolWindowRegistered(id)) { + throw new IllegalStateException("window with id=\"" + id + "\" isn't registered"); + } + } + + /** + * Helper method. It deactivates (and hides) window with specified id. + * + * @param id id of the tool window to be deactivated. + * @param shouldHide if true then also hides specified tool window. + */ + private void deactivateToolWindowImpl(final String id, final boolean shouldHide, final List commandsList) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: deactivateToolWindowImpl(" + id + "," + shouldHide + ")"); + } + final WindowInfoImpl info = getInfo(id); + if (shouldHide && info.isVisible()) { + info.setVisible(false); + if (info.isFloating()) { + appendRemoveFloatingDecoratorCmd(info, commandsList); + } + else { // docked and sliding windows + appendRemoveDecoratorCmd(id, false, commandsList); + } + } + info.setActive(false); + appendApplyWindowInfoCmd(info, commandsList); + } + + public String[] getToolWindowIds() { + ApplicationManager.getApplication().assertIsDispatchThread(); + final WindowInfoImpl[] infos = myLayout.getInfos(); + final String[] ids = ArrayUtil.newStringArray(infos.length); + for (int i = 0; i < infos.length; i++) { + ids[i] = infos[i].getId(); + } + return ids; + } + + public String getActiveToolWindowId() { + ApplicationManager.getApplication().assertIsDispatchThread(); + return myLayout.getActiveId(); + } + + public String getLastActiveToolWindowId() { + return getLastActiveToolWindowId(null); + } + + public String getLastActiveToolWindowId(Condition condition) { + ApplicationManager.getApplication().assertIsDispatchThread(); + String lastActiveToolWindowId = null; + for (int i = 0; i < myActiveStack.getPersistentSize(); i++) { + final String id = myActiveStack.peekPersistent(i); + final ToolWindow toolWindow = getToolWindow(id); + LOG.assertTrue(toolWindow != null); + if (toolWindow.isAvailable()) { + if (condition == null || condition.value(toolWindow.getComponent())) { + lastActiveToolWindowId = id; + break; + } + } + } + return lastActiveToolWindowId; + } + + /** + * @return floating decorator for the tool window with specified ID. + */ + private FloatingDecorator getFloatingDecorator(final String id) { + return myId2FloatingDecorator.get(id); + } + + /** + * @return internal decorator for the tool window with specified ID. + */ + private InternalDecorator getInternalDecorator(final String id) { + return myId2InternalDecorator.get(id); + } + + /** + * @return tool button for the window with specified ID. + */ + private StripeButton getStripeButton(final String id) { + return myId2StripeButton.get(id); + } + + /** + * @return info for the tool window with specified ID. + */ + private WindowInfoImpl getInfo(final String id) { + return myLayout.getInfo(id, true); + } + + public List getIdsOn(@NotNull final ToolWindowAnchor anchor) { + return myLayout.getVisibleIdsOn(anchor, this); + } + + public ToolWindow getToolWindow(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (!myLayout.isToolWindowRegistered(id)) { + return null; + } + InternalDecorator decorator = getInternalDecorator(id); + return decorator != null ? decorator.getToolWindow() : null; + } + + void showToolWindow(final String id) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: showToolWindow(" + id + ")"); + } + ApplicationManager.getApplication().assertIsDispatchThread(); + final ArrayList commandList = new ArrayList(); + showToolWindowImpl(id, false, commandList); + execute(commandList); + } + + public void hideToolWindow(@NotNull final String id, final boolean hideSide) { + hideToolWindow(id, hideSide, true); + } + + public void hideToolWindow(final String id, final boolean hideSide, final boolean moveFocus) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + final WindowInfoImpl info = getInfo(id); + if (!info.isVisible()) return; + final ArrayList commandList = new ArrayList(); + final boolean wasActive = info.isActive(); + + // hide and deactivate + + deactivateToolWindowImpl(id, true, commandList); + + if (hideSide || info.isFloating()) { + final List ids = myLayout.getVisibleIdsOn(info.getAnchor(), this); + for (String each : ids) { + myActiveStack.remove(each, true); + } + + + while (!mySideStack.isEmpty(info.getAnchor())) { + mySideStack.pop(info.getAnchor()); + } + + final String[] all = getToolWindowIds(); + for (String eachId : all) { + final WindowInfoImpl eachInfo = getInfo(eachId); + if (eachInfo.isVisible() && eachInfo.getAnchor() == info.getAnchor()) { + deactivateToolWindowImpl(eachId, true, commandList); + } + } + + activateEditorComponentImpl(getSplittersFromFocus(), commandList, true); + } + else { + + // first of all we have to find tool window that was located at the same side and + // was hidden. + + WindowInfoImpl info2 = null; + while (!mySideStack.isEmpty(info.getAnchor())) { + final WindowInfoImpl storedInfo = mySideStack.pop(info.getAnchor()); + final WindowInfoImpl currentInfo = getInfo(storedInfo.getId()); + LOG.assertTrue(currentInfo != null); + // SideStack contains copies of real WindowInfos. It means that + // these stored infos can be invalid. The following loop removes invalid WindowInfos. + if (storedInfo.getAnchor() == currentInfo.getAnchor() && + storedInfo.getType() == currentInfo.getType() && + storedInfo.isAutoHide() == currentInfo.isAutoHide()) { + info2 = storedInfo; + break; + } + } + if (info2 != null) { + showToolWindowImpl(info2.getId(), false, commandList); + } + + // If we hide currently active tool window then we should activate the previous + // one which is located in the tool window stack. + // Activate another tool window if no active tool window exists and + // window stack is enabled. + + myActiveStack.remove(id, false); // hidden window should be at the top of stack + + if (wasActive && moveFocus) { + if (myActiveStack.isEmpty()) { + if (hasOpenEditorFiles()) { + activateEditorComponentImpl(getSplittersFromFocus(), commandList, false); + } + else { + focusToolWinowByDefault(id); + } + } + else { + final String toBeActivatedId = myActiveStack.pop(); + if (toBeActivatedId != null) { + activateToolWindowImpl(toBeActivatedId, commandList, false, true); + } + else { + focusToolWinowByDefault(id); + } + } + } + } + + execute(commandList); + } + + /** + * @param dirtyMode if true then all UI operations are performed in dirty mode. + */ + private void showToolWindowImpl(final String id, final boolean dirtyMode, final List commandsList) { + final WindowInfoImpl toBeShownInfo = getInfo(id); + if (toBeShownInfo.isVisible() || !getToolWindow(id).isAvailable()) { + return; + } + + if (DumbService.getInstance(myProject).isDumb() && !myDumbAwareIds.contains(id)) { + return; + } + + toBeShownInfo.setVisible(true); + final InternalDecorator decorator = getInternalDecorator(id); + + if (toBeShownInfo.isFloating()) { + commandsList.add(new AddFloatingDecoratorCmd(decorator, toBeShownInfo)); + } + else { // docked and sliding windows + + // If there is tool window on the same side then we have to hide it, i.e. + // clear place for tool window to be shown. + // + // We store WindowInfo of hidden tool window in the SideStack (if the tool window + // is docked and not auto-hide one). Therefore it's possible to restore the + // hidden tool window when showing tool window will be closed. + + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (final WindowInfoImpl info : infos) { + if (id.equals(info.getId())) { + continue; + } + if (info.isVisible() && + info.getType() == toBeShownInfo.getType() && + info.getAnchor() == toBeShownInfo.getAnchor() && + info.isSplit() == toBeShownInfo.isSplit()) { + // hide and deactivate tool window + info.setVisible(false); + appendRemoveDecoratorCmd(info.getId(), false, commandsList); + if (info.isActive()) { + info.setActive(false); + } + appendApplyWindowInfoCmd(info, commandsList); + // store WindowInfo into the SideStack + if (info.isDocked() && !info.isAutoHide()) { + mySideStack.push(info); + } + } + } + appendAddDecoratorCmd(decorator, toBeShownInfo, dirtyMode, commandsList); + + // Remove tool window from the SideStack. + + mySideStack.remove(id); + } + + appendApplyWindowInfoCmd(toBeShownInfo, commandsList); + } + + public ToolWindow registerToolWindow(@NotNull final String id, + @NotNull final JComponent component, + @NotNull final ToolWindowAnchor anchor) { + return registerToolWindow(id, component, anchor, false); + } + + public ToolWindow registerToolWindow(@NotNull final String id, + @NotNull JComponent component, + @NotNull ToolWindowAnchor anchor, + Disposable parentDisposable) { + return registerToolWindow(id, component, anchor, parentDisposable, false); + } + + public ToolWindow registerToolWindow(@NotNull final String id, + @NotNull JComponent component, + @NotNull ToolWindowAnchor anchor, + Disposable parentDisposable, + boolean canWorkInDumbMode) { + return registerDisposable(id, parentDisposable, registerToolWindow(id, component, anchor, canWorkInDumbMode)); + } + + private ToolWindow registerToolWindow(@NotNull final String id, + @NotNull final JComponent component, + @NotNull final ToolWindowAnchor anchor, + boolean canWorkInDumbMode) { + return registerToolWindow(id, component, anchor, false, false, canWorkInDumbMode); + } + + public ToolWindow registerToolWindow(@NotNull final String id, final boolean canCloseContent, @NotNull final ToolWindowAnchor anchor) { + return registerToolWindow(id, null, anchor, false, canCloseContent, false); + } + + public ToolWindow registerToolWindow(@NotNull final String id, + final boolean canCloseContent, + @NotNull final ToolWindowAnchor anchor, + final boolean sideTool) { + return registerToolWindow(id, null, anchor, sideTool, canCloseContent, false); + } + + + public ToolWindow registerToolWindow(@NotNull final String id, + final boolean canCloseContent, + @NotNull final ToolWindowAnchor anchor, + final Disposable parentDisposable, + final boolean canWorkInDumbMode) { + return registerDisposable(id, parentDisposable, registerToolWindow(id, null, anchor, false, canCloseContent, canWorkInDumbMode)); + } + + private ToolWindow registerToolWindow(@NotNull final String id, + @Nullable final JComponent component, + @NotNull final ToolWindowAnchor anchor, + boolean sideTool, + boolean canCloseContent, + final boolean canWorkInDumbMode) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: installToolWindow(" + id + "," + component + "," + anchor + "\")"); + } + ApplicationManager.getApplication().assertIsDispatchThread(); + if (myLayout.isToolWindowRegistered(id)) { + throw new IllegalArgumentException("window with id=\"" + id + "\" is already registered"); + } + + final WindowInfoImpl info = myLayout.register(id, anchor, sideTool); + final boolean wasActive = info.isActive(); + final boolean wasVisible = info.isVisible(); + info.setActive(false); + info.setVisible(false); + + // Create decorator + + final ToolWindowImpl toolWindow = new ToolWindowImpl(this, id, canCloseContent, component); + final InternalDecorator decorator = new InternalDecorator(myProject, info.copy(), toolWindow); + myId2InternalDecorator.put(id, decorator); + decorator.addInternalDecoratorListener(myInternalDecoratorListener); + toolWindow.addPropertyChangeListener(myToolWindowPropertyChangeListener); + myId2FocusWatcher.put(id, new ToolWindowFocusWatcher(toolWindow)); + + // Create and show tool button + + final StripeButton button = new StripeButton(decorator, myToolWindowsPane); + myId2StripeButton.put(id, button); + final ArrayList commandsList = new ArrayList(); + appendAddButtonCmd(button, info, commandsList); + + if (canWorkInDumbMode) { + myDumbAwareIds.add(id); + } + else if (DumbService.getInstance(getProject()).isDumb()) { + button.setEnabled(false); + } + + // If preloaded info is visible or active then we have to show/activate the installed + // tool window. This step has sense only for windows which are not in the autohide + // mode. But if tool window was active but its mode doen't allow to activate it again + // (for example, tool window is in autohide mode) then we just activate editor component. + + if (!info.isAutoHide() && (info.isDocked() || info.isFloating())) { + if (wasActive) { + activateToolWindowImpl(info.getId(), commandsList, true, true); + } + else if (wasVisible) { + showToolWindowImpl(info.getId(), false, commandsList); + } + } + else if (wasActive) { // tool window was active but it cannot be activate again + activateEditorComponentImpl(getSplittersFromFocus(), commandsList, true); + } + + execute(commandsList); + fireToolWindowRegistered(id); + return toolWindow; + } + + private ToolWindow registerDisposable(final String id, final Disposable parentDisposable, final ToolWindow window) { + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + unregisterToolWindow(id); + } + }); + return window; + } + + public void unregisterToolWindow(@NotNull final String id) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: unregisterToolWindow(" + id + ")"); + } + ApplicationManager.getApplication().assertIsDispatchThread(); + if (!myLayout.isToolWindowRegistered(id)) { + return; + } + + final WindowInfoImpl info = getInfo(id); + final ToolWindowEx toolWindow = (ToolWindowEx)getToolWindow(id); + // Save recent appearance of tool window + myLayout.unregister(id); + // Remove decorator and tool button from the screen + final ArrayList commandsList = new ArrayList(); + if (info.isVisible()) { + info.setVisible(false); + if (info.isFloating()) { + appendRemoveFloatingDecoratorCmd(info, commandsList); + } + else { // floating and sliding windows + appendRemoveDecoratorCmd(id, false, commandsList); + } + } + appendRemoveButtonCmd(id, commandsList); + appendApplyWindowInfoCmd(info, commandsList); + execute(commandsList); + // Remove all references on tool window and save its last properties + toolWindow.removePropertyChangeListener(myToolWindowPropertyChangeListener); + myActiveStack.remove(id, true); + mySideStack.remove(id); + // Destroy stripe button + final StripeButton button = getStripeButton(id); + button.dispose(); + myId2StripeButton.remove(id); + // + myId2FocusWatcher.remove(id); + // Destroy decorator + final InternalDecorator decorator = getInternalDecorator(id); + decorator.dispose(); + decorator.removeInternalDecoratorListener(myInternalDecoratorListener); + myId2InternalDecorator.remove(id); + } + + public DesktopLayout getLayout() { + ApplicationManager.getApplication().assertIsDispatchThread(); + return myLayout; + } + + public void setLayoutToRestoreLater(DesktopLayout layout) { + myLayoutToRestoreLater = layout; + } + + public DesktopLayout getLayoutToRestoreLater() { + return myLayoutToRestoreLater; + } + + public void setLayout(@NotNull final DesktopLayout layout) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final ArrayList commandList = new ArrayList(); + // hide tool window that are invisible in new layout + final WindowInfoImpl[] currentInfos = myLayout.getInfos(); + for (final WindowInfoImpl currentInfo : currentInfos) { + final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); + if (info == null) { + continue; + } + if (currentInfo.isVisible() && !info.isVisible()) { + deactivateToolWindowImpl(currentInfo.getId(), true, commandList); + } + } + // change anchor of tool windows + for (final WindowInfoImpl currentInfo : currentInfos) { + final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); + if (info == null) { + continue; + } + if (currentInfo.getAnchor() != info.getAnchor() || currentInfo.getOrder() != info.getOrder()) { + setToolWindowAnchorImpl(currentInfo.getId(), info.getAnchor(), info.getOrder(), commandList); + } + } + // change types of tool windows + for (final WindowInfoImpl currentInfo : currentInfos) { + final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); + if (info == null) { + continue; + } + if (currentInfo.getType() != info.getType()) { + setToolWindowTypeImpl(currentInfo.getId(), info.getType(), commandList); + } + } + // change auto-hide state + for (final WindowInfoImpl currentInfo : currentInfos) { + final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); + if (info == null) { + continue; + } + if (currentInfo.isAutoHide() != info.isAutoHide()) { + setToolWindowAutoHideImpl(currentInfo.getId(), info.isAutoHide(), commandList); + } + } + // restore visibility + for (final WindowInfoImpl currentInfo : currentInfos) { + final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); + if (info == null) { + continue; + } + if (info.isVisible()) { + showToolWindowImpl(currentInfo.getId(), false, commandList); + } + } + // if there is no any active tool window and editor is also inactive + // then activate editor + if (!myEditorComponentActive && getActiveToolWindowId() == null) { + activateEditorComponentImpl(getSplittersFromFocus(), commandList, true); + } + execute(commandList); + } + + public void invokeLater(final Runnable runnable) { + final ArrayList commandList = new ArrayList(); + commandList.add(new InvokeLaterCmd(runnable, myWindowManager.getCommandProcessor())); + execute(commandList); + } + + public IdeFocusManager getFocusManager() { + return IdeFocusManager.getInstance(myProject); + } + + @Override + public void notifyByBalloon(@NotNull final String toolWindowId, @NotNull final MessageType type, @NotNull final String htmlBody) { + notifyByBalloon(toolWindowId, type, htmlBody, null, null); + } + + public void notifyByBalloon(@NotNull final String toolWindowId, + @NotNull final MessageType type, + @NotNull final String text, + @Nullable final Icon icon, + @Nullable HyperlinkListener listener) { + checkId(toolWindowId); + + + Balloon existing = myWindow2Balloon.get(toolWindowId); + if (existing != null) { + existing.hide(); + } + + final Stripe stripe = myToolWindowsPane.getStripeFor(toolWindowId); + final ToolWindowImpl window = getInternalDecorator(toolWindowId).getToolWindow(); + if (!window.isAvailable()) { + window.setPlaceholderMode(true); + stripe.updateState(); + stripe.revalidate(); + stripe.repaint(); + } + + final ToolWindowAnchor anchor = getInfo(toolWindowId).getAnchor(); + final Ref position = Ref.create(Balloon.Position.below); + if (ToolWindowAnchor.TOP == anchor) { + position.set(Balloon.Position.below); + } + else if (ToolWindowAnchor.BOTTOM == anchor) { + position.set(Balloon.Position.above); + } + else if (ToolWindowAnchor.LEFT == anchor) { + position.set(Balloon.Position.atRight); + } + else if (ToolWindowAnchor.RIGHT == anchor) { + position.set(Balloon.Position.atLeft); + } + + Icon actualIcon = icon != null ? icon : type.getDefaultIcon(); + + final Balloon balloon = + JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text.replace("\n", "
"), actualIcon, type.getPopupBackground(), listener) + .setHideOnClickOutside(true).setHideOnFrameResize(false).createBalloon(); + myWindow2Balloon.put(toolWindowId, balloon); + Disposer.register(balloon, new Disposable() { + public void dispose() { + window.setPlaceholderMode(false); + stripe.updateState(); + stripe.revalidate(); + stripe.repaint(); + myWindow2Balloon.remove(toolWindowId); + } + }); + Disposer.register(getProject(), balloon); + + final StripeButton button = stripe.getButtonFor(toolWindowId); + if (button == null) return; + + final Runnable show = new Runnable() { + public void run() { + if (button.isShowing()) { + PositionTracker tracker = new PositionTracker(button) { + @Override + public RelativePoint recalculateLocation(Balloon object) { + Stripe twStripe = myToolWindowsPane.getStripeFor(toolWindowId); + StripeButton twButton = twStripe != null ? twStripe.getButtonFor(toolWindowId) : null; + + if (twButton == null) return null; + + if (getToolWindow(toolWindowId).getAnchor() != anchor) { + object.hide(); + return null; + } + + final Point point = new Point(twButton.getBounds().width / 2, twButton.getHeight() / 2 - 2); + return new RelativePoint(twButton, point); + } + }; + balloon.show(tracker, position.get()); + } + else { + final Rectangle bounds = myToolWindowsPane.getBounds(); + final Point target = UIUtil.getCenterPoint(bounds, new Dimension(1, 1)); + if (ToolWindowAnchor.TOP == anchor) { + target.y = 0; + } + else if (ToolWindowAnchor.BOTTOM == anchor) { + target.y = bounds.height - 3; + } + else if (ToolWindowAnchor.LEFT == anchor) { + target.x = 0; + } + else if (ToolWindowAnchor.RIGHT == anchor) { + target.x = bounds.width; + } + + balloon.show(new RelativePoint(myToolWindowsPane, target), position.get()); + } + } + }; + + if (!button.isValid()) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + show.run(); + } + }); + } + else { + show.run(); + } + + } + + public boolean isEditorComponentActive() { + ApplicationManager.getApplication().assertIsDispatchThread(); + return myEditorComponentActive; + } + + ToolWindowAnchor getToolWindowAnchor(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).getAnchor(); + } + + void setToolWindowAnchor(final String id, final ToolWindowAnchor anchor) { + ApplicationManager.getApplication().assertIsDispatchThread(); + setToolWindowAnchor(id, anchor, -1); + } + + void setToolWindowAnchor(final String id, final ToolWindowAnchor anchor, final int order) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final ArrayList commandList = new ArrayList(); + setToolWindowAnchorImpl(id, anchor, order, commandList); + execute(commandList); + } + + private void setToolWindowAnchorImpl(final String id, + final ToolWindowAnchor anchor, + final int order, + final ArrayList commandsList) { + checkId(id); + final WindowInfoImpl info = getInfo(id); + if (anchor == info.getAnchor() && order == info.getOrder()) { + return; + } + // if tool window isn't visible or only order number is changed then just remove/add stripe button + if (!info.isVisible() || anchor == info.getAnchor() || info.isFloating()) { + appendRemoveButtonCmd(id, commandsList); + myLayout.setAnchor(id, anchor, order); + // update infos for all window. Actually we have to update only infos affected by + // setAnchor method + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (WindowInfoImpl info1 : infos) { + appendApplyWindowInfoCmd(info1, commandsList); + } + appendAddButtonCmd(getStripeButton(id), info, commandsList); + } + else { // for docked and sliding windows we have to move buttons and window's decorators + info.setVisible(false); + appendRemoveDecoratorCmd(id, false, commandsList); + appendRemoveButtonCmd(id, commandsList); + myLayout.setAnchor(id, anchor, order); + // update infos for all window. Actually we have to update only infos affected by + // setAnchor method + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (WindowInfoImpl info1 : infos) { + appendApplyWindowInfoCmd(info1, commandsList); + } + appendAddButtonCmd(getStripeButton(id), info, commandsList); + showToolWindowImpl(id, false, commandsList); + if (info.isActive()) { + appendRequestFocusInToolWindowCmd(id, commandsList, true); + } + } + } + + boolean isSplitMode(String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).isSplit(); + } + + ToolWindowContentUiType getContentUiType(String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).getContentUiType(); + } + + void setSideTool(String id, boolean isSide) { + final ArrayList commandList = new ArrayList(); + setSplitModeImpl(id, isSide, commandList); + execute(commandList); + } + + public void setContentUiType(String id, ToolWindowContentUiType type) { + final ArrayList commandList = new ArrayList(); + checkId(id); + WindowInfoImpl info = getInfo(id); + info.setContentUiType(type); + appendApplyWindowInfoCmd(info, commandList); + execute(commandList); + } + + void setSideToolAndAnchor(String id, ToolWindowAnchor anchor, int order, boolean isSide) { + final ArrayList commandList = new ArrayList(); + setToolWindowAnchor(id, anchor, order); + setSplitModeImpl(id, isSide, commandList); + execute(commandList); + } + + private void setSplitModeImpl(final String id, final boolean isSplit, final ArrayList commandList) { + checkId(id); + final WindowInfoImpl info = getInfo(id); + if (isSplit == info.isSplit()) { + return; + } + + myLayout.setSplitMode(id, isSplit); + + boolean wasActive = info.isActive(); + if (wasActive) { + deactivateToolWindowImpl(id, true, commandList); + } + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (WindowInfoImpl info1 : infos) { + appendApplyWindowInfoCmd(info1, commandList); + } + if (wasActive) { + activateToolWindowImpl(id, commandList, true, true); + } + commandList.add(myToolWindowsPane.createUpdateButtonPositionCmd(id, myWindowManager.getCommandProcessor())); + } + + ToolWindowType getToolWindowInternalType(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).getInternalType(); + } + + ToolWindowType getToolWindowType(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).getType(); + } + + private void fireToolWindowRegistered(final String id) { + final ToolWindowManagerListener[] listeners = myListenerList.getListeners(ToolWindowManagerListener.class); + for (ToolWindowManagerListener listener : listeners) { + listener.toolWindowRegistered(id); + } + } + + private void fireStateChanged() { + final ToolWindowManagerListener[] listeners = myListenerList.getListeners(ToolWindowManagerListener.class); + for (ToolWindowManagerListener listener : listeners) { + listener.stateChanged(); + } + } + + boolean isToolWindowActive(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).isActive(); + } + + boolean isToolWindowAutoHide(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).isAutoHide(); + } + + public boolean isToolWindowFloating(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).isFloating(); + } + + boolean isToolWindowVisible(final String id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + checkId(id); + return getInfo(id).isVisible(); + } + + void setToolWindowAutoHide(final String id, final boolean autoHide) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final ArrayList commandList = new ArrayList(); + setToolWindowAutoHideImpl(id, autoHide, commandList); + execute(commandList); + } + + private void setToolWindowAutoHideImpl(final String id, final boolean autoHide, final ArrayList commandsList) { + checkId(id); + final WindowInfoImpl info = getInfo(id); + if (info.isAutoHide() == autoHide) { + return; + } + info.setAutoHide(autoHide); + appendApplyWindowInfoCmd(info, commandsList); + if (info.isVisible()) { + prepareForActivation(id, commandsList); + showAndActivate(id, false, commandsList, true); + } + } + + void setToolWindowType(final String id, final ToolWindowType type) { + ApplicationManager.getApplication().assertIsDispatchThread(); + final ArrayList commandList = new ArrayList(); + setToolWindowTypeImpl(id, type, commandList); + execute(commandList); + } + + private void setToolWindowTypeImpl(final String id, final ToolWindowType type, final ArrayList commandsList) { + checkId(id); + final WindowInfoImpl info = getInfo(id); + if (info.getType() == type) { + return; + } + if (info.isVisible()) { + final boolean dirtyMode = info.isDocked() || info.isSliding(); + info.setVisible(false); + if (info.isFloating()) { + appendRemoveFloatingDecoratorCmd(info, commandsList); + } + else { // docked and sliding windows + appendRemoveDecoratorCmd(id, dirtyMode, commandsList); + } + info.setType(type); + appendApplyWindowInfoCmd(info, commandsList); + prepareForActivation(id, commandsList); + showAndActivate(id, dirtyMode, commandsList, true); + appendUpdateToolWindowsPaneCmd(commandsList); + } + else { + info.setType(type); + appendApplyWindowInfoCmd(info, commandsList); + } + } + + private void appendApplyWindowInfoCmd(final WindowInfoImpl info, final List commandsList) { + final StripeButton button = getStripeButton(info.getId()); + final InternalDecorator decorator = getInternalDecorator(info.getId()); + commandsList.add(new ApplyWindowInfoCmd(info, button, decorator, myWindowManager.getCommandProcessor())); + } + + /** + * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddDecoratorCmd + */ + private void appendAddDecoratorCmd(final InternalDecorator decorator, + final WindowInfoImpl info, + final boolean dirtyMode, + final List commandsList) { + final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); + final FinalizableCommand command = myToolWindowsPane.createAddDecoratorCmd(decorator, info, dirtyMode, commandProcessor); + commandsList.add(command); + } + + /** + * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createRemoveDecoratorCmd + */ + private void appendRemoveDecoratorCmd(final String id, final boolean dirtyMode, final List commandsList) { + final FinalizableCommand command = myToolWindowsPane.createRemoveDecoratorCmd(id, dirtyMode, myWindowManager.getCommandProcessor()); + commandsList.add(command); + } + + private void appendRemoveFloatingDecoratorCmd(final WindowInfoImpl info, final List commandsList) { + final RemoveFloatingDecoratorCmd command = new RemoveFloatingDecoratorCmd(info); + commandsList.add(command); + } + + /** + * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddButtonCmd + */ + private void appendAddButtonCmd(final StripeButton button, final WindowInfoImpl info, final List commandsList) { + final Comparator comparator = myLayout.comparator(info.getAnchor()); + final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); + final FinalizableCommand command = myToolWindowsPane.createAddButtonCmd(button, info, comparator, commandProcessor); + commandsList.add(command); + } + + /** + * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddButtonCmd + */ + private void appendRemoveButtonCmd(final String id, final List commandsList) { + final FinalizableCommand command = myToolWindowsPane.createRemoveButtonCmd(id, myWindowManager.getCommandProcessor()); + commandsList.add(command); + } + + private ActionCallback appendRequestFocusInEditorComponentCmd(EditorsSplitters splitters, final ArrayList commandList, boolean forced) { + if (myProject.isDisposed()) return new ActionCallback.Done(); + final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); + final RequestFocusInEditorComponentCmd command = + new RequestFocusInEditorComponentCmd(splitters, getFocusManager(), commandProcessor, forced); + commandList.add(command); + return command.getDoneCallback(); + } + + private void appendRequestFocusInToolWindowCmd(final String id, final ArrayList commandList, boolean forced) { + final ToolWindowImpl toolWindow = (ToolWindowImpl)getToolWindow(id); + final FocusWatcher focusWatcher = myId2FocusWatcher.get(id); + commandList + .add(new RequestFocusInToolWindowCmd(getFocusManager(), toolWindow, focusWatcher, myWindowManager.getCommandProcessor(), forced)); + } + + /** + * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createSetEditorComponentCmd + */ + private void appendSetEditorComponentCmd(final JComponent component, final List commandsList) { + final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); + final FinalizableCommand command = myToolWindowsPane.createSetEditorComponentCmd(component, commandProcessor); + commandsList.add(command); + } + + private void appendUpdateToolWindowsPaneCmd(final List commandsList) { + final JRootPane rootPane = myFrame.getRootPane(); + final FinalizableCommand command = new UpdateRootPaneCmd(rootPane, myWindowManager.getCommandProcessor()); + commandsList.add(command); + } + + /** + * @return true if tool window with the specified id + * is floating and has modal showing child dialog. Such windows should not be closed + * when auto-hide windows are gone. + */ + private boolean hasModalChild(final WindowInfoImpl info) { + if (!info.isVisible() || !info.isFloating()) { + return false; + } + final FloatingDecorator decorator = getFloatingDecorator(info.getId()); + LOG.assertTrue(decorator != null); + return isModalOrHasModalChild(decorator); + } + + private static boolean isModalOrHasModalChild(final Window window) { + if (window instanceof Dialog) { + final Dialog dialog = (Dialog)window; + if (dialog.isModal() && dialog.isShowing()) { + return true; + } + final Window[] ownedWindows = dialog.getOwnedWindows(); + for (int i = ownedWindows.length - 1; i >= 0; i--) { + if (isModalOrHasModalChild(ownedWindows[i])) { + return true; + } + } + } + return false; + } + + /** + * Helper method. It deactivates all tool windows excepting the tool window + * which should be activated. + */ + private void prepareForActivation(final String id, final List commandList) { + final WindowInfoImpl toBeActivatedInfo = getInfo(id); + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (final WindowInfoImpl info : infos) { + if (id.equals(info.getId())) { + continue; + } + if (toBeActivatedInfo.isDocked() || toBeActivatedInfo.isSliding()) { + deactivateToolWindowImpl(info.getId(), info.isAutoHide() || info.isSliding(), commandList); + } + else { // floating window is being activated + deactivateToolWindowImpl(info.getId(), info.isAutoHide() && info.isFloating() && !hasModalChild(info), commandList); + } + } + } + + public void clearSideStack() { + mySideStack.clear(); + } + + public void readExternal(final Element element) { + for (final Object o : element.getChildren()) { + final Element e = (Element)o; + if (EDITOR_ELEMENT.equals(e.getName())) { + myEditorComponentActive = Boolean.valueOf(e.getAttributeValue(ACTIVE_ATTR_VALUE)).booleanValue(); + } + else if (DesktopLayout.TAG.equals(e.getName())) { // read layout of tool windows + myLayout.readExternal(e); + final WindowInfoImpl[] windowInfos = myLayout.getAllInfos(); + for (WindowInfoImpl windowInfo : windowInfos) { + myRestoredToolWindowIds.add(windowInfo.getId()); + } + } + } + } + + public void writeExternal(final Element element) { + if (myFrame == null) { + // do nothing if the project was not opened + return; + } + final String[] ids = getToolWindowIds(); + + // Update size of all open floating windows. See SCR #18439 + for (final String id : ids) { + final WindowInfoImpl info = getInfo(id); + if (info.isVisible()) { + final InternalDecorator decorator = getInternalDecorator(id); + LOG.assertTrue(decorator != null); + decorator.fireResized(); + } + } + + // Save frame's bounds + final Rectangle frameBounds = myFrame.getBounds(); + final Element frameElement = new Element(FRAME_ELEMENT); + element.addContent(frameElement); + frameElement.setAttribute(X_ATTR, Integer.toString(frameBounds.x)); + frameElement.setAttribute(Y_ATTR, Integer.toString(frameBounds.y)); + frameElement.setAttribute(WIDTH_ATTR, Integer.toString(frameBounds.width)); + frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(frameBounds.height)); + frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(myFrame.getExtendedState())); + // Save whether editor is active or not + final Element editorElement = new Element(EDITOR_ELEMENT); + editorElement.setAttribute(ACTIVE_ATTR_VALUE, myEditorComponentActive ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); + element.addContent(editorElement); + // Save layout of tool windows + final Element layoutElement = new Element(DesktopLayout.TAG); + element.addContent(layoutElement); + myLayout.writeExternal(layoutElement); + } + + public void setDefaultState(@NotNull final ToolWindowImpl toolWindow, + @Nullable final ToolWindowAnchor anchor, + @Nullable final ToolWindowType type, + @Nullable final Rectangle floatingBounds) { + + final WindowInfoImpl info = getInfo(toolWindow.getId()); + if (info.wasRead()) return; + + if (floatingBounds != null) { + info.setFloatingBounds(floatingBounds); + } + + if (anchor != null) { + toolWindow.setAnchor(anchor, null); + } + + if (type != null) { + toolWindow.setType(type, null); + } + + } + + public void setDefaultContentUiType(ToolWindowImpl toolWindow, ToolWindowContentUiType type) { + final WindowInfoImpl info = getInfo(toolWindow.getId()); + if (info.wasRead()) return; + toolWindow.setContentUiType(type, null); + } + + + public void stretchWidth(ToolWindowImpl toolWindow, int value) { + myToolWindowsPane.stretchWidth(toolWindow, value); + } + + public void stretchHeight(ToolWindowImpl toolWindow, int value) { + myToolWindowsPane.stretchHeight(toolWindow, value); + } + + + /** + * This command creates and shows FloatingDecorator. + */ + private final class AddFloatingDecoratorCmd extends FinalizableCommand { + private final FloatingDecorator myFloatingDecorator; + + /** + * Creates floating decorator for specified floating decorator. + */ + private AddFloatingDecoratorCmd(final InternalDecorator decorator, final WindowInfoImpl info) { + super(myWindowManager.getCommandProcessor()); + myFloatingDecorator = new FloatingDecorator(myFrame, info.copy(), decorator); + myId2FloatingDecorator.put(info.getId(), myFloatingDecorator); + final Rectangle bounds = info.getFloatingBounds(); + if (bounds != null && + bounds.width > 0 && + bounds.height > 0 && + myWindowManager.isInsideScreenBounds(bounds.x, bounds.y, bounds.width)) { + myFloatingDecorator.setBounds(bounds); + } + else { // place new frame at the center of main frame if there are no floating bounds + Dimension size = decorator.getSize(); + if (size.width == 0 || size.height == 0) { + size = decorator.getPreferredSize(); + } + myFloatingDecorator.setSize(size); + myFloatingDecorator.setLocationRelativeTo(myFrame); + } + } + + public void run() { + try { + myFloatingDecorator.show(); + } + finally { + finish(); + } + } + } + + /** + * This command hides and destroys floating decorator for tool window + * with specified ID. + */ + private final class RemoveFloatingDecoratorCmd extends FinalizableCommand { + private final FloatingDecorator myFloatingDecorator; + + private RemoveFloatingDecoratorCmd(final WindowInfoImpl info) { + super(myWindowManager.getCommandProcessor()); + myFloatingDecorator = getFloatingDecorator(info.getId()); + myId2FloatingDecorator.remove(info.getId()); + info.setFloatingBounds(myFloatingDecorator.getBounds()); + } + + public void run() { + try { + if (Patches.SPECIAL_WINPUT_METHOD_PROCESSING) { + myFloatingDecorator.remove(myFloatingDecorator.getRootPane()); + } + myFloatingDecorator.dispose(); + } + finally { + finish(); + } + } + + @Nullable + public Condition getExpireCondition() { + return ApplicationManager.getApplication().getDisposed(); + } + } + + private final class EditorComponentFocusWatcher extends FocusWatcher { + protected void focusedComponentChanged(final Component component, final AWTEvent cause) { + if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) { + return; + } + + // Sometimes focus gained comes when editor is active. For example it can happen when + // user switches between menus or closes some dialog. In that case we just ignore this event, + // i.e. don't initiate deactivation of tool windows and requesting focus in editor. + if (myEditorComponentActive) { + return; + } + + + final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + final Component owner = mgr.getFocusOwner(); + + IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { + public void run() { + if (mgr.getFocusOwner() == owner) { + activateEditorComponent(false); + } + } + }); + } + } + + + /** + * Notifies window manager about focus traversal in tool window + */ + private final class ToolWindowFocusWatcher extends FocusWatcher { + private final String myId; + + + private ToolWindowFocusWatcher(final ToolWindowImpl toolWindow) { + myId = toolWindow.getId(); + install(toolWindow.getComponent()); + } + + protected boolean isFocusedComponentChangeValid(final Component comp, final AWTEvent cause) { + return myWindowManager.getCommandProcessor().getCommandCount() == 0 && comp != null; + } + + protected void focusedComponentChanged(final Component component, final AWTEvent cause) { + if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) { + return; + } + final WindowInfoImpl info = getInfo(myId); + getFocusManagerImpl().myFocusedComponentAlaram.cancelAllRequests(); + + if (!info.isActive()) { + getFocusManagerImpl().myFocusedComponentAlaram.addRequest(new EdtRunnable() { + public void runEdt() { + if (!myLayout.isToolWindowRegistered(myId)) return; + activateToolWindow(myId, false, false); + } + }, 100); + } + } + } + + /** + * Spies on IdeToolWindow properties and applies them to the window + * state. + */ + private final class MyToolWindowPropertyChangeListener implements PropertyChangeListener { + public void propertyChange(final PropertyChangeEvent e) { + final ToolWindowImpl toolWindow = (ToolWindowImpl)e.getSource(); + if (ToolWindowEx.PROP_AVAILABLE.equals(e.getPropertyName())) { + final WindowInfoImpl info = getInfo(toolWindow.getId()); + if (!toolWindow.isAvailable() && info.isVisible()) { + hideToolWindow(toolWindow.getId(), false); + } + } + } + } + + /** + * Translates events from InternalDecorator into ToolWindowManager method invocations. + */ + private final class MyInternalDecoratorListener implements InternalDecoratorListener { + public void anchorChanged(final InternalDecorator source, final ToolWindowAnchor anchor) { + setToolWindowAnchor(source.getToolWindow().getId(), anchor); + } + + public void autoHideChanged(final InternalDecorator source, final boolean autoHide) { + setToolWindowAutoHide(source.getToolWindow().getId(), autoHide); + } + + public void hidden(final InternalDecorator source) { + hideToolWindow(source.getToolWindow().getId(), false); + } + + public void hiddenSide(final InternalDecorator source) { + hideToolWindow(source.getToolWindow().getId(), true); + } + + public void contentUiTypeChanges(InternalDecorator source, ToolWindowContentUiType type) { + setContentUiType(source.getToolWindow().getId(), type); + } + + /** + * Handles event from decorator and modify weight/floating bounds of the + * tool window depending on decoration type. + */ + public void resized(final InternalDecorator source) { + final WindowInfoImpl info = getInfo(source.getToolWindow().getId()); + if (info.isFloating()) { + final Window owner = SwingUtilities.getWindowAncestor(source); + if (owner != null) { + info.setFloatingBounds(owner.getBounds()); + } + } + else { // docked and sliding windows + if (ToolWindowAnchor.TOP == info.getAnchor() || ToolWindowAnchor.BOTTOM == info.getAnchor()) { + info.setWeight((float)source.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight()); + float newSideWeight = (float)source.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth(); + if (newSideWeight < 1.0f) { + info.setSideWeight(newSideWeight); + } + } + else { + info.setWeight((float)source.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth()); + float newSideWeight = (float)source.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight(); + if (newSideWeight < 1.0f) { + info.setSideWeight(newSideWeight); + } + } + } + } + + public void activated(final InternalDecorator source) { + activateToolWindow(source.getToolWindow().getId(), true, true); + } + + public void typeChanged(final InternalDecorator source, final ToolWindowType type) { + setToolWindowType(source.getToolWindow().getId(), type); + } + + public void sideStatusChanged(final InternalDecorator source, final boolean isSideTool) { + setSideTool(source.getToolWindow().getId(), isSideTool); + } + } + + private void updateComponentTreeUI() { + ApplicationManager.getApplication().assertIsDispatchThread(); + final WindowInfoImpl[] infos = myLayout.getInfos(); + for (final WindowInfoImpl info : infos) { + if (info.isVisible()) { // skip visible tool windows (optimization) + continue; + } + SwingUtilities.updateComponentTreeUI(getInternalDecorator(info.getId())); + } + } + + private final class MyUIManagerPropertyChangeListener implements PropertyChangeListener { + public void propertyChange(final PropertyChangeEvent e) { + updateComponentTreeUI(); + } + } + + private final class MyLafManagerListener implements LafManagerListener { + public void lookAndFeelChanged(final LafManager source) { + updateComponentTreeUI(); + } + } + + + @NotNull + public String getComponentName() { + return "ToolWindowManager"; + } + + public ToolWindowsPane getToolWindowsPane() { + return myToolWindowsPane; + } + + public ActionCallback requestDefaultFocus(final boolean forced) { + return getFocusManagerImpl().requestFocus(new FocusCommand() { + public ActionCallback run() { + return processDefaultFocusRequest(forced); + } + }, forced); + } + + private void focusToolWinowByDefault(String idToIngore) { + String toFocus = null; + + for (String each : myActiveStack.getStack()) { + if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue; + + if (getInfo(each).isVisible()) { + toFocus = each; + break; + } + } + + if (toFocus == null) { + for (String each : myActiveStack.getPersistentStack()) { + if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue; + + if (getInfo(each).isVisible()) { + toFocus = each; + break; + } + } + } + + if (toFocus != null) { + activateToolWindow(toFocus, false, true); + } + } + + private ActionCallback processDefaultFocusRequest(boolean forced) { + if (ModalityState.NON_MODAL.equals(ModalityState.current())) { + final String activeId = getActiveToolWindowId(); + if (myEditorComponentActive || activeId == null || getToolWindow(activeId) == null) { + activateEditorComponent(forced, true); + } + else { + activateToolWindow(activeId, forced, true); + } + } + return new ActionCallback.Done(); + } + + + private boolean isProjectComponent(Component c) { + final Component frame = UIUtil.findUltimateParent(c); + if (frame instanceof IdeFrame) { + return frame == myWindowManager.getFrame(myProject); + } + else { + return false; + } + } + + /** + * Delegate method for compatibility with older versions of IDEA + */ + @NotNull + public ActionCallback requestFocus(@NotNull Component c, boolean forced) { + return IdeFocusManager.getInstance(myProject).requestFocus(c, forced); + } + + @NotNull + public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) { + return IdeFocusManager.getInstance(myProject).requestFocus(command, forced); + } + + public JComponent getFocusTargetFor(@NotNull JComponent comp) { + return IdeFocusManager.getInstance(myProject).getFocusTargetFor(comp); + } + + public void doWhenFocusSettlesDown(@NotNull Runnable runnable) { + IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(runnable); + } + + public Component getFocusedDescendantFor(Component comp) { + return IdeFocusManager.getInstance(myProject).getFocusedDescendantFor(comp); + } + + public boolean dispatch(KeyEvent e) { + return IdeFocusManager.getInstance(myProject).dispatch(e); + } + + public void suspendKeyProcessingUntil(@NotNull ActionCallback done) { + IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(done); + } + + public boolean isFocusBeingTransferred() { + return IdeFocusManager.getInstance(myProject).isFocusBeingTransferred(); + } + + public Expirable getTimestamp(boolean trackOnlyForcedCommands) { + return IdeFocusManager.getInstance(myProject).getTimestamp(trackOnlyForcedCommands); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java index 817866c30025..ecbbe84483f2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java @@ -1,730 +1,730 @@ -/* - * 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.wm.impl; - -import com.intellij.Patches; -import com.intellij.ide.DataManager; -import com.intellij.ide.IdeEventQueue; -import com.intellij.ide.impl.DataManagerImpl; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ex.ApplicationInfoEx; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.NamedJDOMExternalizable; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.WindowManagerListener; -import com.intellij.openapi.wm.ex.StatusBarEx; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.util.Alarm; -import com.intellij.util.ArrayUtil; -import com.intellij.util.EventDispatcher; -import com.intellij.util.ui.UIUtil; -import com.sun.jna.examples.WindowUtils; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ComponentEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -/** - * @author Anton Katilin - * @author Vladimir Kondratyev - */ - -public final class WindowManagerImpl extends WindowManagerEx implements ApplicationComponent, NamedJDOMExternalizable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.WindowManagerImpl"); - private static boolean ourAlphaModeLibraryLoaded; - @NonNls private static final String FOCUSED_WINDOW_PROPERTY_NAME = "focusedWindow"; - @NonNls private static final String X_ATTR = "x"; - @NonNls private static final String FRAME_ELEMENT = "frame"; - @NonNls private static final String Y_ATTR = "y"; - @NonNls private static final String WIDTH_ATTR = "width"; - @NonNls private static final String HEIGHT_ATTR = "height"; - @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state"; - private Boolean myAlphaModeSupported = null; - - private final EventDispatcher myEventDispatcher = EventDispatcher.create(WindowManagerListener.class); - - static { - initialize(); - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - private static void initialize() { - try { - System.loadLibrary("jawt"); - ourAlphaModeLibraryLoaded = true; - } - catch (Throwable exc) { - ourAlphaModeLibraryLoaded = false; - } - } - - /** - * Union of bounds of all available default screen devices. - */ - private final Rectangle myScreenBounds; - - private final CommandProcessor myCommandProcessor; - private final WindowWatcher myWindowWatcher; - /** - * That is the default layout. - */ - private final DesktopLayout myLayout; - - private final HashMap myProject2Frame; - - private final HashMap> myDialogsToDispose; - - /** - * This members is needed to read frame's bounds from XML. - * myFrameBounds can be null. - */ - private Rectangle myFrameBounds; - private int myFrameExtendedState; - private final WindowAdapter myActivationListener; - private final ApplicationInfoEx myApplicationInfoEx; - private final DataManager myDataManager; - private final ActionManagerEx myActionManager; - private final UISettings myUiSettings; - - /** - * invoked by reflection - * @param dataManager - * @param applicationInfoEx - * @param actionManager - * @param uiSettings - */ - public WindowManagerImpl(DataManager dataManager, - ApplicationInfoEx applicationInfoEx, - ActionManagerEx actionManager, - UISettings uiSettings) { - myApplicationInfoEx = applicationInfoEx; - myDataManager = dataManager; - myActionManager = actionManager; - myUiSettings = uiSettings; - if (myDataManager instanceof DataManagerImpl) { - ((DataManagerImpl)myDataManager).setWindowManager(this); - } - - myCommandProcessor = new CommandProcessor(); - myWindowWatcher = new WindowWatcher(); - final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, myWindowWatcher); - if (Patches.SUN_BUG_ID_4218084) { - keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, new SUN_BUG_ID_4218084_Patch()); - } - myLayout = new DesktopLayout(); - myProject2Frame = new HashMap(); - myDialogsToDispose = new HashMap>(); - myFrameExtendedState = Frame.NORMAL; - - // Calculate screen bounds. - - Rectangle screenBounds = new Rectangle(); - if (!ApplicationManager.getApplication().isHeadlessEnvironment()) { - final GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); - final GraphicsDevice[] devices = env.getScreenDevices(); - for (final GraphicsDevice device : devices) { - screenBounds = screenBounds.union(device.getDefaultConfiguration().getBounds()); - } - } - myScreenBounds = screenBounds; - - myActivationListener = new WindowAdapter() { - public void windowActivated(WindowEvent e) { - Window activeWindow = e.getWindow(); - if (activeWindow instanceof IdeFrameImpl) { // must be - proceedDialogDisposalQueue(((IdeFrameImpl)activeWindow).getProject()); - } - } - }; - } - - public void showFrame(final String[] args) { - IdeEventQueue.getInstance().setWindowManager(this); - final IdeFrameImpl frame = new IdeFrameImpl(myApplicationInfoEx, myActionManager, myUiSettings, myDataManager, - ApplicationManager.getApplication(), args); - myProject2Frame.put(null, frame); - if (myFrameBounds != null) { - frame.setBounds(myFrameBounds); - } - frame.setVisible(true); - frame.setExtendedState(myFrameExtendedState); - } - - public IdeFrameImpl[] getAllFrames() { - final Collection ideFrames = myProject2Frame.values(); - return ideFrames.toArray(new IdeFrameImpl[ideFrames.size()]); - } - - @Override - public void addListener(final WindowManagerListener listener) { - myEventDispatcher.addListener(listener); - } - - public void removeListener(final WindowManagerListener listener) { - myEventDispatcher.removeListener(listener); - } - - public final Rectangle getScreenBounds() { - return myScreenBounds; - } - - @Override - public Rectangle getScreenBounds(@NotNull Project project) { - final GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); - final Point onScreen = getFrame(project).getLocationOnScreen(); - final GraphicsDevice[] devices = environment.getScreenDevices(); - for (final GraphicsDevice device : devices) { - final Rectangle bounds = device.getDefaultConfiguration().getBounds(); - if (bounds.contains(onScreen)) { - return bounds; - } - } - - return null; - - } - - public final boolean isInsideScreenBounds(final int x, final int y, final int width) { - return - x >= myScreenBounds.x + 50 - width && - y >= myScreenBounds.y - 50 && - x <= myScreenBounds.x + myScreenBounds.width - 50 && - y <= myScreenBounds.y + myScreenBounds.height - 50; - } - - public final boolean isInsideScreenBounds(final int x, final int y) { - return myScreenBounds.contains(x, y); - } - - public final boolean isAlphaModeSupported() { - if (myAlphaModeSupported == null) { - myAlphaModeSupported = calcAlphaModelSupported(); - } - return myAlphaModeSupported.booleanValue(); - } - - private static boolean calcAlphaModelSupported() { - if (AWTUtilitiesWrapper.isTranslucencyAPISupported()) { - return AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT); - } - try { - return WindowUtils.isWindowAlphaSupported(); - } - catch (Throwable e) { - return false; - } - } - - public final void setAlphaModeRatio(final Window window, final float ratio) { - if (!window.isDisplayable() || !window.isShowing()) { - throw new IllegalArgumentException("window must be displayable and showing. window=" + window); - } - if (ratio < 0.0f || ratio > 1.0f) { - throw new IllegalArgumentException("ratio must be in [0..1] range. ratio=" + ratio); - } - if (!isAlphaModeSupported() || !isAlphaModeEnabled(window)) { - return; - } - - - setAlphaMode(window, ratio); - } - - private static void setAlphaMode(Window window, float ratio) { - try { - if (SystemInfo.isMacOSLeopard) { - if (window instanceof JWindow) { - ((JWindow)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio); - } else if (window instanceof JDialog) { - ((JDialog)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio); - } else if (window instanceof JFrame) { - ((JFrame)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio); - } - } - else if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT)) { - AWTUtilitiesWrapper.setWindowOpacity(window, 1.0f - ratio); - } - else { - WindowUtils.setWindowAlpha(window, 1.0f - ratio); - } - } - catch (Throwable e) { - LOG.debug(e); - } - } - - public void setWindowMask(final Window window, final Shape mask) { - try { - if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.PERPIXEL_TRANSPARENT)) { - AWTUtilitiesWrapper.setWindowShape(window, mask); - } - else { - WindowUtils.setWindowMask(window, mask); - } - } - catch (Throwable e) { - LOG.debug(e); - } - } - - @Override - public void setWindowShadow(Window window, WindowShadowMode mode) { - if (window instanceof JWindow) { - JRootPane root = ((JWindow)window).getRootPane(); - - root.putClientProperty("Window.shadow", mode == WindowShadowMode.DISABLED ? Boolean.FALSE : Boolean.TRUE); - root.putClientProperty("Window.style", mode == WindowShadowMode.SMALL ? "small" : null); - } - } - - public void resetWindow(final Window window) { - try { - if (!isAlphaModeSupported()) return; - - WindowUtils.setWindowMask(window, (Shape)null); - setAlphaMode(window, 0f); - setWindowShadow(window, WindowShadowMode.NORMAL); - } - catch (Throwable e) { - LOG.debug(e); - } - } - - public final boolean isAlphaModeEnabled(final Window window) { - if (!window.isDisplayable() || !window.isShowing()) { - throw new IllegalArgumentException("window must be displayable and showing. window=" + window); - } - return isAlphaModeSupported(); - } - - public final void setAlphaModeEnabled(final Window window, final boolean state) { - if (!window.isDisplayable() || !window.isShowing()) { - throw new IllegalArgumentException("window must be displayable and showing. window=" + window); - } - } - - public void hideDialog(JDialog dialog, Project project) { - if (project == null) { - dialog.dispose(); - } - else { - IdeFrameImpl frame = getFrame(project); - if (frame.isActive()) { - dialog.dispose(); - } - else { - queueForDisposal(dialog, project); - dialog.setVisible(false); - } - } - } - - @Override - public void adjustContainerWindow(Component c, Dimension oldSize, Dimension newSize) { - if (c == null) return; - - Window wnd = SwingUtilities.getWindowAncestor(c); - - if (wnd instanceof JWindow) { - JBPopup popup = (JBPopup)((JWindow)wnd).getRootPane().getClientProperty(JBPopup.KEY); - if (popup != null) { - if (oldSize.height < newSize.height) { - Dimension size = popup.getSize(); - size.height += (newSize.height - oldSize.height); - popup.setSize(size); - popup.moveToFitScreen(); - } - } - } - } - - public final void disposeComponent() {} - - public final void initComponent() { - } - - public final void doNotSuggestAsParent(final Window window) { - myWindowWatcher.doNotSuggestAsParent(window); - } - - public final void dispatchComponentEvent(final ComponentEvent e) { - myWindowWatcher.dispatchComponentEvent(e); - } - - public final Window suggestParentWindow(final Project project) { - return myWindowWatcher.suggestParentWindow(project); - } - - public final StatusBar getStatusBar(final Project project) { - if (!myProject2Frame.containsKey(project)) { - return null; - } - final IdeFrameImpl frame = getFrame(project); - LOG.assertTrue(frame != null); - return frame.getStatusBar(); - } - - @Override - public StatusBar getStatusBar(@NotNull Component c) { - Component parent = UIUtil.findUltimateParent(c); - if (parent instanceof IdeFrame) { - return ((IdeFrame)parent).getStatusBar().findChild(c); - } - - IdeFrame frame = findFrameFor(null); - if (frame != null) { - return frame.getStatusBar().findChild(c); - } - - assert false : "Cannot find status bar for " + c; - - return null; - } - - public IdeFrame findFrameFor(@Nullable final Project project) { - IdeFrameImpl frame = null; - if (project != null) { - frame = getFrame(project); - } else { - Container eachParent = getMostRecentFocusedWindow(); - while(eachParent != null) { - if (eachParent instanceof IdeFrameImpl) { - frame = (IdeFrameImpl)eachParent; - break; - } - eachParent = eachParent.getParent(); - } - - if (frame == null) { - frame = tryToFindTheOnlyFrame(); - } - } - - LOG.assertTrue(frame != null, "Project: " + project); - - return frame; - } - - private static IdeFrameImpl tryToFindTheOnlyFrame() { - IdeFrameImpl candidate = null; - final Frame[] all = Frame.getFrames(); - for (Frame each : all) { - if (each instanceof IdeFrameImpl) { - if (candidate == null) { - candidate = (IdeFrameImpl)each; - } else { - candidate = null; - break; - } - } - } - return candidate; - } - - public final IdeFrameImpl getFrame(final Project project) { - // no assert! otherwise WindowWatcher.suggestParentWindow fails for default project - //LOG.assertTrue(myProject2Frame.containsKey(project)); - return myProject2Frame.get(project); - } - - public IdeFrame getIdeFrame(final Project project) { - if (project != null) { - return getFrame(project); - } - final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); - final Component parent = UIUtil.findUltimateParent(window); - if (parent instanceof IdeFrame) return (IdeFrame)parent; - - final Frame[] frames = Frame.getFrames(); - for (Frame each : frames) { - if (each instanceof IdeFrame) { - return (IdeFrame)each; - } - } - - return null; - } - - public final IdeFrameImpl allocateFrame(final Project project) { - LOG.assertTrue(!myProject2Frame.containsKey(project)); - - final IdeFrameImpl frame; - if (myProject2Frame.containsKey(null)) { - frame = myProject2Frame.get(null); - myProject2Frame.remove(null); - myProject2Frame.put(project, frame); - frame.setProject(project); - } - else { - frame = new IdeFrameImpl((ApplicationInfoEx)ApplicationInfo.getInstance(), ActionManagerEx.getInstanceEx(), UISettings.getInstance(), - DataManager.getInstance(), ApplicationManager.getApplication(), ArrayUtil.EMPTY_STRING_ARRAY); - if (myFrameBounds != null) { - frame.setBounds(myFrameBounds); - } - frame.setProject(project); - myProject2Frame.put(project, frame); - frame.setVisible(true); - } - - frame.addWindowListener(myActivationListener); - - myEventDispatcher.getMulticaster().frameCreated(frame); - - return frame; - } - - private void proceedDialogDisposalQueue(Project project) { - Set dialogs = myDialogsToDispose.get(project); - if (dialogs == null) return; - for (JDialog dialog : dialogs) { - dialog.dispose(); - } - myDialogsToDispose.put(project, null); - } - - private void queueForDisposal(JDialog dialog, Project project) { - Set dialogs = myDialogsToDispose.get(project); - if (dialogs == null) { - dialogs = new HashSet(); - myDialogsToDispose.put(project, dialogs); - } - dialogs.add(dialog); - } - - public final void releaseFrame(final IdeFrameImpl frame) { - - myEventDispatcher.getMulticaster().beforeFrameReleased(frame); - - final Project project = frame.getProject(); - LOG.assertTrue(project != null); - - frame.removeWindowListener(myActivationListener); - proceedDialogDisposalQueue(project); - - frame.setProject(null); - frame.setTitle(null); - frame.setFileTitle(null, null); - - myProject2Frame.remove(project); - if (myProject2Frame.isEmpty()) { - myProject2Frame.put(null, frame); - } - else { - Disposer.dispose((StatusBarEx) frame.getStatusBar()); - frame.dispose(); - } - } - - public final Window getMostRecentFocusedWindow() { - return myWindowWatcher.getFocusedWindow(); - } - - public final Component getFocusedComponent(@NotNull final Window window) { - return myWindowWatcher.getFocusedComponent(window); - } - - public final Component getFocusedComponent(final Project project) { - return myWindowWatcher.getFocusedComponent(project); - } - - /** - * Private part - */ - public final CommandProcessor getCommandProcessor() { - return myCommandProcessor; - } - - public final String getExternalFileName() { - return "window.manager"; - } - - public final void readExternal(final Element element) { - final Element frameElement = element.getChild(FRAME_ELEMENT); - if (frameElement != null) { - myFrameBounds = loadFrameBounds(frameElement); - try { - myFrameExtendedState = Integer.parseInt(frameElement.getAttributeValue(EXTENDED_STATE_ATTR)); - if ((myFrameExtendedState & Frame.ICONIFIED) > 0) { - myFrameExtendedState = Frame.NORMAL; - } - } - catch (NumberFormatException ignored) { - myFrameExtendedState = Frame.NORMAL; - } - } - - final Element desktopElement = element.getChild(DesktopLayout.TAG); - if (desktopElement != null) { - myLayout.readExternal(desktopElement); - } - } - - private static Rectangle loadFrameBounds(final Element frameElement) { - Rectangle bounds = new Rectangle(); - try { - bounds.x = Integer.parseInt(frameElement.getAttributeValue(X_ATTR)); - } - catch (NumberFormatException ignored) { - return null; - } - try { - bounds.y = Integer.parseInt(frameElement.getAttributeValue(Y_ATTR)); - } - catch (NumberFormatException ignored) { - return null; - } - try { - bounds.width = Integer.parseInt(frameElement.getAttributeValue(WIDTH_ATTR)); - } - catch (NumberFormatException ignored) { - return null; - } - try { - bounds.height = Integer.parseInt(frameElement.getAttributeValue(HEIGHT_ATTR)); - } - catch (NumberFormatException ignored) { - return null; - } - return bounds; - } - - public final void writeExternal(final Element element) { - // Save frame bounds - final Element frameElement = new Element(FRAME_ELEMENT); - element.addContent(frameElement); - final Project[] projects = ProjectManager.getInstance().getOpenProjects(); - final Project project; - if (projects.length > 0) { - project = projects[projects.length - 1]; - } - else { - project = null; - } - - final IdeFrameImpl frame = getFrame(project); - if (frame != null) { - final Rectangle rectangle = frame.getBounds(); - frameElement.setAttribute(X_ATTR, Integer.toString(rectangle.x)); - frameElement.setAttribute(Y_ATTR, Integer.toString(rectangle.y)); - frameElement.setAttribute(WIDTH_ATTR, Integer.toString(rectangle.width)); - frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(rectangle.height)); - frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(frame.getExtendedState())); - - // Save default layout - final Element layoutElement = new Element(DesktopLayout.TAG); - element.addContent(layoutElement); - myLayout.writeExternal(layoutElement); - } - } - - public final DesktopLayout getLayout() { - return myLayout; - } - - public final void setLayout(final DesktopLayout layout) { - myLayout.copyFrom(layout); - } - - @NotNull - public final String getComponentName() { - return "WindowManager"; - } - - /** - * We cannot clear selected menu path just by changing of focused window. Under Windows LAF - * focused window changes sporadically when user clickes on menu item or submenu. The problem - * is that all popups under Windows LAF always has native window ancestor. This window isn't - * focusable but by mouse click focused window changes in this manner: - * InitialFocusedWindow->null - * null->InitialFocusedWindow - * To fix this problem we use alarm to accumulate such focus events. - */ - private static final class SUN_BUG_ID_4218084_Patch implements PropertyChangeListener { - private final Alarm myAlarm; - private Window myInitialFocusedWindow; - private Window myLastFocusedWindow; - private final Runnable myClearSelectedPathRunnable; - - public SUN_BUG_ID_4218084_Patch() { - myAlarm = new Alarm(); - myClearSelectedPathRunnable = new Runnable() { - public void run() { - if (myInitialFocusedWindow != myLastFocusedWindow) { - MenuSelectionManager.defaultManager().clearSelectedPath(); - } - } - }; - } - - public void propertyChange(final PropertyChangeEvent e) { - if (myAlarm.getActiveRequestCount() == 0) { - myInitialFocusedWindow = (Window)e.getOldValue(); - final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath(); - if (selectedPath.length == 0) { // there is no visible popup - return; - } - Component firstComponent = null; - for (final MenuElement menuElement : selectedPath) { - final Component component = menuElement.getComponent(); - if (component instanceof JMenuBar) { - firstComponent = component; - break; - } else if (component instanceof JPopupMenu) { - firstComponent = ((JPopupMenu) component).getInvoker(); - break; - } - } - if (firstComponent == null) { - return; - } - final Window window = SwingUtilities.getWindowAncestor(firstComponent); - if (window != myInitialFocusedWindow) { // focused window doesn't have popup - return; - } - } - myLastFocusedWindow = (Window)e.getNewValue(); - myAlarm.cancelAllRequests(); - myAlarm.addRequest(myClearSelectedPathRunnable, 150); - } - } - - public WindowWatcher getWindowWatcher() { - return myWindowWatcher; - } -} +/* + * 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.wm.impl; + +import com.intellij.Patches; +import com.intellij.ide.DataManager; +import com.intellij.ide.IdeEventQueue; +import com.intellij.ide.impl.DataManagerImpl; +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.application.ApplicationInfo; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationInfoEx; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.NamedJDOMExternalizable; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.WindowManagerListener; +import com.intellij.openapi.wm.ex.StatusBarEx; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import com.intellij.util.EventDispatcher; +import com.intellij.util.ui.UIUtil; +import com.sun.jna.examples.WindowUtils; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Anton Katilin + * @author Vladimir Kondratyev + */ + +public final class WindowManagerImpl extends WindowManagerEx implements ApplicationComponent, NamedJDOMExternalizable { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.WindowManagerImpl"); + private static boolean ourAlphaModeLibraryLoaded; + @NonNls private static final String FOCUSED_WINDOW_PROPERTY_NAME = "focusedWindow"; + @NonNls private static final String X_ATTR = "x"; + @NonNls private static final String FRAME_ELEMENT = "frame"; + @NonNls private static final String Y_ATTR = "y"; + @NonNls private static final String WIDTH_ATTR = "width"; + @NonNls private static final String HEIGHT_ATTR = "height"; + @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state"; + private Boolean myAlphaModeSupported = null; + + private final EventDispatcher myEventDispatcher = EventDispatcher.create(WindowManagerListener.class); + + static { + initialize(); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static void initialize() { + try { + System.loadLibrary("jawt"); + ourAlphaModeLibraryLoaded = true; + } + catch (Throwable exc) { + ourAlphaModeLibraryLoaded = false; + } + } + + /** + * Union of bounds of all available default screen devices. + */ + private final Rectangle myScreenBounds; + + private final CommandProcessor myCommandProcessor; + private final WindowWatcher myWindowWatcher; + /** + * That is the default layout. + */ + private final DesktopLayout myLayout; + + private final HashMap myProject2Frame; + + private final HashMap> myDialogsToDispose; + + /** + * This members is needed to read frame's bounds from XML. + * myFrameBounds can be null. + */ + private Rectangle myFrameBounds; + private int myFrameExtendedState; + private final WindowAdapter myActivationListener; + private final ApplicationInfoEx myApplicationInfoEx; + private final DataManager myDataManager; + private final ActionManagerEx myActionManager; + private final UISettings myUiSettings; + + /** + * invoked by reflection + * @param dataManager + * @param applicationInfoEx + * @param actionManager + * @param uiSettings + */ + public WindowManagerImpl(DataManager dataManager, + ApplicationInfoEx applicationInfoEx, + ActionManagerEx actionManager, + UISettings uiSettings) { + myApplicationInfoEx = applicationInfoEx; + myDataManager = dataManager; + myActionManager = actionManager; + myUiSettings = uiSettings; + if (myDataManager instanceof DataManagerImpl) { + ((DataManagerImpl)myDataManager).setWindowManager(this); + } + + myCommandProcessor = new CommandProcessor(); + myWindowWatcher = new WindowWatcher(); + final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, myWindowWatcher); + if (Patches.SUN_BUG_ID_4218084) { + keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, new SUN_BUG_ID_4218084_Patch()); + } + myLayout = new DesktopLayout(); + myProject2Frame = new HashMap(); + myDialogsToDispose = new HashMap>(); + myFrameExtendedState = Frame.NORMAL; + + // Calculate screen bounds. + + Rectangle screenBounds = new Rectangle(); + if (!ApplicationManager.getApplication().isHeadlessEnvironment()) { + final GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + final GraphicsDevice[] devices = env.getScreenDevices(); + for (final GraphicsDevice device : devices) { + screenBounds = screenBounds.union(device.getDefaultConfiguration().getBounds()); + } + } + myScreenBounds = screenBounds; + + myActivationListener = new WindowAdapter() { + public void windowActivated(WindowEvent e) { + Window activeWindow = e.getWindow(); + if (activeWindow instanceof IdeFrameImpl) { // must be + proceedDialogDisposalQueue(((IdeFrameImpl)activeWindow).getProject()); + } + } + }; + } + + public void showFrame(final String[] args) { + IdeEventQueue.getInstance().setWindowManager(this); + final IdeFrameImpl frame = new IdeFrameImpl(myApplicationInfoEx, myActionManager, myUiSettings, myDataManager, + ApplicationManager.getApplication(), args); + myProject2Frame.put(null, frame); + if (myFrameBounds != null) { + frame.setBounds(myFrameBounds); + } + frame.setVisible(true); + frame.setExtendedState(myFrameExtendedState); + } + + public IdeFrameImpl[] getAllFrames() { + final Collection ideFrames = myProject2Frame.values(); + return ideFrames.toArray(new IdeFrameImpl[ideFrames.size()]); + } + + @Override + public void addListener(final WindowManagerListener listener) { + myEventDispatcher.addListener(listener); + } + + public void removeListener(final WindowManagerListener listener) { + myEventDispatcher.removeListener(listener); + } + + public final Rectangle getScreenBounds() { + return myScreenBounds; + } + + @Override + public Rectangle getScreenBounds(@NotNull Project project) { + final GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + final Point onScreen = getFrame(project).getLocationOnScreen(); + final GraphicsDevice[] devices = environment.getScreenDevices(); + for (final GraphicsDevice device : devices) { + final Rectangle bounds = device.getDefaultConfiguration().getBounds(); + if (bounds.contains(onScreen)) { + return bounds; + } + } + + return null; + + } + + public final boolean isInsideScreenBounds(final int x, final int y, final int width) { + return + x >= myScreenBounds.x + 50 - width && + y >= myScreenBounds.y - 50 && + x <= myScreenBounds.x + myScreenBounds.width - 50 && + y <= myScreenBounds.y + myScreenBounds.height - 50; + } + + public final boolean isInsideScreenBounds(final int x, final int y) { + return myScreenBounds.contains(x, y); + } + + public final boolean isAlphaModeSupported() { + if (myAlphaModeSupported == null) { + myAlphaModeSupported = calcAlphaModelSupported(); + } + return myAlphaModeSupported.booleanValue(); + } + + private static boolean calcAlphaModelSupported() { + if (AWTUtilitiesWrapper.isTranslucencyAPISupported()) { + return AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT); + } + try { + return WindowUtils.isWindowAlphaSupported(); + } + catch (Throwable e) { + return false; + } + } + + public final void setAlphaModeRatio(final Window window, final float ratio) { + if (!window.isDisplayable() || !window.isShowing()) { + throw new IllegalArgumentException("window must be displayable and showing. window=" + window); + } + if (ratio < 0.0f || ratio > 1.0f) { + throw new IllegalArgumentException("ratio must be in [0..1] range. ratio=" + ratio); + } + if (!isAlphaModeSupported() || !isAlphaModeEnabled(window)) { + return; + } + + + setAlphaMode(window, ratio); + } + + private static void setAlphaMode(Window window, float ratio) { + try { + if (SystemInfo.isMacOSLeopard) { + if (window instanceof JWindow) { + ((JWindow)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio); + } else if (window instanceof JDialog) { + ((JDialog)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio); + } else if (window instanceof JFrame) { + ((JFrame)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio); + } + } + else if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT)) { + AWTUtilitiesWrapper.setWindowOpacity(window, 1.0f - ratio); + } + else { + WindowUtils.setWindowAlpha(window, 1.0f - ratio); + } + } + catch (Throwable e) { + LOG.debug(e); + } + } + + public void setWindowMask(final Window window, final Shape mask) { + try { + if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.PERPIXEL_TRANSPARENT)) { + AWTUtilitiesWrapper.setWindowShape(window, mask); + } + else { + WindowUtils.setWindowMask(window, mask); + } + } + catch (Throwable e) { + LOG.debug(e); + } + } + + @Override + public void setWindowShadow(Window window, WindowShadowMode mode) { + if (window instanceof JWindow) { + JRootPane root = ((JWindow)window).getRootPane(); + + root.putClientProperty("Window.shadow", mode == WindowShadowMode.DISABLED ? Boolean.FALSE : Boolean.TRUE); + root.putClientProperty("Window.style", mode == WindowShadowMode.SMALL ? "small" : null); + } + } + + public void resetWindow(final Window window) { + try { + if (!isAlphaModeSupported()) return; + + WindowUtils.setWindowMask(window, (Shape)null); + setAlphaMode(window, 0f); + setWindowShadow(window, WindowShadowMode.NORMAL); + } + catch (Throwable e) { + LOG.debug(e); + } + } + + public final boolean isAlphaModeEnabled(final Window window) { + if (!window.isDisplayable() || !window.isShowing()) { + throw new IllegalArgumentException("window must be displayable and showing. window=" + window); + } + return isAlphaModeSupported(); + } + + public final void setAlphaModeEnabled(final Window window, final boolean state) { + if (!window.isDisplayable() || !window.isShowing()) { + throw new IllegalArgumentException("window must be displayable and showing. window=" + window); + } + } + + public void hideDialog(JDialog dialog, Project project) { + if (project == null) { + dialog.dispose(); + } + else { + IdeFrameImpl frame = getFrame(project); + if (frame.isActive()) { + dialog.dispose(); + } + else { + queueForDisposal(dialog, project); + dialog.setVisible(false); + } + } + } + + @Override + public void adjustContainerWindow(Component c, Dimension oldSize, Dimension newSize) { + if (c == null) return; + + Window wnd = SwingUtilities.getWindowAncestor(c); + + if (wnd instanceof JWindow) { + JBPopup popup = (JBPopup)((JWindow)wnd).getRootPane().getClientProperty(JBPopup.KEY); + if (popup != null) { + if (oldSize.height < newSize.height) { + Dimension size = popup.getSize(); + size.height += (newSize.height - oldSize.height); + popup.setSize(size); + popup.moveToFitScreen(); + } + } + } + } + + public final void disposeComponent() {} + + public final void initComponent() { + } + + public final void doNotSuggestAsParent(final Window window) { + myWindowWatcher.doNotSuggestAsParent(window); + } + + public final void dispatchComponentEvent(final ComponentEvent e) { + myWindowWatcher.dispatchComponentEvent(e); + } + + public final Window suggestParentWindow(final Project project) { + return myWindowWatcher.suggestParentWindow(project); + } + + public final StatusBar getStatusBar(final Project project) { + if (!myProject2Frame.containsKey(project)) { + return null; + } + final IdeFrameImpl frame = getFrame(project); + LOG.assertTrue(frame != null); + return frame.getStatusBar(); + } + + @Override + public StatusBar getStatusBar(@NotNull Component c) { + Component parent = UIUtil.findUltimateParent(c); + if (parent instanceof IdeFrame) { + return ((IdeFrame)parent).getStatusBar().findChild(c); + } + + IdeFrame frame = findFrameFor(null); + if (frame != null) { + return frame.getStatusBar().findChild(c); + } + + assert false : "Cannot find status bar for " + c; + + return null; + } + + public IdeFrame findFrameFor(@Nullable final Project project) { + IdeFrameImpl frame = null; + if (project != null) { + frame = getFrame(project); + } else { + Container eachParent = getMostRecentFocusedWindow(); + while(eachParent != null) { + if (eachParent instanceof IdeFrameImpl) { + frame = (IdeFrameImpl)eachParent; + break; + } + eachParent = eachParent.getParent(); + } + + if (frame == null) { + frame = tryToFindTheOnlyFrame(); + } + } + + LOG.assertTrue(frame != null, "Project: " + project); + + return frame; + } + + private static IdeFrameImpl tryToFindTheOnlyFrame() { + IdeFrameImpl candidate = null; + final Frame[] all = Frame.getFrames(); + for (Frame each : all) { + if (each instanceof IdeFrameImpl) { + if (candidate == null) { + candidate = (IdeFrameImpl)each; + } else { + candidate = null; + break; + } + } + } + return candidate; + } + + public final IdeFrameImpl getFrame(final Project project) { + // no assert! otherwise WindowWatcher.suggestParentWindow fails for default project + //LOG.assertTrue(myProject2Frame.containsKey(project)); + return myProject2Frame.get(project); + } + + public IdeFrame getIdeFrame(final Project project) { + if (project != null) { + return getFrame(project); + } + final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); + final Component parent = UIUtil.findUltimateParent(window); + if (parent instanceof IdeFrame) return (IdeFrame)parent; + + final Frame[] frames = Frame.getFrames(); + for (Frame each : frames) { + if (each instanceof IdeFrame) { + return (IdeFrame)each; + } + } + + return null; + } + + public final IdeFrameImpl allocateFrame(final Project project) { + LOG.assertTrue(!myProject2Frame.containsKey(project)); + + final IdeFrameImpl frame; + if (myProject2Frame.containsKey(null)) { + frame = myProject2Frame.get(null); + myProject2Frame.remove(null); + myProject2Frame.put(project, frame); + frame.setProject(project); + } + else { + frame = new IdeFrameImpl((ApplicationInfoEx)ApplicationInfo.getInstance(), ActionManagerEx.getInstanceEx(), UISettings.getInstance(), + DataManager.getInstance(), ApplicationManager.getApplication(), ArrayUtil.EMPTY_STRING_ARRAY); + if (myFrameBounds != null) { + frame.setBounds(myFrameBounds); + } + frame.setProject(project); + myProject2Frame.put(project, frame); + frame.setVisible(true); + } + + frame.addWindowListener(myActivationListener); + + myEventDispatcher.getMulticaster().frameCreated(frame); + + return frame; + } + + private void proceedDialogDisposalQueue(Project project) { + Set dialogs = myDialogsToDispose.get(project); + if (dialogs == null) return; + for (JDialog dialog : dialogs) { + dialog.dispose(); + } + myDialogsToDispose.put(project, null); + } + + private void queueForDisposal(JDialog dialog, Project project) { + Set dialogs = myDialogsToDispose.get(project); + if (dialogs == null) { + dialogs = new HashSet(); + myDialogsToDispose.put(project, dialogs); + } + dialogs.add(dialog); + } + + public final void releaseFrame(final IdeFrameImpl frame) { + + myEventDispatcher.getMulticaster().beforeFrameReleased(frame); + + final Project project = frame.getProject(); + LOG.assertTrue(project != null); + + frame.removeWindowListener(myActivationListener); + proceedDialogDisposalQueue(project); + + frame.setProject(null); + frame.setTitle(null); + frame.setFileTitle(null, null); + + myProject2Frame.remove(project); + if (myProject2Frame.isEmpty()) { + myProject2Frame.put(null, frame); + } + else { + Disposer.dispose((StatusBarEx) frame.getStatusBar()); + frame.dispose(); + } + } + + public final Window getMostRecentFocusedWindow() { + return myWindowWatcher.getFocusedWindow(); + } + + public final Component getFocusedComponent(@NotNull final Window window) { + return myWindowWatcher.getFocusedComponent(window); + } + + public final Component getFocusedComponent(final Project project) { + return myWindowWatcher.getFocusedComponent(project); + } + + /** + * Private part + */ + public final CommandProcessor getCommandProcessor() { + return myCommandProcessor; + } + + public final String getExternalFileName() { + return "window.manager"; + } + + public final void readExternal(final Element element) { + final Element frameElement = element.getChild(FRAME_ELEMENT); + if (frameElement != null) { + myFrameBounds = loadFrameBounds(frameElement); + try { + myFrameExtendedState = Integer.parseInt(frameElement.getAttributeValue(EXTENDED_STATE_ATTR)); + if ((myFrameExtendedState & Frame.ICONIFIED) > 0) { + myFrameExtendedState = Frame.NORMAL; + } + } + catch (NumberFormatException ignored) { + myFrameExtendedState = Frame.NORMAL; + } + } + + final Element desktopElement = element.getChild(DesktopLayout.TAG); + if (desktopElement != null) { + myLayout.readExternal(desktopElement); + } + } + + private static Rectangle loadFrameBounds(final Element frameElement) { + Rectangle bounds = new Rectangle(); + try { + bounds.x = Integer.parseInt(frameElement.getAttributeValue(X_ATTR)); + } + catch (NumberFormatException ignored) { + return null; + } + try { + bounds.y = Integer.parseInt(frameElement.getAttributeValue(Y_ATTR)); + } + catch (NumberFormatException ignored) { + return null; + } + try { + bounds.width = Integer.parseInt(frameElement.getAttributeValue(WIDTH_ATTR)); + } + catch (NumberFormatException ignored) { + return null; + } + try { + bounds.height = Integer.parseInt(frameElement.getAttributeValue(HEIGHT_ATTR)); + } + catch (NumberFormatException ignored) { + return null; + } + return bounds; + } + + public final void writeExternal(final Element element) { + // Save frame bounds + final Element frameElement = new Element(FRAME_ELEMENT); + element.addContent(frameElement); + final Project[] projects = ProjectManager.getInstance().getOpenProjects(); + final Project project; + if (projects.length > 0) { + project = projects[projects.length - 1]; + } + else { + project = null; + } + + final IdeFrameImpl frame = getFrame(project); + if (frame != null) { + final Rectangle rectangle = frame.getBounds(); + frameElement.setAttribute(X_ATTR, Integer.toString(rectangle.x)); + frameElement.setAttribute(Y_ATTR, Integer.toString(rectangle.y)); + frameElement.setAttribute(WIDTH_ATTR, Integer.toString(rectangle.width)); + frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(rectangle.height)); + frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(frame.getExtendedState())); + + // Save default layout + final Element layoutElement = new Element(DesktopLayout.TAG); + element.addContent(layoutElement); + myLayout.writeExternal(layoutElement); + } + } + + public final DesktopLayout getLayout() { + return myLayout; + } + + public final void setLayout(final DesktopLayout layout) { + myLayout.copyFrom(layout); + } + + @NotNull + public final String getComponentName() { + return "WindowManager"; + } + + /** + * We cannot clear selected menu path just by changing of focused window. Under Windows LAF + * focused window changes sporadically when user clickes on menu item or submenu. The problem + * is that all popups under Windows LAF always has native window ancestor. This window isn't + * focusable but by mouse click focused window changes in this manner: + * InitialFocusedWindow->null + * null->InitialFocusedWindow + * To fix this problem we use alarm to accumulate such focus events. + */ + private static final class SUN_BUG_ID_4218084_Patch implements PropertyChangeListener { + private final Alarm myAlarm; + private Window myInitialFocusedWindow; + private Window myLastFocusedWindow; + private final Runnable myClearSelectedPathRunnable; + + public SUN_BUG_ID_4218084_Patch() { + myAlarm = new Alarm(); + myClearSelectedPathRunnable = new Runnable() { + public void run() { + if (myInitialFocusedWindow != myLastFocusedWindow) { + MenuSelectionManager.defaultManager().clearSelectedPath(); + } + } + }; + } + + public void propertyChange(final PropertyChangeEvent e) { + if (myAlarm.getActiveRequestCount() == 0) { + myInitialFocusedWindow = (Window)e.getOldValue(); + final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath(); + if (selectedPath.length == 0) { // there is no visible popup + return; + } + Component firstComponent = null; + for (final MenuElement menuElement : selectedPath) { + final Component component = menuElement.getComponent(); + if (component instanceof JMenuBar) { + firstComponent = component; + break; + } else if (component instanceof JPopupMenu) { + firstComponent = ((JPopupMenu) component).getInvoker(); + break; + } + } + if (firstComponent == null) { + return; + } + final Window window = SwingUtilities.getWindowAncestor(firstComponent); + if (window != myInitialFocusedWindow) { // focused window doesn't have popup + return; + } + } + myLastFocusedWindow = (Window)e.getNewValue(); + myAlarm.cancelAllRequests(); + myAlarm.addRequest(myClearSelectedPathRunnable, 150); + } + } + + public WindowWatcher getWindowWatcher() { + return myWindowWatcher; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EditorBasedWidget.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EditorBasedWidget.java index 971658b99899..df72216fec50 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EditorBasedWidget.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EditorBasedWidget.java @@ -1,101 +1,101 @@ -/* - * 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.wm.impl.status; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.*; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public abstract class EditorBasedWidget extends FileEditorManagerAdapter implements StatusBarWidget { - - protected StatusBar myStatusBar; - private Project myProject; - - private MessageBusConnection myConnection; - - protected EditorBasedWidget(Project project) { - myProject = project; - myConnection = myProject.getMessageBus().connect(); - myConnection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this); - } - - protected EditorBasedWidget() { - } - - @Nullable - protected final Editor getEditor() { - final Project project = getProject(); - if (project != null) { - final FileEditorManager manager = FileEditorManager.getInstance(project); - Editor editor = manager.getSelectedTextEditor(); - if (editor != null && WindowManager.getInstance().getStatusBar(editor.getComponent()) == myStatusBar) { - return editor; - } - } - - return null; - } - - - protected boolean isOurEditor(Editor editor) { - if (editor == null) return false; - return getEditor() == editor; - } - - @Nullable - protected VirtualFile getSelectedFile() { - final Editor editor = getEditor(); - if (editor == null) return null; - Document document = editor.getDocument(); - return FileDocumentManager.getInstance().getFile(document); - } - - - @NotNull - protected final Project getProject() { - if (myProject != null) return myProject; - - if (myStatusBar != null && myStatusBar.getFrame() != null) { - return myStatusBar.getFrame().getProject(); - } else { - assert false : "Cannot find project (unititialized status bar widget?)"; - return null; - } - } - - @Override - public void install(@NotNull StatusBar statusBar) { - myStatusBar = statusBar; - } - - @Override - public void dispose() { - myStatusBar = null; - if (myConnection != null) { - myConnection.disconnect(); - myConnection = null; - } - myProject = null; - } -} +/* + * 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.wm.impl.status; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class EditorBasedWidget extends FileEditorManagerAdapter implements StatusBarWidget { + + protected StatusBar myStatusBar; + private Project myProject; + + private MessageBusConnection myConnection; + + protected EditorBasedWidget(Project project) { + myProject = project; + myConnection = myProject.getMessageBus().connect(); + myConnection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this); + } + + protected EditorBasedWidget() { + } + + @Nullable + protected final Editor getEditor() { + final Project project = getProject(); + if (project != null) { + final FileEditorManager manager = FileEditorManager.getInstance(project); + Editor editor = manager.getSelectedTextEditor(); + if (editor != null && WindowManager.getInstance().getStatusBar(editor.getComponent()) == myStatusBar) { + return editor; + } + } + + return null; + } + + + protected boolean isOurEditor(Editor editor) { + if (editor == null) return false; + return getEditor() == editor; + } + + @Nullable + protected VirtualFile getSelectedFile() { + final Editor editor = getEditor(); + if (editor == null) return null; + Document document = editor.getDocument(); + return FileDocumentManager.getInstance().getFile(document); + } + + + @NotNull + protected final Project getProject() { + if (myProject != null) return myProject; + + if (myStatusBar != null && myStatusBar.getFrame() != null) { + return myStatusBar.getFrame().getProject(); + } else { + assert false : "Cannot find project (unititialized status bar widget?)"; + return null; + } + } + + @Override + public void install(@NotNull StatusBar statusBar) { + myStatusBar = statusBar; + } + + @Override + public void dispose() { + myStatusBar = null; + if (myConnection != null) { + myConnection.disconnect(); + myConnection = null; + } + myProject = null; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java index f88d5783e748..3465cf7c97b5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java @@ -1,126 +1,126 @@ -/* - * 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.wm.impl.status; - -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.actionSystem.impl.SimpleDataContext; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.*; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.ListPopup; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.encoding.ChooseFileEncodingAction; -import com.intellij.openapi.vfs.encoding.EncodingManager; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; -import java.awt.event.MouseEvent; -import java.nio.charset.Charset; - -/** - * @author cdr - */ -public class EncodingPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.MultipleTextValuesPresentation { - private String mySelected; - - public EncodingPanel(@NotNull final Project project) { - super(project); - } - - @Override - public void selectionChanged(FileEditorManagerEvent event) { - update(); - } - - - @Override - public void fileOpened(FileEditorManager source, VirtualFile file) { - update(); - } - - @Override - public StatusBarWidget copy() { - return new EncodingPanel(getProject()); - } - - @NotNull - public String ID() { - return "Encoding"; - } - - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return this; - } - - @NotNull - public String getMaxValue() { - return "windows-1251"; - } - - @NotNull - public ListPopup getPopupStep() { - final DataContext parent = DataManager.getInstance().getDataContext((Component)myStatusBar); - final DataContext dataContext = - SimpleDataContext.getSimpleContext(PlatformDataKeys.VIRTUAL_FILE.getName(), getSelectedFile(), - SimpleDataContext.getSimpleContext(PlatformDataKeys.PROJECT.getName(), getProject(), parent)); - return JBPopupFactory.getInstance().createActionGroupPopup(null, new ChooseFileEncodingAction(getSelectedFile()) { - @Override - protected void chosen(VirtualFile virtualFile, Charset charset) { - if (virtualFile != null) { - EncodingManager.getInstance().setEncoding(virtualFile, charset); - EncodingPanel.this.update(); - } - } - }.createGroup(false), dataContext, false, false, false, null, 30, null); - } - - public String getTooltipText() { - return null; - } - - public Consumer getClickConsumer() { - return null; - } - - private void setSelectedValue(@NotNull final Charset charset) { - mySelected = charset.displayName(); - } - - public String getSelectedValue() { - return mySelected; - } - - private void update() { - final VirtualFile file = getSelectedFile(); - if (file != null) { - Charset charset = ChooseFileEncodingAction.charsetFromContent(file); - if (charset == null) charset = file.getCharset(); - setSelectedValue(charset); - myStatusBar.updateWidget(ID()); - } else { - mySelected = ""; - myStatusBar.updateWidget(ID()); - } - } -} +/* + * 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.wm.impl.status; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.impl.SimpleDataContext; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.encoding.ChooseFileEncodingAction; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.nio.charset.Charset; + +/** + * @author cdr + */ +public class EncodingPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.MultipleTextValuesPresentation { + private String mySelected; + + public EncodingPanel(@NotNull final Project project) { + super(project); + } + + @Override + public void selectionChanged(FileEditorManagerEvent event) { + update(); + } + + + @Override + public void fileOpened(FileEditorManager source, VirtualFile file) { + update(); + } + + @Override + public StatusBarWidget copy() { + return new EncodingPanel(getProject()); + } + + @NotNull + public String ID() { + return "Encoding"; + } + + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return this; + } + + @NotNull + public String getMaxValue() { + return "windows-1251"; + } + + @NotNull + public ListPopup getPopupStep() { + final DataContext parent = DataManager.getInstance().getDataContext((Component)myStatusBar); + final DataContext dataContext = + SimpleDataContext.getSimpleContext(PlatformDataKeys.VIRTUAL_FILE.getName(), getSelectedFile(), + SimpleDataContext.getSimpleContext(PlatformDataKeys.PROJECT.getName(), getProject(), parent)); + return JBPopupFactory.getInstance().createActionGroupPopup(null, new ChooseFileEncodingAction(getSelectedFile()) { + @Override + protected void chosen(VirtualFile virtualFile, Charset charset) { + if (virtualFile != null) { + EncodingManager.getInstance().setEncoding(virtualFile, charset); + EncodingPanel.this.update(); + } + } + }.createGroup(false), dataContext, false, false, false, null, 30, null); + } + + public String getTooltipText() { + return null; + } + + public Consumer getClickConsumer() { + return null; + } + + private void setSelectedValue(@NotNull final Charset charset) { + mySelected = charset.displayName(); + } + + public String getSelectedValue() { + return mySelected; + } + + private void update() { + final VirtualFile file = getSelectedFile(); + if (file != null) { + Charset charset = ChooseFileEncodingAction.charsetFromContent(file); + if (charset == null) charset = file.getCharset(); + setSelectedValue(charset); + myStatusBar.updateWidget(ID()); + } else { + mySelected = ""; + myStatusBar.updateWidget(ID()); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/IdeStatusBarImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/IdeStatusBarImpl.java index e82141ccd15c..8703a993e592 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/IdeStatusBarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/IdeStatusBarImpl.java @@ -1,734 +1,734 @@ -/* - * 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.wm.impl.status; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.TaskInfo; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.popup.BalloonHandler; -import com.intellij.openapi.ui.popup.ListPopup; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.wm.CustomStatusBarWidget; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.openapi.wm.ex.ProgressIndicatorEx; -import com.intellij.openapi.wm.ex.StatusBarEx; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.popup.NotificationPopup; -import com.intellij.util.Consumer; -import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.HyperlinkListener; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.*; -import java.util.List; - -/** - * User: spLeaner - */ -public class IdeStatusBarImpl extends JComponent implements StatusBarEx { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.status.IdeStatusBarImpl"); - - private InfoAndProgressPanel myInfoAndProgressPanel; - private IdeStatusBarImpl myMaster; - private IdeFrame myFrame; - - private enum Position {LEFT, RIGHT, CENTER} - - private static final String uiClassID = "IdeStatusBarUI"; - - private final Map myWidgetMap = new HashMap(); - private final List myOrderedWidgets = new ArrayList(); - - private JPanel myLeftPanel; - private JPanel myRightPanel; - private JPanel myCenterPanel; - - private String myInfo; - - private List myCustomComponentIds = new ArrayList(); - - private Set myChildren = new HashSet(); - - private static class WidgetBean { - JComponent component; - Position position; - StatusBarWidget widget; - String anchor; - - static WidgetBean create(@NotNull final StatusBarWidget widget, @NotNull final Position position, @NotNull final JComponent component, @NotNull String anchor) { - final WidgetBean bean = new WidgetBean(); - bean.widget = widget; - bean.position = position; - bean.component = component; - bean.anchor = anchor; - return bean; - } - } - - @Override - public StatusBar findChild(Component c) { - Component eachParent = c; - IdeFrame frame = null; - while (eachParent != null) { - if (eachParent instanceof IdeFrame) { - frame = (IdeFrame)eachParent; - } - eachParent = eachParent.getParent(); - } - - return frame != null ? frame.getStatusBar() : this; - } - - @Override - public void install(IdeFrame frame) { - myFrame = frame; - } - - private void updateChildren(ChildAction action) { - for (IdeStatusBarImpl child : myChildren) { - action.update(child); - } - } - - interface ChildAction { - void update(IdeStatusBarImpl child); - } - - public StatusBar createChild() { - final IdeStatusBarImpl bar = new IdeStatusBarImpl(this); - myChildren.add(bar); - Disposer.register(bar, new Disposable() { - @Override - public void dispose() { - myChildren.remove(bar); - } - }); - - for (String eachId : myOrderedWidgets) { - WidgetBean eachBean = myWidgetMap.get(eachId); - if (eachBean.widget instanceof StatusBarWidget.Multiframe) { - StatusBarWidget copy = ((StatusBarWidget.Multiframe)eachBean.widget).copy(); - bar.addWidget(copy, eachBean.position); - } - } - - return bar; - } - - @Override - public JComponent getComponent() { - return this; - } - - IdeStatusBarImpl(IdeStatusBarImpl master) { - myMaster = master; - - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(1, 4, 0, SystemInfo.isMac ? 2 : 0)); - - myInfoAndProgressPanel = new InfoAndProgressPanel(master == null); - addWidget(myInfoAndProgressPanel, Position.CENTER); - - setOpaque(true); - updateUI(); - - if (master == null) { - Disposer.register(Disposer.get("ui"), this); - } - } - - - public IdeStatusBarImpl() { - this(null); - } - - public void addWidget(@NotNull final StatusBarWidget widget) { - addWidget(widget, Position.RIGHT, "__AUTODETECT__"); - } - - public void addWidget(@NotNull final StatusBarWidget widget, @NotNull String anchor) { - addWidget(widget, Position.RIGHT, anchor); - } - - private void addWidget(@NotNull final StatusBarWidget widget, @NotNull final Position pos) { - addWidget(widget, pos, "__IGNORED__"); - } - - public void addWidget(@NotNull final StatusBarWidget widget, @NotNull final Disposable parentDisposable) { - addWidget(widget); - Disposer.register(parentDisposable, new Disposable() { - public void dispose() { - removeWidget(widget.ID()); - } - }); - } - - public void addWidget(@NotNull final StatusBarWidget widget, @NotNull String anchor, @NotNull final Disposable parentDisposable) { - addWidget(widget, anchor); - Disposer.register(parentDisposable, new Disposable() { - public void dispose() { - removeWidget(widget.ID()); - } - }); - } - - public void removeCustomIndicationComponents() { - for (final String id : myCustomComponentIds) { - removeWidget(id); - } - - myCustomComponentIds.clear(); - } - - public void addCustomIndicationComponent(@NotNull final JComponent c) { - final String customId = c.getClass().getName() + new Random().nextLong(); - addWidget(new CustomStatusBarWidget() { - @NotNull - public String ID() { - return customId; - } - - @Nullable - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return null; - } - - public void install(@NotNull StatusBar statusBar) { - } - - public void dispose() { - } - - public JComponent getComponent() { - return c; - } - }); - - myCustomComponentIds.add(customId); - } - - public void removeCustomIndicationComponent(@NotNull final JComponent c) { - final Set keySet = myWidgetMap.keySet(); - final String[] keys = keySet.toArray(new String[keySet.size()]); - for (final String key : keys) { - final WidgetBean value = myWidgetMap.get(key); - if (value.widget instanceof CustomStatusBarWidget && value.component == c) { - removeWidget(key); - myCustomComponentIds.remove(key); - } - } - } - - public void dispose() { - final Iterator iterator = myWidgetMap.values().iterator(); - //noinspection WhileLoopReplaceableByForEach - while (iterator.hasNext()) { - Disposer.dispose(iterator.next().widget); - } - - myWidgetMap.clear(); - } - - private void addWidget(@NotNull final StatusBarWidget widget, @NotNull final Position pos, @NotNull final String anchor) { - myOrderedWidgets.add(widget.ID()); - - JPanel panel; - if (pos == Position.RIGHT) { - if (myRightPanel == null) { - myRightPanel = new JPanel(); - myRightPanel.setLayout(new BoxLayout(myRightPanel, BoxLayout.X_AXIS)); - myRightPanel.setOpaque(false); - add(myRightPanel, BorderLayout.EAST); - } - - panel = myRightPanel; - } - else if (pos == Position.LEFT) { - if (myLeftPanel == null) { - myLeftPanel = new JPanel(); - myLeftPanel.setLayout(new BoxLayout(myLeftPanel, BoxLayout.X_AXIS)); - myLeftPanel.setOpaque(false); - add(myLeftPanel, BorderLayout.WEST); - } - - panel = myLeftPanel; - } - else { - if (myCenterPanel == null) { - myCenterPanel = new JPanel(new BorderLayout()); - myCenterPanel.setOpaque(false); - add(myCenterPanel, BorderLayout.CENTER); - } - - panel = myCenterPanel; - } - - final JComponent c = widget instanceof CustomStatusBarWidget ? ((CustomStatusBarWidget)widget).getComponent() : wrap(widget); - if (Position.RIGHT == pos && panel.getComponentCount() > 0) { - String wid; - boolean before; - if (!anchor.equals("__AUTODETECT__")) { - final List parts = StringUtil.split(anchor, " "); - if (parts.size() < 2 || !myWidgetMap.keySet().contains(parts.get(1))) { - wid = "Notifications"; - before = true; - } else { - wid = parts.get(1); - before = "before".equalsIgnoreCase(parts.get(0)); - } - } else { - wid = "Notifications"; - before = true; - } - - for (final String id : myWidgetMap.keySet()) { - if (id.equalsIgnoreCase(wid)) { - final WidgetBean bean = myWidgetMap.get(id); - int i = 0; - for (final Component component : myRightPanel.getComponents()) { - if (component == bean.component) { - if (before) { - panel.add(c, i); - updateBorder(i); - } - else { - final int ndx = i + 1; - panel.add(c, i + 1); - updateBorder(ndx); - } - - installWidget(widget, pos, c, anchor); - return; - } - - i++; - } - } - } - } - - if (Position.LEFT == pos && panel.getComponentCount() == 0) { - c.setBorder(SystemInfo.isMac ? BorderFactory.createEmptyBorder(2, 0, 2, 4) : BorderFactory.createEmptyBorder()); - } - - panel.add(c); - installWidget(widget, pos, c, anchor); - - if (widget instanceof StatusBarWidget.Multiframe) { - final StatusBarWidget.Multiframe mfw = (StatusBarWidget.Multiframe)widget; - updateChildren(new ChildAction() { - @Override - public void update(IdeStatusBarImpl child) { - StatusBarWidget widgetCopy = mfw.copy(); - child.addWidget(widgetCopy, pos, anchor); - } - }); - } - } - - private void updateBorder(final int ndx) { - final JComponent self = (JComponent)myRightPanel.getComponent(ndx); - if (self instanceof IconPresentationWrapper) { - final int prev = ndx - 1; - final int next = ndx + 1; - - final JComponent p = prev >= 0 ? (JComponent)myRightPanel.getComponent(prev) : null; - final JComponent n = next < myRightPanel.getComponentCount() ? (JComponent)myRightPanel.getComponent(next) : null; - - final boolean prevIcon = p instanceof IconPresentationWrapper; - final boolean nextIcon = n instanceof IconPresentationWrapper; - - self.setBorder(prevIcon ? BorderFactory.createEmptyBorder(2, 2, 2, 2) : StatusBarWidget.WidgetBorder.INSTANCE); - if (nextIcon) n.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - } - } - - public void setInfo(@Nullable final String s) { - setInfo(s, null); - } - - @Override - public void setInfo(@Nullable final String s, @Nullable final String requestor) { - UIUtil.invokeLaterIfNeeded(new Runnable() { - public void run() { - if (myInfoAndProgressPanel != null) { - myInfo = myInfoAndProgressPanel.setText(s, requestor); - } - } - }); - } - - public String getInfo() { - return myInfo; - } - - public void addProgress(ProgressIndicatorEx indicator, TaskInfo info) { - myInfoAndProgressPanel.addProgress(indicator, info); - } - - public void setProcessWindowOpen(final boolean open) { - myInfoAndProgressPanel.setProcessWindowOpen(open); - } - - public boolean isProcessWindowOpen() { - return myInfoAndProgressPanel.isProcessWindowOpen(); - } - - public void startRefreshIndication(final String tooltipText) { - myInfoAndProgressPanel.setRefreshToolTipText(tooltipText); - myInfoAndProgressPanel.setRefreshVisible(true); - - updateChildren(new ChildAction() { - @Override - public void update(IdeStatusBarImpl child) { - child.startRefreshIndication(tooltipText); - } - }); - } - - public void stopRefreshIndication() { - myInfoAndProgressPanel.setRefreshVisible(false); - - updateChildren(new ChildAction() { - @Override - public void update(IdeStatusBarImpl child) { - child.stopRefreshIndication(); - } - }); - } - - public BalloonHandler notifyProgressByBalloon(@NotNull MessageType type, @NotNull String htmlBody) { - return notifyProgressByBalloon(type, htmlBody, null, null); - } - - public BalloonHandler notifyProgressByBalloon(@NotNull MessageType type, - @NotNull String htmlBody, - @Nullable Icon icon, - @Nullable HyperlinkListener listener) { - return myInfoAndProgressPanel.notifyByBalloon(type, htmlBody, icon, listener); - } - - public void fireNotificationPopup(@NotNull JComponent content, Color backgroundColor) { - new NotificationPopup(this, content, backgroundColor); - } - - private void installWidget(@NotNull final StatusBarWidget widget, @NotNull final Position pos, @NotNull final JComponent c, String anchor) { - myWidgetMap.put(widget.ID(), WidgetBean.create(widget, pos, c, anchor)); - widget.install(this); - } - - private static JComponent wrap(@NotNull final StatusBarWidget widget) { - final StatusBarWidget.WidgetPresentation presentation = - widget.getPresentation(SystemInfo.isMac ? StatusBarWidget.PlatformType.MAC : StatusBarWidget.PlatformType.DEFAULT); - assert presentation != null : "Presentation should not be null!"; - - JComponent wrapper; - if (presentation instanceof StatusBarWidget.IconPresentation) { - wrapper = new IconPresentationWrapper((StatusBarWidget.IconPresentation)presentation); - } - else if (presentation instanceof StatusBarWidget.TextPresentation) { - wrapper = new TextPresentationWrapper((StatusBarWidget.TextPresentation)presentation); - wrapper.setBorder(StatusBarWidget.WidgetBorder.INSTANCE); - } - else if (presentation instanceof StatusBarWidget.MultipleTextValuesPresentation) { - wrapper = new MultipleTextValuesPresentationWrapper((StatusBarWidget.MultipleTextValuesPresentation)presentation); - wrapper.setBorder(StatusBarWidget.WidgetBorder.INSTANCE); - } - else { - throw new IllegalArgumentException("Unable to find a wrapper for presentation: " + presentation.getClass().getSimpleName()); - } - - return wrapper; - } - - public String getUIClassID() { - return uiClassID; - } - - @SuppressWarnings({"MethodOverloadsMethodOfSuperclass"}) - protected void setUI(StatusBarUI ui) { - super.setUI(ui); - } - - @Override - public void updateUI() { - if (UIManager.get(getUIClassID()) != null) { - setUI((StatusBarUI)UIManager.getUI(this)); - } - else { - setUI(SystemInfo.isMac ? new MacStatusBarUI() : new StatusBarUI()); - } - } - - @Override - protected void paintChildren(final Graphics g) { - if (getUI() instanceof MacStatusBarUI && !MacStatusBarUI.isActive(this)) { - final Graphics2D g2d = (Graphics2D)g.create(); - //g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.4f)); - super.paintChildren(g2d); - g2d.dispose(); - } - else { - super.paintChildren(g); - } - } - - public StatusBarUI getUI() { - return (StatusBarUI)ui; - } - - public void removeWidget(@NotNull final String id) { - final WidgetBean bean = myWidgetMap.get(id); - if (bean != null) { - if (Position.LEFT == bean.position) { - myLeftPanel.remove(bean.component); - } - else if (Position.RIGHT == bean.position) { - final Component[] components = myRightPanel.getComponents(); - int i = 0; - for (final Component c : components) { - if (c == bean.component) break; - i++; - } - - myRightPanel.remove(bean.component); - updateBorder(i); - } else { - myCenterPanel.remove(bean.component); - } - - myWidgetMap.remove(bean.widget.ID()); - Disposer.dispose(bean.widget); - } - - updateChildren(new ChildAction() { - @Override - public void update(IdeStatusBarImpl child) { - child.removeWidget(id); - } - }); - - myOrderedWidgets.remove(id); - } - - public void updateWidgets() { - for (final String s : myWidgetMap.keySet()) { - updateWidget(s); - } - - updateChildren(new ChildAction() { - @Override - public void update(IdeStatusBarImpl child) { - child.updateWidgets(); - } - }); - } - - public void updateWidget(@NotNull final String id) { - UIUtil.invokeLaterIfNeeded(new Runnable() { - public void run() { - final WidgetBean bean = myWidgetMap.get(id); - if (bean != null) { - if (bean.component instanceof StatusBarWrapper) { - ((StatusBarWrapper)bean.component).beforeUpdate(); - } - - bean.component.repaint(); - } - - updateChildren(new ChildAction() { - @Override - public void update(IdeStatusBarImpl child) { - child.updateWidget(id); - } - }); - } - }); - } - - private interface StatusBarWrapper { - void beforeUpdate(); - } - - private static final class MultipleTextValuesPresentationWrapper extends TextPanel implements StatusBarWrapper { - private static final Icon ARROWS_ICON = IconLoader.getIcon("/ide/statusbar_arrows.png"); - - private StatusBarWidget.MultipleTextValuesPresentation myPresentation; - - private MultipleTextValuesPresentationWrapper(@NotNull final StatusBarWidget.MultipleTextValuesPresentation presentation) { - super(presentation.getMaxValue()); - myPresentation = presentation; - - putClientProperty(UIUtil.CENTER_TOOLTIP, Boolean.TRUE); - - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - final ListPopup popup = myPresentation.getPopupStep(); - final Dimension dimension = popup.getContent().getPreferredSize(); - final Point at = new Point(0, -dimension.height); - popup.show(new RelativePoint(e.getComponent(), at)); - } - }); - - setOpaque(false); - } - - @SuppressWarnings({"unchecked"}) - public void beforeUpdate() { - setText(myPresentation.getSelectedValue()); - } - - @Override - protected void paintComponent(@NotNull final Graphics g) { - super.paintComponent(g); - - if (getText() != null) { - final Rectangle r = getBounds(); - final Insets insets = getInsets(); - ARROWS_ICON - .paintIcon(this, g, r.width - insets.right - ARROWS_ICON.getIconWidth() - 2, r.height / 2 - ARROWS_ICON.getIconHeight() / 2); - } - } - - @Override - public Dimension getPreferredSize() { - final Dimension preferredSize = super.getPreferredSize(); - return new Dimension(preferredSize.width + ARROWS_ICON.getIconWidth() + 4, preferredSize.height); - } - } - - private static final class TextPresentationWrapper extends TextPanel implements StatusBarWrapper { - private StatusBarWidget.TextPresentation myPresentation; - private Consumer myClickConsumer; - private boolean myMouseOver; - - private TextPresentationWrapper(@NotNull final StatusBarWidget.TextPresentation presentation) { - super(presentation.getMaxPossibleText()); - myPresentation = presentation; - myClickConsumer = myPresentation.getClickConsumer(); - - setTextAlignment(presentation.getAlignment()); - - putClientProperty(UIUtil.CENTER_TOOLTIP, Boolean.TRUE); - - addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - if (myClickConsumer != null && !e.isPopupTrigger() && MouseEvent.BUTTON1 == e.getButton()) { - myClickConsumer.consume(e); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - myMouseOver = true; - } - - @Override - public void mouseExited(MouseEvent e) { - myMouseOver = false; - } - }); - - setOpaque(false); - } - - public void beforeUpdate() { - setText(myPresentation.getText()); - } - } - - private static final class IconPresentationWrapper extends JComponent implements StatusBarWrapper { - private StatusBarWidget.IconPresentation myPresentation; - private Consumer myClickConsumer; - private Icon myIcon; - - private IconPresentationWrapper(@NotNull final StatusBarWidget.IconPresentation presentation) { - myPresentation = presentation; - myClickConsumer = myPresentation.getClickConsumer(); - myIcon = myPresentation.getIcon(); - - putClientProperty(UIUtil.CENTER_TOOLTIP, Boolean.TRUE); - - addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - if (myClickConsumer != null && !e.isPopupTrigger() && MouseEvent.BUTTON1 == e.getButton()) { - myClickConsumer.consume(e); - } - } - }); - - ToolTipManager.sharedInstance().registerComponent(this); - setOpaque(false); - - - } - - public void beforeUpdate() { - myIcon = myPresentation.getIcon(); - } - - @Override - @Nullable - public String getToolTipText() { - return myPresentation.getTooltipText(); - } - - @Override - protected void paintComponent(final Graphics g) { - final Rectangle bounds = getBounds(); - final Insets insets = getInsets(); - - if (myIcon != null) { - final int iconWidth = myIcon.getIconWidth(); - final int iconHeight = myIcon.getIconHeight(); - - myIcon.paintIcon(this, g, insets.left + (bounds.width - insets.left - insets.right - iconWidth) / 2, - insets.top + (bounds.height - insets.top - insets.bottom - iconHeight) / 2); - } - } - - @Override - public Dimension getMinimumSize() { - return new Dimension(24, 18); - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - } - - @Override - public IdeFrame getFrame() { - return myFrame; - } -} - +/* + * 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.wm.impl.status; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.TaskInfo; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.popup.BalloonHandler; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.CustomStatusBarWidget; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.openapi.wm.ex.ProgressIndicatorEx; +import com.intellij.openapi.wm.ex.StatusBarEx; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.popup.NotificationPopup; +import com.intellij.util.Consumer; +import com.intellij.util.containers.HashMap; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.HyperlinkListener; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; + +/** + * User: spLeaner + */ +public class IdeStatusBarImpl extends JComponent implements StatusBarEx { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.status.IdeStatusBarImpl"); + + private InfoAndProgressPanel myInfoAndProgressPanel; + private IdeStatusBarImpl myMaster; + private IdeFrame myFrame; + + private enum Position {LEFT, RIGHT, CENTER} + + private static final String uiClassID = "IdeStatusBarUI"; + + private final Map myWidgetMap = new HashMap(); + private final List myOrderedWidgets = new ArrayList(); + + private JPanel myLeftPanel; + private JPanel myRightPanel; + private JPanel myCenterPanel; + + private String myInfo; + + private List myCustomComponentIds = new ArrayList(); + + private Set myChildren = new HashSet(); + + private static class WidgetBean { + JComponent component; + Position position; + StatusBarWidget widget; + String anchor; + + static WidgetBean create(@NotNull final StatusBarWidget widget, @NotNull final Position position, @NotNull final JComponent component, @NotNull String anchor) { + final WidgetBean bean = new WidgetBean(); + bean.widget = widget; + bean.position = position; + bean.component = component; + bean.anchor = anchor; + return bean; + } + } + + @Override + public StatusBar findChild(Component c) { + Component eachParent = c; + IdeFrame frame = null; + while (eachParent != null) { + if (eachParent instanceof IdeFrame) { + frame = (IdeFrame)eachParent; + } + eachParent = eachParent.getParent(); + } + + return frame != null ? frame.getStatusBar() : this; + } + + @Override + public void install(IdeFrame frame) { + myFrame = frame; + } + + private void updateChildren(ChildAction action) { + for (IdeStatusBarImpl child : myChildren) { + action.update(child); + } + } + + interface ChildAction { + void update(IdeStatusBarImpl child); + } + + public StatusBar createChild() { + final IdeStatusBarImpl bar = new IdeStatusBarImpl(this); + myChildren.add(bar); + Disposer.register(bar, new Disposable() { + @Override + public void dispose() { + myChildren.remove(bar); + } + }); + + for (String eachId : myOrderedWidgets) { + WidgetBean eachBean = myWidgetMap.get(eachId); + if (eachBean.widget instanceof StatusBarWidget.Multiframe) { + StatusBarWidget copy = ((StatusBarWidget.Multiframe)eachBean.widget).copy(); + bar.addWidget(copy, eachBean.position); + } + } + + return bar; + } + + @Override + public JComponent getComponent() { + return this; + } + + IdeStatusBarImpl(IdeStatusBarImpl master) { + myMaster = master; + + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(1, 4, 0, SystemInfo.isMac ? 2 : 0)); + + myInfoAndProgressPanel = new InfoAndProgressPanel(master == null); + addWidget(myInfoAndProgressPanel, Position.CENTER); + + setOpaque(true); + updateUI(); + + if (master == null) { + Disposer.register(Disposer.get("ui"), this); + } + } + + + public IdeStatusBarImpl() { + this(null); + } + + public void addWidget(@NotNull final StatusBarWidget widget) { + addWidget(widget, Position.RIGHT, "__AUTODETECT__"); + } + + public void addWidget(@NotNull final StatusBarWidget widget, @NotNull String anchor) { + addWidget(widget, Position.RIGHT, anchor); + } + + private void addWidget(@NotNull final StatusBarWidget widget, @NotNull final Position pos) { + addWidget(widget, pos, "__IGNORED__"); + } + + public void addWidget(@NotNull final StatusBarWidget widget, @NotNull final Disposable parentDisposable) { + addWidget(widget); + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + removeWidget(widget.ID()); + } + }); + } + + public void addWidget(@NotNull final StatusBarWidget widget, @NotNull String anchor, @NotNull final Disposable parentDisposable) { + addWidget(widget, anchor); + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + removeWidget(widget.ID()); + } + }); + } + + public void removeCustomIndicationComponents() { + for (final String id : myCustomComponentIds) { + removeWidget(id); + } + + myCustomComponentIds.clear(); + } + + public void addCustomIndicationComponent(@NotNull final JComponent c) { + final String customId = c.getClass().getName() + new Random().nextLong(); + addWidget(new CustomStatusBarWidget() { + @NotNull + public String ID() { + return customId; + } + + @Nullable + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return null; + } + + public void install(@NotNull StatusBar statusBar) { + } + + public void dispose() { + } + + public JComponent getComponent() { + return c; + } + }); + + myCustomComponentIds.add(customId); + } + + public void removeCustomIndicationComponent(@NotNull final JComponent c) { + final Set keySet = myWidgetMap.keySet(); + final String[] keys = keySet.toArray(new String[keySet.size()]); + for (final String key : keys) { + final WidgetBean value = myWidgetMap.get(key); + if (value.widget instanceof CustomStatusBarWidget && value.component == c) { + removeWidget(key); + myCustomComponentIds.remove(key); + } + } + } + + public void dispose() { + final Iterator iterator = myWidgetMap.values().iterator(); + //noinspection WhileLoopReplaceableByForEach + while (iterator.hasNext()) { + Disposer.dispose(iterator.next().widget); + } + + myWidgetMap.clear(); + } + + private void addWidget(@NotNull final StatusBarWidget widget, @NotNull final Position pos, @NotNull final String anchor) { + myOrderedWidgets.add(widget.ID()); + + JPanel panel; + if (pos == Position.RIGHT) { + if (myRightPanel == null) { + myRightPanel = new JPanel(); + myRightPanel.setLayout(new BoxLayout(myRightPanel, BoxLayout.X_AXIS)); + myRightPanel.setOpaque(false); + add(myRightPanel, BorderLayout.EAST); + } + + panel = myRightPanel; + } + else if (pos == Position.LEFT) { + if (myLeftPanel == null) { + myLeftPanel = new JPanel(); + myLeftPanel.setLayout(new BoxLayout(myLeftPanel, BoxLayout.X_AXIS)); + myLeftPanel.setOpaque(false); + add(myLeftPanel, BorderLayout.WEST); + } + + panel = myLeftPanel; + } + else { + if (myCenterPanel == null) { + myCenterPanel = new JPanel(new BorderLayout()); + myCenterPanel.setOpaque(false); + add(myCenterPanel, BorderLayout.CENTER); + } + + panel = myCenterPanel; + } + + final JComponent c = widget instanceof CustomStatusBarWidget ? ((CustomStatusBarWidget)widget).getComponent() : wrap(widget); + if (Position.RIGHT == pos && panel.getComponentCount() > 0) { + String wid; + boolean before; + if (!anchor.equals("__AUTODETECT__")) { + final List parts = StringUtil.split(anchor, " "); + if (parts.size() < 2 || !myWidgetMap.keySet().contains(parts.get(1))) { + wid = "Notifications"; + before = true; + } else { + wid = parts.get(1); + before = "before".equalsIgnoreCase(parts.get(0)); + } + } else { + wid = "Notifications"; + before = true; + } + + for (final String id : myWidgetMap.keySet()) { + if (id.equalsIgnoreCase(wid)) { + final WidgetBean bean = myWidgetMap.get(id); + int i = 0; + for (final Component component : myRightPanel.getComponents()) { + if (component == bean.component) { + if (before) { + panel.add(c, i); + updateBorder(i); + } + else { + final int ndx = i + 1; + panel.add(c, i + 1); + updateBorder(ndx); + } + + installWidget(widget, pos, c, anchor); + return; + } + + i++; + } + } + } + } + + if (Position.LEFT == pos && panel.getComponentCount() == 0) { + c.setBorder(SystemInfo.isMac ? BorderFactory.createEmptyBorder(2, 0, 2, 4) : BorderFactory.createEmptyBorder()); + } + + panel.add(c); + installWidget(widget, pos, c, anchor); + + if (widget instanceof StatusBarWidget.Multiframe) { + final StatusBarWidget.Multiframe mfw = (StatusBarWidget.Multiframe)widget; + updateChildren(new ChildAction() { + @Override + public void update(IdeStatusBarImpl child) { + StatusBarWidget widgetCopy = mfw.copy(); + child.addWidget(widgetCopy, pos, anchor); + } + }); + } + } + + private void updateBorder(final int ndx) { + final JComponent self = (JComponent)myRightPanel.getComponent(ndx); + if (self instanceof IconPresentationWrapper) { + final int prev = ndx - 1; + final int next = ndx + 1; + + final JComponent p = prev >= 0 ? (JComponent)myRightPanel.getComponent(prev) : null; + final JComponent n = next < myRightPanel.getComponentCount() ? (JComponent)myRightPanel.getComponent(next) : null; + + final boolean prevIcon = p instanceof IconPresentationWrapper; + final boolean nextIcon = n instanceof IconPresentationWrapper; + + self.setBorder(prevIcon ? BorderFactory.createEmptyBorder(2, 2, 2, 2) : StatusBarWidget.WidgetBorder.INSTANCE); + if (nextIcon) n.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + } + } + + public void setInfo(@Nullable final String s) { + setInfo(s, null); + } + + @Override + public void setInfo(@Nullable final String s, @Nullable final String requestor) { + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + if (myInfoAndProgressPanel != null) { + myInfo = myInfoAndProgressPanel.setText(s, requestor); + } + } + }); + } + + public String getInfo() { + return myInfo; + } + + public void addProgress(ProgressIndicatorEx indicator, TaskInfo info) { + myInfoAndProgressPanel.addProgress(indicator, info); + } + + public void setProcessWindowOpen(final boolean open) { + myInfoAndProgressPanel.setProcessWindowOpen(open); + } + + public boolean isProcessWindowOpen() { + return myInfoAndProgressPanel.isProcessWindowOpen(); + } + + public void startRefreshIndication(final String tooltipText) { + myInfoAndProgressPanel.setRefreshToolTipText(tooltipText); + myInfoAndProgressPanel.setRefreshVisible(true); + + updateChildren(new ChildAction() { + @Override + public void update(IdeStatusBarImpl child) { + child.startRefreshIndication(tooltipText); + } + }); + } + + public void stopRefreshIndication() { + myInfoAndProgressPanel.setRefreshVisible(false); + + updateChildren(new ChildAction() { + @Override + public void update(IdeStatusBarImpl child) { + child.stopRefreshIndication(); + } + }); + } + + public BalloonHandler notifyProgressByBalloon(@NotNull MessageType type, @NotNull String htmlBody) { + return notifyProgressByBalloon(type, htmlBody, null, null); + } + + public BalloonHandler notifyProgressByBalloon(@NotNull MessageType type, + @NotNull String htmlBody, + @Nullable Icon icon, + @Nullable HyperlinkListener listener) { + return myInfoAndProgressPanel.notifyByBalloon(type, htmlBody, icon, listener); + } + + public void fireNotificationPopup(@NotNull JComponent content, Color backgroundColor) { + new NotificationPopup(this, content, backgroundColor); + } + + private void installWidget(@NotNull final StatusBarWidget widget, @NotNull final Position pos, @NotNull final JComponent c, String anchor) { + myWidgetMap.put(widget.ID(), WidgetBean.create(widget, pos, c, anchor)); + widget.install(this); + } + + private static JComponent wrap(@NotNull final StatusBarWidget widget) { + final StatusBarWidget.WidgetPresentation presentation = + widget.getPresentation(SystemInfo.isMac ? StatusBarWidget.PlatformType.MAC : StatusBarWidget.PlatformType.DEFAULT); + assert presentation != null : "Presentation should not be null!"; + + JComponent wrapper; + if (presentation instanceof StatusBarWidget.IconPresentation) { + wrapper = new IconPresentationWrapper((StatusBarWidget.IconPresentation)presentation); + } + else if (presentation instanceof StatusBarWidget.TextPresentation) { + wrapper = new TextPresentationWrapper((StatusBarWidget.TextPresentation)presentation); + wrapper.setBorder(StatusBarWidget.WidgetBorder.INSTANCE); + } + else if (presentation instanceof StatusBarWidget.MultipleTextValuesPresentation) { + wrapper = new MultipleTextValuesPresentationWrapper((StatusBarWidget.MultipleTextValuesPresentation)presentation); + wrapper.setBorder(StatusBarWidget.WidgetBorder.INSTANCE); + } + else { + throw new IllegalArgumentException("Unable to find a wrapper for presentation: " + presentation.getClass().getSimpleName()); + } + + return wrapper; + } + + public String getUIClassID() { + return uiClassID; + } + + @SuppressWarnings({"MethodOverloadsMethodOfSuperclass"}) + protected void setUI(StatusBarUI ui) { + super.setUI(ui); + } + + @Override + public void updateUI() { + if (UIManager.get(getUIClassID()) != null) { + setUI((StatusBarUI)UIManager.getUI(this)); + } + else { + setUI(SystemInfo.isMac ? new MacStatusBarUI() : new StatusBarUI()); + } + } + + @Override + protected void paintChildren(final Graphics g) { + if (getUI() instanceof MacStatusBarUI && !MacStatusBarUI.isActive(this)) { + final Graphics2D g2d = (Graphics2D)g.create(); + //g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.4f)); + super.paintChildren(g2d); + g2d.dispose(); + } + else { + super.paintChildren(g); + } + } + + public StatusBarUI getUI() { + return (StatusBarUI)ui; + } + + public void removeWidget(@NotNull final String id) { + final WidgetBean bean = myWidgetMap.get(id); + if (bean != null) { + if (Position.LEFT == bean.position) { + myLeftPanel.remove(bean.component); + } + else if (Position.RIGHT == bean.position) { + final Component[] components = myRightPanel.getComponents(); + int i = 0; + for (final Component c : components) { + if (c == bean.component) break; + i++; + } + + myRightPanel.remove(bean.component); + updateBorder(i); + } else { + myCenterPanel.remove(bean.component); + } + + myWidgetMap.remove(bean.widget.ID()); + Disposer.dispose(bean.widget); + } + + updateChildren(new ChildAction() { + @Override + public void update(IdeStatusBarImpl child) { + child.removeWidget(id); + } + }); + + myOrderedWidgets.remove(id); + } + + public void updateWidgets() { + for (final String s : myWidgetMap.keySet()) { + updateWidget(s); + } + + updateChildren(new ChildAction() { + @Override + public void update(IdeStatusBarImpl child) { + child.updateWidgets(); + } + }); + } + + public void updateWidget(@NotNull final String id) { + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + final WidgetBean bean = myWidgetMap.get(id); + if (bean != null) { + if (bean.component instanceof StatusBarWrapper) { + ((StatusBarWrapper)bean.component).beforeUpdate(); + } + + bean.component.repaint(); + } + + updateChildren(new ChildAction() { + @Override + public void update(IdeStatusBarImpl child) { + child.updateWidget(id); + } + }); + } + }); + } + + private interface StatusBarWrapper { + void beforeUpdate(); + } + + private static final class MultipleTextValuesPresentationWrapper extends TextPanel implements StatusBarWrapper { + private static final Icon ARROWS_ICON = IconLoader.getIcon("/ide/statusbar_arrows.png"); + + private StatusBarWidget.MultipleTextValuesPresentation myPresentation; + + private MultipleTextValuesPresentationWrapper(@NotNull final StatusBarWidget.MultipleTextValuesPresentation presentation) { + super(presentation.getMaxValue()); + myPresentation = presentation; + + putClientProperty(UIUtil.CENTER_TOOLTIP, Boolean.TRUE); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + final ListPopup popup = myPresentation.getPopupStep(); + final Dimension dimension = popup.getContent().getPreferredSize(); + final Point at = new Point(0, -dimension.height); + popup.show(new RelativePoint(e.getComponent(), at)); + } + }); + + setOpaque(false); + } + + @SuppressWarnings({"unchecked"}) + public void beforeUpdate() { + setText(myPresentation.getSelectedValue()); + } + + @Override + protected void paintComponent(@NotNull final Graphics g) { + super.paintComponent(g); + + if (getText() != null) { + final Rectangle r = getBounds(); + final Insets insets = getInsets(); + ARROWS_ICON + .paintIcon(this, g, r.width - insets.right - ARROWS_ICON.getIconWidth() - 2, r.height / 2 - ARROWS_ICON.getIconHeight() / 2); + } + } + + @Override + public Dimension getPreferredSize() { + final Dimension preferredSize = super.getPreferredSize(); + return new Dimension(preferredSize.width + ARROWS_ICON.getIconWidth() + 4, preferredSize.height); + } + } + + private static final class TextPresentationWrapper extends TextPanel implements StatusBarWrapper { + private StatusBarWidget.TextPresentation myPresentation; + private Consumer myClickConsumer; + private boolean myMouseOver; + + private TextPresentationWrapper(@NotNull final StatusBarWidget.TextPresentation presentation) { + super(presentation.getMaxPossibleText()); + myPresentation = presentation; + myClickConsumer = myPresentation.getClickConsumer(); + + setTextAlignment(presentation.getAlignment()); + + putClientProperty(UIUtil.CENTER_TOOLTIP, Boolean.TRUE); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + if (myClickConsumer != null && !e.isPopupTrigger() && MouseEvent.BUTTON1 == e.getButton()) { + myClickConsumer.consume(e); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + myMouseOver = true; + } + + @Override + public void mouseExited(MouseEvent e) { + myMouseOver = false; + } + }); + + setOpaque(false); + } + + public void beforeUpdate() { + setText(myPresentation.getText()); + } + } + + private static final class IconPresentationWrapper extends JComponent implements StatusBarWrapper { + private StatusBarWidget.IconPresentation myPresentation; + private Consumer myClickConsumer; + private Icon myIcon; + + private IconPresentationWrapper(@NotNull final StatusBarWidget.IconPresentation presentation) { + myPresentation = presentation; + myClickConsumer = myPresentation.getClickConsumer(); + myIcon = myPresentation.getIcon(); + + putClientProperty(UIUtil.CENTER_TOOLTIP, Boolean.TRUE); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + if (myClickConsumer != null && !e.isPopupTrigger() && MouseEvent.BUTTON1 == e.getButton()) { + myClickConsumer.consume(e); + } + } + }); + + ToolTipManager.sharedInstance().registerComponent(this); + setOpaque(false); + + + } + + public void beforeUpdate() { + myIcon = myPresentation.getIcon(); + } + + @Override + @Nullable + public String getToolTipText() { + return myPresentation.getTooltipText(); + } + + @Override + protected void paintComponent(final Graphics g) { + final Rectangle bounds = getBounds(); + final Insets insets = getInsets(); + + if (myIcon != null) { + final int iconWidth = myIcon.getIconWidth(); + final int iconHeight = myIcon.getIconHeight(); + + myIcon.paintIcon(this, g, insets.left + (bounds.width - insets.left - insets.right - iconWidth) / 2, + insets.top + (bounds.height - insets.top - insets.bottom - iconHeight) / 2); + } + } + + @Override + public Dimension getMinimumSize() { + return new Dimension(24, 18); + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + } + + @Override + public IdeFrame getFrame() { + return myFrame; + } +} + diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.java index 467cfff91005..d58ef3bbe5cd 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.java @@ -1,602 +1,602 @@ -/* - * 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.wm.impl.status; - -import com.intellij.idea.ActionsBundle; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.TaskInfo; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.ui.popup.BalloonHandler; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.MultiValuesMap; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.wm.CustomStatusBarWidget; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.openapi.wm.ex.ProgressIndicatorEx; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.components.labels.LinkLabel; -import com.intellij.ui.components.labels.LinkListener; -import com.intellij.ui.components.panels.Wrapper; -import com.intellij.util.Alarm; -import com.intellij.util.ui.AbstractLayoutManager; -import com.intellij.util.ui.AsyncProcessIcon; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.update.MergingUpdateQueue; -import com.intellij.util.ui.update.Update; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.HyperlinkListener; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.*; - -public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidget { - private final ProcessPopup myPopup; - - private final TextPanel myInfoPanel = new TextPanel(); - private final JPanel myRefreshAndInfoPanel = new JPanel(); - private final AsyncProcessIcon myProgressIcon; - - private final ArrayList myOriginals = new ArrayList(); - private final ArrayList myInfos = new ArrayList(); - private final Map myInline2Original - = new HashMap(); - private final MultiValuesMap myOriginal2Inlines - = new MultiValuesMap(); - - private final MergingUpdateQueue myUpdateQueue; - private final Alarm myQueryAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - - private boolean myShouldClosePopupAndOnProcessFinish; - private AsyncProcessIcon myRefreshIcon; - private EmptyIcon myEmptyRefreshIcon; - - private String myCurrentRequestor; - private boolean myProgressEnabled; - - public InfoAndProgressPanel(boolean progressEnabled) { - myProgressEnabled = progressEnabled; - - setOpaque(false); - - myRefreshIcon = new AsyncProcessIcon("Refreshing filesystem") { - protected Icon getPassiveIcon() { - return myEmptyRefreshIcon; - } - - @Override - public Dimension getPreferredSize() { - if (!isRunning()) return new Dimension(0, 0); - return super.getPreferredSize(); - } - }; - - myRefreshIcon.setPaintPassiveIcon(false); - myEmptyRefreshIcon = new EmptyIcon(0, myRefreshIcon.getPreferredSize().height); - - myRefreshAndInfoPanel.setLayout(new BorderLayout()); - myRefreshAndInfoPanel.setOpaque(false); - myRefreshAndInfoPanel.add(myRefreshIcon, BorderLayout.WEST); - myRefreshAndInfoPanel.add(myInfoPanel, BorderLayout.CENTER); - - myProgressIcon = new AsyncProcessIcon("Background process"); - myProgressIcon.setOpaque(false); - - myProgressIcon.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - handle(e); - } - @Override - public void mouseReleased(MouseEvent e) { - handle(e); - } - }); - - myProgressIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - myProgressIcon.setBorder(StatusBarWidget.WidgetBorder.INSTANCE); - myProgressIcon.setToolTipText(ActionsBundle.message("action.ShowProcessWindow.double.click")); - - myUpdateQueue = new MergingUpdateQueue("Progress indicator", 50, true, MergingUpdateQueue.ANY_COMPONENT); - myPopup = new ProcessPopup(this); - - setRefreshVisible(false); - - restoreEmptyStatus(); - } - - private void handle(MouseEvent e) { - if (UIUtil.isActionClick(e, MouseEvent.MOUSE_PRESSED)) { - if (!myPopup.isShowing()) { - openProcessPopup(true); - } else { - hideProcessPopup(); - } - } else if (e.isPopupTrigger()) { - ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction("BackgroundTasks"); - ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(e.getComponent(), e.getX(), e.getY()); - } - } - - @NotNull - public String ID() { - return "InfoAndProgress"; - } - - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return null; - } - - public void install(@NotNull StatusBar statusBar) { - } - - public void dispose() { - setRefreshVisible(false); - } - - public JComponent getComponent() { - return this; - } - - public void addProgress(final ProgressIndicatorEx original, TaskInfo info) { - synchronized (myOriginals) { - final boolean veryFirst = myOriginals.isEmpty(); - - myOriginals.add(original); - myInfos.add(info); - - final InlineProgressIndicator expanded = createInlineDelegate(info, original, false); - final InlineProgressIndicator compact = createInlineDelegate(info, original, true); - - myPopup.addIndicator(expanded); - myProgressIcon.resume(); - - if (veryFirst && !myPopup.isShowing()) { - buildInInlineIndicator(compact); - } - else { - buildInProcessCount(); - if (myInfos.size() > 1 && Registry.is("ide.windowSystem.autoShowProcessPopup")) { - openProcessPopup(false); - } - } - - runQuery(); - } - } - - private void removeProgress(InlineProgressIndicator progress) { - synchronized (myOriginals) { - if (!myInline2Original.containsKey(progress)) return; - - final boolean last = myOriginals.size() == 1; - final boolean beforeLast = myOriginals.size() == 2; - - myPopup.removeIndicator(progress); - - final ProgressIndicatorEx original = removeFromMaps(progress); - if (myOriginals.contains(original)) return; - - if (last) { - restoreEmptyStatus(); - if (myShouldClosePopupAndOnProcessFinish) { - hideProcessPopup(); - } - } - else { - if (myPopup.isShowing() || myOriginals.size() > 1) { - buildInProcessCount(); - } - else if (beforeLast) { - buildInInlineIndicator(createInlineDelegate(myInfos.get(0), myOriginals.get(0), true)); - } - else { - restoreEmptyStatus(); - } - } - - runQuery(); - } - } - - private ProgressIndicatorEx removeFromMaps(final InlineProgressIndicator progress) { - final ProgressIndicatorEx original = myInline2Original.get(progress); - - myInline2Original.remove(progress); - - myOriginal2Inlines.remove(original, progress); - if (myOriginal2Inlines.get(original) == null) { - final int originalIndex = myOriginals.indexOf(original); - myOriginals.remove(originalIndex); - myInfos.remove(originalIndex); - } - - return original; - } - - private void openProcessPopup(boolean requestFocus) { - synchronized (myOriginals) { - if (myPopup.isShowing()) return; - if (!myOriginals.isEmpty()) { - myShouldClosePopupAndOnProcessFinish = true; - buildInProcessCount(); - } - else { - myShouldClosePopupAndOnProcessFinish = false; - restoreEmptyStatus(); - } - myPopup.show(requestFocus); - } - } - - void hideProcessPopup() { - synchronized (myOriginals) { - if (!myPopup.isShowing()) return; - - if (myOriginals.size() == 1) { - buildInInlineIndicator(createInlineDelegate(myInfos.get(0), myOriginals.get(0), true)); - } - else if (myOriginals.isEmpty()) { - restoreEmptyStatus(); - } - else { - buildInProcessCount(); - } - - myPopup.hide(); - } - } - - private void buildInProcessCount() { - removeAll(); - setLayout(new BorderLayout()); - - final JPanel progressCountPanel = new JPanel(new BorderLayout(0, 0)); - progressCountPanel.setOpaque(false); - String processWord = myOriginals.size() == 1 ? " process" : " processes"; - final LinkLabel label = new LinkLabel(myOriginals.size() + processWord + " running...", null, new LinkListener() { - public void linkSelected(final LinkLabel aSource, final Object aLinkData) { - triggerPopupShowing(); - } - }); - - if (SystemInfo.isMac) label.setFont(UIUtil.getLabelFont().deriveFont(11.0f)); - - label.setOpaque(false); - - final Wrapper labelComp = new Wrapper(label); - labelComp.setOpaque(false); - progressCountPanel.add(labelComp, BorderLayout.CENTER); - - //myProgressIcon.setBorder(new IdeStatusBarImpl.MacStatusBarWidgetBorder()); - progressCountPanel.add(myProgressIcon, BorderLayout.WEST); - - add(myRefreshAndInfoPanel, BorderLayout.CENTER); - - progressCountPanel.setBorder(new EmptyBorder(0, 0, 0, 4)); - add(progressCountPanel, BorderLayout.EAST); - - revalidate(); - repaint(); - } - - private void buildInInlineIndicator(final InlineProgressIndicator inline) { - removeAll(); - setLayout(new InlineLayout()); - add(myRefreshAndInfoPanel); - - final JPanel inlinePanel = new JPanel(new BorderLayout()) { - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - } - }; - - inline.getComponent().setBorder(new EmptyBorder(0, 0, 0, 2)); - final JComponent inlineComponent = inline.getComponent(); - inlineComponent.setOpaque(false); - inlinePanel.add(inlineComponent, BorderLayout.CENTER); - - //myProgressIcon.setBorder(new IdeStatusBarImpl.MacStatusBarWidgetBorder()); - inlinePanel.add(myProgressIcon, BorderLayout.WEST); - - inline.updateProgressNow(); - inlinePanel.setOpaque(false); - - add(inlinePanel); - - myRefreshAndInfoPanel.revalidate(); - myRefreshAndInfoPanel.repaint(); - } - - public String setText(final String text, final String requestor) { - if (text == null || text.length() == 0) { - if ((requestor != null && !requestor.equals(myCurrentRequestor)) - || (myCurrentRequestor != null && !myCurrentRequestor.equals(requestor))) { - return myInfoPanel.getText(); - } - } - - myInfoPanel.setText(text); - myCurrentRequestor = requestor; - return text; - } - - public void setRefreshVisible(final boolean visible) { - if (visible) { - myRefreshIcon.resume(); - } - else { - myRefreshIcon.suspend(); - } - - myRefreshIcon.revalidate(); - myRefreshIcon.repaint(); - } - - public void setRefreshToolTipText(final String tooltip) { - myRefreshIcon.setToolTipText(tooltip); - } - - public BalloonHandler notifyByBalloon(MessageType type, String htmlBody, Icon icon, HyperlinkListener listener) { - final Balloon balloon = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder( - htmlBody.replace("\n", "
"), - icon != null ? icon : type.getDefaultIcon(), - type.getPopupBackground(), - listener).createBalloon(); - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - Component comp = InfoAndProgressPanel.this; - if (comp.isShowing()) { - int offset = comp.getHeight() / 2; - Point point = new Point(comp.getWidth() - offset, comp.getHeight() - offset); - balloon.show(new RelativePoint(comp, point), Balloon.Position.above); - } else { - final JRootPane rootPane = SwingUtilities.getRootPane(comp); - if (rootPane != null && rootPane.isShowing()) { - final Container contentPane = rootPane.getContentPane(); - final Rectangle bounds = contentPane.getBounds(); - final Point target = UIUtil.getCenterPoint(bounds, new Dimension(1, 1)); - target.y = bounds.height - 3; - balloon.show(new RelativePoint(contentPane, target), Balloon.Position.above); - } - } - } - }); - - return new BalloonHandler() { - public void hide() { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - balloon.hide(); - } - }); - } - }; - } - - private static class InlineLayout extends AbstractLayoutManager { - - public Dimension preferredLayoutSize(final Container parent) { - Dimension result = new Dimension(); - for (int i = 0; i < parent.getComponentCount(); i++) { - final Dimension prefSize = parent.getComponent(i).getPreferredSize(); - result.width += prefSize.width; - result.height = Math.max(prefSize.height, result.height); - } - return result; - } - - public void layoutContainer(final Container parent) { - final Dimension size = parent.getSize(); - int compWidth = size.width / parent.getComponentCount(); - int eachX = 0; - for (int i = 0; i < parent.getComponentCount(); i++) { - final Component each = parent.getComponent(i); - if (i == parent.getComponentCount() - 1) { - compWidth = size.width - eachX; - } - each.setBounds(eachX, 0, compWidth, size.height); - eachX += compWidth; - } - } - } - - private InlineProgressIndicator createInlineDelegate(final TaskInfo info, final ProgressIndicatorEx original, final boolean compact) { - final Collection inlines = myOriginal2Inlines.get(original); - if (inlines != null) { - for (InlineProgressIndicator eachInline : inlines) { - if (eachInline.isCompact() == compact) return eachInline; - } - } - - final InlineProgressIndicator inline = new MyInlineProgressIndicator(compact, info, original); - - myInline2Original.put(inline, original); - myOriginal2Inlines.put(original, inline); - - if (compact) { - inline.getComponent().addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - handle(e); - } - - @Override - public void mouseReleased(MouseEvent e) { - handle(e); - } - }); - } - - return inline; - } - - private void triggerPopupShowing() { - if (myPopup.isShowing()) { - hideProcessPopup(); - } - else { - openProcessPopup(true); - } - } - - private void restoreEmptyStatus() { - removeAll(); - setLayout(new BorderLayout()); - add(myRefreshAndInfoPanel, BorderLayout.CENTER); - - //long wastedTime = ProgressManagerImpl.getWastedTime(); - //if (ApplicationManagerEx.getApplicationEx().isInternal() && wastedTime > 10 * 60 * 1000) { - // JPanel wrapper = new JPanel(new BorderLayout()); - // wrapper.setOpaque(false); - // JLabel label = new JLabel(" Wasted time: " + formatTime(wastedTime) + " "); - // label.setForeground(UIUtil.getPanelBackground().darker()); - // label.setOpaque(false); - // wrapper.add(label, BorderLayout.CENTER); - // wrapper.add(myProgressIcon, BorderLayout.EAST); - // - // long time = System.currentTimeMillis() - ApplicationManagerEx.getApplicationEx().getStartTime(); - // long percentage = wastedTime * 100 / time; - // String period = new SimpleDateFormat("m 'min' H 'hours'").format(new Date(2000, 0, 1, 0, 0, 0).getTime() + time); - // - // List> list = ProgressManagerImpl.getTimeWasters(); - // StringBuilder s = new StringBuilder("Successfully wasted " + percentage +"% of your time in " + period + ":
"); - // for (Pair each : list) { - // s.append(""); - // s.append(each.first); - // s.append(":"); - // s.append(formatTime(each.second)); - // s.append(""); - // } - // s.append(""); - // wrapper.setToolTipText(s.toString()); - // add(wrapper, BorderLayout.EAST); - //} else { - - if (myProgressEnabled) { - add(myProgressIcon, BorderLayout.EAST); - } - - //} - - myProgressIcon.suspend(); - myRefreshAndInfoPanel.revalidate(); - myRefreshAndInfoPanel.repaint(); - } - - //private String formatTime(long t) { - // if (t < 1000) return "< 1 sec"; - // if (t < 60 * 1000) return (t / 1000) + " sec"; - // return "~" + (int)Math.ceil(t / (60 * 1000f)) + " min"; - //} - - public boolean isProcessWindowOpen() { - return myPopup.isShowing(); - } - - public void setProcessWindowOpen(final boolean open) { - if (open) { - openProcessPopup(true); - } - else { - hideProcessPopup(); - } - } - - private class MyInlineProgressIndicator extends InlineProgressIndicator { - private final ProgressIndicatorEx myOriginal; - private final TaskInfo myTask; - - public MyInlineProgressIndicator(final boolean compact, final TaskInfo task, final ProgressIndicatorEx original) { - super(compact, task); - myOriginal = original; - myTask = task; - original.addStateDelegate(this); - } - - public void cancel() { - super.cancel(); - updateProgress(); - } - - @Override - public void stop() { - super.stop(); - updateProgress(); - } - - @Override - protected boolean isFinished() { - return isFinished(myTask); - } - - @Override - public void finish(@NotNull final TaskInfo task) { - super.finish(task); - queueRunningUpdate(new Runnable() { - public void run() { - removeProgress(MyInlineProgressIndicator.this); - dispose(); - } - }); - } - - protected void cancelRequest() { - myOriginal.cancel(); - } - - protected void queueProgressUpdate(final Runnable update) { - myUpdateQueue.queue(new Update(MyInlineProgressIndicator.this, false, 1) { - public void run() { - ApplicationManager.getApplication().invokeLater(update); - } - }); - } - - protected void queueRunningUpdate(final Runnable update) { - myUpdateQueue.queue(new Update(new Object(), false, 0) { - public void run() { - ApplicationManager.getApplication().invokeLater(update); - } - }); - } - } - - private void runQuery() { - if (getRootPane() == null) return; - - synchronized (myOriginals) { - for (InlineProgressIndicator each : myInline2Original.keySet()) { - each.updateProgress(); - } - } - myQueryAlarm.addRequest(new Runnable() { - public void run() { - runQuery(); - } - }, 2000); - } -} +/* + * 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.wm.impl.status; + +import com.intellij.idea.ActionsBundle; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.TaskInfo; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.ui.popup.BalloonHandler; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.MultiValuesMap; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.CustomStatusBarWidget; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.openapi.wm.ex.ProgressIndicatorEx; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.labels.LinkLabel; +import com.intellij.ui.components.labels.LinkListener; +import com.intellij.ui.components.panels.Wrapper; +import com.intellij.util.Alarm; +import com.intellij.util.ui.AbstractLayoutManager; +import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.HyperlinkListener; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; + +public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidget { + private final ProcessPopup myPopup; + + private final TextPanel myInfoPanel = new TextPanel(); + private final JPanel myRefreshAndInfoPanel = new JPanel(); + private final AsyncProcessIcon myProgressIcon; + + private final ArrayList myOriginals = new ArrayList(); + private final ArrayList myInfos = new ArrayList(); + private final Map myInline2Original + = new HashMap(); + private final MultiValuesMap myOriginal2Inlines + = new MultiValuesMap(); + + private final MergingUpdateQueue myUpdateQueue; + private final Alarm myQueryAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + + private boolean myShouldClosePopupAndOnProcessFinish; + private AsyncProcessIcon myRefreshIcon; + private EmptyIcon myEmptyRefreshIcon; + + private String myCurrentRequestor; + private boolean myProgressEnabled; + + public InfoAndProgressPanel(boolean progressEnabled) { + myProgressEnabled = progressEnabled; + + setOpaque(false); + + myRefreshIcon = new AsyncProcessIcon("Refreshing filesystem") { + protected Icon getPassiveIcon() { + return myEmptyRefreshIcon; + } + + @Override + public Dimension getPreferredSize() { + if (!isRunning()) return new Dimension(0, 0); + return super.getPreferredSize(); + } + }; + + myRefreshIcon.setPaintPassiveIcon(false); + myEmptyRefreshIcon = new EmptyIcon(0, myRefreshIcon.getPreferredSize().height); + + myRefreshAndInfoPanel.setLayout(new BorderLayout()); + myRefreshAndInfoPanel.setOpaque(false); + myRefreshAndInfoPanel.add(myRefreshIcon, BorderLayout.WEST); + myRefreshAndInfoPanel.add(myInfoPanel, BorderLayout.CENTER); + + myProgressIcon = new AsyncProcessIcon("Background process"); + myProgressIcon.setOpaque(false); + + myProgressIcon.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + handle(e); + } + @Override + public void mouseReleased(MouseEvent e) { + handle(e); + } + }); + + myProgressIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + myProgressIcon.setBorder(StatusBarWidget.WidgetBorder.INSTANCE); + myProgressIcon.setToolTipText(ActionsBundle.message("action.ShowProcessWindow.double.click")); + + myUpdateQueue = new MergingUpdateQueue("Progress indicator", 50, true, MergingUpdateQueue.ANY_COMPONENT); + myPopup = new ProcessPopup(this); + + setRefreshVisible(false); + + restoreEmptyStatus(); + } + + private void handle(MouseEvent e) { + if (UIUtil.isActionClick(e, MouseEvent.MOUSE_PRESSED)) { + if (!myPopup.isShowing()) { + openProcessPopup(true); + } else { + hideProcessPopup(); + } + } else if (e.isPopupTrigger()) { + ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction("BackgroundTasks"); + ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(e.getComponent(), e.getX(), e.getY()); + } + } + + @NotNull + public String ID() { + return "InfoAndProgress"; + } + + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return null; + } + + public void install(@NotNull StatusBar statusBar) { + } + + public void dispose() { + setRefreshVisible(false); + } + + public JComponent getComponent() { + return this; + } + + public void addProgress(final ProgressIndicatorEx original, TaskInfo info) { + synchronized (myOriginals) { + final boolean veryFirst = myOriginals.isEmpty(); + + myOriginals.add(original); + myInfos.add(info); + + final InlineProgressIndicator expanded = createInlineDelegate(info, original, false); + final InlineProgressIndicator compact = createInlineDelegate(info, original, true); + + myPopup.addIndicator(expanded); + myProgressIcon.resume(); + + if (veryFirst && !myPopup.isShowing()) { + buildInInlineIndicator(compact); + } + else { + buildInProcessCount(); + if (myInfos.size() > 1 && Registry.is("ide.windowSystem.autoShowProcessPopup")) { + openProcessPopup(false); + } + } + + runQuery(); + } + } + + private void removeProgress(InlineProgressIndicator progress) { + synchronized (myOriginals) { + if (!myInline2Original.containsKey(progress)) return; + + final boolean last = myOriginals.size() == 1; + final boolean beforeLast = myOriginals.size() == 2; + + myPopup.removeIndicator(progress); + + final ProgressIndicatorEx original = removeFromMaps(progress); + if (myOriginals.contains(original)) return; + + if (last) { + restoreEmptyStatus(); + if (myShouldClosePopupAndOnProcessFinish) { + hideProcessPopup(); + } + } + else { + if (myPopup.isShowing() || myOriginals.size() > 1) { + buildInProcessCount(); + } + else if (beforeLast) { + buildInInlineIndicator(createInlineDelegate(myInfos.get(0), myOriginals.get(0), true)); + } + else { + restoreEmptyStatus(); + } + } + + runQuery(); + } + } + + private ProgressIndicatorEx removeFromMaps(final InlineProgressIndicator progress) { + final ProgressIndicatorEx original = myInline2Original.get(progress); + + myInline2Original.remove(progress); + + myOriginal2Inlines.remove(original, progress); + if (myOriginal2Inlines.get(original) == null) { + final int originalIndex = myOriginals.indexOf(original); + myOriginals.remove(originalIndex); + myInfos.remove(originalIndex); + } + + return original; + } + + private void openProcessPopup(boolean requestFocus) { + synchronized (myOriginals) { + if (myPopup.isShowing()) return; + if (!myOriginals.isEmpty()) { + myShouldClosePopupAndOnProcessFinish = true; + buildInProcessCount(); + } + else { + myShouldClosePopupAndOnProcessFinish = false; + restoreEmptyStatus(); + } + myPopup.show(requestFocus); + } + } + + void hideProcessPopup() { + synchronized (myOriginals) { + if (!myPopup.isShowing()) return; + + if (myOriginals.size() == 1) { + buildInInlineIndicator(createInlineDelegate(myInfos.get(0), myOriginals.get(0), true)); + } + else if (myOriginals.isEmpty()) { + restoreEmptyStatus(); + } + else { + buildInProcessCount(); + } + + myPopup.hide(); + } + } + + private void buildInProcessCount() { + removeAll(); + setLayout(new BorderLayout()); + + final JPanel progressCountPanel = new JPanel(new BorderLayout(0, 0)); + progressCountPanel.setOpaque(false); + String processWord = myOriginals.size() == 1 ? " process" : " processes"; + final LinkLabel label = new LinkLabel(myOriginals.size() + processWord + " running...", null, new LinkListener() { + public void linkSelected(final LinkLabel aSource, final Object aLinkData) { + triggerPopupShowing(); + } + }); + + if (SystemInfo.isMac) label.setFont(UIUtil.getLabelFont().deriveFont(11.0f)); + + label.setOpaque(false); + + final Wrapper labelComp = new Wrapper(label); + labelComp.setOpaque(false); + progressCountPanel.add(labelComp, BorderLayout.CENTER); + + //myProgressIcon.setBorder(new IdeStatusBarImpl.MacStatusBarWidgetBorder()); + progressCountPanel.add(myProgressIcon, BorderLayout.WEST); + + add(myRefreshAndInfoPanel, BorderLayout.CENTER); + + progressCountPanel.setBorder(new EmptyBorder(0, 0, 0, 4)); + add(progressCountPanel, BorderLayout.EAST); + + revalidate(); + repaint(); + } + + private void buildInInlineIndicator(final InlineProgressIndicator inline) { + removeAll(); + setLayout(new InlineLayout()); + add(myRefreshAndInfoPanel); + + final JPanel inlinePanel = new JPanel(new BorderLayout()) { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + } + }; + + inline.getComponent().setBorder(new EmptyBorder(0, 0, 0, 2)); + final JComponent inlineComponent = inline.getComponent(); + inlineComponent.setOpaque(false); + inlinePanel.add(inlineComponent, BorderLayout.CENTER); + + //myProgressIcon.setBorder(new IdeStatusBarImpl.MacStatusBarWidgetBorder()); + inlinePanel.add(myProgressIcon, BorderLayout.WEST); + + inline.updateProgressNow(); + inlinePanel.setOpaque(false); + + add(inlinePanel); + + myRefreshAndInfoPanel.revalidate(); + myRefreshAndInfoPanel.repaint(); + } + + public String setText(final String text, final String requestor) { + if (text == null || text.length() == 0) { + if ((requestor != null && !requestor.equals(myCurrentRequestor)) + || (myCurrentRequestor != null && !myCurrentRequestor.equals(requestor))) { + return myInfoPanel.getText(); + } + } + + myInfoPanel.setText(text); + myCurrentRequestor = requestor; + return text; + } + + public void setRefreshVisible(final boolean visible) { + if (visible) { + myRefreshIcon.resume(); + } + else { + myRefreshIcon.suspend(); + } + + myRefreshIcon.revalidate(); + myRefreshIcon.repaint(); + } + + public void setRefreshToolTipText(final String tooltip) { + myRefreshIcon.setToolTipText(tooltip); + } + + public BalloonHandler notifyByBalloon(MessageType type, String htmlBody, Icon icon, HyperlinkListener listener) { + final Balloon balloon = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder( + htmlBody.replace("\n", "
"), + icon != null ? icon : type.getDefaultIcon(), + type.getPopupBackground(), + listener).createBalloon(); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Component comp = InfoAndProgressPanel.this; + if (comp.isShowing()) { + int offset = comp.getHeight() / 2; + Point point = new Point(comp.getWidth() - offset, comp.getHeight() - offset); + balloon.show(new RelativePoint(comp, point), Balloon.Position.above); + } else { + final JRootPane rootPane = SwingUtilities.getRootPane(comp); + if (rootPane != null && rootPane.isShowing()) { + final Container contentPane = rootPane.getContentPane(); + final Rectangle bounds = contentPane.getBounds(); + final Point target = UIUtil.getCenterPoint(bounds, new Dimension(1, 1)); + target.y = bounds.height - 3; + balloon.show(new RelativePoint(contentPane, target), Balloon.Position.above); + } + } + } + }); + + return new BalloonHandler() { + public void hide() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + balloon.hide(); + } + }); + } + }; + } + + private static class InlineLayout extends AbstractLayoutManager { + + public Dimension preferredLayoutSize(final Container parent) { + Dimension result = new Dimension(); + for (int i = 0; i < parent.getComponentCount(); i++) { + final Dimension prefSize = parent.getComponent(i).getPreferredSize(); + result.width += prefSize.width; + result.height = Math.max(prefSize.height, result.height); + } + return result; + } + + public void layoutContainer(final Container parent) { + final Dimension size = parent.getSize(); + int compWidth = size.width / parent.getComponentCount(); + int eachX = 0; + for (int i = 0; i < parent.getComponentCount(); i++) { + final Component each = parent.getComponent(i); + if (i == parent.getComponentCount() - 1) { + compWidth = size.width - eachX; + } + each.setBounds(eachX, 0, compWidth, size.height); + eachX += compWidth; + } + } + } + + private InlineProgressIndicator createInlineDelegate(final TaskInfo info, final ProgressIndicatorEx original, final boolean compact) { + final Collection inlines = myOriginal2Inlines.get(original); + if (inlines != null) { + for (InlineProgressIndicator eachInline : inlines) { + if (eachInline.isCompact() == compact) return eachInline; + } + } + + final InlineProgressIndicator inline = new MyInlineProgressIndicator(compact, info, original); + + myInline2Original.put(inline, original); + myOriginal2Inlines.put(original, inline); + + if (compact) { + inline.getComponent().addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + handle(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + handle(e); + } + }); + } + + return inline; + } + + private void triggerPopupShowing() { + if (myPopup.isShowing()) { + hideProcessPopup(); + } + else { + openProcessPopup(true); + } + } + + private void restoreEmptyStatus() { + removeAll(); + setLayout(new BorderLayout()); + add(myRefreshAndInfoPanel, BorderLayout.CENTER); + + //long wastedTime = ProgressManagerImpl.getWastedTime(); + //if (ApplicationManagerEx.getApplicationEx().isInternal() && wastedTime > 10 * 60 * 1000) { + // JPanel wrapper = new JPanel(new BorderLayout()); + // wrapper.setOpaque(false); + // JLabel label = new JLabel(" Wasted time: " + formatTime(wastedTime) + " "); + // label.setForeground(UIUtil.getPanelBackground().darker()); + // label.setOpaque(false); + // wrapper.add(label, BorderLayout.CENTER); + // wrapper.add(myProgressIcon, BorderLayout.EAST); + // + // long time = System.currentTimeMillis() - ApplicationManagerEx.getApplicationEx().getStartTime(); + // long percentage = wastedTime * 100 / time; + // String period = new SimpleDateFormat("m 'min' H 'hours'").format(new Date(2000, 0, 1, 0, 0, 0).getTime() + time); + // + // List> list = ProgressManagerImpl.getTimeWasters(); + // StringBuilder s = new StringBuilder("Successfully wasted " + percentage +"% of your time in " + period + ":
"); + // for (Pair each : list) { + // s.append(""); + // s.append(each.first); + // s.append(":"); + // s.append(formatTime(each.second)); + // s.append(""); + // } + // s.append(""); + // wrapper.setToolTipText(s.toString()); + // add(wrapper, BorderLayout.EAST); + //} else { + + if (myProgressEnabled) { + add(myProgressIcon, BorderLayout.EAST); + } + + //} + + myProgressIcon.suspend(); + myRefreshAndInfoPanel.revalidate(); + myRefreshAndInfoPanel.repaint(); + } + + //private String formatTime(long t) { + // if (t < 1000) return "< 1 sec"; + // if (t < 60 * 1000) return (t / 1000) + " sec"; + // return "~" + (int)Math.ceil(t / (60 * 1000f)) + " min"; + //} + + public boolean isProcessWindowOpen() { + return myPopup.isShowing(); + } + + public void setProcessWindowOpen(final boolean open) { + if (open) { + openProcessPopup(true); + } + else { + hideProcessPopup(); + } + } + + private class MyInlineProgressIndicator extends InlineProgressIndicator { + private final ProgressIndicatorEx myOriginal; + private final TaskInfo myTask; + + public MyInlineProgressIndicator(final boolean compact, final TaskInfo task, final ProgressIndicatorEx original) { + super(compact, task); + myOriginal = original; + myTask = task; + original.addStateDelegate(this); + } + + public void cancel() { + super.cancel(); + updateProgress(); + } + + @Override + public void stop() { + super.stop(); + updateProgress(); + } + + @Override + protected boolean isFinished() { + return isFinished(myTask); + } + + @Override + public void finish(@NotNull final TaskInfo task) { + super.finish(task); + queueRunningUpdate(new Runnable() { + public void run() { + removeProgress(MyInlineProgressIndicator.this); + dispose(); + } + }); + } + + protected void cancelRequest() { + myOriginal.cancel(); + } + + protected void queueProgressUpdate(final Runnable update) { + myUpdateQueue.queue(new Update(MyInlineProgressIndicator.this, false, 1) { + public void run() { + ApplicationManager.getApplication().invokeLater(update); + } + }); + } + + protected void queueRunningUpdate(final Runnable update) { + myUpdateQueue.queue(new Update(new Object(), false, 0) { + public void run() { + ApplicationManager.getApplication().invokeLater(update); + } + }); + } + } + + private void runQuery() { + if (getRootPane() == null) return; + + synchronized (myOriginals) { + for (InlineProgressIndicator each : myInline2Original.keySet()) { + each.updateProgress(); + } + } + myQueryAlarm.addRequest(new Runnable() { + public void run() { + runQuery(); + } + }, 2000); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InlineProgressIndicator.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InlineProgressIndicator.java index 50f660544e4d..e160f4945b3f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InlineProgressIndicator.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InlineProgressIndicator.java @@ -1,393 +1,393 @@ -/* - * 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.wm.impl.status; - -import com.intellij.ide.IdeBundle; -import com.intellij.ide.ui.LafManager; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.progress.TaskInfo; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.ui.popup.IconButton; -import com.intellij.openapi.util.EmptyRunnable; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.wm.impl.content.GraphicsConfig; -import com.intellij.ui.InplaceButton; -import com.intellij.ui.components.panels.NonOpaquePanel; -import com.intellij.ui.components.panels.Wrapper; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class InlineProgressIndicator extends ProgressIndicatorBase implements Disposable { - - private final TextPanel myText = new TextPanel(); - private final TextPanel myText2 = new TextPanel(); - - private MyProgressBar myProgress; - - private JPanel myComponent; - - private final InplaceButton myCancelButton; - - private final boolean myCompact; - private TaskInfo myInfo; - - private final TextPanel myProcessName = new TextPanel(); - private boolean myDisposed; - - private long myLastTimeProgressWasAtZero; - private boolean myLastTimeProgressWasZero; - - public InlineProgressIndicator(boolean compact, TaskInfo processInfo) { - myCompact = compact; - myInfo = processInfo; - - myCancelButton = new InplaceButton(new IconButton(processInfo.getCancelTooltipText(), - IconLoader.getIcon("/process/stop.png"), - IconLoader.getIcon("/process/stopHovered.png")) { - }, new ActionListener() { - public void actionPerformed(final ActionEvent e) { - cancelRequest(); - } - }).setFillBg(true); - - myCancelButton.setVisible(myInfo.isCancellable()); - myCancelButton.setOpaque(false); - myCancelButton.setToolTipText(processInfo.getCancelTooltipText()); - myCancelButton.setFillBg(false); - - myProgress = new MyProgressBar(JProgressBar.HORIZONTAL, compact); - - myComponent = new MyComponent(compact, myProcessName); - if (myCompact) { - myComponent.setOpaque(false); - myComponent.setLayout(new BorderLayout(2, 0)); - final JPanel textAndProgress = new JPanel(new BorderLayout()); - textAndProgress.setOpaque(false); - textAndProgress.add(myText, BorderLayout.CENTER); - - final NonOpaquePanel progressWrapper = new NonOpaquePanel(new GridBagLayout()); - progressWrapper.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); - final GridBagConstraints c = new GridBagConstraints(); - c.weightx = 1; - c.weighty = 1; - c.insets = new Insets(0, 0, 1, myInfo.isCancellable() ? 0 : 4); - c.fill = GridBagConstraints.HORIZONTAL; - progressWrapper.add(myProgress, c); - - textAndProgress.add(progressWrapper, BorderLayout.EAST); - myComponent.add(textAndProgress, BorderLayout.CENTER); - myComponent.add(myCancelButton, BorderLayout.EAST); - myComponent.setToolTipText(processInfo.getTitle() + ". " + IdeBundle.message("progress.text.clickToViewProgressWindow")); - myProgress.setActive(false); - } else { - myComponent.setLayout(new BorderLayout()); - myProcessName.setText(processInfo.getTitle()); - myComponent.add(myProcessName, BorderLayout.NORTH); - final Font font = myProcessName.getFont(); - - final boolean aqua = LafManager.getInstance().isUnderAquaLookAndFeel(); - - int size = font.getSize() - (aqua ? 4 : 2); - if (size < (aqua ? 8 : 10)) { - size = (aqua ? 8 : 10); - } - myProcessName.setFont(font.deriveFont(Font.PLAIN, size)); - myProcessName.setForeground(UIUtil.getPanelBackground().brighter().brighter()); - myProcessName.setBorder(new EmptyBorder(2, 2, 2, 2)); - myProcessName.setDecorate(false); - - final NonOpaquePanel content = new NonOpaquePanel(new BorderLayout()); - content.setBorder(new EmptyBorder(2, 2, 2, myInfo.isCancellable() ? 2 : 4)); - myComponent.add(content, BorderLayout.CENTER); - - final Wrapper cancelWrapper = new Wrapper(myCancelButton); - cancelWrapper.setOpaque(false); - cancelWrapper.setBorder(new EmptyBorder(0, 3, 0, 2)); - - content.add(cancelWrapper, BorderLayout.EAST); - content.add(myText, BorderLayout.NORTH); - content.add(myProgress, BorderLayout.CENTER); - content.add(myText2, BorderLayout.SOUTH); - - myText.setDecorate(false); - myText2.setDecorate(false); - - myComponent.setBorder(new EmptyBorder(2, 2, 2, 2)); - myProgress.setActive(false); - } - - UIUtil.removeQuaquaVisualMarginsIn(myComponent); - - if (!myCompact) { - myProcessName.recomputeSize(); - myText.recomputeSize(); - myText2.recomputeSize(); - } - - } - - protected void cancelRequest() { - cancel(); - } - - private void updateRunning() { - queueRunningUpdate(EmptyRunnable.getInstance()); - } - - protected void updateProgress() { - queueProgressUpdate(new Runnable() { - public void run() { - if (isDisposed()) return; - - updateProgressNow(); - - myComponent.repaint(); - } - }); - } - - public void updateProgressNow() { - if (myLastTimeProgressWasAtZero == 0 && getFraction() == 0) { - myLastTimeProgressWasAtZero = System.currentTimeMillis(); - } - - final long delta = System.currentTimeMillis() - myLastTimeProgressWasAtZero; - boolean forcedIndeterminite = false; - - boolean indeterminate = isIndeterminate(); - if (!indeterminate && getFraction() == 0) { - if (delta > 2000 && !myCompact) { - indeterminate = true; - forcedIndeterminite = true; - } else { - forcedIndeterminite = false; - } - } - - final boolean visible = getFraction() > 0 || (indeterminate || forcedIndeterminite); - updateVisibility(myProgress, visible); - if (indeterminate || forcedIndeterminite) { - myProgress.setIndeterminate(true); - } - else { - myProgress.setIndeterminate(false); - myProgress.setMinimum(0); - myProgress.setMaximum(100); - } - if (getFraction() > 0) { - myProgress.setValue((int)(getFraction() * 99 + 1)); - } - - myText.setText(getText() != null ? getText() : ""); - myText2.setText(getText2() != null ? getText2() : ""); - - if (myCompact && myText.getText().length() == 0) { - myText.setText(myInfo.getTitle()); - } - - myCancelButton.setPainting(isCancelable()); - - if (getFraction() == 0) { - if (!myLastTimeProgressWasZero) { - myLastTimeProgressWasAtZero = System.currentTimeMillis(); - myLastTimeProgressWasZero = true; - } - } else { - myLastTimeProgressWasZero = false; - } - - final boolean isStopping = wasStarted() && (isCanceled() || !isRunning()) && !isFinished(); - if (isStopping) { - if (myCompact) { - myText.setText("Stopping - " + myText.getText()); - } else { - myProcessName.setText("Stopping - " + myInfo.getTitle()); - } - myText.setEnabled(false); - myText2.setEnabled(false); - myProgress.setEnabled(false); - - myCancelButton.setPainting(false); - } else { - myText.setEnabled(true); - myText2.setEnabled(true); - myProgress.setEnabled(true); - myCancelButton.setPainting(true); - } - } - - protected boolean isFinished() { - return false; - } - - protected void queueProgressUpdate(Runnable update) { - update.run(); - } - - protected void queueRunningUpdate(Runnable update) { - update.run(); - } - - - private void updateVisibility(MyProgressBar bar, boolean holdsValue) { - if (holdsValue && !bar.isActive()) { - bar.setActive(true); - bar.revalidate(); - bar.repaint(); - myComponent.revalidate(); - myComponent.repaint(); - } - else if (!holdsValue && bar.isActive()) { - bar.setActive(false); - bar.revalidate(); - bar.repaint(); - myComponent.revalidate(); - myComponent.repaint(); - } - } - - protected void onProgressChange() { - updateProgress(); - } - - protected void onRunningChange() { - updateRunning(); - } - - public JComponent getComponent() { - return myComponent; - } - - public boolean isCompact() { - return myCompact; - } - - public TaskInfo getInfo() { - return myInfo; - } - - private static class MyProgressBar extends JProgressBar { - - private boolean myActive = true; - private final boolean myCompact; - - public MyProgressBar(final int orient, boolean compact) { - super(orient); - myCompact = compact; - putClientProperty("JComponent.sizeVariant", "mini"); - } - - - public void paint(final Graphics g) { - if (!myActive) return; - super.paint(g); - } - - - public boolean isActive() { - return myActive; - } - - - public Dimension getPreferredSize() { - if (!myActive && myCompact) return new Dimension(0, 0); - return super.getPreferredSize(); - } - - public void setActive(final boolean active) { - myActive = active; - } - } - - private class MyComponent extends JPanel { - private final boolean myCompact; - private final JComponent myProcessName; - - private MyComponent(final boolean compact, final JComponent processName) { - myCompact = compact; - myProcessName = processName; - addMouseListener(new MouseAdapter() { - public void mousePressed(final MouseEvent e) { - if (UIUtil.isCloseClick(e) && getBounds().contains(e.getX(), e.getY())) { - cancelRequest(); - } - } - }); - } - - protected void paintComponent(final Graphics g) { - if (myCompact) { - super.paintComponent(g); - return; - } - - final GraphicsConfig c = new GraphicsConfig(g); - c.setAntialiasing(true); - - int arc = 8; - - g.setColor(UIUtil.getPanelBackground()); - g.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, arc, arc); - - Color bg = getBackground().darker().darker(); - bg = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 230); - - g.setColor(bg); - - final Rectangle bounds = myProcessName.getBounds(); - final Rectangle label = SwingUtilities.convertRectangle(myProcessName.getParent(), bounds, this); - - - g.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, arc, arc); - - g.setColor(UIUtil.getPanelBackground()); - g.fillRoundRect(0, getHeight() / 2, getWidth() - 1, getHeight() / 2, arc, arc); - g.fillRect(0, (int)label.getMaxY() + 1, getWidth() - 1, getHeight() / 2); - - g.setColor(bg); - g.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, arc, arc); - - c.restore(); - } - } - - public void dispose() { - if (myDisposed) return; - - myDisposed = true; - - myComponent.removeAll(); - - myComponent = null; - - if (myProgress != null) { - UIUtil.disposeProgress(myProgress); - } - myProgress = null; - myInfo = null; - } - - private boolean isDisposed() { - return myDisposed; - } -} +/* + * 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.wm.impl.status; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.ui.LafManager; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.progress.TaskInfo; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.ui.popup.IconButton; +import com.intellij.openapi.util.EmptyRunnable; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.wm.impl.content.GraphicsConfig; +import com.intellij.ui.InplaceButton; +import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.ui.components.panels.Wrapper; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class InlineProgressIndicator extends ProgressIndicatorBase implements Disposable { + + private final TextPanel myText = new TextPanel(); + private final TextPanel myText2 = new TextPanel(); + + private MyProgressBar myProgress; + + private JPanel myComponent; + + private final InplaceButton myCancelButton; + + private final boolean myCompact; + private TaskInfo myInfo; + + private final TextPanel myProcessName = new TextPanel(); + private boolean myDisposed; + + private long myLastTimeProgressWasAtZero; + private boolean myLastTimeProgressWasZero; + + public InlineProgressIndicator(boolean compact, TaskInfo processInfo) { + myCompact = compact; + myInfo = processInfo; + + myCancelButton = new InplaceButton(new IconButton(processInfo.getCancelTooltipText(), + IconLoader.getIcon("/process/stop.png"), + IconLoader.getIcon("/process/stopHovered.png")) { + }, new ActionListener() { + public void actionPerformed(final ActionEvent e) { + cancelRequest(); + } + }).setFillBg(true); + + myCancelButton.setVisible(myInfo.isCancellable()); + myCancelButton.setOpaque(false); + myCancelButton.setToolTipText(processInfo.getCancelTooltipText()); + myCancelButton.setFillBg(false); + + myProgress = new MyProgressBar(JProgressBar.HORIZONTAL, compact); + + myComponent = new MyComponent(compact, myProcessName); + if (myCompact) { + myComponent.setOpaque(false); + myComponent.setLayout(new BorderLayout(2, 0)); + final JPanel textAndProgress = new JPanel(new BorderLayout()); + textAndProgress.setOpaque(false); + textAndProgress.add(myText, BorderLayout.CENTER); + + final NonOpaquePanel progressWrapper = new NonOpaquePanel(new GridBagLayout()); + progressWrapper.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); + final GridBagConstraints c = new GridBagConstraints(); + c.weightx = 1; + c.weighty = 1; + c.insets = new Insets(0, 0, 1, myInfo.isCancellable() ? 0 : 4); + c.fill = GridBagConstraints.HORIZONTAL; + progressWrapper.add(myProgress, c); + + textAndProgress.add(progressWrapper, BorderLayout.EAST); + myComponent.add(textAndProgress, BorderLayout.CENTER); + myComponent.add(myCancelButton, BorderLayout.EAST); + myComponent.setToolTipText(processInfo.getTitle() + ". " + IdeBundle.message("progress.text.clickToViewProgressWindow")); + myProgress.setActive(false); + } else { + myComponent.setLayout(new BorderLayout()); + myProcessName.setText(processInfo.getTitle()); + myComponent.add(myProcessName, BorderLayout.NORTH); + final Font font = myProcessName.getFont(); + + final boolean aqua = LafManager.getInstance().isUnderAquaLookAndFeel(); + + int size = font.getSize() - (aqua ? 4 : 2); + if (size < (aqua ? 8 : 10)) { + size = (aqua ? 8 : 10); + } + myProcessName.setFont(font.deriveFont(Font.PLAIN, size)); + myProcessName.setForeground(UIUtil.getPanelBackground().brighter().brighter()); + myProcessName.setBorder(new EmptyBorder(2, 2, 2, 2)); + myProcessName.setDecorate(false); + + final NonOpaquePanel content = new NonOpaquePanel(new BorderLayout()); + content.setBorder(new EmptyBorder(2, 2, 2, myInfo.isCancellable() ? 2 : 4)); + myComponent.add(content, BorderLayout.CENTER); + + final Wrapper cancelWrapper = new Wrapper(myCancelButton); + cancelWrapper.setOpaque(false); + cancelWrapper.setBorder(new EmptyBorder(0, 3, 0, 2)); + + content.add(cancelWrapper, BorderLayout.EAST); + content.add(myText, BorderLayout.NORTH); + content.add(myProgress, BorderLayout.CENTER); + content.add(myText2, BorderLayout.SOUTH); + + myText.setDecorate(false); + myText2.setDecorate(false); + + myComponent.setBorder(new EmptyBorder(2, 2, 2, 2)); + myProgress.setActive(false); + } + + UIUtil.removeQuaquaVisualMarginsIn(myComponent); + + if (!myCompact) { + myProcessName.recomputeSize(); + myText.recomputeSize(); + myText2.recomputeSize(); + } + + } + + protected void cancelRequest() { + cancel(); + } + + private void updateRunning() { + queueRunningUpdate(EmptyRunnable.getInstance()); + } + + protected void updateProgress() { + queueProgressUpdate(new Runnable() { + public void run() { + if (isDisposed()) return; + + updateProgressNow(); + + myComponent.repaint(); + } + }); + } + + public void updateProgressNow() { + if (myLastTimeProgressWasAtZero == 0 && getFraction() == 0) { + myLastTimeProgressWasAtZero = System.currentTimeMillis(); + } + + final long delta = System.currentTimeMillis() - myLastTimeProgressWasAtZero; + boolean forcedIndeterminite = false; + + boolean indeterminate = isIndeterminate(); + if (!indeterminate && getFraction() == 0) { + if (delta > 2000 && !myCompact) { + indeterminate = true; + forcedIndeterminite = true; + } else { + forcedIndeterminite = false; + } + } + + final boolean visible = getFraction() > 0 || (indeterminate || forcedIndeterminite); + updateVisibility(myProgress, visible); + if (indeterminate || forcedIndeterminite) { + myProgress.setIndeterminate(true); + } + else { + myProgress.setIndeterminate(false); + myProgress.setMinimum(0); + myProgress.setMaximum(100); + } + if (getFraction() > 0) { + myProgress.setValue((int)(getFraction() * 99 + 1)); + } + + myText.setText(getText() != null ? getText() : ""); + myText2.setText(getText2() != null ? getText2() : ""); + + if (myCompact && myText.getText().length() == 0) { + myText.setText(myInfo.getTitle()); + } + + myCancelButton.setPainting(isCancelable()); + + if (getFraction() == 0) { + if (!myLastTimeProgressWasZero) { + myLastTimeProgressWasAtZero = System.currentTimeMillis(); + myLastTimeProgressWasZero = true; + } + } else { + myLastTimeProgressWasZero = false; + } + + final boolean isStopping = wasStarted() && (isCanceled() || !isRunning()) && !isFinished(); + if (isStopping) { + if (myCompact) { + myText.setText("Stopping - " + myText.getText()); + } else { + myProcessName.setText("Stopping - " + myInfo.getTitle()); + } + myText.setEnabled(false); + myText2.setEnabled(false); + myProgress.setEnabled(false); + + myCancelButton.setPainting(false); + } else { + myText.setEnabled(true); + myText2.setEnabled(true); + myProgress.setEnabled(true); + myCancelButton.setPainting(true); + } + } + + protected boolean isFinished() { + return false; + } + + protected void queueProgressUpdate(Runnable update) { + update.run(); + } + + protected void queueRunningUpdate(Runnable update) { + update.run(); + } + + + private void updateVisibility(MyProgressBar bar, boolean holdsValue) { + if (holdsValue && !bar.isActive()) { + bar.setActive(true); + bar.revalidate(); + bar.repaint(); + myComponent.revalidate(); + myComponent.repaint(); + } + else if (!holdsValue && bar.isActive()) { + bar.setActive(false); + bar.revalidate(); + bar.repaint(); + myComponent.revalidate(); + myComponent.repaint(); + } + } + + protected void onProgressChange() { + updateProgress(); + } + + protected void onRunningChange() { + updateRunning(); + } + + public JComponent getComponent() { + return myComponent; + } + + public boolean isCompact() { + return myCompact; + } + + public TaskInfo getInfo() { + return myInfo; + } + + private static class MyProgressBar extends JProgressBar { + + private boolean myActive = true; + private final boolean myCompact; + + public MyProgressBar(final int orient, boolean compact) { + super(orient); + myCompact = compact; + putClientProperty("JComponent.sizeVariant", "mini"); + } + + + public void paint(final Graphics g) { + if (!myActive) return; + super.paint(g); + } + + + public boolean isActive() { + return myActive; + } + + + public Dimension getPreferredSize() { + if (!myActive && myCompact) return new Dimension(0, 0); + return super.getPreferredSize(); + } + + public void setActive(final boolean active) { + myActive = active; + } + } + + private class MyComponent extends JPanel { + private final boolean myCompact; + private final JComponent myProcessName; + + private MyComponent(final boolean compact, final JComponent processName) { + myCompact = compact; + myProcessName = processName; + addMouseListener(new MouseAdapter() { + public void mousePressed(final MouseEvent e) { + if (UIUtil.isCloseClick(e) && getBounds().contains(e.getX(), e.getY())) { + cancelRequest(); + } + } + }); + } + + protected void paintComponent(final Graphics g) { + if (myCompact) { + super.paintComponent(g); + return; + } + + final GraphicsConfig c = new GraphicsConfig(g); + c.setAntialiasing(true); + + int arc = 8; + + g.setColor(UIUtil.getPanelBackground()); + g.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, arc, arc); + + Color bg = getBackground().darker().darker(); + bg = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 230); + + g.setColor(bg); + + final Rectangle bounds = myProcessName.getBounds(); + final Rectangle label = SwingUtilities.convertRectangle(myProcessName.getParent(), bounds, this); + + + g.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, arc, arc); + + g.setColor(UIUtil.getPanelBackground()); + g.fillRoundRect(0, getHeight() / 2, getWidth() - 1, getHeight() / 2, arc, arc); + g.fillRect(0, (int)label.getMaxY() + 1, getWidth() - 1, getHeight() / 2); + + g.setColor(bg); + g.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, arc, arc); + + c.restore(); + } + } + + public void dispose() { + if (myDisposed) return; + + myDisposed = true; + + myComponent.removeAll(); + + myComponent = null; + + if (myProgress != null) { + UIUtil.disposeProgress(myProgress); + } + myProgress = null; + myInfo = null; + } + + private boolean isDisposed() { + return myDisposed; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InsertOverwritePanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InsertOverwritePanel.java index f48cf2212baa..8ca63ae949b3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InsertOverwritePanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InsertOverwritePanel.java @@ -1,87 +1,87 @@ -/* - * 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.wm.impl.status; - -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.ui.UIBundle; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseEvent; - -/** - * @author cdr - */ -public class InsertOverwritePanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation { - - @NotNull - public String ID() { - return "InsertOverwrite"; - } - - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return this; - } - - @Override - public StatusBarWidget copy() { - return new InsertOverwritePanel(); - } - - - @NotNull - public String getText() { - final Editor editor = getEditor(); - if (editor != null) { - return editor.isColumnMode() - ? UIBundle.message("status.bar.column.status.text") - : editor.isInsertMode() - ? UIBundle.message("status.bar.insert.status.text") - : UIBundle.message("status.bar.overwrite.status.text"); - - } - - return ""; - } - - @NotNull - public String getMaxPossibleText() { - return UIBundle.message("status.bar.overwrite.status.text"); - } - - @Override - public float getAlignment() { - return JComponent.LEFT_ALIGNMENT; - } - - public String getTooltipText() { - return null; - } - - public Consumer getClickConsumer() { - return null; - } - -} +/* + * 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.wm.impl.status; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.ui.UIBundle; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; + +/** + * @author cdr + */ +public class InsertOverwritePanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation { + + @NotNull + public String ID() { + return "InsertOverwrite"; + } + + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return this; + } + + @Override + public StatusBarWidget copy() { + return new InsertOverwritePanel(); + } + + + @NotNull + public String getText() { + final Editor editor = getEditor(); + if (editor != null) { + return editor.isColumnMode() + ? UIBundle.message("status.bar.column.status.text") + : editor.isInsertMode() + ? UIBundle.message("status.bar.insert.status.text") + : UIBundle.message("status.bar.overwrite.status.text"); + + } + + return ""; + } + + @NotNull + public String getMaxPossibleText() { + return UIBundle.message("status.bar.overwrite.status.text"); + } + + @Override + public float getAlignment() { + return JComponent.LEFT_ALIGNMENT; + } + + public String getTooltipText() { + return null; + } + + public Consumer getClickConsumer() { + return null; + } + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java index cde8f41a22af..3c74a11a89d9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java @@ -1,190 +1,190 @@ -/* - * 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.wm.impl.status; - -import com.intellij.ide.DataManager; -import com.intellij.ide.util.GotoLineNumberDialog; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.SelectionModel; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.ui.UIBundle; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseEvent; - -public class PositionPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation, CaretListener, SelectionListener { - private String myText; - - public PositionPanel(@NotNull final Project project) { - super(project); - } - - @Override - public void selectionChanged(FileEditorManagerEvent event) { - updatePosition(getEditor()); - } - - @NotNull - public String ID() { - return "Position"; - } - - @Override - public StatusBarWidget copy() { - return new PositionPanel(getProject()); - } - - public WidgetPresentation getPresentation(@NotNull final PlatformType type) { - return this; - } - - @NotNull - public String getText() { - return myText == null ? "" : myText; - } - - @NotNull - public String getMaxPossibleText() { - return "#############"; - } - - @Override - public float getAlignment() { - return JComponent.CENTER_ALIGNMENT; - } - - public String getTooltipText() { - return UIBundle.message("go.to.line.command.double.click"); - } - - public Consumer getClickConsumer() { - return new Consumer() { - public void consume(MouseEvent mouseEvent) { - final Project project = getProject(); - if (project == null) return; - final Editor editor = getEditor(); - if (editor == null) return; - final CommandProcessor processor = CommandProcessor.getInstance(); - processor.executeCommand( - project, new Runnable() { - public void run() { - final GotoLineNumberDialog dialog = new GotoLineNumberDialog(project, editor); - dialog.show(); - IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation(); - } - }, - UIBundle.message("go.to.line.command.name"), - null - ); - } - }; - } - - public void dispose() { - final EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); - multicaster.removeCaretListener(this); - multicaster.removeSelectionListener(this); - super.dispose(); - } - - public void install(@NotNull StatusBar statusBar) { - super.install(statusBar); - final EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); - multicaster.addCaretListener(this); - multicaster.addSelectionListener(this); - } - - private static void appendLogicalPosition(LogicalPosition caret, StringBuilder message) { - message.append(caret.line + 1); - message.append(":"); - message.append(caret.column + 1); - } - - @Override - public void selectionChanged(final SelectionEvent e) { - final Editor editor = e.getEditor(); - Project project = editor.getProject(); - if (project != null && FileEditorManager.getInstance(project).getSelectedTextEditor() == e.getEditor()) { - updatePosition(editor); - } - } - - public void caretPositionChanged(final CaretEvent e) { - final Editor editor = e.getEditor(); - Project project = editor.getProject(); - if (project != null && FileEditorManager.getInstance(project).getSelectedTextEditor() == e.getEditor()) { - updatePosition(editor); - } - } - - private void updatePosition(final Editor editor) { - if (editor == null) { - myText = ""; - myStatusBar.updateWidget(ID()); - } else { - if (!isOurEditor(editor)) return; - myText = getPositionText(editor); - myStatusBar.updateWidget(ID()); - } - } - - private String getPositionText(Editor editor) { - if (!editor.isDisposed() && myStatusBar != null) { - StringBuilder message = new StringBuilder(); - - SelectionModel selectionModel = editor.getSelectionModel(); - if (selectionModel.hasBlockSelection()) { - LogicalPosition start = selectionModel.getBlockStart(); - LogicalPosition end = selectionModel.getBlockEnd(); - appendLogicalPosition(start, message); - message.append("-"); - appendLogicalPosition(new LogicalPosition(Math.abs(end.line - start.line), Math.abs(end.column - start.column) - 1), message); - } - else { - LogicalPosition caret = editor.getCaretModel().getLogicalPosition(); - - appendLogicalPosition(caret, message); - if (selectionModel.hasSelection()) { - int len = Math.abs(selectionModel.getSelectionStart() - selectionModel.getSelectionEnd()); - message.append("/"); - message.append(len); - } - } - - return message.toString(); - } - else { - return ""; - } - } -} +/* + * 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.wm.impl.status; + +import com.intellij.ide.DataManager; +import com.intellij.ide.util.GotoLineNumberDialog; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.ui.UIBundle; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; + +public class PositionPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation, CaretListener, SelectionListener { + private String myText; + + public PositionPanel(@NotNull final Project project) { + super(project); + } + + @Override + public void selectionChanged(FileEditorManagerEvent event) { + updatePosition(getEditor()); + } + + @NotNull + public String ID() { + return "Position"; + } + + @Override + public StatusBarWidget copy() { + return new PositionPanel(getProject()); + } + + public WidgetPresentation getPresentation(@NotNull final PlatformType type) { + return this; + } + + @NotNull + public String getText() { + return myText == null ? "" : myText; + } + + @NotNull + public String getMaxPossibleText() { + return "#############"; + } + + @Override + public float getAlignment() { + return JComponent.CENTER_ALIGNMENT; + } + + public String getTooltipText() { + return UIBundle.message("go.to.line.command.double.click"); + } + + public Consumer getClickConsumer() { + return new Consumer() { + public void consume(MouseEvent mouseEvent) { + final Project project = getProject(); + if (project == null) return; + final Editor editor = getEditor(); + if (editor == null) return; + final CommandProcessor processor = CommandProcessor.getInstance(); + processor.executeCommand( + project, new Runnable() { + public void run() { + final GotoLineNumberDialog dialog = new GotoLineNumberDialog(project, editor); + dialog.show(); + IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation(); + } + }, + UIBundle.message("go.to.line.command.name"), + null + ); + } + }; + } + + public void dispose() { + final EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); + multicaster.removeCaretListener(this); + multicaster.removeSelectionListener(this); + super.dispose(); + } + + public void install(@NotNull StatusBar statusBar) { + super.install(statusBar); + final EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); + multicaster.addCaretListener(this); + multicaster.addSelectionListener(this); + } + + private static void appendLogicalPosition(LogicalPosition caret, StringBuilder message) { + message.append(caret.line + 1); + message.append(":"); + message.append(caret.column + 1); + } + + @Override + public void selectionChanged(final SelectionEvent e) { + final Editor editor = e.getEditor(); + Project project = editor.getProject(); + if (project != null && FileEditorManager.getInstance(project).getSelectedTextEditor() == e.getEditor()) { + updatePosition(editor); + } + } + + public void caretPositionChanged(final CaretEvent e) { + final Editor editor = e.getEditor(); + Project project = editor.getProject(); + if (project != null && FileEditorManager.getInstance(project).getSelectedTextEditor() == e.getEditor()) { + updatePosition(editor); + } + } + + private void updatePosition(final Editor editor) { + if (editor == null) { + myText = ""; + myStatusBar.updateWidget(ID()); + } else { + if (!isOurEditor(editor)) return; + myText = getPositionText(editor); + myStatusBar.updateWidget(ID()); + } + } + + private String getPositionText(Editor editor) { + if (!editor.isDisposed() && myStatusBar != null) { + StringBuilder message = new StringBuilder(); + + SelectionModel selectionModel = editor.getSelectionModel(); + if (selectionModel.hasBlockSelection()) { + LogicalPosition start = selectionModel.getBlockStart(); + LogicalPosition end = selectionModel.getBlockEnd(); + appendLogicalPosition(start, message); + message.append("-"); + appendLogicalPosition(new LogicalPosition(Math.abs(end.line - start.line), Math.abs(end.column - start.column) - 1), message); + } + else { + LogicalPosition caret = editor.getCaretModel().getLogicalPosition(); + + appendLogicalPosition(caret, message); + if (selectionModel.hasSelection()) { + int len = Math.abs(selectionModel.getSelectionStart() - selectionModel.getSelectionEnd()); + message.append("/"); + message.append(len); + } + } + + return message.toString(); + } + else { + return ""; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/StatusBarUI.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/StatusBarUI.java index 0ac8138a1979..dbe3f867d0ff 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/StatusBarUI.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/StatusBarUI.java @@ -1,112 +1,112 @@ -/* - * 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.wm.impl.status; - -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.plaf.ComponentUI; -import java.awt.*; - -/** - * User: spLeaner - */ -public class StatusBarUI extends ComponentUI { - private static final Dimension MAX_SIZE = new Dimension(Integer.MAX_VALUE, 23); - private static final Dimension MIN_SIZE = new Dimension(100, 23); - - private static final Border BACKGROUND_PAINTER = new BackgroundPainter(); - - @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"}) - public static ComponentUI createUI(JComponent x) { - return new StatusBarUI(); - } - - @Override - public void installUI(JComponent c) { - super.installUI(c); - } - - @Override - public void uninstallUI(JComponent c) { - super.uninstallUI(c); - } - - @Override - public void paint(final Graphics g, final JComponent c) { - final Rectangle bounds = c.getBounds(); - BACKGROUND_PAINTER.paintBorder(c, g, 0, 0, bounds.width, bounds.height); - } - - @Override - public void update(Graphics g, JComponent c) { - super.update(g, c); - } - - @Override - public Dimension getPreferredSize(JComponent c) { - return getMaximumSize(c); - } - - @Override - public Dimension getMinimumSize(JComponent c) { - return MIN_SIZE; // TODO - } - - @Override - public Dimension getMaximumSize(JComponent c) { - return MAX_SIZE; - } - - private static final class BackgroundPainter implements Border { - private static final Color BORDER_TOP_COLOR = new Color(145, 145, 145); - private static final Color BORDER2_TOP_COLOR = new Color(255, 255, 255); - private static final Color BORDER_BOTTOM_COLOR = new Color(255, 255, 255); - - private static final Color BG_COLOR = new Color(238, 238, 238); - - private static final Insets INSETS = new Insets(0, 0, 0, 0); - - public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { - final Graphics2D g2d = (Graphics2D) g.create(); - - final Color background = UIUtil.getPanelBackground(); - - g2d.setColor(background); - g2d.fillRect(0, 0, width, height); - - g2d.setColor(BORDER_TOP_COLOR); - g2d.drawLine(0, 0, width, 0); - - g2d.setColor(BORDER2_TOP_COLOR); - g2d.drawLine(0, 1, width, 1); - - g2d.setColor(BORDER_BOTTOM_COLOR); - g2d.drawLine(0, height, width, height); - - g2d.dispose(); - } - - public Insets getBorderInsets(Component c) { - return INSETS; - } - - public boolean isBorderOpaque() { - return true; - } - } -} +/* + * 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.wm.impl.status; + +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.plaf.ComponentUI; +import java.awt.*; + +/** + * User: spLeaner + */ +public class StatusBarUI extends ComponentUI { + private static final Dimension MAX_SIZE = new Dimension(Integer.MAX_VALUE, 23); + private static final Dimension MIN_SIZE = new Dimension(100, 23); + + private static final Border BACKGROUND_PAINTER = new BackgroundPainter(); + + @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"}) + public static ComponentUI createUI(JComponent x) { + return new StatusBarUI(); + } + + @Override + public void installUI(JComponent c) { + super.installUI(c); + } + + @Override + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + } + + @Override + public void paint(final Graphics g, final JComponent c) { + final Rectangle bounds = c.getBounds(); + BACKGROUND_PAINTER.paintBorder(c, g, 0, 0, bounds.width, bounds.height); + } + + @Override + public void update(Graphics g, JComponent c) { + super.update(g, c); + } + + @Override + public Dimension getPreferredSize(JComponent c) { + return getMaximumSize(c); + } + + @Override + public Dimension getMinimumSize(JComponent c) { + return MIN_SIZE; // TODO + } + + @Override + public Dimension getMaximumSize(JComponent c) { + return MAX_SIZE; + } + + private static final class BackgroundPainter implements Border { + private static final Color BORDER_TOP_COLOR = new Color(145, 145, 145); + private static final Color BORDER2_TOP_COLOR = new Color(255, 255, 255); + private static final Color BORDER_BOTTOM_COLOR = new Color(255, 255, 255); + + private static final Color BG_COLOR = new Color(238, 238, 238); + + private static final Insets INSETS = new Insets(0, 0, 0, 0); + + public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { + final Graphics2D g2d = (Graphics2D) g.create(); + + final Color background = UIUtil.getPanelBackground(); + + g2d.setColor(background); + g2d.fillRect(0, 0, width, height); + + g2d.setColor(BORDER_TOP_COLOR); + g2d.drawLine(0, 0, width, 0); + + g2d.setColor(BORDER2_TOP_COLOR); + g2d.drawLine(0, 1, width, 1); + + g2d.setColor(BORDER_BOTTOM_COLOR); + g2d.drawLine(0, height, width, height); + + g2d.dispose(); + } + + public Insets getBorderInsets(Component c) { + return INSETS; + } + + public boolean isBorderOpaque() { + return true; + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/ToggleReadOnlyAttributePanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/ToggleReadOnlyAttributePanel.java index e5a3b99baae6..48605f2b992c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/ToggleReadOnlyAttributePanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/ToggleReadOnlyAttributePanel.java @@ -1,136 +1,136 @@ -/* - * 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.wm.impl.status; - -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.StatusBarWidget; -import com.intellij.ui.UIBundle; -import com.intellij.util.Consumer; -import com.intellij.util.io.ReadOnlyAttributeUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.event.MouseEvent; -import java.io.IOException; - -public class ToggleReadOnlyAttributePanel implements StatusBarWidget.Multiframe, StatusBarWidget.IconPresentation { - private static final Icon myLockedIcon = IconLoader.getIcon("/ide/readonly.png"); - private static final Icon myUnlockedIcon = IconLoader.getIcon("/ide/readwrite.png"); - - private StatusBar myStatusBar; - - @NotNull - public Icon getIcon() { - final Editor editor = getEditor(); - return editor == null || editor.getDocument().isWritable() ? myUnlockedIcon : myLockedIcon; - } - - @NotNull - public String ID() { - return "ReadOnlyAttribute"; - } - - - @Override - public StatusBarWidget copy() { - return new ToggleReadOnlyAttributePanel(); - } - - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return this; - } - - public void dispose() { - myStatusBar = null; - } - - public void install(@NotNull StatusBar statusBar) { - myStatusBar = statusBar; - } - - public String getTooltipText() { - return isReadonlyApplicable() ? UIBundle.message("read.only.attr.panel.double.click.to.toggle.attr.tooltip.text") : null; - } - - public Consumer getClickConsumer() { - return new Consumer() { - public void consume(MouseEvent mouseEvent) { - final Project project = getProject(); - if (project == null) { - return; - } - final FileEditorManager editorManager = FileEditorManager.getInstance(project); - final VirtualFile[] files = editorManager.getSelectedFiles(); - if (!isReadOnlyApplicableForFiles(files)) { - return; - } - FileDocumentManager.getInstance().saveAllDocuments(); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - ReadOnlyAttributeUtil.setReadOnlyAttribute(files[0], files[0].isWritable()); - myStatusBar.updateWidget(ID()); - } - catch (IOException e) { - Messages.showMessageDialog(project, e.getMessage(), UIBundle.message("error.dialog.title"), Messages.getErrorIcon()); - } - } - }); - } - }; - } - - private boolean isReadonlyApplicable() { - final Project project = getProject(); - if (project == null) return false; - final FileEditorManager editorManager = FileEditorManager.getInstance(project); - if (editorManager == null) return false; - VirtualFile[] selectedFiles = editorManager.getSelectedFiles(); - return isReadOnlyApplicableForFiles(selectedFiles); - } - - private static boolean isReadOnlyApplicableForFiles(final VirtualFile[] files) { - return files.length > 0 && !files[0].getFileSystem().isReadOnly(); - } - - @Nullable - private Editor getEditor() { - final Project project = getProject(); - if (project != null) { - final FileEditorManager manager = FileEditorManager.getInstance(project); - return manager.getSelectedTextEditor(); - } - - return null; - } - - @Nullable - private Project getProject() { - return PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext((JComponent) myStatusBar)); - } -} +/* + * 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.wm.impl.status; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.ui.UIBundle; +import com.intellij.util.Consumer; +import com.intellij.util.io.ReadOnlyAttributeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.event.MouseEvent; +import java.io.IOException; + +public class ToggleReadOnlyAttributePanel implements StatusBarWidget.Multiframe, StatusBarWidget.IconPresentation { + private static final Icon myLockedIcon = IconLoader.getIcon("/ide/readonly.png"); + private static final Icon myUnlockedIcon = IconLoader.getIcon("/ide/readwrite.png"); + + private StatusBar myStatusBar; + + @NotNull + public Icon getIcon() { + final Editor editor = getEditor(); + return editor == null || editor.getDocument().isWritable() ? myUnlockedIcon : myLockedIcon; + } + + @NotNull + public String ID() { + return "ReadOnlyAttribute"; + } + + + @Override + public StatusBarWidget copy() { + return new ToggleReadOnlyAttributePanel(); + } + + public WidgetPresentation getPresentation(@NotNull PlatformType type) { + return this; + } + + public void dispose() { + myStatusBar = null; + } + + public void install(@NotNull StatusBar statusBar) { + myStatusBar = statusBar; + } + + public String getTooltipText() { + return isReadonlyApplicable() ? UIBundle.message("read.only.attr.panel.double.click.to.toggle.attr.tooltip.text") : null; + } + + public Consumer getClickConsumer() { + return new Consumer() { + public void consume(MouseEvent mouseEvent) { + final Project project = getProject(); + if (project == null) { + return; + } + final FileEditorManager editorManager = FileEditorManager.getInstance(project); + final VirtualFile[] files = editorManager.getSelectedFiles(); + if (!isReadOnlyApplicableForFiles(files)) { + return; + } + FileDocumentManager.getInstance().saveAllDocuments(); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + try { + ReadOnlyAttributeUtil.setReadOnlyAttribute(files[0], files[0].isWritable()); + myStatusBar.updateWidget(ID()); + } + catch (IOException e) { + Messages.showMessageDialog(project, e.getMessage(), UIBundle.message("error.dialog.title"), Messages.getErrorIcon()); + } + } + }); + } + }; + } + + private boolean isReadonlyApplicable() { + final Project project = getProject(); + if (project == null) return false; + final FileEditorManager editorManager = FileEditorManager.getInstance(project); + if (editorManager == null) return false; + VirtualFile[] selectedFiles = editorManager.getSelectedFiles(); + return isReadOnlyApplicableForFiles(selectedFiles); + } + + private static boolean isReadOnlyApplicableForFiles(final VirtualFile[] files) { + return files.length > 0 && !files[0].getFileSystem().isReadOnly(); + } + + @Nullable + private Editor getEditor() { + final Project project = getProject(); + if (project != null) { + final FileEditorManager manager = FileEditorManager.getInstance(project); + return manager.getSelectedTextEditor(); + } + + return null; + } + + @Nullable + private Project getProject() { + return PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext((JComponent) myStatusBar)); + } +} diff --git a/platform/platform-impl/src/com/intellij/platform/PlatformProjectOpenProcessor.java b/platform/platform-impl/src/com/intellij/platform/PlatformProjectOpenProcessor.java index 5fdaaf1030ec..c65e2bac9e46 100644 --- a/platform/platform-impl/src/com/intellij/platform/PlatformProjectOpenProcessor.java +++ b/platform/platform-impl/src/com/intellij/platform/PlatformProjectOpenProcessor.java @@ -1,145 +1,145 @@ -/* - * 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.platform; - -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.DumbAwareRunnable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.project.impl.ProjectManagerImpl; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.projectImport.ProjectOpenProcessor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.File; - -/** - * @author max - */ -public class PlatformProjectOpenProcessor extends ProjectOpenProcessor { - private static final Logger LOG = Logger.getInstance("#com.intellij.platform.PlatformProjectOpenProcessor"); - - public static PlatformProjectOpenProcessor getInstance() { - PlatformProjectOpenProcessor projectOpenProcessor = getInstanceIfItExists(); - assert projectOpenProcessor != null; - return projectOpenProcessor; - } - - public static PlatformProjectOpenProcessor getInstanceIfItExists() { - ProjectOpenProcessor[] processors = Extensions.getExtensions(EXTENSION_POINT_NAME); - for(ProjectOpenProcessor processor: processors) { - if (processor instanceof PlatformProjectOpenProcessor) { - return (PlatformProjectOpenProcessor) processor; - } - } - return null; - } - - public boolean canOpenProject(final VirtualFile file) { - return file.isDirectory() || !file.getFileType().isBinary(); - } - - @Override - public boolean lookForProjectsInDirectory() { - return false; - } - - @Nullable - public Project doOpenProject(@NotNull final VirtualFile virtualFile, final Project projectToClose, final boolean forceOpenInNewFrame) { - VirtualFile baseDir = virtualFile.isDirectory() ? virtualFile : virtualFile.getParent(); - final File projectDir = new File(baseDir.getPath(), ".idea"); - - Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); - if (!forceOpenInNewFrame && openProjects.length > 0) { - int exitCode = ProjectUtil.confirmOpenNewProject(); - if (exitCode == 1) { // "No" option - if (!ProjectUtil.closeAndDispose(projectToClose != null ? projectToClose : openProjects[openProjects.length - 1])) return null; - } - else if (exitCode != 0) { // not "Yes" - return null; - } - } - - final ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); - Project project = null; - Ref cancelled = new Ref(); - if (projectDir.exists()) { - try { - project = ((ProjectManagerImpl) projectManager).loadProjectWithProgress(baseDir.getPath(), cancelled); - } - catch (Exception e) { - // ignore - } - } - else { - projectDir.mkdirs(); - } - if (project == null && cancelled.isNull()) { - project = projectManager.newProject(projectDir.getParentFile().getName(), projectDir.getParent(), true, false); - } - if (project == null) return null; - ProjectBaseDirectory.getInstance(project).setBaseDir(baseDir); - for(DirectoryProjectConfigurator configurator: Extensions.getExtensions(DirectoryProjectConfigurator.EP_NAME)) { - try { - configurator.configureProject(project, baseDir); - } - catch (Exception e) { - LOG.error(e); - } - } - - openFileFromCommandLine(project, virtualFile); - projectManager.openProject(project); - - return project; - } - - private static void openFileFromCommandLine(final Project project, final VirtualFile virtualFile) { - StartupManager.getInstance(project).registerPostStartupActivity(new DumbAwareRunnable() { - public void run() { - ToolWindowManager.getInstance(project).invokeLater(new Runnable() { - public void run() { - ToolWindowManager.getInstance(project).invokeLater(new Runnable() { - public void run() { - if (!virtualFile.isDirectory()) { - FileEditorManager.getInstance(project).openFile(virtualFile, true); - } - } - }); - } - }); - } - }); - } - - @Nullable - public Icon getIcon() { - return null; - } - - public String getName() { - return "text editor"; - } -} +/* + * 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.platform; + +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.project.impl.ProjectManagerImpl; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.projectImport.ProjectOpenProcessor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; + +/** + * @author max + */ +public class PlatformProjectOpenProcessor extends ProjectOpenProcessor { + private static final Logger LOG = Logger.getInstance("#com.intellij.platform.PlatformProjectOpenProcessor"); + + public static PlatformProjectOpenProcessor getInstance() { + PlatformProjectOpenProcessor projectOpenProcessor = getInstanceIfItExists(); + assert projectOpenProcessor != null; + return projectOpenProcessor; + } + + public static PlatformProjectOpenProcessor getInstanceIfItExists() { + ProjectOpenProcessor[] processors = Extensions.getExtensions(EXTENSION_POINT_NAME); + for(ProjectOpenProcessor processor: processors) { + if (processor instanceof PlatformProjectOpenProcessor) { + return (PlatformProjectOpenProcessor) processor; + } + } + return null; + } + + public boolean canOpenProject(final VirtualFile file) { + return file.isDirectory() || !file.getFileType().isBinary(); + } + + @Override + public boolean lookForProjectsInDirectory() { + return false; + } + + @Nullable + public Project doOpenProject(@NotNull final VirtualFile virtualFile, final Project projectToClose, final boolean forceOpenInNewFrame) { + VirtualFile baseDir = virtualFile.isDirectory() ? virtualFile : virtualFile.getParent(); + final File projectDir = new File(baseDir.getPath(), ".idea"); + + Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + if (!forceOpenInNewFrame && openProjects.length > 0) { + int exitCode = ProjectUtil.confirmOpenNewProject(); + if (exitCode == 1) { // "No" option + if (!ProjectUtil.closeAndDispose(projectToClose != null ? projectToClose : openProjects[openProjects.length - 1])) return null; + } + else if (exitCode != 0) { // not "Yes" + return null; + } + } + + final ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); + Project project = null; + Ref cancelled = new Ref(); + if (projectDir.exists()) { + try { + project = ((ProjectManagerImpl) projectManager).loadProjectWithProgress(baseDir.getPath(), cancelled); + } + catch (Exception e) { + // ignore + } + } + else { + projectDir.mkdirs(); + } + if (project == null && cancelled.isNull()) { + project = projectManager.newProject(projectDir.getParentFile().getName(), projectDir.getParent(), true, false); + } + if (project == null) return null; + ProjectBaseDirectory.getInstance(project).setBaseDir(baseDir); + for(DirectoryProjectConfigurator configurator: Extensions.getExtensions(DirectoryProjectConfigurator.EP_NAME)) { + try { + configurator.configureProject(project, baseDir); + } + catch (Exception e) { + LOG.error(e); + } + } + + openFileFromCommandLine(project, virtualFile); + projectManager.openProject(project); + + return project; + } + + private static void openFileFromCommandLine(final Project project, final VirtualFile virtualFile) { + StartupManager.getInstance(project).registerPostStartupActivity(new DumbAwareRunnable() { + public void run() { + ToolWindowManager.getInstance(project).invokeLater(new Runnable() { + public void run() { + ToolWindowManager.getInstance(project).invokeLater(new Runnable() { + public void run() { + if (!virtualFile.isDirectory()) { + FileEditorManager.getInstance(project).openFile(virtualFile, true); + } + } + }); + } + }); + } + }); + } + + @Nullable + public Icon getIcon() { + return null; + } + + public String getName() { + return "text editor"; + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/HorizontalLabeledIcon.java b/platform/platform-impl/src/com/intellij/ui/HorizontalLabeledIcon.java index 809bc5059b70..d33b6d1ddfb6 100644 --- a/platform/platform-impl/src/com/intellij/ui/HorizontalLabeledIcon.java +++ b/platform/platform-impl/src/com/intellij/ui/HorizontalLabeledIcon.java @@ -1,125 +1,125 @@ -/* - * 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.ui; - -import com.intellij.util.text.StringTokenizer; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; - -/** - * @author Vladimir Kondratyev - */ -public class HorizontalLabeledIcon implements Icon { - private final Icon myIcon; - private final String[] myStrings; - private final String myMnemonic; - - /** - * @param icon not null icon. - * @param text to be painted under the icon. This parameter can - * be null if text isn't specified. In that case LabeledIcon - */ - public HorizontalLabeledIcon(Icon icon, String text, String mnemonic) { - myIcon = icon; - if (text != null) { - StringTokenizer tokenizer = new StringTokenizer(text, "\n"); - myStrings = new String[tokenizer.countTokens()]; - for (int i = 0; tokenizer.hasMoreTokens(); i++) { - myStrings[i] = tokenizer.nextToken(); - } - } - else { - myStrings = null; - } - myMnemonic = mnemonic; - } - - public int getIconHeight() { - return Math.max(myIcon.getIconHeight(), getTextHeight()); - } - - public int getIconWidth() { - return myIcon.getIconWidth() + getTextWidth() + 5; - } - - private int getTextHeight() { - if (myStrings != null) { - Font font = UIUtil.getLabelFont(); - FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - return fontMetrics.getHeight() * myStrings.length; - } - else { - return 0; - } - } - - private int getTextWidth() { - if (myStrings != null) { - int width = 0; - Font font = UIUtil.getLabelFont(); - FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - for (int i = 0; i < myStrings.length; i++) { - String string = myStrings[i]; - if (myMnemonic != null && i == myStrings.length-1) { - string += " "+myMnemonic; - } - width = Math.max(width, fontMetrics.stringWidth(string)); - } - - return width; - } - else { - return 0; - } - } - - public void paintIcon(Component c, Graphics g, int x, int y) { - // Draw icon - int height = getIconHeight(); - int iconHeight = myIcon.getIconHeight(); - if (height > iconHeight) { - myIcon.paintIcon(c, g, x, y + (height - iconHeight) / 2); - } - else { - myIcon.paintIcon(c, g, x, y); - } - - // Draw text - if (myStrings != null) { - Font font = UIUtil.getLabelFont(); - FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - g.setFont(fontMetrics.getFont()); - g.setColor(UIUtil.getLabelForeground()); - - x += myIcon.getIconWidth() + 5; - y += (height - getTextHeight()) / 2 + fontMetrics.getHeight() - fontMetrics.getDescent(); - for (int i = 0; i < myStrings.length; i++) { - String string = myStrings[i]; - g.drawString(string, x, y); - y += fontMetrics.getHeight(); - } - if (myMnemonic != null) { - g.setColor(UIUtil.getInactiveTextColor()); - int offset = fontMetrics.stringWidth(myStrings[myStrings.length-1]+" "); - y -= fontMetrics.getHeight(); - g.drawString(myMnemonic, x + offset, y); - } - } - } - -} +/* + * 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.ui; + +import com.intellij.util.text.StringTokenizer; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Vladimir Kondratyev + */ +public class HorizontalLabeledIcon implements Icon { + private final Icon myIcon; + private final String[] myStrings; + private final String myMnemonic; + + /** + * @param icon not null icon. + * @param text to be painted under the icon. This parameter can + * be null if text isn't specified. In that case LabeledIcon + */ + public HorizontalLabeledIcon(Icon icon, String text, String mnemonic) { + myIcon = icon; + if (text != null) { + StringTokenizer tokenizer = new StringTokenizer(text, "\n"); + myStrings = new String[tokenizer.countTokens()]; + for (int i = 0; tokenizer.hasMoreTokens(); i++) { + myStrings[i] = tokenizer.nextToken(); + } + } + else { + myStrings = null; + } + myMnemonic = mnemonic; + } + + public int getIconHeight() { + return Math.max(myIcon.getIconHeight(), getTextHeight()); + } + + public int getIconWidth() { + return myIcon.getIconWidth() + getTextWidth() + 5; + } + + private int getTextHeight() { + if (myStrings != null) { + Font font = UIUtil.getLabelFont(); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + return fontMetrics.getHeight() * myStrings.length; + } + else { + return 0; + } + } + + private int getTextWidth() { + if (myStrings != null) { + int width = 0; + Font font = UIUtil.getLabelFont(); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + for (int i = 0; i < myStrings.length; i++) { + String string = myStrings[i]; + if (myMnemonic != null && i == myStrings.length-1) { + string += " "+myMnemonic; + } + width = Math.max(width, fontMetrics.stringWidth(string)); + } + + return width; + } + else { + return 0; + } + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + // Draw icon + int height = getIconHeight(); + int iconHeight = myIcon.getIconHeight(); + if (height > iconHeight) { + myIcon.paintIcon(c, g, x, y + (height - iconHeight) / 2); + } + else { + myIcon.paintIcon(c, g, x, y); + } + + // Draw text + if (myStrings != null) { + Font font = UIUtil.getLabelFont(); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + g.setFont(fontMetrics.getFont()); + g.setColor(UIUtil.getLabelForeground()); + + x += myIcon.getIconWidth() + 5; + y += (height - getTextHeight()) / 2 + fontMetrics.getHeight() - fontMetrics.getDescent(); + for (int i = 0; i < myStrings.length; i++) { + String string = myStrings[i]; + g.drawString(string, x, y); + y += fontMetrics.getHeight(); + } + if (myMnemonic != null) { + g.setColor(UIUtil.getInactiveTextColor()); + int offset = fontMetrics.stringWidth(myStrings[myStrings.length-1]+" "); + y -= fontMetrics.getHeight(); + g.drawString(myMnemonic, x + offset, y); + } + } + } + +} diff --git a/platform/platform-impl/src/com/intellij/ui/LabeledIcon.java b/platform/platform-impl/src/com/intellij/ui/LabeledIcon.java index 972707b7bea2..8c728c5ba13b 100644 --- a/platform/platform-impl/src/com/intellij/ui/LabeledIcon.java +++ b/platform/platform-impl/src/com/intellij/ui/LabeledIcon.java @@ -1,133 +1,133 @@ -/* - * 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.ui; - -import com.intellij.util.text.StringTokenizer; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; - -/** - * @author Vladimir Kondratyev - */ -public class LabeledIcon implements Icon { - private final Icon myIcon; - private final String myMnemonic; - private final String[] myStrings; - private int myIconTextGap = 0; - - /** - * @param icon not null icon. - * @param text to be painted under the icon. This parameter can - * be null if text isn't specified. In that case LabeledIcon - * @param mnemonic - */ - public LabeledIcon(Icon icon, String text, String mnemonic) { - myIcon = icon; - myMnemonic = mnemonic; - if (text != null) { - StringTokenizer tokenizer = new StringTokenizer(text, "\n"); - myStrings = new String[tokenizer.countTokens()]; - for (int i = 0; tokenizer.hasMoreTokens(); i++) { - myStrings[i] = tokenizer.nextToken(); - } - } - else { - myStrings = null; - } - } - - public void setIconTextGap(int iconTextGap) { - myIconTextGap = iconTextGap; - } - - public int getIconTextGap() { - return myIconTextGap; - } - - public int getIconHeight() { - return myIcon.getIconHeight() + getTextHeight() + myIconTextGap; - } - - public int getIconWidth() { - return Math.max(myIcon.getIconWidth(), getTextWidth()); - } - - private int getTextHeight() { - if (myStrings != null) { - Font font = UIUtil.getLabelFont(); - FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - return fontMetrics.getHeight() * myStrings.length; - } - else { - return 0; - } - } - - private int getTextWidth() { - if (myStrings != null) { - int width = 0; - Font font = UIUtil.getLabelFont(); - FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - for (String string : myStrings) { - width = fontMetrics.stringWidth(string); - } - - if (myMnemonic != null) { - width += fontMetrics.stringWidth(myMnemonic); - } - return width; - } - else { - return 0; - } - } - - public void paintIcon(Component c, Graphics g, int x, int y) { - // Draw icon - int width = getIconWidth(); - int iconWidth = myIcon.getIconWidth(); - if (width > iconWidth) { - myIcon.paintIcon(c, g, x + (width - iconWidth) / 2, y); - } - else { - myIcon.paintIcon(c, g, x, y); - } - // Draw text - if (myStrings != null) { - Font font = UIUtil.getLabelFont(); - FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); - g.setFont(fontMetrics.getFont()); - if (myMnemonic != null) { - width -= fontMetrics.stringWidth(myMnemonic); - } - g.setColor(UIUtil.getLabelForeground()); - y += myIcon.getIconHeight() + fontMetrics.getMaxAscent() + myIconTextGap; - for (String string : myStrings) { - g.drawString(string, x + (width - fontMetrics.stringWidth(string)) / 2, y); - y += fontMetrics.getHeight(); - } - - if (myMnemonic != null) { - y -= fontMetrics.getHeight(); - g.setColor(UIUtil.getInactiveTextColor()); - int offset = getTextWidth() - fontMetrics.stringWidth(myMnemonic); - g.drawString(myMnemonic, x + offset, y); - } - } - } -} +/* + * 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.ui; + +import com.intellij.util.text.StringTokenizer; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Vladimir Kondratyev + */ +public class LabeledIcon implements Icon { + private final Icon myIcon; + private final String myMnemonic; + private final String[] myStrings; + private int myIconTextGap = 0; + + /** + * @param icon not null icon. + * @param text to be painted under the icon. This parameter can + * be null if text isn't specified. In that case LabeledIcon + * @param mnemonic + */ + public LabeledIcon(Icon icon, String text, String mnemonic) { + myIcon = icon; + myMnemonic = mnemonic; + if (text != null) { + StringTokenizer tokenizer = new StringTokenizer(text, "\n"); + myStrings = new String[tokenizer.countTokens()]; + for (int i = 0; tokenizer.hasMoreTokens(); i++) { + myStrings[i] = tokenizer.nextToken(); + } + } + else { + myStrings = null; + } + } + + public void setIconTextGap(int iconTextGap) { + myIconTextGap = iconTextGap; + } + + public int getIconTextGap() { + return myIconTextGap; + } + + public int getIconHeight() { + return myIcon.getIconHeight() + getTextHeight() + myIconTextGap; + } + + public int getIconWidth() { + return Math.max(myIcon.getIconWidth(), getTextWidth()); + } + + private int getTextHeight() { + if (myStrings != null) { + Font font = UIUtil.getLabelFont(); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + return fontMetrics.getHeight() * myStrings.length; + } + else { + return 0; + } + } + + private int getTextWidth() { + if (myStrings != null) { + int width = 0; + Font font = UIUtil.getLabelFont(); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + for (String string : myStrings) { + width = fontMetrics.stringWidth(string); + } + + if (myMnemonic != null) { + width += fontMetrics.stringWidth(myMnemonic); + } + return width; + } + else { + return 0; + } + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + // Draw icon + int width = getIconWidth(); + int iconWidth = myIcon.getIconWidth(); + if (width > iconWidth) { + myIcon.paintIcon(c, g, x + (width - iconWidth) / 2, y); + } + else { + myIcon.paintIcon(c, g, x, y); + } + // Draw text + if (myStrings != null) { + Font font = UIUtil.getLabelFont(); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + g.setFont(fontMetrics.getFont()); + if (myMnemonic != null) { + width -= fontMetrics.stringWidth(myMnemonic); + } + g.setColor(UIUtil.getLabelForeground()); + y += myIcon.getIconHeight() + fontMetrics.getMaxAscent() + myIconTextGap; + for (String string : myStrings) { + g.drawString(string, x + (width - fontMetrics.stringWidth(string)) / 2, y); + y += fontMetrics.getHeight(); + } + + if (myMnemonic != null) { + y -= fontMetrics.getHeight(); + g.setColor(UIUtil.getInactiveTextColor()); + int offset = getTextWidth() - fontMetrics.stringWidth(myMnemonic); + g.drawString(myMnemonic, x + offset, y); + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/SeparatorComponent.java b/platform/platform-impl/src/com/intellij/ui/SeparatorComponent.java index df5f0ac346cf..6be7f19bc486 100644 --- a/platform/platform-impl/src/com/intellij/ui/SeparatorComponent.java +++ b/platform/platform-impl/src/com/intellij/ui/SeparatorComponent.java @@ -1,111 +1,111 @@ -/* - * 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.ui; - -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; - -public class SeparatorComponent extends JComponent { - private int myVGap = 3; - private Color myColor = Color.lightGray; - private Color myShadow = new Color(240, 240, 240); - private int myHGap = 1; - private SeparatorOrientation myOrientation = SeparatorOrientation.HORIZONTAL; - - public SeparatorComponent() { - - } - - public SeparatorComponent(int aVerticalGap) { - myVGap = aVerticalGap; - setBorder(BorderFactory.createEmptyBorder(myVGap, 0, myVGap, 0)); - } - - public SeparatorComponent(int aVerticalGap, Color aColor, Color aShadowColor) { - this(aVerticalGap, 1, aColor, aShadowColor); - } - - public SeparatorComponent(int aVerticalGap, int horizontalGap, Color aColor, Color aShadowColor) { - myVGap = aVerticalGap; - myHGap = horizontalGap; - myColor = aColor; - myShadow = aShadowColor; - setBorder(BorderFactory.createEmptyBorder(myVGap, 0, myVGap, 0)); - } - - public SeparatorComponent(Color color, SeparatorOrientation orientation) { - myColor = color; - myOrientation = orientation; - myShadow = null; - myHGap = 0; - myVGap = 0; - } - - protected void paintComponent(Graphics g) { - if (!isVisible()) return; - - if (myColor == null) return; - - g.setColor(myColor); - if (myOrientation != SeparatorOrientation.VERTICAL) { - g.drawLine(myHGap, myVGap, getWidth() - myHGap - 1, myVGap); - if (myShadow != null) { - g.setColor(myShadow); - g.drawLine(myHGap + 1, myVGap + 1, getWidth() - myHGap, myVGap + 1); - } - } else { - g.drawLine(myHGap, myVGap, myHGap, getHeight() - myVGap - 1); - if (myShadow != null) { - g.setColor(myShadow); - g.drawLine(myHGap + 1, myVGap + 1, myHGap + 1, getHeight() - myVGap); - } - } - - } - - public Dimension getPreferredSize() { - if (myOrientation != SeparatorOrientation.VERTICAL) - return new Dimension(0, myVGap * 2 + 1); - else - return new Dimension(myHGap * 2 + 1, 1 + ((myShadow != null) ? 1 : 0)); - } - - public Dimension getMinimumSize() { - return getPreferredSize(); - } - - /** - * Create control what consist of label with title text in the left side and single line at all rest space. - * @param titleText text for a label. - * @param containerBackgroungColor background color of container in that control will be putted on. - */ - public static JComponent createLabbeledLineSeparator(final String titleText, final Color containerBackgroungColor) { - JLabel titleLabel = new JLabel(titleText); - titleLabel.setFont(UIUtil.getLabelFont()); - titleLabel.setForeground(Colors.DARK_BLUE); - - SeparatorComponent separatorComponent = new SeparatorComponent(5, containerBackgroungColor.darker(), containerBackgroungColor.brighter()); - - int hgap = titleText.length() > 0 ? 5 : 0; - JPanel result = new JPanel(new BorderLayout(hgap, 10)); - result.add(titleLabel, BorderLayout.WEST); - result.add(separatorComponent, BorderLayout.CENTER); - - return result; - } +/* + * 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.ui; + +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; + +public class SeparatorComponent extends JComponent { + private int myVGap = 3; + private Color myColor = Color.lightGray; + private Color myShadow = new Color(240, 240, 240); + private int myHGap = 1; + private SeparatorOrientation myOrientation = SeparatorOrientation.HORIZONTAL; + + public SeparatorComponent() { + + } + + public SeparatorComponent(int aVerticalGap) { + myVGap = aVerticalGap; + setBorder(BorderFactory.createEmptyBorder(myVGap, 0, myVGap, 0)); + } + + public SeparatorComponent(int aVerticalGap, Color aColor, Color aShadowColor) { + this(aVerticalGap, 1, aColor, aShadowColor); + } + + public SeparatorComponent(int aVerticalGap, int horizontalGap, Color aColor, Color aShadowColor) { + myVGap = aVerticalGap; + myHGap = horizontalGap; + myColor = aColor; + myShadow = aShadowColor; + setBorder(BorderFactory.createEmptyBorder(myVGap, 0, myVGap, 0)); + } + + public SeparatorComponent(Color color, SeparatorOrientation orientation) { + myColor = color; + myOrientation = orientation; + myShadow = null; + myHGap = 0; + myVGap = 0; + } + + protected void paintComponent(Graphics g) { + if (!isVisible()) return; + + if (myColor == null) return; + + g.setColor(myColor); + if (myOrientation != SeparatorOrientation.VERTICAL) { + g.drawLine(myHGap, myVGap, getWidth() - myHGap - 1, myVGap); + if (myShadow != null) { + g.setColor(myShadow); + g.drawLine(myHGap + 1, myVGap + 1, getWidth() - myHGap, myVGap + 1); + } + } else { + g.drawLine(myHGap, myVGap, myHGap, getHeight() - myVGap - 1); + if (myShadow != null) { + g.setColor(myShadow); + g.drawLine(myHGap + 1, myVGap + 1, myHGap + 1, getHeight() - myVGap); + } + } + + } + + public Dimension getPreferredSize() { + if (myOrientation != SeparatorOrientation.VERTICAL) + return new Dimension(0, myVGap * 2 + 1); + else + return new Dimension(myHGap * 2 + 1, 1 + ((myShadow != null) ? 1 : 0)); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + /** + * Create control what consist of label with title text in the left side and single line at all rest space. + * @param titleText text for a label. + * @param containerBackgroungColor background color of container in that control will be putted on. + */ + public static JComponent createLabbeledLineSeparator(final String titleText, final Color containerBackgroungColor) { + JLabel titleLabel = new JLabel(titleText); + titleLabel.setFont(UIUtil.getLabelFont()); + titleLabel.setForeground(Colors.DARK_BLUE); + + SeparatorComponent separatorComponent = new SeparatorComponent(5, containerBackgroungColor.darker(), containerBackgroungColor.brighter()); + + int hgap = titleText.length() > 0 ? 5 : 0; + JPanel result = new JPanel(new BorderLayout(hgap, 10)); + result.add(titleLabel, BorderLayout.WEST); + result.add(separatorComponent, BorderLayout.CENTER); + + return result; + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java b/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java index 3d9f72e56cf4..fee71dd0f81c 100644 --- a/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java @@ -1,509 +1,509 @@ -/* - * 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.ui.docking.impl; - -import com.intellij.ide.IdeEventQueue; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.FrameWrapper; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.BusyObject; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.MutualMap; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.openapi.wm.impl.IdeFocusManagerImpl; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.awt.RelativeRectangle; -import com.intellij.ui.components.panels.NonOpaquePanel; -import com.intellij.ui.docking.*; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.update.UiNotifyConnector; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.util.*; -import java.util.List; - -@State( - name = "DockManager", - storages = {@Storage( - id = "other", - file = "$WORKSPACE_FILE$")}) - -public class DockManagerImpl extends DockManager implements PersistentStateComponent{ - - private Project myProject; - - private Map myFactories = new HashMap(); - - private Set myContainers = new HashSet(); - - private MutualMap myWindows = new MutualMap(); - - private MyDragSession myCurrentDragSession; - - private BusyObject.Impl myBusyObject = new BusyObject.Impl() { - @Override - protected boolean isReady() { - return myCurrentDragSession == null; - } - }; - - private int myWindowIdCounter = 1; - - private Element myLoadedState; - - public DockManagerImpl(Project project) { - myProject = project; - } - - public void register(final DockContainer container) { - myContainers.add(container); - Disposer.register(container, new Disposable() { - @Override - public void dispose() { - myContainers.remove(container); - } - }); - } - - @Override - public void register(final String id, DockContainerFactory factory) { - myFactories.put(id, factory); - Disposer.register(factory, new Disposable() { - @Override - public void dispose() { - myFactories.remove(id); - } - }); - - readStateFor(id); - } - - @Override - public Set getContainers() { - return Collections.unmodifiableSet(myContainers); - } - - @Override - public IdeFrame getIdeFrame(DockContainer container) { - Component parent = UIUtil.findUltimateParent(container.getComponent()); - if (parent instanceof IdeFrame) { - return (IdeFrame)parent; - } - return null; - } - - @Override - public String getDimensionKeyForFocus(@NotNull String key) { - Component owner = IdeFocusManagerImpl.getInstance(myProject).getFocusOwner(); - if (owner == null) return key; - - DockWindow wnd = myWindows.getValue(getContainerFor(owner)); - - return wnd != null ? key + "#" + wnd.myId : key; - } - - public DockContainer getContainerFor(Component c) { - if (c == null) return null; - - for (DockContainer eachContainer : myContainers) { - if (SwingUtilities.isDescendingFrom(c, eachContainer.getComponent())) { - return eachContainer; - } - } - - Component parent = UIUtil.findUltimateParent(c); - if (parent == null) return null; - - for (DockContainer eachContainer : myContainers) { - if (parent == UIUtil.findUltimateParent(eachContainer.getComponent())) { - return eachContainer; - } - } - - return null; - } - - @Override - public DragSession createDragSession(MouseEvent mouseEvent, DockableContent content) { - stopCurrentDragSession(); - - for (DockContainer each : myContainers) { - if (each.isEmpty() && each.isDisposeWhenEmpty()) { - DockWindow window = myWindows.getValue(each); - if (window != null) { - window.setTransparrent(true); - } - } - } - - myCurrentDragSession = new MyDragSession(mouseEvent, content); - return myCurrentDragSession; - } - - - private void stopCurrentDragSession() { - if (myCurrentDragSession != null) { - myCurrentDragSession.cancel(); - myCurrentDragSession = null; - myBusyObject.onReady(); - - for (DockContainer each : myContainers) { - if (!each.isEmpty()) { - DockWindow window = myWindows.getValue(each); - if (window != null) { - window.setTransparrent(false); - } - } - } - } - } - - private ActionCallback getReady() { - return myBusyObject.getReady(this); - } - - @Override - public void projectOpened() { - } - - @Override - public void projectClosed() { - } - - @NotNull - @Override - public String getComponentName() { - return "DockManager"; - } - - @Override - public void initComponent() { - } - - @Override - public void disposeComponent() { - } - - private class MyDragSession implements DragSession { - - private JWindow myWindow; - - private Image myDragImage; - private Image myDefaultDragImage; - - private DockableContent myContent; - - private DockContainer myCurrentOverContainer; - private JLabel myImageContainer; - - private MyDragSession(MouseEvent me, DockableContent content) { - myWindow = new JWindow(); - myContent = content; - - Image previewImage = content.getPreviewImage(); - - double requiredSize = 220; - - double width = previewImage.getWidth(null); - double height = previewImage.getHeight(null); - - double ratio; - if (width > height) { - ratio = requiredSize / width; - } - else { - ratio = requiredSize / height; - } - - BufferedImage buffer = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB); - buffer.createGraphics().drawImage(previewImage, 0, 0, (int)width, (int)height, null); - - myDefaultDragImage = buffer.getScaledInstance((int)(width * ratio), (int)(height * ratio), Image.SCALE_SMOOTH); - myDragImage = myDefaultDragImage; - - myWindow.getContentPane().setLayout(new BorderLayout()); - myImageContainer = new JLabel(new ImageIcon(myDragImage)); - myImageContainer.setBorder(new LineBorder(Color.lightGray)); - myWindow.getContentPane().add(myImageContainer, BorderLayout.CENTER); - - setLocationFrom(me); - - myWindow.setVisible(true); - - WindowManagerEx.getInstanceEx().setAlphaModeEnabled(myWindow, true); - WindowManagerEx.getInstanceEx().setAlphaModeRatio(myWindow, 0.1f); - myWindow.getRootPane().putClientProperty("Window.shadow", Boolean.FALSE); - } - - private void setLocationFrom(MouseEvent me) { - Point showPoint = me.getPoint(); - SwingUtilities.convertPointToScreen(showPoint, me.getComponent()); - - showPoint.x -= myDragImage.getWidth(null) / 2; - showPoint.y += 10; - myWindow.setBounds(new Rectangle(showPoint, new Dimension(myDragImage.getWidth(null), myDragImage.getHeight(null)))); - } - - @Override - public void process(MouseEvent e) { - RelativePoint point = new RelativePoint(e); - - Image img = null; - if (e.getID() == MouseEvent.MOUSE_DRAGGED) { - DockContainer over = findContainerFor(point, myContent); - if (myCurrentOverContainer != null && myCurrentOverContainer != over) { - myCurrentOverContainer.resetDropOver(myContent); - myCurrentOverContainer = null; - } - - if (myCurrentOverContainer == null && over != null) { - myCurrentOverContainer = over; - img = myCurrentOverContainer.startDropOver(myContent, point); - } - - if (myCurrentOverContainer != null) { - img = myCurrentOverContainer.processDropOver(myContent, point); - } - - if (img == null) { - img = myDefaultDragImage; - } - - if (img != myDragImage) { - myDragImage = img; - myImageContainer.setIcon(new ImageIcon(myDragImage)); - myWindow.pack(); - } - - setLocationFrom(e); - } - else if (e.getID() == MouseEvent.MOUSE_RELEASED) { - if (myCurrentOverContainer == null) { - createNewDockContainerFor(myContent, point); - stopCurrentDragSession(); - } else { - myCurrentOverContainer.add(myContent, point); - stopCurrentDragSession(); - } - } - } - - public void cancel() { - myWindow.dispose(); - - if (myCurrentOverContainer != null) { - myCurrentOverContainer.resetDropOver(myContent); - myCurrentOverContainer = null; - } - } - } - - @Nullable - private DockContainer findContainerFor(RelativePoint point, DockableContent content) { - for (DockContainer each : myContainers) { - RelativeRectangle rec = each.getAcceptArea(); - if (rec.contains(point) && each.canAccept(content, point)) { - return each; - } - } - - return null; - } - - - - private DockContainerFactory getFactory(String type) { - assert myFactories.containsKey(type) : "No factory for content type=" + type; - return myFactories.get(type); - } - - private void createNewDockContainerFor(DockableContent content, RelativePoint point) { - DockContainer container = getFactory(content.getDockContainerType()).createContainer(); - register(container); - - DockWindow window = createWindowFor(null, container); - - Dimension size = content.getPreferredSize(); - Point showPoint = point.getScreenPoint(); - showPoint.x -= size.width / 2; - showPoint.y -= size.height / 2; - - window.show(); - - window.setLocation(showPoint); - window.setSize(size); - - container.add(content, new RelativePoint(showPoint)); - } - - private DockWindow createWindowFor(@Nullable String id, DockContainer container) { - DockWindow window = new DockWindow(id != null ? id : String.valueOf(myWindowIdCounter++) , myProject, container); - window.setDimensionKey("dock-window-" + id); - myWindows.put(container, window); - window.show(); - return window; - } - - private class DockWindow extends FrameWrapper implements IdeEventQueue.EventDispatcher { - - private String myId; - private DockContainer myContainer; - - private DockWindow(String id, Project project, DockContainer container) { - super(project); - myId = id; - myContainer = container; - setProject(project); - - setStatusBar(WindowManager.getInstance().getStatusBar(project).createChild()); - - NonOpaquePanel comp = new NonOpaquePanel(new BorderLayout()); - comp.add(myContainer.getComponent(), BorderLayout.CENTER); - comp.add(myStatusBar.getComponent(), BorderLayout.SOUTH); - - setComponent(comp); - addDisposable(container); - - IdeEventQueue.getInstance().addPostprocessor(this, this); - - myContainer.addListener(new DockContainer.Listener.Adapter() { - @Override - public void contentRemoved(Object key) { - getReady().doWhenDone(new Runnable() { - @Override - public void run() { - if (myContainer.isEmpty()) { - close(); - } - } - }); - } - }, this); - } - - public void setTransparrent(boolean transparrent) { - if (transparrent) { - WindowManagerEx.getInstanceEx().setAlphaModeEnabled(getFrame(), true); - WindowManagerEx.getInstanceEx().setAlphaModeRatio(getFrame(), 1f); - } else { - WindowManagerEx.getInstanceEx().setAlphaModeEnabled(getFrame(), true); - WindowManagerEx.getInstanceEx().setAlphaModeRatio(getFrame(), 0f); - } - } - - @Override - public void dispose() { - super.dispose(); - myWindows.remove(myContainer); - } - - @Override - public boolean dispatch(AWTEvent e) { - if (e instanceof KeyEvent) { - if (myCurrentDragSession != null) { - stopCurrentDragSession(); - } - } - return false; - } - - @Override - protected JFrame createJFrame(IdeFrame parent) { - JFrame frame = super.createJFrame(parent); - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - myContainer.closeAll(); - } - }); - - new UiNotifyConnector(frame.getContentPane(), myContainer); - - return frame; - } - } - - @Override - public Element getState() { - Element root = new Element("DockManager"); - for (DockContainer each : myContainers) { - DockWindow eachWindow = myWindows.getValue(each); - if (eachWindow != null) { - if (each instanceof DockContainer.Persistent) { - DockContainer.Persistent eachContainer = (DockContainer.Persistent)each; - Element eachWindowElement = new Element("window"); - eachWindowElement.setAttribute("id", eachWindow.myId); - Element content = new Element("content"); - content.setAttribute("type", eachContainer.getDockContainerType()); - content.addContent(eachContainer.getState()); - eachWindowElement.addContent(content); - - root.addContent(eachWindowElement); - } - } - } - return root; - } - - @Override - public void loadState(Element state) { - myLoadedState = state; - } - - private void readStateFor(String type) { - if (myLoadedState == null) return; - - List windows = myLoadedState.getChildren("window"); - for (int i = 0; i < windows.size(); i++) { - Element eachWindow = (Element)windows.get(i); - if (eachWindow == null) continue; - - String eachId = eachWindow.getAttributeValue("id"); - - Element eachContent = eachWindow.getChild("content"); - if (eachContent == null) continue; - - String eachType = eachContent.getAttributeValue("type"); - if (eachType == null || !type.equals(eachType) || !myFactories.containsKey(eachType)) continue; - - DockContainerFactory factory = myFactories.get(eachType); - if (!(factory instanceof DockContainerFactory.Persistent)) continue; - - DockContainerFactory.Persistent persistentFactory = (DockContainerFactory.Persistent)factory; - DockContainer container = persistentFactory.loadContainerFrom(eachContent); - register(container); - - createWindowFor(eachId, container); - } - } -} +/* + * 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.ui.docking.impl; + +import com.intellij.ide.IdeEventQueue; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.FrameWrapper; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.BusyObject; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.MutualMap; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.openapi.wm.impl.IdeFocusManagerImpl; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.awt.RelativeRectangle; +import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.ui.docking.*; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.UiNotifyConnector; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.LineBorder; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.util.*; +import java.util.List; + +@State( + name = "DockManager", + storages = {@Storage( + id = "other", + file = "$WORKSPACE_FILE$")}) + +public class DockManagerImpl extends DockManager implements PersistentStateComponent{ + + private Project myProject; + + private Map myFactories = new HashMap(); + + private Set myContainers = new HashSet(); + + private MutualMap myWindows = new MutualMap(); + + private MyDragSession myCurrentDragSession; + + private BusyObject.Impl myBusyObject = new BusyObject.Impl() { + @Override + protected boolean isReady() { + return myCurrentDragSession == null; + } + }; + + private int myWindowIdCounter = 1; + + private Element myLoadedState; + + public DockManagerImpl(Project project) { + myProject = project; + } + + public void register(final DockContainer container) { + myContainers.add(container); + Disposer.register(container, new Disposable() { + @Override + public void dispose() { + myContainers.remove(container); + } + }); + } + + @Override + public void register(final String id, DockContainerFactory factory) { + myFactories.put(id, factory); + Disposer.register(factory, new Disposable() { + @Override + public void dispose() { + myFactories.remove(id); + } + }); + + readStateFor(id); + } + + @Override + public Set getContainers() { + return Collections.unmodifiableSet(myContainers); + } + + @Override + public IdeFrame getIdeFrame(DockContainer container) { + Component parent = UIUtil.findUltimateParent(container.getComponent()); + if (parent instanceof IdeFrame) { + return (IdeFrame)parent; + } + return null; + } + + @Override + public String getDimensionKeyForFocus(@NotNull String key) { + Component owner = IdeFocusManagerImpl.getInstance(myProject).getFocusOwner(); + if (owner == null) return key; + + DockWindow wnd = myWindows.getValue(getContainerFor(owner)); + + return wnd != null ? key + "#" + wnd.myId : key; + } + + public DockContainer getContainerFor(Component c) { + if (c == null) return null; + + for (DockContainer eachContainer : myContainers) { + if (SwingUtilities.isDescendingFrom(c, eachContainer.getComponent())) { + return eachContainer; + } + } + + Component parent = UIUtil.findUltimateParent(c); + if (parent == null) return null; + + for (DockContainer eachContainer : myContainers) { + if (parent == UIUtil.findUltimateParent(eachContainer.getComponent())) { + return eachContainer; + } + } + + return null; + } + + @Override + public DragSession createDragSession(MouseEvent mouseEvent, DockableContent content) { + stopCurrentDragSession(); + + for (DockContainer each : myContainers) { + if (each.isEmpty() && each.isDisposeWhenEmpty()) { + DockWindow window = myWindows.getValue(each); + if (window != null) { + window.setTransparrent(true); + } + } + } + + myCurrentDragSession = new MyDragSession(mouseEvent, content); + return myCurrentDragSession; + } + + + private void stopCurrentDragSession() { + if (myCurrentDragSession != null) { + myCurrentDragSession.cancel(); + myCurrentDragSession = null; + myBusyObject.onReady(); + + for (DockContainer each : myContainers) { + if (!each.isEmpty()) { + DockWindow window = myWindows.getValue(each); + if (window != null) { + window.setTransparrent(false); + } + } + } + } + } + + private ActionCallback getReady() { + return myBusyObject.getReady(this); + } + + @Override + public void projectOpened() { + } + + @Override + public void projectClosed() { + } + + @NotNull + @Override + public String getComponentName() { + return "DockManager"; + } + + @Override + public void initComponent() { + } + + @Override + public void disposeComponent() { + } + + private class MyDragSession implements DragSession { + + private JWindow myWindow; + + private Image myDragImage; + private Image myDefaultDragImage; + + private DockableContent myContent; + + private DockContainer myCurrentOverContainer; + private JLabel myImageContainer; + + private MyDragSession(MouseEvent me, DockableContent content) { + myWindow = new JWindow(); + myContent = content; + + Image previewImage = content.getPreviewImage(); + + double requiredSize = 220; + + double width = previewImage.getWidth(null); + double height = previewImage.getHeight(null); + + double ratio; + if (width > height) { + ratio = requiredSize / width; + } + else { + ratio = requiredSize / height; + } + + BufferedImage buffer = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB); + buffer.createGraphics().drawImage(previewImage, 0, 0, (int)width, (int)height, null); + + myDefaultDragImage = buffer.getScaledInstance((int)(width * ratio), (int)(height * ratio), Image.SCALE_SMOOTH); + myDragImage = myDefaultDragImage; + + myWindow.getContentPane().setLayout(new BorderLayout()); + myImageContainer = new JLabel(new ImageIcon(myDragImage)); + myImageContainer.setBorder(new LineBorder(Color.lightGray)); + myWindow.getContentPane().add(myImageContainer, BorderLayout.CENTER); + + setLocationFrom(me); + + myWindow.setVisible(true); + + WindowManagerEx.getInstanceEx().setAlphaModeEnabled(myWindow, true); + WindowManagerEx.getInstanceEx().setAlphaModeRatio(myWindow, 0.1f); + myWindow.getRootPane().putClientProperty("Window.shadow", Boolean.FALSE); + } + + private void setLocationFrom(MouseEvent me) { + Point showPoint = me.getPoint(); + SwingUtilities.convertPointToScreen(showPoint, me.getComponent()); + + showPoint.x -= myDragImage.getWidth(null) / 2; + showPoint.y += 10; + myWindow.setBounds(new Rectangle(showPoint, new Dimension(myDragImage.getWidth(null), myDragImage.getHeight(null)))); + } + + @Override + public void process(MouseEvent e) { + RelativePoint point = new RelativePoint(e); + + Image img = null; + if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + DockContainer over = findContainerFor(point, myContent); + if (myCurrentOverContainer != null && myCurrentOverContainer != over) { + myCurrentOverContainer.resetDropOver(myContent); + myCurrentOverContainer = null; + } + + if (myCurrentOverContainer == null && over != null) { + myCurrentOverContainer = over; + img = myCurrentOverContainer.startDropOver(myContent, point); + } + + if (myCurrentOverContainer != null) { + img = myCurrentOverContainer.processDropOver(myContent, point); + } + + if (img == null) { + img = myDefaultDragImage; + } + + if (img != myDragImage) { + myDragImage = img; + myImageContainer.setIcon(new ImageIcon(myDragImage)); + myWindow.pack(); + } + + setLocationFrom(e); + } + else if (e.getID() == MouseEvent.MOUSE_RELEASED) { + if (myCurrentOverContainer == null) { + createNewDockContainerFor(myContent, point); + stopCurrentDragSession(); + } else { + myCurrentOverContainer.add(myContent, point); + stopCurrentDragSession(); + } + } + } + + public void cancel() { + myWindow.dispose(); + + if (myCurrentOverContainer != null) { + myCurrentOverContainer.resetDropOver(myContent); + myCurrentOverContainer = null; + } + } + } + + @Nullable + private DockContainer findContainerFor(RelativePoint point, DockableContent content) { + for (DockContainer each : myContainers) { + RelativeRectangle rec = each.getAcceptArea(); + if (rec.contains(point) && each.canAccept(content, point)) { + return each; + } + } + + return null; + } + + + + private DockContainerFactory getFactory(String type) { + assert myFactories.containsKey(type) : "No factory for content type=" + type; + return myFactories.get(type); + } + + private void createNewDockContainerFor(DockableContent content, RelativePoint point) { + DockContainer container = getFactory(content.getDockContainerType()).createContainer(); + register(container); + + DockWindow window = createWindowFor(null, container); + + Dimension size = content.getPreferredSize(); + Point showPoint = point.getScreenPoint(); + showPoint.x -= size.width / 2; + showPoint.y -= size.height / 2; + + window.show(); + + window.setLocation(showPoint); + window.setSize(size); + + container.add(content, new RelativePoint(showPoint)); + } + + private DockWindow createWindowFor(@Nullable String id, DockContainer container) { + DockWindow window = new DockWindow(id != null ? id : String.valueOf(myWindowIdCounter++) , myProject, container); + window.setDimensionKey("dock-window-" + id); + myWindows.put(container, window); + window.show(); + return window; + } + + private class DockWindow extends FrameWrapper implements IdeEventQueue.EventDispatcher { + + private String myId; + private DockContainer myContainer; + + private DockWindow(String id, Project project, DockContainer container) { + super(project); + myId = id; + myContainer = container; + setProject(project); + + setStatusBar(WindowManager.getInstance().getStatusBar(project).createChild()); + + NonOpaquePanel comp = new NonOpaquePanel(new BorderLayout()); + comp.add(myContainer.getComponent(), BorderLayout.CENTER); + comp.add(myStatusBar.getComponent(), BorderLayout.SOUTH); + + setComponent(comp); + addDisposable(container); + + IdeEventQueue.getInstance().addPostprocessor(this, this); + + myContainer.addListener(new DockContainer.Listener.Adapter() { + @Override + public void contentRemoved(Object key) { + getReady().doWhenDone(new Runnable() { + @Override + public void run() { + if (myContainer.isEmpty()) { + close(); + } + } + }); + } + }, this); + } + + public void setTransparrent(boolean transparrent) { + if (transparrent) { + WindowManagerEx.getInstanceEx().setAlphaModeEnabled(getFrame(), true); + WindowManagerEx.getInstanceEx().setAlphaModeRatio(getFrame(), 1f); + } else { + WindowManagerEx.getInstanceEx().setAlphaModeEnabled(getFrame(), true); + WindowManagerEx.getInstanceEx().setAlphaModeRatio(getFrame(), 0f); + } + } + + @Override + public void dispose() { + super.dispose(); + myWindows.remove(myContainer); + } + + @Override + public boolean dispatch(AWTEvent e) { + if (e instanceof KeyEvent) { + if (myCurrentDragSession != null) { + stopCurrentDragSession(); + } + } + return false; + } + + @Override + protected JFrame createJFrame(IdeFrame parent) { + JFrame frame = super.createJFrame(parent); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + myContainer.closeAll(); + } + }); + + new UiNotifyConnector(frame.getContentPane(), myContainer); + + return frame; + } + } + + @Override + public Element getState() { + Element root = new Element("DockManager"); + for (DockContainer each : myContainers) { + DockWindow eachWindow = myWindows.getValue(each); + if (eachWindow != null) { + if (each instanceof DockContainer.Persistent) { + DockContainer.Persistent eachContainer = (DockContainer.Persistent)each; + Element eachWindowElement = new Element("window"); + eachWindowElement.setAttribute("id", eachWindow.myId); + Element content = new Element("content"); + content.setAttribute("type", eachContainer.getDockContainerType()); + content.addContent(eachContainer.getState()); + eachWindowElement.addContent(content); + + root.addContent(eachWindowElement); + } + } + } + return root; + } + + @Override + public void loadState(Element state) { + myLoadedState = state; + } + + private void readStateFor(String type) { + if (myLoadedState == null) return; + + List windows = myLoadedState.getChildren("window"); + for (int i = 0; i < windows.size(); i++) { + Element eachWindow = (Element)windows.get(i); + if (eachWindow == null) continue; + + String eachId = eachWindow.getAttributeValue("id"); + + Element eachContent = eachWindow.getChild("content"); + if (eachContent == null) continue; + + String eachType = eachContent.getAttributeValue("type"); + if (eachType == null || !type.equals(eachType) || !myFactories.containsKey(eachType)) continue; + + DockContainerFactory factory = myFactories.get(eachType); + if (!(factory instanceof DockContainerFactory.Persistent)) continue; + + DockContainerFactory.Persistent persistentFactory = (DockContainerFactory.Persistent)factory; + DockContainer container = persistentFactory.loadContainerFrom(eachContent); + register(container); + + createWindowFor(eachId, container); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/plaf/beg/BegCellRenderer.java b/platform/platform-impl/src/com/intellij/ui/plaf/beg/BegCellRenderer.java index 041c8cd69425..36bc9562780e 100644 --- a/platform/platform-impl/src/com/intellij/ui/plaf/beg/BegCellRenderer.java +++ b/platform/platform-impl/src/com/intellij/ui/plaf/beg/BegCellRenderer.java @@ -1,267 +1,267 @@ - -/* - * 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.ui.plaf.beg; - -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import javax.swing.plaf.ColorUIResource; -import javax.swing.tree.TreeCellRenderer; -import java.awt.*; - -//TODO remove the class - -@SuppressWarnings({"HardCodedStringLiteral"}) -public class BegCellRenderer extends JLabel implements TreeCellRenderer, ListCellRenderer { - protected Icon myLeafIcon; - protected Color myTextBackground; - protected boolean mySelected; - protected Color mySelectionForeground; - protected Color mySelectionBorderColor; - protected Icon myOpenIcon; - protected boolean myHasFocus; - protected Color myTextForeground; - protected boolean myDrawsFocusBorderAroundIcon; - protected Color mySelectionBackground; - protected Icon myClosedIcon; - - public BegCellRenderer() { - setHorizontalAlignment(SwingConstants.LEFT); - } - - public void setSelectionBorderColor(Color color) { - mySelectionBorderColor = color; - } - - public Component getTreeCellRendererComponent(JTree tree, Object obj, boolean selected, boolean expanded, boolean leaf, int i1, boolean hasFocus) { - setFont(UIUtil.getLabelFont()); - setLeafIcon(UIManager.getIcon("Tree.leafIcon")); - setClosedIcon(UIManager.getIcon("Tree.closedIcon")); - setOpenIcon(UIManager.getIcon("Tree.openIcon")); - setSelectionForeground(UIUtil.getTreeSelectionForeground()); - setTextForeground(UIUtil.getTreeTextForeground()); - setSelectionBackground(UIUtil.getTreeSelectionBackground()); - setTextBackground(UIUtil.getTreeTextBackground()); - setSelectionBorderColor(UIUtil.getTreeSelectionBorderColor()); - Object obj1 = UIManager.get("Tree.drawsFocusBorderAroundIcon"); - myDrawsFocusBorderAroundIcon = obj1 != null && ((Boolean)obj1).booleanValue(); - - myHasFocus = hasFocus; - mySelected = selected; - String text = tree.convertValueToText(obj, selected, expanded, leaf, i1, hasFocus); - setText(text); - if (selected){ - if(hasFocus){ - setForeground(getSelectionForeground()); - }else{ - setForeground(getTextForeground()); - } - } - else{ - setForeground(getTextForeground()); - } - Icon icon = getIcon(tree, obj, selected, expanded, leaf, i1, hasFocus); - setIcon(icon); - return this; - } - - public Component getListCellRendererComponent(JList list, Object obj, int index, boolean selected, boolean hasFocus) { - setFont(UIUtil.getLabelFont()); - setSelectionForeground(UIUtil.getListSelectionForeground()); - setTextForeground(UIUtil.getListForeground()); - setSelectionBackground(UIManager.getColor("List.selectionBackground")); - setTextBackground(UIUtil.getListBackground()); - setSelectionBorderColor(UIUtil.getTreeSelectionBorderColor()); - Object obj1 = UIManager.get("Tree.drawsFocusBorderAroundIcon"); - myDrawsFocusBorderAroundIcon = obj1 != null && ((Boolean)obj1).booleanValue(); - - myHasFocus = hasFocus; - mySelected = selected; - String text = String.valueOf(obj); - setText(text); - if (hasFocus && selected){ - setForeground(getSelectionForeground()); - }else{ - setForeground(getTextForeground()); - } - return this; - } - - - public void setSelectionBackground(Color color) { - mySelectionBackground = color; - } - - public Icon getClosedIcon() { - return myClosedIcon; - } - - public Dimension getPreferredSize() { - Dimension dimension = super.getPreferredSize(); - if (dimension != null){ - dimension = new Dimension(dimension.width + 3, dimension.height); - } - return dimension; - } - - public Icon getOriginalClosedIcon() { - return UIManager.getIcon("Tree.closedIcon"); - } - - public Color getSelectionBorderColor() { - return mySelectionBorderColor; - } - - public Color getSelectionForeground() { - return mySelectionForeground; - } - - public Color getTextBackground() { - return myTextBackground; - } - - public void setClosedIcon(Icon icon) { - myClosedIcon = icon; - } - - public void setTextBackground(Color color) { - myTextBackground = color; - } - - public void setTextForeground(Color color) { - myTextForeground = color; - } - - protected Icon getIcon(JTree jtree, Object obj, boolean selected, boolean expanded, boolean leaf, int i1, boolean flag3) { - Icon icon; - if (leaf){ - icon = getLeafIcon(); - } - else - if (expanded) - icon = getOpenIcon(); - else - icon = getClosedIcon(); - return icon; - } - - public Icon getLeafIcon() { - return myLeafIcon; - } - - public void setOpenIcon(Icon icon) { - myOpenIcon = icon; - } - - protected void paintNode(Graphics g) { - Color color; - if (mySelected) { - if (myHasFocus){ - color = getSelectionBackground(); - } - else{ - color = new Color(223, 223, 223); - } - } - else{ - color = getTextBackground(); - if (color == null){ - color = getBackground(); - } - } - if (color != null){ - int i1 = getBorderLeft(); - g.setColor(color); - g.fillRect(i1 - 1, 0, getWidth() - i1, getHeight()); - } - } - - protected void paintBorder(Graphics g) { -// if (/*myHasFocus ||*/ mySelected) { - if (myHasFocus){ - int i1 = getBorderLeft(); - if (myDrawsFocusBorderAroundIcon){ - i1 = 0; - } - else - if (i1 == -1){ - i1 = getBorderLeft(); - } - Color color = getSelectionBorderColor(); - if (color != null){ - g.setColor(color); - int j1 = getWidth() - 1; - int k1 = getHeight() - 1; - UIUtil.drawDottedRectangle(g, i1 > 0 ? i1 - 1 : i1, 0, j1, k1); - } - } - } - - public void paint(Graphics g) { - paintNode(g); - paintBorder(g); - super.paint(g); - } - - public Icon getOpenIcon() { - return myOpenIcon; - } - - public void setSelectionForeground(Color color) { - mySelectionForeground = color; - } - - public Icon getOriginalOpenIcon() { - return UIManager.getIcon("Tree.openIcon"); - } - - public void setLeafIcon(Icon icon) { - myLeafIcon = icon; - } - - public void setBackground(Color color) { - if (color instanceof ColorUIResource){ - color = null; - } - super.setBackground(color); - } - - public Color getSelectionBackground() { - return mySelectionBackground; - } - - - public Icon getOriginalLeafIcon() { - return UIManager.getIcon("Tree.leafIcon"); - } - - protected int getBorderLeft() { - Icon icon = getIcon(); - if (icon != null && getText() != null){ - return icon.getIconWidth() + Math.max(0, getIconTextGap() - 1); - //return 0; - } - else{ - return 0; - } - } - - public Color getTextForeground() { - return myTextForeground; - } - -} + +/* + * 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.ui.plaf.beg; + +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import javax.swing.plaf.ColorUIResource; +import javax.swing.tree.TreeCellRenderer; +import java.awt.*; + +//TODO remove the class + +@SuppressWarnings({"HardCodedStringLiteral"}) +public class BegCellRenderer extends JLabel implements TreeCellRenderer, ListCellRenderer { + protected Icon myLeafIcon; + protected Color myTextBackground; + protected boolean mySelected; + protected Color mySelectionForeground; + protected Color mySelectionBorderColor; + protected Icon myOpenIcon; + protected boolean myHasFocus; + protected Color myTextForeground; + protected boolean myDrawsFocusBorderAroundIcon; + protected Color mySelectionBackground; + protected Icon myClosedIcon; + + public BegCellRenderer() { + setHorizontalAlignment(SwingConstants.LEFT); + } + + public void setSelectionBorderColor(Color color) { + mySelectionBorderColor = color; + } + + public Component getTreeCellRendererComponent(JTree tree, Object obj, boolean selected, boolean expanded, boolean leaf, int i1, boolean hasFocus) { + setFont(UIUtil.getLabelFont()); + setLeafIcon(UIManager.getIcon("Tree.leafIcon")); + setClosedIcon(UIManager.getIcon("Tree.closedIcon")); + setOpenIcon(UIManager.getIcon("Tree.openIcon")); + setSelectionForeground(UIUtil.getTreeSelectionForeground()); + setTextForeground(UIUtil.getTreeTextForeground()); + setSelectionBackground(UIUtil.getTreeSelectionBackground()); + setTextBackground(UIUtil.getTreeTextBackground()); + setSelectionBorderColor(UIUtil.getTreeSelectionBorderColor()); + Object obj1 = UIManager.get("Tree.drawsFocusBorderAroundIcon"); + myDrawsFocusBorderAroundIcon = obj1 != null && ((Boolean)obj1).booleanValue(); + + myHasFocus = hasFocus; + mySelected = selected; + String text = tree.convertValueToText(obj, selected, expanded, leaf, i1, hasFocus); + setText(text); + if (selected){ + if(hasFocus){ + setForeground(getSelectionForeground()); + }else{ + setForeground(getTextForeground()); + } + } + else{ + setForeground(getTextForeground()); + } + Icon icon = getIcon(tree, obj, selected, expanded, leaf, i1, hasFocus); + setIcon(icon); + return this; + } + + public Component getListCellRendererComponent(JList list, Object obj, int index, boolean selected, boolean hasFocus) { + setFont(UIUtil.getLabelFont()); + setSelectionForeground(UIUtil.getListSelectionForeground()); + setTextForeground(UIUtil.getListForeground()); + setSelectionBackground(UIManager.getColor("List.selectionBackground")); + setTextBackground(UIUtil.getListBackground()); + setSelectionBorderColor(UIUtil.getTreeSelectionBorderColor()); + Object obj1 = UIManager.get("Tree.drawsFocusBorderAroundIcon"); + myDrawsFocusBorderAroundIcon = obj1 != null && ((Boolean)obj1).booleanValue(); + + myHasFocus = hasFocus; + mySelected = selected; + String text = String.valueOf(obj); + setText(text); + if (hasFocus && selected){ + setForeground(getSelectionForeground()); + }else{ + setForeground(getTextForeground()); + } + return this; + } + + + public void setSelectionBackground(Color color) { + mySelectionBackground = color; + } + + public Icon getClosedIcon() { + return myClosedIcon; + } + + public Dimension getPreferredSize() { + Dimension dimension = super.getPreferredSize(); + if (dimension != null){ + dimension = new Dimension(dimension.width + 3, dimension.height); + } + return dimension; + } + + public Icon getOriginalClosedIcon() { + return UIManager.getIcon("Tree.closedIcon"); + } + + public Color getSelectionBorderColor() { + return mySelectionBorderColor; + } + + public Color getSelectionForeground() { + return mySelectionForeground; + } + + public Color getTextBackground() { + return myTextBackground; + } + + public void setClosedIcon(Icon icon) { + myClosedIcon = icon; + } + + public void setTextBackground(Color color) { + myTextBackground = color; + } + + public void setTextForeground(Color color) { + myTextForeground = color; + } + + protected Icon getIcon(JTree jtree, Object obj, boolean selected, boolean expanded, boolean leaf, int i1, boolean flag3) { + Icon icon; + if (leaf){ + icon = getLeafIcon(); + } + else + if (expanded) + icon = getOpenIcon(); + else + icon = getClosedIcon(); + return icon; + } + + public Icon getLeafIcon() { + return myLeafIcon; + } + + public void setOpenIcon(Icon icon) { + myOpenIcon = icon; + } + + protected void paintNode(Graphics g) { + Color color; + if (mySelected) { + if (myHasFocus){ + color = getSelectionBackground(); + } + else{ + color = new Color(223, 223, 223); + } + } + else{ + color = getTextBackground(); + if (color == null){ + color = getBackground(); + } + } + if (color != null){ + int i1 = getBorderLeft(); + g.setColor(color); + g.fillRect(i1 - 1, 0, getWidth() - i1, getHeight()); + } + } + + protected void paintBorder(Graphics g) { +// if (/*myHasFocus ||*/ mySelected) { + if (myHasFocus){ + int i1 = getBorderLeft(); + if (myDrawsFocusBorderAroundIcon){ + i1 = 0; + } + else + if (i1 == -1){ + i1 = getBorderLeft(); + } + Color color = getSelectionBorderColor(); + if (color != null){ + g.setColor(color); + int j1 = getWidth() - 1; + int k1 = getHeight() - 1; + UIUtil.drawDottedRectangle(g, i1 > 0 ? i1 - 1 : i1, 0, j1, k1); + } + } + } + + public void paint(Graphics g) { + paintNode(g); + paintBorder(g); + super.paint(g); + } + + public Icon getOpenIcon() { + return myOpenIcon; + } + + public void setSelectionForeground(Color color) { + mySelectionForeground = color; + } + + public Icon getOriginalOpenIcon() { + return UIManager.getIcon("Tree.openIcon"); + } + + public void setLeafIcon(Icon icon) { + myLeafIcon = icon; + } + + public void setBackground(Color color) { + if (color instanceof ColorUIResource){ + color = null; + } + super.setBackground(color); + } + + public Color getSelectionBackground() { + return mySelectionBackground; + } + + + public Icon getOriginalLeafIcon() { + return UIManager.getIcon("Tree.leafIcon"); + } + + protected int getBorderLeft() { + Icon icon = getIcon(); + if (icon != null && getText() != null){ + return icon.getIconWidth() + Math.max(0, getIconTextGap() - 1); + //return 0; + } + else{ + return 0; + } + } + + public Color getTextForeground() { + return myTextForeground; + } + +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java b/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java index 9dcd90611f1e..c4eb6386647b 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java @@ -1,170 +1,170 @@ -/* - * 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.ui.popup; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.ui.popup.util.PopupUtil; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.ReflectionUtil; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; - -public interface PopupComponent { - - Logger LOG = Logger.getInstance("#com.intellij.ui.popup.PopupComponent"); - - void hide(boolean dispose); - - void show(); - - Window getWindow(); - - void setRequestFocus(boolean requestFocus); - - interface Factory { - PopupComponent getPopup(Component owner, Component content, int x, int y); - - boolean isNativePopup(); - - class AwtDefault implements Factory { - public PopupComponent getPopup(Component owner, Component content, int x, int y) { - return new AwtPopupWrapper(PopupFactory.getSharedInstance().getPopup(owner, content, x, y)); - } - - public boolean isNativePopup() { - return true; - } - } - - class AwtHeavyweight implements Factory { - public PopupComponent getPopup(Component owner, Component content, int x, int y) { - final PopupFactory factory = PopupFactory.getSharedInstance(); - - final int oldType = PopupUtil.getPopupType(factory); - PopupUtil.setPopupType(factory, 2); - final Popup popup = factory.getPopup(owner, content, x, y); - if (oldType >= 0) PopupUtil.setPopupType(factory, oldType); - - return new AwtPopupWrapper(popup); - } - - public boolean isNativePopup() { - return true; - } - } - - class Dialog implements Factory { - public PopupComponent getPopup(Component owner, Component content, int x, int y) { - return new DialogPopupWrapper(owner, content, x, y); - }public boolean isNativePopup() { - return false; - } - } - } - - class DialogPopupWrapper implements PopupComponent { - private final JDialog myDialog; - private boolean myRequestFocus = true; - - public void setRequestFocus(boolean requestFocus) { - myRequestFocus = requestFocus; - } - - public DialogPopupWrapper(Component owner, Component content, int x, int y) { - if (!owner.isShowing()) { - throw new IllegalArgumentException("Popup owner must be showing"); - } - - final Window wnd = owner instanceof Window ? (Window)owner: SwingUtilities.getWindowAncestor(owner); - if (wnd instanceof Frame) { - myDialog = new JDialog((Frame)wnd, false); - } else { - myDialog = new JDialog((Dialog)wnd, false); - } - - myDialog.getContentPane().setLayout(new BorderLayout()); - myDialog.getContentPane().add(content, BorderLayout.CENTER); - - myDialog.setUndecorated(true); - myDialog.pack(); - myDialog.setLocation(x, y); - } - - public Window getWindow() { - return myDialog; - } - - public void hide(boolean dispose) { - myDialog.setVisible(false); - if (dispose) { - myDialog.dispose(); - } - } - - public void show() { - if (!myRequestFocus) { - myDialog.setFocusableWindowState(false); - } - myDialog.setVisible(true); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - myDialog.setFocusableWindowState(true); - } - }); - } - } - - class AwtPopupWrapper implements PopupComponent { - - private final Popup myPopup; - - public AwtPopupWrapper(Popup popup) { - myPopup = popup; - - if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) { - final Component c = (Component)ReflectionUtil.getField(Popup.class, myPopup, Component.class, "component"); - c.setBackground(UIUtil.getPanelBackground()); - } - } - - public void hide(boolean dispose) { - myPopup.hide(); - - Window wnd = getWindow(); - if (wnd instanceof JWindow) { - JRootPane rootPane = ((JWindow)wnd).getRootPane(); - if (rootPane != null) { - ReflectionUtil.resetField(rootPane, "clientProperties"); - } - } - } - - public void show() { - myPopup.show(); - } - - public Window getWindow() { - final Component c = (Component)ReflectionUtil.getField(Popup.class, myPopup, Component.class, "component"); - return c instanceof JWindow ? (JWindow)c : null; - } - - public void setRequestFocus(boolean requestFocus) { - } - } - -} +/* + * 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.ui.popup; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.ui.popup.util.PopupUtil; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.ReflectionUtil; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; + +public interface PopupComponent { + + Logger LOG = Logger.getInstance("#com.intellij.ui.popup.PopupComponent"); + + void hide(boolean dispose); + + void show(); + + Window getWindow(); + + void setRequestFocus(boolean requestFocus); + + interface Factory { + PopupComponent getPopup(Component owner, Component content, int x, int y); + + boolean isNativePopup(); + + class AwtDefault implements Factory { + public PopupComponent getPopup(Component owner, Component content, int x, int y) { + return new AwtPopupWrapper(PopupFactory.getSharedInstance().getPopup(owner, content, x, y)); + } + + public boolean isNativePopup() { + return true; + } + } + + class AwtHeavyweight implements Factory { + public PopupComponent getPopup(Component owner, Component content, int x, int y) { + final PopupFactory factory = PopupFactory.getSharedInstance(); + + final int oldType = PopupUtil.getPopupType(factory); + PopupUtil.setPopupType(factory, 2); + final Popup popup = factory.getPopup(owner, content, x, y); + if (oldType >= 0) PopupUtil.setPopupType(factory, oldType); + + return new AwtPopupWrapper(popup); + } + + public boolean isNativePopup() { + return true; + } + } + + class Dialog implements Factory { + public PopupComponent getPopup(Component owner, Component content, int x, int y) { + return new DialogPopupWrapper(owner, content, x, y); + }public boolean isNativePopup() { + return false; + } + } + } + + class DialogPopupWrapper implements PopupComponent { + private final JDialog myDialog; + private boolean myRequestFocus = true; + + public void setRequestFocus(boolean requestFocus) { + myRequestFocus = requestFocus; + } + + public DialogPopupWrapper(Component owner, Component content, int x, int y) { + if (!owner.isShowing()) { + throw new IllegalArgumentException("Popup owner must be showing"); + } + + final Window wnd = owner instanceof Window ? (Window)owner: SwingUtilities.getWindowAncestor(owner); + if (wnd instanceof Frame) { + myDialog = new JDialog((Frame)wnd, false); + } else { + myDialog = new JDialog((Dialog)wnd, false); + } + + myDialog.getContentPane().setLayout(new BorderLayout()); + myDialog.getContentPane().add(content, BorderLayout.CENTER); + + myDialog.setUndecorated(true); + myDialog.pack(); + myDialog.setLocation(x, y); + } + + public Window getWindow() { + return myDialog; + } + + public void hide(boolean dispose) { + myDialog.setVisible(false); + if (dispose) { + myDialog.dispose(); + } + } + + public void show() { + if (!myRequestFocus) { + myDialog.setFocusableWindowState(false); + } + myDialog.setVisible(true); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myDialog.setFocusableWindowState(true); + } + }); + } + } + + class AwtPopupWrapper implements PopupComponent { + + private final Popup myPopup; + + public AwtPopupWrapper(Popup popup) { + myPopup = popup; + + if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) { + final Component c = (Component)ReflectionUtil.getField(Popup.class, myPopup, Component.class, "component"); + c.setBackground(UIUtil.getPanelBackground()); + } + } + + public void hide(boolean dispose) { + myPopup.hide(); + + Window wnd = getWindow(); + if (wnd instanceof JWindow) { + JRootPane rootPane = ((JWindow)wnd).getRootPane(); + if (rootPane != null) { + ReflectionUtil.resetField(rootPane, "clientProperties"); + } + } + } + + public void show() { + myPopup.show(); + } + + public Window getWindow() { + final Component c = (Component)ReflectionUtil.getField(Popup.class, myPopup, Component.class, "component"); + return c instanceof JWindow ? (JWindow)c : null; + } + + public void setRequestFocus(boolean requestFocus) { + } + } + +} diff --git a/platform/platform-impl/src/com/intellij/util/CachedValueBase.java b/platform/platform-impl/src/com/intellij/util/CachedValueBase.java index dd2ba64e42a9..305e21b700d1 100644 --- a/platform/platform-impl/src/com/intellij/util/CachedValueBase.java +++ b/platform/platform-impl/src/com/intellij/util/CachedValueBase.java @@ -1,223 +1,223 @@ -/* - * 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.util; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.ModificationTracker; -import com.intellij.openapi.util.Ref; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.reference.SoftReference; -import gnu.trove.TLongArrayList; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.ref.Reference; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Dmitry Avdeev - */ -public abstract class CachedValueBase { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.CachedValueImpl"); - private final MyTimedReference myData = new MyTimedReference(); - - protected Data computeData(T value, Object[] dependencies) { - if (dependencies == null) { - return new Data(value, null, null); - } - - TLongArrayList timeStamps = new TLongArrayList(dependencies.length); - List deps = new ArrayList(dependencies.length); - collectDependencies(timeStamps, deps, dependencies); - - return new Data(value, ArrayUtil.toObjectArray(deps), timeStamps.toNativeArray()); - } - - protected void setValue(final T value, final CachedValueProvider.Result result) { - myData.setData(computeData(value == null ? (T)ObjectUtils.NULL : value, getDependencies(result))); - myData.setIsLocked(result != null && result.isLockValue()); - } - - @Nullable - protected Object[] getDependencies(CachedValueProvider.Result result) { - return result == null ? null : result.getDependencyItems(); - } - - @Nullable - protected Object[] getDependenciesPlusValue(CachedValueProvider.Result result) { - if (result == null) { - return null; - } - else { - Object[] items = result.getDependencyItems(); - T value = result.getValue(); - return value == null ? items : items == null ? new Object[] {value}: ArrayUtil.append(items, value); - } - } - - public void clear() { - myData.set(null); - } - - public void setDataLocked(boolean value) { - myData.setIsLocked(value); - } - - public boolean hasUpToDateValue() { - return getUpToDateOrNull(false) != null; - } - - @Nullable - private T getUpToDateOrNull(boolean dispose) { - final Data data = myData.getData(); - - if (data != null) { - T value = data.myValue; - if (isUpToDate(data)) { - return value; - } - if (dispose && value instanceof Disposable) { - Disposer.dispose((Disposable)value); - } - } - return null; - } - - protected boolean isUpToDate(@NotNull Data data) { - if (data.myTimeStamps == null) return true; - - for (int i = 0; i < data.myDependencies.length; i++) { - Object dependency = data.myDependencies[i]; - if (dependency == null) continue; - if (isDependencyOutOfDate(dependency, data.myTimeStamps[i])) return false; - } - - return true; - } - - protected boolean isDependencyOutOfDate(Object dependency, long oldTimeStamp) { - final long timeStamp = getTimeStamp(dependency); - return timeStamp < 0 || timeStamp != oldTimeStamp; - } - - protected void collectDependencies(TLongArrayList timeStamps, List resultingDeps, Object[] dependencies) { - for (Object dependency : dependencies) { - if (dependency == null || dependency == ObjectUtils.NULL) continue; - if (dependency instanceof Object[]) { - collectDependencies(timeStamps, resultingDeps, (Object[])dependency); - } - else { - resultingDeps.add(dependency); - timeStamps.add(getTimeStamp(dependency)); - } - } - } - - protected long getTimeStamp(Object dependency) { - if (dependency instanceof ModificationTracker) { - return ((ModificationTracker)dependency).getModificationCount(); - } - else if (dependency instanceof Reference){ - final Object original = ((Reference)dependency).get(); - if(original == null) return -1; - return getTimeStamp(original); - } - else if (dependency instanceof Ref) { - final Object original = ((Ref)dependency).get(); - if(original == null) return -1; - return getTimeStamp(original); - } - else if (dependency instanceof Document) { - return ((Document)dependency).getModificationStamp(); - } - else { - LOG.error("Wrong dependency type: " + dependency.getClass()); - return -1; - } - } - - public T setValue(final CachedValueProvider.Result result) { - T value = result == null ? null : result.getValue(); - setValue(value, result); - return value; - } - - public abstract boolean isFromMyProject(Project project); - - protected static class Data implements Disposable { - private final T myValue; - private final Object[] myDependencies; - private final long[] myTimeStamps; - - public Data(final T value, final Object[] dependencies, final long[] timeStamps) { - myValue = value; - myDependencies = dependencies; - myTimeStamps = timeStamps; - } - - public void dispose() { - if (myValue instanceof Disposable) { - Disposer.dispose((Disposable)myValue); - } - } - } - - @Nullable - protected

T getValueWithLock(P param) { - T value = getUpToDateOrNull(true); - if (value != null) { - return value == ObjectUtils.NULL ? null : value; - } - - // compute outside lock to avoid deadlock - CachedValueProvider.Result result = doCompute(param); - - return setValue(result); - } - - protected abstract

CachedValueProvider.Result doCompute(P param); - - private static class MyTimedReference extends TimedReference>> { - private boolean myIsLocked; - - public MyTimedReference() { - super(null); - } - - public synchronized void setIsLocked(final boolean isLocked) { - myIsLocked = isLocked; - } - - protected synchronized boolean isLocked() { - return super.isLocked() || myIsLocked; - } - - public void setData(final Data data) { - set(new SoftReference>(data)); - } - - @Nullable - public Data getData() { - final SoftReference> ref = get(); - return ref != null ? ref.get() : null; - } - } -} +/* + * 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.util; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.ModificationTracker; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.reference.SoftReference; +import gnu.trove.TLongArrayList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.Reference; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Dmitry Avdeev + */ +public abstract class CachedValueBase { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.CachedValueImpl"); + private final MyTimedReference myData = new MyTimedReference(); + + protected Data computeData(T value, Object[] dependencies) { + if (dependencies == null) { + return new Data(value, null, null); + } + + TLongArrayList timeStamps = new TLongArrayList(dependencies.length); + List deps = new ArrayList(dependencies.length); + collectDependencies(timeStamps, deps, dependencies); + + return new Data(value, ArrayUtil.toObjectArray(deps), timeStamps.toNativeArray()); + } + + protected void setValue(final T value, final CachedValueProvider.Result result) { + myData.setData(computeData(value == null ? (T)ObjectUtils.NULL : value, getDependencies(result))); + myData.setIsLocked(result != null && result.isLockValue()); + } + + @Nullable + protected Object[] getDependencies(CachedValueProvider.Result result) { + return result == null ? null : result.getDependencyItems(); + } + + @Nullable + protected Object[] getDependenciesPlusValue(CachedValueProvider.Result result) { + if (result == null) { + return null; + } + else { + Object[] items = result.getDependencyItems(); + T value = result.getValue(); + return value == null ? items : items == null ? new Object[] {value}: ArrayUtil.append(items, value); + } + } + + public void clear() { + myData.set(null); + } + + public void setDataLocked(boolean value) { + myData.setIsLocked(value); + } + + public boolean hasUpToDateValue() { + return getUpToDateOrNull(false) != null; + } + + @Nullable + private T getUpToDateOrNull(boolean dispose) { + final Data data = myData.getData(); + + if (data != null) { + T value = data.myValue; + if (isUpToDate(data)) { + return value; + } + if (dispose && value instanceof Disposable) { + Disposer.dispose((Disposable)value); + } + } + return null; + } + + protected boolean isUpToDate(@NotNull Data data) { + if (data.myTimeStamps == null) return true; + + for (int i = 0; i < data.myDependencies.length; i++) { + Object dependency = data.myDependencies[i]; + if (dependency == null) continue; + if (isDependencyOutOfDate(dependency, data.myTimeStamps[i])) return false; + } + + return true; + } + + protected boolean isDependencyOutOfDate(Object dependency, long oldTimeStamp) { + final long timeStamp = getTimeStamp(dependency); + return timeStamp < 0 || timeStamp != oldTimeStamp; + } + + protected void collectDependencies(TLongArrayList timeStamps, List resultingDeps, Object[] dependencies) { + for (Object dependency : dependencies) { + if (dependency == null || dependency == ObjectUtils.NULL) continue; + if (dependency instanceof Object[]) { + collectDependencies(timeStamps, resultingDeps, (Object[])dependency); + } + else { + resultingDeps.add(dependency); + timeStamps.add(getTimeStamp(dependency)); + } + } + } + + protected long getTimeStamp(Object dependency) { + if (dependency instanceof ModificationTracker) { + return ((ModificationTracker)dependency).getModificationCount(); + } + else if (dependency instanceof Reference){ + final Object original = ((Reference)dependency).get(); + if(original == null) return -1; + return getTimeStamp(original); + } + else if (dependency instanceof Ref) { + final Object original = ((Ref)dependency).get(); + if(original == null) return -1; + return getTimeStamp(original); + } + else if (dependency instanceof Document) { + return ((Document)dependency).getModificationStamp(); + } + else { + LOG.error("Wrong dependency type: " + dependency.getClass()); + return -1; + } + } + + public T setValue(final CachedValueProvider.Result result) { + T value = result == null ? null : result.getValue(); + setValue(value, result); + return value; + } + + public abstract boolean isFromMyProject(Project project); + + protected static class Data implements Disposable { + private final T myValue; + private final Object[] myDependencies; + private final long[] myTimeStamps; + + public Data(final T value, final Object[] dependencies, final long[] timeStamps) { + myValue = value; + myDependencies = dependencies; + myTimeStamps = timeStamps; + } + + public void dispose() { + if (myValue instanceof Disposable) { + Disposer.dispose((Disposable)myValue); + } + } + } + + @Nullable + protected

T getValueWithLock(P param) { + T value = getUpToDateOrNull(true); + if (value != null) { + return value == ObjectUtils.NULL ? null : value; + } + + // compute outside lock to avoid deadlock + CachedValueProvider.Result result = doCompute(param); + + return setValue(result); + } + + protected abstract

CachedValueProvider.Result doCompute(P param); + + private static class MyTimedReference extends TimedReference>> { + private boolean myIsLocked; + + public MyTimedReference() { + super(null); + } + + public synchronized void setIsLocked(final boolean isLocked) { + myIsLocked = isLocked; + } + + protected synchronized boolean isLocked() { + return super.isLocked() || myIsLocked; + } + + public void setData(final Data data) { + set(new SoftReference>(data)); + } + + @Nullable + public Data getData() { + final SoftReference> ref = get(); + return ref != null ? ref.get() : null; + } + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java index 6821504d696a..3347c2bc15e4 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java @@ -1,227 +1,227 @@ -/* - * 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.editor.impl.softwrap.mapping; - -import com.intellij.openapi.editor.FoldRegion; -import com.intellij.openapi.editor.FoldingModel; -import com.intellij.openapi.editor.VisualPosition; -import com.intellij.openapi.editor.ex.SoftWrapModelEx; -import com.intellij.openapi.editor.impl.SoftWrapModelImpl; -import com.intellij.testFramework.LightPlatformCodeInsightTestCase; - -import java.awt.*; -import java.io.IOException; - -/** - * @author Denis Zhdanov - * @since 09/16/2010 - */ -public class SoftWrapApplianceOnDocumentModificationTest extends LightPlatformCodeInsightTestCase { - - //private static final String PATH = "/codeInsight/softwrap/"; - - @Override - protected void tearDown() throws Exception { - myEditor.getSettings().setUseSoftWraps(false); - super.tearDown(); - } - - public void testSoftWrapAdditionOnTyping() throws Exception { - String text = - "this is a test string that is expected to end just before right margin"; - init(800, text); - - int offset = myEditor.getDocument().getTextLength() + 1; - assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); - type(" thisisalongtokenthatisnotexpectedtobebrokenintopartsduringsoftwrapping"); - assertNotNull(myEditor.getSoftWrapModel().getSoftWrap(offset)); - } - - public void testLongLineOfIdSymbolsIsNotSoftWrapped() throws Exception { - String text = - "abcdefghijklmnopqrstuvwxyz\n" + - "123\n" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - init(100, text); - assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); - type('1'); - assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); - - int offset = myEditor.getDocument().getText().indexOf("\n"); - type(" test"); - assertEquals(1, getSoftWrapModel().getRegisteredSoftWraps().size()); - assertNotNull(getSoftWrapModel().getSoftWrap(offset)); - } - - public void testFoldRegionCollapsing() throws Exception { - String text = - "class Test {\n" + - " public void foo() {\n" + - " System.out.println(\"test\");\n" + - " }\n" + - "}"; - - init(300, text); - final FoldingModel foldingModel = myEditor.getFoldingModel(); - assertEmpty(foldingModel.getAllFoldRegions()); - - final int startOffset = text.indexOf('{'); - final int endOffset = text.indexOf('}') + 1; - - VisualPosition foldStartPosition = myEditor.offsetToVisualPosition(startOffset); - - addFoldRegion(startOffset, endOffset, "..."); - - final FoldRegion foldRegion = foldingModel.getAllFoldRegions()[0]; - assertNotNull(foldRegion); - assertTrue(foldRegion.isExpanded()); - toggleFoldRegionState(myEditor.getFoldingModel().getAllFoldRegions()[0], false); - - // Expecting that all offsets that belong to collapsed fold region point to the region's start. - assertEquals(foldStartPosition, myEditor.offsetToVisualPosition(startOffset + 5)); - } - - public void testTypingEnterAtDocumentEnd() throws IOException { - String text = - "class Test {\n" + - " public void foo() {\n" + - " System.out.println(\"test\");\n" + - " }\n" + - "}"; - - init(300, text); - type('\n'); - VisualPosition position = myEditor.getCaretModel().getVisualPosition(); - assertEquals(new VisualPosition(5, 0), position); - } - - public void testDeleteDocumentTail() throws IOException { - String text = - "class Test {\n" + - " public void foo() {\n" + - " System.out.println(\"test\");\n" + - " }\n" + - "}\n" + - "abcde"; - - init(300, text); - int offset = text.indexOf("abcde"); - myEditor.getSelectionModel().setSelection(offset, text.length()); - delete(); - assertEquals(new VisualPosition(5, 0), myEditor.getCaretModel().getVisualPosition()); - } - - public void testTypingTabOnLastEmptyLine() throws IOException { - String text = - "class Test {\n" + - "}\n" + - ""; - - init(300, text); - type('\t'); - assertEquals(new VisualPosition(2, 4), myEditor.getCaretModel().getVisualPosition()); - } - - public void testTrailingFoldRegionRemoval() throws IOException { - String text = - "public class BrokenAlignment {\n" + - " @SuppressWarnings({ \"SomeInspectionIWantToIgnore\" })\n" + - " public void doSomething(int x, int y) {\n" + - " }\n" + - "}"; - - init(700, text); - - int startFoldOffset = text.indexOf('@'); - int endFoldOffset = text.indexOf(')'); - addFoldRegion(startFoldOffset, endFoldOffset, "/SomeInspectionIWantToIgnore/"); - toggleFoldRegionState(myEditor.getFoldingModel().getAllFoldRegions()[0], false); - - int endSelectionOffset = text.lastIndexOf("}\n") + 1; - myEditor.getSelectionModel().setSelection(startFoldOffset, endSelectionOffset); - - delete(); - // Don't expect any exceptions here. - } - - public void testTypeNewLastLineAndSymbolOnIt() throws IOException { - // Inspired by IDEA-59439 - String text = - "This is a test document\n" + - "line1\n" + - "line2\n" + - "line3\n" + - "line4\n" + - "line5\n" + - "line6"; - - init(700, text); - type("\nq"); - assertEquals(new VisualPosition(7, 1), myEditor.offsetToVisualPosition(myEditor.getDocument().getTextLength())); - } - - //private void init(final int visibleWidth) throws Exception { - // configureByFile(PATH + getFileName()); - // initCommon(visibleWidth); - //} - - private void init(int visibleWidth, String fileText) throws IOException { - configureFromFileText(getFileName(), fileText); - initCommon(visibleWidth); - } - - private String getFileName() { - return getTestName(false) + ".txt"; - } - - private static void addFoldRegion(final int startOffset, final int endOffset, final String placeholder) { - myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - myEditor.getFoldingModel().addFoldRegion(startOffset, endOffset, placeholder); - } - }); - } - - private static void toggleFoldRegionState(final FoldRegion foldRegion, final boolean expanded) { - myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - foldRegion.setExpanded(expanded); - } - }); - } - - private static void initCommon(final int visibleWidth) { - myEditor.getSettings().setUseSoftWraps(true); - SoftWrapModelImpl model = (SoftWrapModelImpl)myEditor.getSoftWrapModel(); - model.refreshSettings(); - - SoftWrapApplianceManager applianceManager = model.getApplianceManager(); - applianceManager.setWidthProvider(new SoftWrapApplianceManager.VisibleAreaWidthProvider() { - @Override - public int getVisibleAreaWidth() { - return visibleWidth; - } - }); - applianceManager.registerSoftWrapIfNecessary(new Rectangle(visibleWidth, visibleWidth * 2), 0); - } - - private static SoftWrapModelEx getSoftWrapModel() { - return (SoftWrapModelEx)myEditor.getSoftWrapModel(); - } -} +/* + * 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.editor.impl.softwrap.mapping; + +import com.intellij.openapi.editor.FoldRegion; +import com.intellij.openapi.editor.FoldingModel; +import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.editor.ex.SoftWrapModelEx; +import com.intellij.openapi.editor.impl.SoftWrapModelImpl; +import com.intellij.testFramework.LightPlatformCodeInsightTestCase; + +import java.awt.*; +import java.io.IOException; + +/** + * @author Denis Zhdanov + * @since 09/16/2010 + */ +public class SoftWrapApplianceOnDocumentModificationTest extends LightPlatformCodeInsightTestCase { + + //private static final String PATH = "/codeInsight/softwrap/"; + + @Override + protected void tearDown() throws Exception { + myEditor.getSettings().setUseSoftWraps(false); + super.tearDown(); + } + + public void testSoftWrapAdditionOnTyping() throws Exception { + String text = + "this is a test string that is expected to end just before right margin"; + init(800, text); + + int offset = myEditor.getDocument().getTextLength() + 1; + assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); + type(" thisisalongtokenthatisnotexpectedtobebrokenintopartsduringsoftwrapping"); + assertNotNull(myEditor.getSoftWrapModel().getSoftWrap(offset)); + } + + public void testLongLineOfIdSymbolsIsNotSoftWrapped() throws Exception { + String text = + "abcdefghijklmnopqrstuvwxyz\n" + + "123\n" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + init(100, text); + assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); + type('1'); + assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); + + int offset = myEditor.getDocument().getText().indexOf("\n"); + type(" test"); + assertEquals(1, getSoftWrapModel().getRegisteredSoftWraps().size()); + assertNotNull(getSoftWrapModel().getSoftWrap(offset)); + } + + public void testFoldRegionCollapsing() throws Exception { + String text = + "class Test {\n" + + " public void foo() {\n" + + " System.out.println(\"test\");\n" + + " }\n" + + "}"; + + init(300, text); + final FoldingModel foldingModel = myEditor.getFoldingModel(); + assertEmpty(foldingModel.getAllFoldRegions()); + + final int startOffset = text.indexOf('{'); + final int endOffset = text.indexOf('}') + 1; + + VisualPosition foldStartPosition = myEditor.offsetToVisualPosition(startOffset); + + addFoldRegion(startOffset, endOffset, "..."); + + final FoldRegion foldRegion = foldingModel.getAllFoldRegions()[0]; + assertNotNull(foldRegion); + assertTrue(foldRegion.isExpanded()); + toggleFoldRegionState(myEditor.getFoldingModel().getAllFoldRegions()[0], false); + + // Expecting that all offsets that belong to collapsed fold region point to the region's start. + assertEquals(foldStartPosition, myEditor.offsetToVisualPosition(startOffset + 5)); + } + + public void testTypingEnterAtDocumentEnd() throws IOException { + String text = + "class Test {\n" + + " public void foo() {\n" + + " System.out.println(\"test\");\n" + + " }\n" + + "}"; + + init(300, text); + type('\n'); + VisualPosition position = myEditor.getCaretModel().getVisualPosition(); + assertEquals(new VisualPosition(5, 0), position); + } + + public void testDeleteDocumentTail() throws IOException { + String text = + "class Test {\n" + + " public void foo() {\n" + + " System.out.println(\"test\");\n" + + " }\n" + + "}\n" + + "abcde"; + + init(300, text); + int offset = text.indexOf("abcde"); + myEditor.getSelectionModel().setSelection(offset, text.length()); + delete(); + assertEquals(new VisualPosition(5, 0), myEditor.getCaretModel().getVisualPosition()); + } + + public void testTypingTabOnLastEmptyLine() throws IOException { + String text = + "class Test {\n" + + "}\n" + + ""; + + init(300, text); + type('\t'); + assertEquals(new VisualPosition(2, 4), myEditor.getCaretModel().getVisualPosition()); + } + + public void testTrailingFoldRegionRemoval() throws IOException { + String text = + "public class BrokenAlignment {\n" + + " @SuppressWarnings({ \"SomeInspectionIWantToIgnore\" })\n" + + " public void doSomething(int x, int y) {\n" + + " }\n" + + "}"; + + init(700, text); + + int startFoldOffset = text.indexOf('@'); + int endFoldOffset = text.indexOf(')'); + addFoldRegion(startFoldOffset, endFoldOffset, "/SomeInspectionIWantToIgnore/"); + toggleFoldRegionState(myEditor.getFoldingModel().getAllFoldRegions()[0], false); + + int endSelectionOffset = text.lastIndexOf("}\n") + 1; + myEditor.getSelectionModel().setSelection(startFoldOffset, endSelectionOffset); + + delete(); + // Don't expect any exceptions here. + } + + public void testTypeNewLastLineAndSymbolOnIt() throws IOException { + // Inspired by IDEA-59439 + String text = + "This is a test document\n" + + "line1\n" + + "line2\n" + + "line3\n" + + "line4\n" + + "line5\n" + + "line6"; + + init(700, text); + type("\nq"); + assertEquals(new VisualPosition(7, 1), myEditor.offsetToVisualPosition(myEditor.getDocument().getTextLength())); + } + + //private void init(final int visibleWidth) throws Exception { + // configureByFile(PATH + getFileName()); + // initCommon(visibleWidth); + //} + + private void init(int visibleWidth, String fileText) throws IOException { + configureFromFileText(getFileName(), fileText); + initCommon(visibleWidth); + } + + private String getFileName() { + return getTestName(false) + ".txt"; + } + + private static void addFoldRegion(final int startOffset, final int endOffset, final String placeholder) { + myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + myEditor.getFoldingModel().addFoldRegion(startOffset, endOffset, placeholder); + } + }); + } + + private static void toggleFoldRegionState(final FoldRegion foldRegion, final boolean expanded) { + myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + foldRegion.setExpanded(expanded); + } + }); + } + + private static void initCommon(final int visibleWidth) { + myEditor.getSettings().setUseSoftWraps(true); + SoftWrapModelImpl model = (SoftWrapModelImpl)myEditor.getSoftWrapModel(); + model.refreshSettings(); + + SoftWrapApplianceManager applianceManager = model.getApplianceManager(); + applianceManager.setWidthProvider(new SoftWrapApplianceManager.VisibleAreaWidthProvider() { + @Override + public int getVisibleAreaWidth() { + return visibleWidth; + } + }); + applianceManager.registerSoftWrapIfNecessary(new Rectangle(visibleWidth, visibleWidth * 2), 0); + } + + private static SoftWrapModelEx getSoftWrapModel() { + return (SoftWrapModelEx)myEditor.getSoftWrapModel(); + } +} diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index a39a2d244aa9..fdb7e90904d3 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -1,1198 +1,1198 @@ -# suppress inspection "UnusedProperty" for whole file -# IDEA core action texts and descriptions go here. -# The key patterns are "action." + id + ".text" and "action." + id + ".description" - -action.CaptureMemorySnapShot.text=Capture Memory Snapshot -action.CaptureMemorySnapShot.description=Capture memory snapshot -action.CaptureCPUUsageData.text=Capture CPU Usage Data -action.CaptureCPUUsageData.description=Capture CPU usage data -action.CaptureAllocations.text=Capture Allocations -action.CaptureAllocations.description=Capture memory allocations data -action.ContextHelp.text=Conte_xt Help -action.ContextHelp.description=Show context help -action.RunConfiguration.text=Select Run/Debug Configuration -action.SwitchCoverage.text=Show Code Co_verage Data -action.GenerateCoverageReport.text=_Generate Coverage Report -action.RunConfiguration.description=Select Run/Debug Configuration -action.ShowIntentionActions.text=Show Intention Actions -action.ToggleReadOnlyAttribute.text=Toggle Read-Only Attribute -action.ToggleReadOnlyAttribute.description=Toggle read-only attribute of current file -action.TogglePopupHints.text=Toggle Popup Hints for Current File -action.RunGc.text=Run Garbage Collector -action.ShowPopupMenu.text=Show Context Menu -action.CloseWindow.text=Close Active Window -action.Vcs.ShowMessageHistory.text=Message History -action.Vcs.ShowMessageHistory.description=Show commit message history -action.Vcs.ShowHistoryForBlock.text=Show History _for Selection -action.Vcs.ShowHistoryForBlock.description=Show history for editor selection -action.Vcs.ShowTabbedFileHistory.text=Show _History -action.Vcs.ShowTabbedFileHistory.description=Show history of file -action.AddToFavoritesPopup.text=Add To Favorites -action.AddToFavoritesPopup.description=Add selected node to favorites -group.AddToFavorites.text=Add To F_avorites -group.AddAllToFavorites.text=Add A_ll To Favorites -action.AddNewFavoritesList.text=Add New Favorites List -action.AddNewFavoritesList.description=Add New Favorites List -action.RemoveFromFavorites.text=Remove From Favorites -action.RemoveFromFavorites.description=Remove selected node from favorites -action.RenameFavoritesList.text=Rename Favorites List -action.RenameFavoritesList.description=Rename the favorites list the selected node belongs to -action.RemoveFavoritesList.text=Delete Favorites List -action.RemoveFavoritesList.description=Delete all favorites the selected node belongs to -action.RemoveAllFavoritesListsButThis.text=Delete All Favorites List But This -action.RemoveAllFavoritesListsButThis.description=Delete all favorites except these -group.SendToFavoritesGroup.text=_Send To Favorites -action.ActivateNavBar.text=Activate Navigation _Bar -action.ActivateNavBar.description=Activate navigation bar -action.CreateRunConfiguration.text=Create Run Configuration -action.CreateRunConfiguration.description=Create run configuration from context -group.RunContextPopupGroup.text=Run Configurations -group.EditorActions.text=Editor Actions -action.EditorChooseLookupItem.text=Choose Lookup Item -action.EditorChooseLookupItemReplace.text=Choose Lookup Item Replace -action.EditorChooseLookupItemCompleteStatement.text=Choose Lookup Item and Invoke Complete Statement -action.NextTemplateVariable.text=Next Template Variable -action.PreviousTemplateVariable.text=Previous Template Variable -action.NextParameter.text=Next Parameter -action.PrevParameter.text=Prev Parameter -action.EditorPageUp.text=Page Up -action.EditorPageDown.text=Page Down -action.EditorPageUpWithSelection.text=Page Up with Selection -action.EditorPageDownWithSelection.text=Page Down with Selection -action.EditorCopy.text=Copy -action.EditorCut.text=Cut -action.EditorPaste.text=Paste -action.EditorPasteSimple.text=Paste S_imple -action.EditorPasteSimple.description=Paste without formatting, autoimport, literal escaping etc. -action.EditorPasteFromX11.text=Paste from X clipboard -action.EditorDelete.text=Delete -action.EditorBackSpace.text=Backspace -action.EditorMoveToPageTop.text=Move Caret to Page Top -action.EditorMoveToPageBottom.text=Move Caret to Page Bottom -action.EditorMoveToPageTopWithSelection.text=Move Caret to Page Top with Selection -action.EditorMoveToPageBottomWithSelection.text=Move Caret to Page Bottom with Selection -action.EditorEnter.text=Enter -action.EditorEscape.text=Escape -action.EditorUp.text=Up -action.EditorDown.text=Down -action.EditorLeft.text=Left -action.EditorRight.text=Right -action.EditorUpWithSelection.text=Up with Selection -action.EditorDownWithSelection.text=Down with Selection -action.EditorLeftWithSelection.text=Left with Selection -action.EditorRightWithSelection.text=Right with Selection -action.EditorIndentSelection.text=Indent Selection -action.EditorUnindentSelection.text=Unindent Selection -action.EditorTab.text=Tab -action.EmacsStyleIndent.text=Emacs Tab -action.EditorScrollUp.text=Scroll Up -action.EditorScrollDown.text=Scroll Down -action.EditorMoveUpAndScroll.text=Move Up and Scroll -action.EditorMoveDownAndScroll.text=Move Down and Scroll -action.EditorMoveUpAndScrollWithSelection.text=Move Up and Scroll with Selection -action.EditorMoveDownAndScrollWithSelection.text=Move Down and Scroll with Selection -action.EditorLineStart.text=Move Caret to Line Start -action.EditorLineEnd.text=Move Caret to Line End -action.EditorCutLineEnd.text=Cut up to Line End -action.EditorTextStart.text=Move Caret to Text Start -action.EditorTextEnd.text=Move Caret to Text End -action.EditorLineStartWithSelection.text=Move Caret to Line Start with Selection -action.EditorLineEndWithSelection.text=Move Caret to Line End with Selection -action.EditorTextStartWithSelection.text=Move Caret to Text Start with Selection -action.EditorTextEndWithSelection.text=Move Caret to Text End with Selection -action.EditorNextWord.text=Move Caret to Next Word -action.EditorPreviousWord.text=Move Caret to Previous Word -action.EditorNextWordWithSelection.text=Move Caret to Next Word with Selection -action.EditorPreviousWordWithSelection.text=Move Caret to Previous Word with Selection -action.EditorCodeBlockStart.text=Move Caret to Code Block Start -action.EditorCodeBlockEnd.text=Move Caret to Code Block End -action.EditorCodeBlockStartWithSelection.text=Move Caret to Code Block Start with Selection -action.EditorCodeBlockEndWithSelection.text=Move Caret to Code Block End with Selection -action.EditorDeleteToWordStart.text=Delete to Word Start -action.EditorDeleteToWordEnd.text=Delete to Word End -action.EditorDeleteLine.text=Delete Line -action.EditorDuplicate.text=Duplicate Line or Block -action.EditorSelectWord.text=Select Word at Caret -action.EditorUnSelectWord.text=Unselect Word at Caret -action.EditorToggleInsertState.text=Toggle Insert/Overwrite -action.EditorToggleColumnMode.text=Column _Mode -action.EditorToggleColumnMode.description=Toggle editor column mode -action.EditorToggleShowWhitespaces.text=Sh_ow Whitespaces -action.EditorToggleShowWhitespaces.description=Toggle display whitespaces in current editor -action.EditorToggleShowIndentLines.text=Sh_ow Indent Guides -action.EditorToggleShowIndentLines.description=Toggle display indent guides in current editor -action.EditorToggleShowLineNumbers.text=Show L_ine Numbers -action.EditorToggleShowLineNumbers.description=Toggle display line numbers in current editor -action.EditorToggleUseSoftWraps.text=Use Soft Wraps -action.EditorToggleUseSoftWraps.description=Toggle using soft wraps in current editor -action.EditorConsoleScrollToTheEnd.text=Scroll to the end -action.ViewImportPopups.text=Show Import Popups -action.ViewImportPopups.description=Toggle display import popups in current file -action.EditorScrollToCenter.text=Scroll to Center -action.EditorToggleCase.text=Toggle Case -action.EditorJoinLines.text=Join Lines -action.EditorSelectLine.text=Select Line at Caret -action.EditorSplitLine.text=Split Line -action.EditorStartNewLine.text=Start New Line -action.EditorCompleteStatement.text=Complete Current Statement -action.ExpandAll.text=Expand All -action.CollapseAll.text=Collapse All -action.ExportToTextFile.text=Export to Text File -action.ExpandTreeNode.text=Expand Tree Node -action.CollapseTreeNode.text=Collapse Tree Node -action.FullyExpandTreeNode.text=Fully Expand Tree Node -group.GenerateGroup.text=_Generate -action.GenerateTestMethod.text=Test Method -action.GenerateSetUpMethod.text=SetUp Method -action.GenerateTearDownMethod.text=TearDown Method -action.GenerateConstructor.text=Constructor -action.GenerateGetter.text=Getter -action.GenerateSetter.text=Setter -action.GenerateGetterAndSetter.text=Getter and Setter -action.GenerateEquals.text=equals() and hashCode() -action.OverrideMethods.text=_Override Methods... -action.OverrideMethods.description=Override base class methods in the current class -action.ImplementMethods.text=_Implement Methods... -action.ImplementMethods.description=Implement base interface/class methods in the current class -action.DelegateMethods.text=_Delegate Methods... -action.DelegateMethods.description=Generate delegate method to a field/getter -action.GenerateSuperMethodCall.text=super method call -action.NextDiff.text=Move to the next difference -action.NextDiff.description=Move to the Next Difference -action.PreviousDiff.text=Move to the Previous Difference -action.PreviousDiff.description=Move to the previous difference -action.Refresh.text=R_efresh -action.CheckinProject.text=Check _In Project -action.CheckinFiles.text=Check _In -action.UpdateFiles.text=_Update -action.CheckStatusForFiles.text=Chec_k Status -action.Annotate.text=A_nnotate -action.Annotate.description=Show information about last modification date and author for each line -action.Compare.SameVersion.text=Compare with the Same Repositor_y Version -action.Compare.LastVersion.text=Compare with Latest Repository _Version -action.Compare.Selected.text=_Compare with... -group.NewGroup.text=_New -group.WeighingNewGroup.text=_New -action.NewClass.text=Java Class -action.NewFile.text=File -action.NewDir.text=Directory/Package -action.NewFromTemplate.text=From Template -action.CompareTwoFiles.text=Compare Two _Files -action.CompareTwoFiles.description=Compare two selected files -action.CompareFileWithEditor.text=Co_mpare File with Editor -action.CompareFileWithEditor.description=Compare selected file with editor - -group.LocalHistory.text=Local _History -action.LocalHistory.ShowHistory.text=Show _History -action.LocalHistory.ShowSelectionHistory.text=Show History for Selection -action.LocalHistory.PutLabel.text=Put _Label... - -group.VcsGroups.text=Version _Control -group.VcsGlobalGroup.text=Version Control Group -action.Vcs.FileView.text=Show Fi_le View -action.Vcs.FileView.description=Show version control related information for all local files -action.Vcs.ChangesView.text=Show Cha_nges View -action.Vcs.ChangesView.description=Show window for managing changes and changelists -action.Vcs.UpdateProject.text=_Update Project -action.Vcs.ProjectStatus.text=C_heck Project Status -group.VcsGroup.text=Version Control -group.VcsFileGroupPopup.text=Version Control Group -group.FileMenu.text=_File -group.ProjectImport.text=Import _Projects -action.NewProject.text=_New Project... -action.NewProject.description=Create a new project -action.NewModule.text=New _Module... -action.NewModule.description=Add new module to the project -action.OpenProject.text=_Open Project... -action.OpenProject.description=Open an existing project -action.OpenFile.text=Open Fi_le... -action.OpenFile.description=Open file in editor -group.reopen.win.text=_Reopen -group.reopen.mac.text=Open _Recent -group.$LRU.description=List of recently opened projects -action.CloseProject.text=Close Pro_ject -action.CloseProject.description=Close current project -action.ShowSettings.text=Se_ttings -action.ShowSettings.description=Configure application settings -action.ShowProjectStructureSettings.text=Pro_ject Structure -action.ShowProjectStructureSettings.description=Configure project structure -action.TemplateProjectProperties.text=Templ_ate Settings -action.TemplateProjectProperties.description=Configure template settings for all new projects -action.TemplateProjectStructure.text=Te_mplate Project Structure -action.TemplateProjectStructure.description=Configure template structure for all new projects -action.SaveAll.text=_Save All -action.SaveAll.description=Save all files and settings -action.ExportSettings.text=_Export Settings... -action.ExportSettings.description=Export global settings -action.ImportSettings.text=_Import Settings... -action.ImportSettings.description=Import global settings -action.ImportFromJBuilder.text=Import _From JBuilder... -action.ImportFromJBuilder.description=Load Modules From JBuilder Project -action.ImportFromEclipse.text=Import From Eclipse... -action.ImportFromEclipse.description=Create project from Eclipse projects -action.ExportToEclipse.text=Export To Eclipse... -action.ExportToEclipse.description=Create Eclipse projects for each module -action.Synchronize.text=S_ynchronize -action.Synchronize.description=Detect all externally changed files and reload them from disk -action.ReloadFromDisk.text=Reload from Dis_k -action.ReloadFromDisk.description=Reload current file from disk and lose all changes -group.Vcs.Checkout.text=Check_out from Version Control -action.ExportToHTML.text=Export to _HTML... -action.ExportToHTML.description=Save file content in HTML format with syntax coloring -action.Print.text=_Print... -action.Print.description=Print file -action.Exit.text=E_xit -action.Exit.description=Quit IDE -group.EditMenu.text=_Edit -action.undo.text=_Undo {0} -action.undo.description=Undo {0} -action.undo.description.empty=last operation -action.redo.text=_Redo {0} -action.redo.description=Redo {0} -action.redo.description.empty=last undone operation -action.$Cut.text=Cu_t -action.$Cut.description=Cut to clipboard -action.$Copy.text=_Copy -action.$Copy.description=Copy to clipboard -action.CopyPaths.text=C_opy Paths -action.CopyPaths.description=Copy paths corresponding to selected files or directories to clipboard -action.CopyReference.text=Copy Re_ference -action.CopyReference.description=Copy Java class/method/field reference -action.$Paste.text=_Paste -action.$Paste.description=Paste from clipboard -action.PasteMultiple.text=Past_e from History... -action.PasteMultiple.description=Paste from recent clipboards -action.$SelectAll.text=Select _All -action.$SelectAll.description=Select all -action.NewElement.text=_New... -action.NewElement.description=Create new class, interface, file or directory -action.NewElementSamePlace.text=_New... -action.NewElementSamePlace.description=Create new class, interface, file or directory -action.$Delete.text=_Delete -action.$Delete.description=Delete selected item -action.ToggleBookmark.text=Toggle _Bookmark -action.ToggleBookmark.description=Toggle bookmark at the current location -action.ShowBookmarks.text=_Show Bookmarks -action.ShowBookmarks.description=Show list of all bookmarks -group.SearchMenu.text=_Search -action.Find.text=_Find... -action.Find.description=Find a string in active editor -action.FindModal.text=_Find... -action.FindModal.description=Find a string in active editor, shows modal dialog -action.Replace.text=_Replace... -action.Replace.description=Replace a string in active editor with another string -action.FindNext.text=Find _Next -action.FindNext.description=Repeat the last Find/Replace operation -action.FindPrevious.text=Find Pre_vious -action.FindPrevious.description=Repeat the last Find/Replace operation in reverse direction -action.FindWordAtCaret.text=Find _Word at Caret -action.FindWordAtCaret.description=Find next occurrence of the word at caret -action.IncrementalSearch.text=Incrementa_l Search -action.IncrementalSearch.description=Start incremental search (searches string as you type), IntelliJ IDEA 6.0 implementation. -action.FindUsages.text=Find _Usages... -action.FindUsages.description=Find usages of the symbol at caret -action.ShowUsages.text=_Show Usages -action.ShowUsages.description=Show usages of the symbol at caret -action.ShowUsagesSettings.text=_Show Usages Settings -action.ShowUsagesSettings.description=Choose scope and edit options for the Show Usages action -action.FindUsagesInFile.text=F_ind Usages in File -action.FindUsagesInFile.description=Find usages of the symbol at caret in active editor -action.HighlightUsagesInFile.text=_Highlight Usages in File -action.HighlightUsagesInFile.description=Highlight usages of the symbol at caret in active editor -action.FindJoinpointsByPointcutAction.text=Find _Join Point Identified by Pointcut -action.FindJoinpointsByPointcutAction.description=Find join point identified by pointcut -action.FindInPath.text=Find in _Path... -action.FindInPath.description=Find a string in multiple files -action.ReplaceInPath.text=Repl_ace in Path... -action.ReplaceInPath.description=Replace a string in multiple files with another string -group.ViewMenu.text=_View -action.ViewToolBar.text=_Toolbar -action.ViewToolBar.description=Show/hide the main toolbar -action.ViewStatusBar.text=_Status Bar -action.ViewStatusBar.description=Show/hide the status bar -action.ViewToolButtons.text=_Tool Buttons -action.ViewToolButtons.description=Show/hide tool window buttons -action.ViewNavigationBar.text=Na_vigation Bar -action.ViewNavigationBar.description=Show/hide the navigation bar -action.FileStructurePopup.text=_File Structure Popup -action.FileStructurePopup.description=Popup structure of the current file for quick navigation -action.ShowFilePath.text=File _Path -action.ShowFilePath.description=Popup path of the current file for opening in external file manager -action.SelectIn.text=Se_lect In... -action.SelectIn.description=Select the current class or method in any view -action.QuickJavaDoc.text=Quick _Documentation Lookup -action.QuickJavaDoc.description=Show a popup window with documentation for the symbol at caret -action.QuickImplementations.text=Quick Definition Loo_kup -action.QuickImplementations.description=Show a popup window with the symbol (and its implementations) content -action.ParameterInfo.text=_Parameter Info -action.ParameterInfo.description=Show parameters of the method call at caret -action.EditorContextInfo.text=_Context Info -action.EditorContextInfo.description=Show the current method or class declaration when it is not visible -action.ShowErrorDescription.text=E_rror Description -action.ShowErrorDescription.description=Show description of error or warning at caret -action.ProjectViewChangeView.text=Change View -action.ProjectViewChangeView.description=Choose Project, Sourcepath or Classpath tree in the Project View window -action.EditSource.text=_Jump to Source -action.EditSource.description=Open editor for the selected item and give focus to it -action.ViewSource.text=Sho_w Source -action.ViewSource.description=Open editor for the selected item -action.ExternalJavaDoc.text=E_xternal Documentation -action.ExternalJavaDoc.description=Open browser with documentation for the selected item -action.RecentFiles.text=Rece_nt Files -action.RecentFiles.description=Show list of recently viewed files -action.RecentChangedFiles.text=Recently Changed Files -action.RecentChangedFiles.description=Show list of recently changed files -action.QuickChangeScheme.text=_Quick Switch Scheme -action.QuickChangeScheme.description=Change current code style scheme, color\\&fonts scheme, keymap or Look\\&Feel -action.TypeHierarchy.text=Type _Hierarchy -action.TypeHierarchy.description=Browse hierarchy for the selected class -action.MethodHierarchy.text=_Method Hierarchy -action.MethodHierarchy.description=Browse method hierarchy for the selected method -action.CallHierarchy.text=Call Hierarch_y -action.CallHierarchy.description=Browse call hierarchy for the selected method -group.FoldingGroup.text=Foldin_g -action.ExpandRegion.text=E_xpand -action.ExpandRegion.description=Expand folding region at caret -action.CollapseRegion.text=_Collapse -action.CollapseRegion.description=Collapse folding region at caret -action.ExpandAllRegions.text=_Expand All -action.ExpandAllRegions.description=Expand all folding regions -action.CollapseAllRegions.text=Collapse _All -action.CollapseAllRegions.description=Collapse all folding regions -action.ExpandJavadocs.text=Expand Java_docs -action.ExpandJavadocs.description=Expand all javadoc comments -action.CollapseJavadocs.text=Collapse _Javadocs -action.CollapseJavadocs.description=Collapse all javadoc comments -action.CollapseSelection.text=Fold _Selection / Remove region -action.CollapseSelection.description=Fold selected block -action.CollapseBlock.text=Fold Code _Block -action.CollapseBlock.description=Fold current code block -action.RecentChanges.text=R_ecent Changes -group.GoToMenu.text=_Go To -action.GotoClass.text=_Class... -action.GotoClass.description=Quickly navigate to a class by name -action.GotoFile.text=_File... -action.GotoFile.description=Quickly navigate to a file by name -action.GotoSymbol.text=_Symbol... -action.GotoSymbol.description=Quickly navigate to any symbol by name -action.GotoAction.text=Find _Action... -action.GotoAction.description=Quickly navigate to action by name -action.GotoLine.text=_Line... -action.GotoLine.description=Navigate to a line in editor by its number -action.GotoDeclaration.text=_Declaration -action.GotoDeclaration.description=Navigate to the declaration of the symbol at caret -action.GotoImplementation.text=I_mplementation(s) -action.GotoImplementation.description=Navigate to the implementation(s) of the selected class or method -action.GotoTypeDeclaration.text=_Type Declaration -action.GotoTypeDeclaration.description=Navigate to the declaration of the symbol's type at caret -action.GotoSuperMethod.text=S_uper Method -action.GotoSuperMethod.description=Navigate to the declaration of the method that current method overrides or implements -action.GotoTest.text=T_est -action.GotoTest.description=Navigate to the test of the selected class -action.GotoTestSubject.text=T_est Subject -action.GotoTestSubject.description=Navigate to the subject of the selected test -action.GotoAspect.text=_Aspect -action.GotoAspect.description=Navigate to the declaration of the advice, error or warning declaration that affects a join point at caret -action.GotoNextError.text=_Next Highlighted Error -action.GotoNextError.description=Navigate to the next highlighted error in the active editor -action.GotoPreviousError.text=_Previous Highlighted Error -action.GotoPreviousError.description=Navigate to the previous highlighted error in the active editor -action.MethodDown.text=N_ext Method -action.MethodDown.description=Navigate to the next method in the active editor -action.MethodUp.text=Prev_ious Method -action.MethodUp.description=Navigate to the previous method in the active editor -action.GotoNextBookmark.text=Ne_xt Bookmark -action.GotoNextBookmark.description=Navigate to the next bookmark in the active editor -action.GotoPreviousBookmark.text=P_revious Bookmark -action.GotoPreviousBookmark.description=Navigate to the previous bookmark in the active editor -action.Back.text=_Back -action.Back.description=Undo last navigation operation -action.Forward.text=For_ward -action.Forward.description=Redo last undone navigation operation -action.JumpToLastChange.text=Last Edit L_ocation -action.JumpToLastChange.description=Move through the most recent change points -action.PreviousOccurence.text=Previ_ous Occurrence -action.PreviousOccurence.description=Navigate to the previous occurrence -action.NextOccurence.text=Ne_xt Occurrence -action.NextOccurence.description=Navigate to the next occurrence -action.VcsShowNextChangeMarker.text=Next C_hange -action.VcsShowNextChangeMarker.description=Go to next change -action.VcsShowPrevChangeMarker.text=Prev Ch_ange -action.VcsShowPrevChangeMarker.description=Go to previous change -group.CodeMenu.text=Co_de -action.SurroundWith.text=_Surround With... -action.SurroundWith.description=Surround selected code fragment with if, while, try/catch or other construct -action.Generate.text=_Generate... -action.Generate.description=Generate constructor, getter or setter method, etc. -action.Unwrap.text=Un_wrap/Remove... -action.Unwrap.description=Safely unwraps or removes the selected scope -group.CodeCompletionGroup.text=_Complete Code -action.CodeCompletion.text=_Basic -action.CodeCompletion.description=Complete code -action.HippieCompletion.text=Cyclic _Expand Word -action.HippieCompletion.description=Cyclic Expand word -action.HippieBackwardCompletion.text=Cyclic _Expand Word (Backward) -action.HippieBackwardCompletion.description=Cyclic Expand word (Backward) -action.SmartTypeCompletion.text=_SmartType -action.SmartTypeCompletion.description=Complete code basing on expected type -action.ClassNameCompletion.text=Class _Name -action.ClassNameCompletion.description=Complete class name and add import for it -action.InsertLiveTemplate.text=Insert Live _Template... -action.InsertLiveTemplate.description=Show popup list of live templates starting with the specified prefix -action.SurroundWithLiveTemplate.text=Surround with Live Tem_plate... -action.SurroundWithLiveTemplate.description=Surrounds the selection with one of the template -action.CommentByLineComment.text=Comment with _Line Comment -action.CommentByLineComment.description=Comment/uncomment current line or selected block with line comments -action.CommentByBlockComment.text=Comment with _Block Comment -action.CommentByBlockComment.description=Comment/uncomment code with block comments -action.ReformatCode.text=_Reformat Code... -action.ReformatCode.description=Reformat code -action.AutoIndentLines.text=_Auto-Indent Lines -action.AutoIndentLines.description=Indent current line or selected block according to the code style settings -action.OptimizeImports.text=Optimi_ze Imports... -action.OptimizeImports.description=Remove unused imports and reorder/reorganize imports -action.MoveStatementDown.text=Move Statement Do_wn -action.MoveStatementDown.description=Move selected statements one line down -action.MoveStatementUp.text=Move Statement _Up -action.MoveStatementUp.description=Move selected statements one line up -group.AnalyzeMenu.text=Analy_ze -action.InspectCode.text=_Inspect Code... -action.InspectCode.description=Inspect code -action.InferNullity.text=Infer _Nullity... -action.InferNullity.description=Infer nullity -action.ViewOfflineInspection.text=View _Offline Inspection Results... -action.ViewOfflineInspection.description=Load offline inspection results -action.ShowPackageDeps.text=Analyze _Dependencies... -action.ShowPackageDeps.description=Browse code choosen analysis item depends on -action.ShowBackwardPackageDeps.text=Analyze _Backward Dependencies... -action.ShowBackwardPackageDeps.description=Browse code choosen analysis item used in -action.ShowPackageCycles.text=Analyze _Cyclic Dependencies... -action.ShowPackageCycles.description=Browse code choosen analysis item used in cycles -action.ShowModulesDependencies.text=Analyze _Module Dependencies... -action.ShowModulesDependencies.description=Show dependencies between modules in project -action.Unscramble.text=Analyze _Stacktrace... -action.Unscramble.description=Open console with the navigatable stacktrace -group.RefactoringMenu.text=_Refactor -action.RenameElement.text=_Rename... -action.RenameElement.description=Rename the selected symbol and correct all references -action.ChangeSignature.text=Change Si_gnature... -action.ChangeSignature.description=Change signature of the selected method or class and correct all references -action.MoveToTheLeft.text=Move parameter left -action.MoveToTheLeft.description=Move parameter left -action.MoveToTheRight.text=Move parameter right -action.MoveToTheRight.description=Move parameter right -action.ChangeTypeSignature.text=T_ype Migration... -action.ChangeTypeSignature.description=Change type of the return type of the method, field, parameter, variable or class type argumeants and correct all references -action.MakeStatic.text=Make S_tatic... -action.MakeStatic.description=Convert method or inner class to static and correct all references -action.ConvertToInstanceMethod.text=C_onvert To Instance Method... -action.ConvertToInstanceMethod.description=Convert static method to instance method and correct all references -action.Move.text=_Move... -action.Move.description=Move the selected class, method, package or static member to another package or class and correct all references -action.CopyElement.text=C_opy... -action.CopyElement.description=Create a copy of the selected class, file(s) or directory(es) -action.CloneElement.text=Clo_ne... -action.CloneElement.description=Create a copy of the selected class, file or directory in the same package/directory -action.SafeDelete.text=Safe _Delete... -action.SafeDelete.description=Delete the selected class, method or field, checking for usages -action.ExtractMethod.text=E_xtract Method... -action.ExtractMethod.description=Turn the selected code fragment into a method -action.RemoveMiddleman.text=Remove _Middleman -action.RemoveMiddleman.description=Get the client to call the delegate directly -action.MethodDuplicates.text=Replace Met_hod Code Duplicates... -action.MethodDuplicates.description=Finds code in current file that can be transformed into a call of selected method -action.InvertBoolean.text=Invert _Boolean... -action.InvertBoolean.description=Makes the method return or variable contain the opposite value and corrects the references -action.IntroduceParameterObject.text=Introduce Parameter Ob_ject... -action.IntroduceParameterObject.description=Replaces method parameters list with object -action.ExtractClass.text=E_xtract Delegate... -action.ExtractClass.description=Extract Delegate -action.IntroduceVariable.text=Introduce _Variable... -action.IntroduceVariable.description=Put a result of the selected expression into a variable -action.IntroduceField.text=Introduce _Field... -action.IntroduceField.description=Put a result of the selected expression into a field -action.IntroduceConstant.text=Introduce _Constant... -action.IntroduceConstant.description=Replace selected expression with a constant (static final field) -action.IntroduceParameter.text=Introduce _Parameter... -action.IntroduceParameter.description=Turn the selected expression into method parameter -action.ExtractInterface.text=Extract _Interface... -action.ExtractInterface.description=Extract interface from the selected class -action.ExtractModule.text=Extract _Module... -action.ExtractModule.description=Extract module from the selected class -action.ExtractSuperclass.text=Extract S_uperclass... -action.ExtractSuperclass.description=Extract superclass from the selected class -action.TurnRefsToSuper.text=Use Interface _Where Possible... -action.TurnRefsToSuper.description=Change usages of a class to those of its superclass or interface -action.MembersPullUp.text=Pu_ll Members Up... -action.MembersPullUp.description=Pull class members to superclass or implemented interface -action.MemberPushDown.text=Pu_sh Members Down... -action.MemberPushDown.description=Push class members down to directly inheriting subclasses -action.InheritanceToDelegation.text=Replace _Inheritance with Delegation... -action.InheritanceToDelegation.description=Replace inheritance with delegation -action.Inline.text=I_nline... -action.Inline.description=Inline the selected method or variable -action.AnonymousToInner.text=Convert Anon_ymous to Inner... -action.AnonymousToInner.description=Convert anonymous class into an inner class -action.EncapsulateFields.text=_Encapsulate Fields... -action.EncapsulateFields.description=Replace direct access to fields in the selected class with use of accessor methods -action.ReplaceTempWithQuery.text=Replace Temp with _Query... -action.ReplaceTempWithQuery.description=Turn the selected variable into a method -action.ReplaceConstructorWithFactory.text=Replace Constructor with F_actory Method... -action.ReplaceConstructorWithFactory.description=Create a static factory method and use it instead of a constructor -action.ReplaceMethodWithMethodObject.text=Extract Method Ob_ject... -action.ReplaceMethodWithMethodObject.description=Turn the method into its own object so that all the parameters become fields on that object -action.Generify.text=Ge_nerify... -action.Generify.description=Convert your code to use generic types -action.Migrate.text=_Migrate... -action.Migrate.description=Open migration dialog -action.Type\ Migration.text=Type Migration... -action.Type\ Migration.description=Migrate one type to another -action.ExtractInclude.text=E_xtract Include File... -action.ExtractInclude.description=Turn the selected code fragment into included file -group.BuildMenu.text=_Build -action.CompileProject.text=_Rebuild Project -action.CompileProject.description=Recompile all files in the project -action.CompileDirty.text=Make _Project -action.CompileDirty.description=Compile all modified and dependent files in the project -action.MakeModule.text=Make _Module -action.MakeModule.description=Compile all modified and dependent files in the module -action.Compile.text=Compil_e -action.Compile.description=Compile the selected file or package -action.RunAPT.text=Process _Annotations -action.RunAPT.1.text=Process {0} _Annotations -action.RunAPT.description=Run Annotation Processors on the selected scope -action.GenerateAntBuild.text=_Generate Ant Build... -action.GenerateAntBuild.description=Generate Ant Build File from the Project -action.BuildAllArtifacts.text=Build All Artifacts -group.BuildArtifactsGroup.text=Build Artifact -group.RunMenu.text=R_un -group.RunnerActions.text=Run/Debug -action.editRunConfigurations.text=Edit Configu_rations -action.editRunConfigurations.description=Open edit Run/Debug configurations dialog -action.Stop.text=Stop -action.Stop.description=Stop process -action.Hotswap.text=Relo_ad Changed Classes -action.Hotswap.description=Reload all changed classes into application being debugged (HotSwap) -action.StepOver.text=Step _Over -action.StepOver.description=Step to the next line in this file -action.StepInto.text=Step _Into -action.StepInto.description=Step to the next line executed -action.SmartStepInto.text=Smart Ste_p Into -action.SmartStepInto.description=Step into the particular method -action.StepOut.text=Step Ou_t -action.StepOut.description=Step to the first line executed after returning from this method -action.ForceStepOver.text=Force Step O_ver -action.ForceStepOver.description=Step over, disable all breakpoints before stepping. -action.ForceStepInto.text=Force Step I_nto -action.ForceStepInto.description=Step into, ignore stepping filters for libraries, constructors, etc. -action.RunToCursor.text=Run to _Cursor -action.RunToCursor.description=Run to the line where the caret is -action.ForceRunToCursor.text=Force Run to Cur_sor -action.ForceRunToCursor.description=Run to the line where the caret is, ignoring any breakpoints -action.Debugger.PopFrame.text=Drop _Frame -action.Debugger.PopFrame.description=Moves execution point back to the method call dropping current method frames from the stack -action.Pause.text=_Pause Program -action.Pause.description=Suspend program execution and enable debugging -action.Resume.text=Resume Pro_gram -action.Resume.description=Resume program execution -action.EvaluateExpression.text=_Evaluate Expression... -action.EvaluateExpression.description=Evaluate arbitrary expression -action.QuickEvaluateExpression.text=_Quick Evaluate Expression -action.QuickEvaluateExpression.description=Evaluate selected expression -action.ShowExecutionPoint.text=Show E_xecution Point -action.ShowExecutionPoint.description=Show the current program execution point -action.ToggleLineBreakpoint.text=Toggle _Line Breakpoint -action.ToggleLineBreakpoint.description=Toggle line breakpoint at caret -action.ToggleMethodBreakpoint.text=Toggle _Method Breakpoint -action.ToggleMethodBreakpoint.description=Toggle method breakpoint for the method at caret -action.ToggleFieldBreakpoint.text=Toggle _Field Watchpoint -action.ToggleFieldBreakpoint.description=Toggle field watchpoint for the field at caret -action.ToggleBreakpointEnabled.text=Toggle _Breakpoint Enabled -action.ToggleBreakpointEnabled.description=Enable/disable breakpoint at the current line -action.ViewBreakpoints.text=View Brea_kpoints -action.ViewBreakpoints.description=View and manage all breakpoints and watchpoints -action.ExportThreads.text=Export T_hreads... -action.ExportThreads.description=Save all threads information to a text file or clipboard -action.Runner.RestoreLayout.text=Restore Layout -action.Runner.RestoreLayout.description=Restore visual elements of debugger session to default state -action.Runner.ToggleToolbarLayout.text=Horizontal Toolbars -action.Runner.ToggleToolbarLayout.description=Toggles horizontal/vertical toolbar orientation -group.Runner.Layout.text=Layout -group.Runner.Layout.description=Layout configuration -action.Runner.MinimizeView.text=Hide -action.Runner.MinimizeView.description=Hide view -action.Runner.CloseView.text=Close -action.Runner.CloseView.description=Close view -action.Runner.MoveViewToGrid.text=Move To Grid -action.Runner.MoveViewToGrid.description=Move view to the debugger grid container -action.Runner.MoveViewToTab.text=Move To Tab -action.Runner.MoveViewToTab.description=Move view to the debugger tab container -action.Runner.DetachCell.text=Detach -action.Runner.DetachCells.text=Detach {0} Views -action.Runner.DetachCell.description=Detach selected views to a separate window -action.Runner.AttachCell.text=Attach -action.Runner.AttachCells.text=Attach {0} Views -action.Runner.AttachCell.description=Attach selected views to the debugger tool window -action.Runner.RestoreView.text=Restore ''{0}'' View -action.Runner.RestoreView.description=Restore view in the debugger grid -action.Runner.FocusOnStartup.text=Focus On Startup -action.Runner.FocusOnStartup.description=Focus this view by default when UI is shown -action.Debugger.FocusOnBreakpoint.text=Focus On Breakpoint -action.Debugger.FocusOnBreakpoint.description=Focus this view by default when breakpont is reached -group.ToolsMenu.text=_Tools -action.SaveAsTemplate.text=Save as L_ive Template... -action.SaveAsTemplate.description=Save the selected text as live template -action.SaveFileAsTemplate.text=Save File as Temp_late... -action.SaveFileAsTemplate.description=Save current file as template -action.GenerateJavadoc.text=Generate Java_Doc... -action.GenerateJavadoc.description=Run the JavaDoc tool -group.Macros.text=_Macros -group.Macros.description=View, Change, Record, Play Macros -group.StanardMacroActions.text=Standard Macro Actions -action.PlaybackLastMacro.text=Pla_yback Last Macro -action.PlaybackLastMacro.description=Playback last used macro -action.StartStopMacroRecording.text=Start/Stop Macro _Recording -action.StartStopMacroRecording.description=Start/Stop recording of a new macro -action.EditMacros.text=_Edit Macros -action.EditMacros.description=Configure existing macros -action.ValidateXml.text=_Validate -action.ValidateXml.description=Validate the current xml file -action.GenerateDTD.text=Generate DTD from _XML file -group.ExternalToolsGroup.text=External Tools -action.PsiViewer.text=_View PSI Structure... -action.DebugListen.text=Throw Debug Exception -action.VirtualFileInfo.text=Virtual File Info -action.Merge3Files.text=Merge -action.LocalizePlugin.text=Localize Plugin -group.MemoryManagement.text=Memory Management -action.DumpPsiMemoryCaches.text=Dump what is cached in PSI -action.DecodeBytesAction.text=Decode Bytes -action.ReloadProjectAction.text=Reload project -action.LocalVcs.ShowStatistics.text=Show Local VCS Statistics... -action.LocalVcs.ShowStatistics.description=Show Local VCS statistics -action.LocalVcs.ShowRepHistory.text=Repository History -action.DropAnError.text=Drop an error -action.DropAnOutOfMemoryError.text=Drop an OutOfMemoryError -action.DropAnOutOfPermGenMemoryError.text=Drop an perm gen OutOfMemoryError -action.DumpInspectionDescriptions.text=Dump inspection descriptions -action.DumpIntentionsDescriptions.text=Dump intentions descriptions -action.ShowUserActivities.text=Show Activities -group.WindowMenu.text=_Window -group.ToolWindowsGroup.text=_Tool Windows -group.BackgroundTasks.text=Background Tasks -action.ShowProcessWindow.text=Show -action.ShowProcessWindow.description=Show/Hide background tasks window -action.ShowProcessWindow.double.click=Click to show/hide background tasks window -action.AutoShowProcessWindow.text=Auto Show -action.AutoShowProcessWindow.description=Show background tasks window on starting of any progress -action.StoreDefaultLayout.text=Store Current La_yout as Default -action.StoreDefaultLayout.description=Store current layout as default -action.RestoreDefaultLayout.text=Restore De_fault Layout -action.RestoreDefaultLayout.description=Restore default layout -action.HideActiveWindow.text=Hi_de Active Tool Window -action.HideActiveWindow.description=Hide active tool window -action.HideSideWindows.text=Hide Side Tool Windows -action.HideSideWindows.description=Hide all windows on this side -action.HideAllWindows.text=Hide All _Tool Windows -action.HideAllWindows.description=Hide all tool windows -action.JumpToLastWindow.text=_Jump to Last Tool Window -action.JumpToLastWindow.description=Activate last focused tool window -action.TogglePinnedMode.text=_Pinned Mode -action.TogglePinnedMode.description=Pin/unpin active tool window -action.ToggleDockMode.text=Dock_ed Mode -action.ToggleDockMode.description=Dock/undock active tool window -action.ToggleFloatingMode.text=Floating _Mode -action.ToggleFloatingMode.description=Float/unfloat active tool window -action.ToggleSideMode.text=Split Mode -action.ToggleSideMode.description=Tool windows split mode on/off -action.ToggleContentUiTypeMode.text=Tabbed Content -action.ToggleContentUiTypeMode.description=Toggle between tabbed/combo presentation of contents -action.ShowContent.text=Show Content -action.ShowContent.description=Show tool window content popup -group.ResizeToolWindowGroup.text=Resize -action.ResizeToolWindowLeft.text=Stretch to Left -action.ResizeToolWindowLeft.description=Resize active tool window to the left -action.ResizeToolWindowRight.text=Stretch to Right -action.ResizeToolWindowRight.description=Resize active tool window to the right -action.ResizeToolWindowUp.text=Stretch to Top -action.ResizeToolWindowUp.description=Resize active tool window to the top -action.ResizeToolWindowDown.text=Stretch to Bottom -action.ResizeToolWindowDown.description=Resize active tool window to the bottom -action.IncrementWindowWidth.text=Increment Width -action.DecrementWindowWidth.text=Decrement Width -action.IncrementWindowHeight.text=Increment Height -action.DecrementWindowHeight.text=Decrement Height -action.NextTab.text=Select Ne_xt Tab -action.NextTab.description=Activate next tab -action.PreviousTab.text=Se_lect Previous Tab -action.PreviousTab.description=Activate previous tab -action.NextEditorTab.text=Select Next Tab in multi-editor file -action.PreviousEditorTab.text=Select Previous Tab in multi-editor file -action.CloseActiveTab.text=Cl_ose Active Tab -action.CloseActiveTab.description=Close active tool window tab -action.PinActiveTab.text=P_in Active Tab -action.PinActiveTab.description=Pin active tool window or editor tab -action.CloseEditor.text=_Close Active Editor -action.CloseEditor.description=Close active editor -action.CloseAllEditors.text=Close _All -action.CloseAllEditors.description=Close all editors in active tab group -action.CloseAllEditorsButActive.text=Close _Others -action.CloseAllEditorsButActive.description=Close all open editors except the active one -action.CloseAllUnmodifiedEditors.text=Close _Unmodified -action.CloseAllUnmodifiedEditors.description=Close all nonmodified editors -action.CloseAllUnpinnedEditors.text=Close All But Pinned -action.CloseAllUnpinnedEditors.description=Close all editors which are not pinned -action.CloseContent.text=Close -action.CloseContent.description=Close currently focused content -action.SplitVertically.text=Split _Vertically -action.SplitVertically.description=Split editor area into 2 tab groups and move the current tab to the right group -action.SplitHorizontally.text=Split _Horizontally -action.SplitHorizontally.description=Split editor area into 2 tab groups and move the current tab to the bottom group -action.ChangeSplitOrientation.text=Change Splitter O_rientation -action.ChangeSplitOrientation.description=Changes orientation of splitter -action.Unsplit.text=Un_split -action.Unsplit.description=Unsplit Window -action.UnsplitAll.text=U_nsplit All -action.UnsplitAll.description=Remove all splitters -action.NextSplitter.text=_Goto Next Splitter -action.NextSplitter.description=Goto next splitter window -action.PrevSplitter.text=Goto Previous Splitter -action.PrevSplitter.description=Goto previous splitter window -group.HelpMenu.text=_Help -action.HelpTopics.text=Help _Topics -action.HelpTopics.description=Show help contents -action.ShowTips.text=Tip of the _Day -action.ShowTips.description=Show Tips of the Day -action.ProductivityGude.text=_Productivity Guide -action.ProductivityGude.description=Show productivity features usages statistics -action.OnlineDocAction.text=_Online Documentation -action.OnlineDocAction.description=Show online documentation -action.WhatsNewAction.text=What's _New in IntelliJ IDEA -action.WhatsNewAction.description=Find out about the new features in this version of IntelliJ IDEA -action.Help.KeymapReference.text=Default _Keymap Reference -action.Help.KeymapReference.description=Open PDF file with the default keymap reference card. -action.CheckForUpdate.text=_Check for Update... -action.CheckForUpdate.description=Check for available update -action.About.text=_About -action.About.description=Show information about IntelliJ IDEA -group.OtherMenu.text=Other -group.OtherMenu.description=Actions that are not properly registered -group.MainToolBar.text=Main Toolbar -group.Bookmarks.text=Bookmarks -action.ToggleBookmark0.text=Toggle Bookmark 0 -action.ToggleBookmark1.text=Toggle Bookmark 1 -action.ToggleBookmark2.text=Toggle Bookmark 2 -action.ToggleBookmark3.text=Toggle Bookmark 3 -action.ToggleBookmark4.text=Toggle Bookmark 4 -action.ToggleBookmark5.text=Toggle Bookmark 5 -action.ToggleBookmark6.text=Toggle Bookmark 6 -action.ToggleBookmark7.text=Toggle Bookmark 7 -action.ToggleBookmark8.text=Toggle Bookmark 8 -action.ToggleBookmark9.text=Toggle Bookmark 9 -action.GotoBookmark0.text=Go to Bookmark 0 -action.GotoBookmark1.text=Go to Bookmark 1 -action.GotoBookmark2.text=Go to Bookmark 2 -action.GotoBookmark3.text=Go to Bookmark 3 -action.GotoBookmark4.text=Go to Bookmark 4 -action.GotoBookmark5.text=Go to Bookmark 5 -action.GotoBookmark6.text=Go to Bookmark 6 -action.GotoBookmark7.text=Go to Bookmark 7 -action.GotoBookmark8.text=Go to Bookmark 8 -action.GotoBookmark9.text=Go to Bookmark 9 - -action.XDebugger.SetValue.text=Set Value... -action.XDebugger.SetValue.description=Modify value of selected node -action.XDebugger.CopyValue.text=Copy Value -action.XDebugger.CopyValue.description=Copy value of selected node to clipboard -action.XDebugger.JumpToSource.text=Jump To Source -action.XDebugger.JumpToSource.description=Open for the selected item -action.XDebugger.Inspect.text=Inspect -action.XDebugger.AddToWatches.text=Add to Watches -action.XDebugger.RemoveWatch.text=Remove Watch -action.XDebugger.NewWatch.text=New Watch... -action.XDebugger.EditWatch.text=Edit... -action.XDebugger.AutoTooltip.text=Value Auto Tooltip -action.XDebugger.AutoTooltip.description=Toggles if value tooltip is shown automatically on mouse over -group.XDebugger.settings.text=Settings - -action.Debugger.AdjustArrayRange.text=Adjust Range... -action.Debugger.Inspect.text=Inspect -action.Debugger.CopyValue.text=Copy Value -action.Debugger.CompareValueWithClipboard.text=Compare Value with Clipboard -action.Debugger.CustomizeContextView.text=Customize Data Views... -action.Debugger.CustomizeThreadsView.text=Customize Threads View... -action.Debugger.NewWatch.text=New Watch... -action.Debugger.EditWatch.text=Edit Watch -action.Debugger.EditFrameSource.text=EditFrameSource -action.Debugger.EditNodeSource.text=EditNodeSource -action.Debugger.EditTypeSource.text=Jump to Object Source -action.Debugger.RemoveAllWatches.text=Remove All Watches -action.Debugger.RemoveWatch.text=Remove Watch -action.Debugger.ViewAsGroup.text=View as -action.Debugger.SetValue.text=Set Value... -action.Debugger.ShowFrame.text=Show Frame -action.Debugger.ResumeThread.text=Resume -action.Debugger.FreezeThread.text=Freeze -action.Debugger.InterruptThread.text=Interrupt -action.Debugger.MuteBreakpoints.text=Mute Breakpoints -action.Debugger.MuteBreakpoints.description=Mute/unmute all breakpoints in a debug session. -action.Debugger.MarkObject.text=Mark Object... -action.Debugger.MarkObject.unmark.text=Unmark Object -action.Debugger.MarkObject.description=Mark/unmark the object so that it can be visually distinguished in in debugger views -action.Debugger.AddToWatch.text=Add to Watches -action.Debugger.AutoRenderer.text=Auto -group.EditorPopupMenu.text=Editor Popup Menu -group.EditorPopupMenu.description=Editor Popup Menu -action.Add\ to\ migration\ roots.text=_Add to migration roots -action.Add\ to\ migration\ roots.description=Add selected element to migration roots -group.EditorPopupMenu.GoTo.text=_Go To -group.EditorPopupMenu.GoTo.description=Go To Editor Popup Menu Group -action.CompareClipboardWithSelection.text=Compare with Clip_board -action.CompareClipboardWithSelection.description=Compare current selection with clipboard -action.ChangeColorScheme.text=Switch Color Scheme -action.ChangeColorScheme.description=Change current color scheme -action.ChangeCodeStyleScheme.text=Switch Code Style Scheme -action.ChangeCodeStyleScheme.description=Change current code style scheme -action.ChangeKeymap.text=Switch Keymap -action.ChangeKeymap.description=Change current keymap -action.ChangeLaf.text=Switch Look\\&\\&Feel -action.ChangeLaf.description=Change current Look\\&Feel -action.ChangeInspectionProfile.text=Switch Error Highlighting -action.ChangeInspectionProfile.description=Change current editor inspection profile -action.ChangeCustomizationSchema.text=Switch Customization Scheme -action.ChangeCustomizationSchema.description=Change Current Customization Scheme -action.MoveEditorToOppositeTabGroup.text=_Move To Opposite Group -action.MoveEditorToOppositeTabGroup.description=Move Editor To Opposite Tab Group -action.ModuleSettings.text=Module _Settings -action.ModuleSettings.description=Open settings dialog for selected module -group.MoveModuleToGroup.text=Move Module to Group -group.MoveModuleToGroup.description=_Move module -action.AssociateWithFileType.text=Associate with File Type -action.AssociateWithFileType.description=Associate selected file with one of file types -action.SynchronizeCurrentFile.text=Synchronize selected file -action.TypeHierarchy.BaseOnThisType.text=Base On This Type -action.TypeHierarchy.Class.text=Class Hierarchy -action.TypeHierarchy.Subtypes.text=Subtypes Hierarchy -action.TypeHierarchy.Supertypes.text=Supertypes Hierarchy -action.MethodHierarchy.ImplementMethodAction.text=Implement Method -action.MethodHierarchy.ImplementMethodAction.description=Implement Method (from Method Hierarchy) -action.MethodHierarchy.OverrideMethodAction.text=Override Method -action.MethodHierarchy.OverrideMethodAction.description=Override Method (from Method Hierarchy) -action.MethodHierarchy.BaseOnThisType.text=Base On This Type -action.MethodHierarchy.BaseOnThisType.description=Show hierarchy for method of selected class/interface -action.CallHierarchy.BaseOnThisType.text=Base On This Method -action.CallHierarchy.BaseOnThisType.description=Show call hierarchy for selected method -action.Lvcs.ShowLvcsDiffAction.text=Compare Versions -action.Lvcs.ShowLvcsDiffAction.description=Show changes -action.Lvcs.RollbackFileAction.text=Rollback File -action.Lvcs.RollbackFileAction.description=Rollback -action.Vcs.ShowDiffAction.text=Show Changes -action.Vcs.ShowDiffAction.description=Show changes -action.Vcs.RollbackChanges.text=Rollback Changes -action.Vcs.RollbackChanges.description=Rollback changes -action.Vcs.EditSourceAction.text=Edit Source -action.Vcs.EditSourceAction.description=Edit source -action.Vcs.ExcludeAction.text=Exclude from Commit -action.Vcs.ExcludeAction.description=Exclude from commit -action.Vcs.IncludeAction.text=Include into Commit -action.Vcs.IncludeAction.description=Include into commit -action.openAssertEqualsDiff.text=View assertEquals Difference -action.UsageView.Include.text=Include -action.UsageView.Include.description=Include this usage(s) into processing -action.UsageView.Exclude.text=Exclude -action.UsageView.Exclude.description=Exlude this usage(s) from processing -action.UsageView.ShowRecentFindUsages.text=R_ecent Find Usages -action.UsageView.ShowRecentFindUsages.description=Choose and re-run recent find usages -group.ShowRecentFindUsagesGroup.text=Recent Find Usages -group.ShowRecentFindUsagesGroup.description=Choose and re-run recent find usages - -stop.capture.cpu.usage.data.action.name=Capture CPU usage data, now capturing -stop.capture.cpu.usage.data.action.description=Stop capturing CPU usage data -stop.capture.allocations.usage.data.action.name=Capture allocations data, now capturing -stop.capture.allocations.usage.data.action.description=Stop capturing allocations data - -action.IntegrateFiles.text=Integrate -action.IntegrateFiles.description=Integrate selected files or directories -action.Vcs.IntegrateProject.text=Inte_grate Project -action.Vcs.IntegrateProject.description=Integrate project - -action.Vcs.RefreshStatuses.text=R_efresh File Status -action.Vcs.RefreshStatuses.description=Refresh VCS status for all files in project - -action.ShowNavBar.text=Show Na_vigation Bar - -action.ScopeView.EditScopes.text=Ed_it Scopes -action.ScopeView.EditScopes.description=Edit Scopes - -action.CodeInspection.OnEditor.text=Inspect Code With _Editor Settings -action.CodeInspection.OnEditor.description=Inspect Code With _Editor Settings - -action.ShowCoverageInfo.text=Show Co&verage Info -action.HideCoverageInfo.text=Hide Co&verage Info - -action.Graph.layout.circular=Circular Layout -action.Graph.layout.balloon=Balloon Layout -action.Graph.layout.directed.orthogonal=Directed Orthogonal Layout -action.Graph.layout.orthogonal=Orthogonal Layout -action.Graph.layout.organic=Organic Layout -action.Graph.layout.hierarchic.group=Hierarchic Group Layout -action.Graph.layout.tree=Tree Layout -action.Graph.layout.apply.current=Apply current layout -action.Graph.layout.fit.content=Fit Content After Layout - -action.Graph.zoom.actual=Actual size -action.Graph.zoom.in=Zoom in -action.Graph.zoom.out=Zoom out - -action.Graph.grid.hide=Hide Grid -action.Graph.grid.show=Show Grid -action.Graph.grid.snap=Snap To Grid -action.Graph.show.edge.label=Show Edge Labels -action.Graph.show.selected.node.dependencies.label=Show selected nodes with dependencies -action.Graph.export.to.file=Export to file... -action.Graph.export.to.file.dialog.caption=Export to file -action.Graph.choose.directory=Choose Directory -action.Graph.fit.content=Fit Content - -action.Graph.print=Print Graph -action.Graph.print.preview=Print Preview - -action.SendFeedback.text=Submit _Feedback -action.SendFeedback.description=Submit feedback to the JetBrains Web site -action.TechnicalSupport.text=Contact _Support Team -action.TechnicalSupport.description=Access technical support on JetBrains Web site - -action.MoveToChangeList.text=Move to Changelist... -action.MoveToChangeList.description=Move selected file(s) to a different changelist - -action.TextComponent.ClearAction.text=Clear text -action.TextComponent.ClearAction.description=Clear text in text components - -action.ConvertProject.text=&Convert Project -action.ConvertProject.description=Convert project to the new format. You will not be able to open the project by earlier versions of IDEA. - -action.ChangesView.Refresh.text=Refresh -action.ChangesView.Refresh.description=Refresh VCS changes -action.ChangesView.NewChangeList.text=New Changelist -action.ChangesView.NewChangeList.description=Create new changelist -action.ChangesView.Commit.text=Comm_it Changes -action.ChangesView.Commit.description=Commit the changes in selected changelist -action.ChangesView.Rollback.text=Rollback -action.ChangesView.Rollback.description=Rollback selected changes -action.ChangesView.RemoveChangeList.text=Delete Changelist -action.ChangesView.RemoveChangeList.description=Remove changelist and move all changes to default -action.ChangesView.SetDefault.text=Set Active Changelist -action.ChangesView.SetDefault.description=Set changelist to which new changes are placed by default -action.ChangesView.Move.text=Move to Another Changelist -action.ChangesView.Move.description=Move selected changes to another changelist -action.ChangesView.Diff.text=Show Diff -action.ChangesView.Diff.description=Show diff for selected change -action.ChangesView.DeleteUnversioned.text=Delete -action.ChangesView.DeleteUnversioned.description= -action.ChangesView.AddUnversioned.text=Add to VCS -action.ChangesView.AddUnversioned.description=Schedule selected files to be added to VCS -action.ChangesView.RemoveDeleted.text=Remove from VCS -action.ChangesView.RemoveDeleted.description=Schedule selected files to be removed from VCS -action.ChangesView.Rename.text=Edit Changelist... -action.ChangesView.Rename.description=Edit name and description of selected changelist -action.ChangesView.CreatePatch.text=Create Patch... -action.ChangesView.CreatePatch.description=Create a patch from the selected changes -action.ChangesView.ApplyPatch.text=Apply Patch... -action.ChangesView.ApplyPatch.description=Apply a patch to the sources of the project -action.ChangesView.Shelve.text=Shelve Changes... -action.ChangesView.Shelve.description=Save changes to an external patch file and remove them from the code -action.ShelvedChanges.Unshelve.text=Unshelve Changes -action.ShelvedChanges.Unshelve.description=Apply saved changes to current codebase -action.ShelvedChanges.Rename.text=Rename... -action.ShelvedChanges.Rename.description=Rename shelved changelist -action.ChangesView.Browse.text=Browse Changes... -action.ChangesView.Browse.description=View history of committed changes -action.ChangesView.Edit.text=Check Out -action.ChangesView.Edit.description=Check out selected files for editing -action.ChangesView.Ignore.text=Ignore... -action.ChangesView.Ignore.description=Do not show selected files as unversioned -action.ChangesView.CreatePatchFromChanges.text=Create Patch... -action.ChangesView.CreatePatchFromChanges.description=Create a patch from the selected changes -action.CommittedChanges.Refresh.text=Refresh -action.CommittedChanges.Refresh.description=Refresh the list of committed changes -action.CommittedChanges.Filter.text=Filter -action.CommittedChanges.Filter.description=Change filtering criteria -action.CommittedChanges.Details.text=Show Details -action.CommittedChanges.Details.description=View complete information about the selected changelist -action.IncomingChanges.Refresh.text=Refresh -action.IncomingChanges.Refresh.description=Refresh the list of incoming changes -action.IncomingChanges.Get.text=Get Changelist -action.IncomingChanges.Get.description=Get latest version of files modified in selected changelist -action.FileChooser.TogglePathShowing.text=Show/Hide path text -action.ChangeFileEncoding.text=Change File Encoding -action.ChangeFileEncoding.description=Change file encoding -action.ConfigureFileDefaultEncoding.text=Configure Default Encoding... -action.ConfigureFileDefaultEncoding.description=Configure default encoding for files, files in directories or in the whole project -action.ChangeFileEncodingGroupAction.text=Change File Encoding -action.ChangeFileEncodingGroupAction.description=Change File Encoding - -action.AddFrameworkSupport.text=Add Framework Support... - -action.Diff.FocusOppositePane.text=Select opposite pane in diff view -action.Diff.PrevChange.text=Compare Previous File -action.Diff.NextChange.text=Compare Next File - -action.FileChooser.ShowHiddens.text=Show Hidden Files and Directories -action.FileChooser.ShowHiddens.description=Show hidden files and directories -action.FileChooser.Refresh.text=Refresh -action.FileChooser.Refresh.description=Refresh the file system tree -action.FileChooser.Delete.text=Delete... -action.FileChooser.Delete.text.description=Delete -action.FileChooser.NewFolder.text=New Folder... -action.FileChooser.NewFolder.description=Create new folder -action.FileChooser.NewFile.text=New File... -action.FileChooser.NewFile.description=Create new file -action.FileChooser.GotoHome.text=Home -action.FileChooser.GotoHome.description=Go to home directory -action.FileChooser.GotoProject.text=Project -action.FileChooser.GotoProject.description=Go to project directory -action.FileChooser.GotoModule.text=Module -action.FileChooser.GotoModule.description=Go to module directory - -action.WelcomeScreen.DevelopPlugins.text=Plugin Development -action.WelcomeScreen.ReadHelp.text=Read Help -action.WelcomeScreen.GetFromVcs.text=Check out from Version Control -action.WelcomeScreen.GetFromVcs.description=You can check out an entire project from a Version Control System. Click the icon or link to select your VCS. -action.WelcomeScreen.ReopenRecent.text=Reopen Recent Project -action.WelcomeScreen.ReopenRecent.description=You can open one of the most recent projects you were working with. Click the icon or link to select a project from the list. -action.WelcomeScreen.OpenProject.text=Open Project -action.WelcomeScreen.CreateNewProject.text=Create New Project -action.WelcomeScreen.CreateNewProject.description=Start the \"New Project\" Wizard that will guide you through the steps necessary for creating a new or import from existent project. - -action.CommittedChanges.Revert.text=Revert Changes -action.CommittedChanges.Revert.description=Apply the reverse of the selected changes to the working copy - -action.OpenDirectoryProject.text=Open Directory... -action.OpenDirectoryProject.description=Start editing code in the specified directory -action.WelcomeScreen.OpenDirectoryProject.text=Open Directory -action.WelcomeScreen.OpenDirectoryProject.description=Start editing code in the specified directory -action.WelcomeScreen.CreateDirectoryProject.text=Create New Project -action.WelcomeScreen.CreateDirectoryProject.description=Create a new project -action.NewDirectoryProject.text=New Project... -action.NewDirectoryProject.description=Create a new project - -action.SliceBackward.text=Analyze Dataflow _to here -action.SliceBackward.description=Search for values which can be assigned to selected expression - -action.SliceForward.text=Analyze Dataflow _from here -action.SliceSliceForward.description=Search for values escaped from this location - -action.ShelvedChanges.Diff.text=Show Diff -action.ShelvedChanges.Diff.description=Show diff for selected change - -action.ChangeTemplateDataLanguage.text=Change Template Data Language - -group.FileOpenGroup.text=File Open Actions -group.FileOtherSettingsGroup.text=Other Settings -group.FileMainSettingsGroup.text=Settings Actions -group.FilSettingsGroup.text=Settings Actions -group.ExportImportGroup.text=Export/Import Actions -group.PrintExportGroup.text=Print/Export Actions -group.EditSelectGroup.text=Editor Select Actions -group.EditCreateDeleteGroup.text=Editor Create/Delete Actions -group.CutCopyPasteGroup.text=Cut/Copy/Paste Actions -group.EditSelectWordGroup.text=Select Word Actions -group.EditBookmarksGroup.text=Bookmark Actions -group.OpenProjectGroup.text=Open Project Actions -group.EditSmartGroup.text=Edit Code Actions -group.ProjectViewGroup.text=Project View Actions -group.CodeEditorViewGroup.text=Code View Actions -group.CodeEditorBaseGroup.text=Code Editor View Actions -group.HierarchyGroup.text=Hierarchy Actions -group.DebugMainMenu.text=Debugger Actions -group.ToggleBreakpointAction.text=Breakpoint Actions -group.CommentGroup.text=Comment Actions -group.CodeFormatGroup.text=Code Formatting Actions -group.GoToTargetEx.text=Goto by Name Actions -group.GoToErrorGroup.text=Goto Error/Bookmark Actions -group.GoToMenuEx.text=Method Navigation Actions -group.GoToCodeGroup.text=Goto by Reference Actions -group.GoToChangeMarkerGroup.text=Change Navigation Actions -group.CoverageMenu.text=Code Coverage Actions -group.ToolsXmlGroup.text=XML Actions -group.XmlGenerateToolsGroup.text=XML Generate Actions -group.EditorPopupMenuDebug.text=Debug Actions -group.VersionControlsGroup.text=VCS/LVCS Actions -group.EditorPopupMenu.Run.text=Compile/Run Actions -group.ProjectViewCompileGroup.text=Compile/Debug Actions -group.ProjectViewAnalysisGroup.text=Analysis Actions -action.PerformanceReport.text=Submit Performance Report -group.MacrosGroup.text=Macro Actions -group.PairFileActions.text=Actions on Pairs of Files -group.RefactoringMenu1.text=Refactoring Actions (1) -group.RefactoringMenu2.text=Refactoring Actions (2) -group.RefactoringMenu3.text=Refactoring Actions (3) -group.ToolbarFindGroup.text=Toolbar Find Actions -group.ToolbarRunGroup.text=Toolbar Run Actions -group.ToolbarMakeGroup.text=Toolbar Make Actions -group.ChangeFileEncodingGroup.text=File Encoding Actions -group.EditorPopupMenu1.text=Editor Popup Menu Actions (1) -group.EditorLangPopupMenu.text=Editor Popup Menu Actions (2) -group.CloseEditorsGroup.text=Editor Close Actions -group.EditorTabPopupMenuEx.text=Editor Tab Popup Menu Actions (1) -group.ProjectViewPopupMenuModifyGroup.text=Project View Popup Menu Modify Group -group.ProjectViewPopupMenuRunGroup.text=Project View Popup Menu Run Group -group.ProjectViewPopupMenuSettingsGroup.text=Project View Popup Menu Settings Group -group.ProjectViewPopupMenuRefactoringGroup.text = Project View Popup Refactoring Group -group.ViewRecentActions.text=View Recent Actions Group -group.NewGroup1.text=New Group (1) -group.NewGroup2.text=New Group (2) -action.NewModuleInGroup.text=Module -action.WrapReturnValue.text=Wr_ap Return Value... -action.WrapReturnValue.description=Wrap return value of the specified method with object -action.ReplaceConstructorWithBuilder.text=Replace Constructor with Builder... -action.ReplaceConstructorWithBuilder.description=Replace Constructor with Builder -action.WelcomeScreen.ShowTips.text=Tips of the Day -action.WelcomeScreen.ShowTips.description=Learn how to increase your coding productivity. -action.ShelvedChanges.ShowHideDeleted.text=Show/Hide Applied Shelved Changes -action.XDebugger.MuteBreakpoints.text=Mute Breakpoints -action.ShelvedChanges.DeleteDeleted.text=Delete Applied Shelved Changes -action.Diff.IgnoreWhitespace.text=Toggle Diff Ignore Whitespace -action.ShelvedChanges.Restore.text=Restore Applied Shelved Change -action.Graph.Current.Node.Dependencies.Filter.text=Show selected nodes with dependencies -group.VcsToobarActions.text=VCS Actions -action.StartupWizard.text=Configure Plugins -action.StartupWizard.description=Run a wizard for selecting the set of enabled plugins -action.PopupHector.text=Popup Per File Highlighting Settings -action.PopupHector.description=Show panel to configure highlighting mode for the current file -group.EditorTabPopupMenu.text=Editor Tab Popup Menu -group.MainMenu.text=Main menu -group.ProjectViewPopupMenu.text=Project View Popup Menu -group.FavoritesViewPopupMenu.text=Favorites View Popup Menu -group.CommanderViewPopupMenu.text=Commander View Popup Menu -group.NavbarPopupMenu.text=Navigation Bar -action.NewInspectionsInLatestVersion.text=What's new in IDEA 9 -action.NewInspectionsInLatestVersion.description=Show inspections new in IDEA 9 -action.RerunFailedTests.text=Rerun Failed Tests -action.RerunFailedTests.description=Run only tests that failed/crashed after last run -group.AnalyzeJavaMenu.text=Analyze Java Menu -group.AnalyzeJavaMenu.description=Analyze Java Menu -action.InvalidateCaches.text=Invalidate Caches... -action.InvalidateCaches.description=Force rebuild of all caches and indices on next startup -group.PlaySavedMacroActions.text=Play Saved Macros -group.PlaySavedMacroActions.description=Play Saved Macros -group.ToolsBasicGroup.text=Tools Basic Group -group.ToolsBasicGroup.description=Tools Basic Group -group.ToolbarNewElement.text=Toolbar New Element Group -action.NewElementToolbarAction.text=Create New File -action.ShowRegistry.text=Registry -action.ExportTestResults.text=Export Test Results... -action.ExportTestResults.description=Export test results to the file -action.TogglePowerSave.text=Power Save Mode -action.TogglePowerSave.description=Power save mode disables background code analysis and other background operations -action.ConvertIndentsToSpaces.text=Convert Indents to Spaces -action.ConvertIndentsToSpaces.description=Convert the indentation in the file or selected block to use spaces instead of tabs -action.ConvertIndentsToTabs.text=Convert Indents to Tabs -action.ConvertIndentsToTabs.description=Convert the indentation in the file or selected block to use tabs instead of spaces -group.Vcs.Import.text=Import into Version Control -group.Vcs.Browse.text=Browse VCS Repository -action.Graph.print.reset=Reset -action.MarkSourceRoot.text=Source Root -action.MarkTestSourceRoot.text=Test Source Root -action.MarkExcludeRoot.text=Excluded -group.MarkRootGroup.text=Mark Directory As +# suppress inspection "UnusedProperty" for whole file +# IDEA core action texts and descriptions go here. +# The key patterns are "action." + id + ".text" and "action." + id + ".description" + +action.CaptureMemorySnapShot.text=Capture Memory Snapshot +action.CaptureMemorySnapShot.description=Capture memory snapshot +action.CaptureCPUUsageData.text=Capture CPU Usage Data +action.CaptureCPUUsageData.description=Capture CPU usage data +action.CaptureAllocations.text=Capture Allocations +action.CaptureAllocations.description=Capture memory allocations data +action.ContextHelp.text=Conte_xt Help +action.ContextHelp.description=Show context help +action.RunConfiguration.text=Select Run/Debug Configuration +action.SwitchCoverage.text=Show Code Co_verage Data +action.GenerateCoverageReport.text=_Generate Coverage Report +action.RunConfiguration.description=Select Run/Debug Configuration +action.ShowIntentionActions.text=Show Intention Actions +action.ToggleReadOnlyAttribute.text=Toggle Read-Only Attribute +action.ToggleReadOnlyAttribute.description=Toggle read-only attribute of current file +action.TogglePopupHints.text=Toggle Popup Hints for Current File +action.RunGc.text=Run Garbage Collector +action.ShowPopupMenu.text=Show Context Menu +action.CloseWindow.text=Close Active Window +action.Vcs.ShowMessageHistory.text=Message History +action.Vcs.ShowMessageHistory.description=Show commit message history +action.Vcs.ShowHistoryForBlock.text=Show History _for Selection +action.Vcs.ShowHistoryForBlock.description=Show history for editor selection +action.Vcs.ShowTabbedFileHistory.text=Show _History +action.Vcs.ShowTabbedFileHistory.description=Show history of file +action.AddToFavoritesPopup.text=Add To Favorites +action.AddToFavoritesPopup.description=Add selected node to favorites +group.AddToFavorites.text=Add To F_avorites +group.AddAllToFavorites.text=Add A_ll To Favorites +action.AddNewFavoritesList.text=Add New Favorites List +action.AddNewFavoritesList.description=Add New Favorites List +action.RemoveFromFavorites.text=Remove From Favorites +action.RemoveFromFavorites.description=Remove selected node from favorites +action.RenameFavoritesList.text=Rename Favorites List +action.RenameFavoritesList.description=Rename the favorites list the selected node belongs to +action.RemoveFavoritesList.text=Delete Favorites List +action.RemoveFavoritesList.description=Delete all favorites the selected node belongs to +action.RemoveAllFavoritesListsButThis.text=Delete All Favorites List But This +action.RemoveAllFavoritesListsButThis.description=Delete all favorites except these +group.SendToFavoritesGroup.text=_Send To Favorites +action.ActivateNavBar.text=Activate Navigation _Bar +action.ActivateNavBar.description=Activate navigation bar +action.CreateRunConfiguration.text=Create Run Configuration +action.CreateRunConfiguration.description=Create run configuration from context +group.RunContextPopupGroup.text=Run Configurations +group.EditorActions.text=Editor Actions +action.EditorChooseLookupItem.text=Choose Lookup Item +action.EditorChooseLookupItemReplace.text=Choose Lookup Item Replace +action.EditorChooseLookupItemCompleteStatement.text=Choose Lookup Item and Invoke Complete Statement +action.NextTemplateVariable.text=Next Template Variable +action.PreviousTemplateVariable.text=Previous Template Variable +action.NextParameter.text=Next Parameter +action.PrevParameter.text=Prev Parameter +action.EditorPageUp.text=Page Up +action.EditorPageDown.text=Page Down +action.EditorPageUpWithSelection.text=Page Up with Selection +action.EditorPageDownWithSelection.text=Page Down with Selection +action.EditorCopy.text=Copy +action.EditorCut.text=Cut +action.EditorPaste.text=Paste +action.EditorPasteSimple.text=Paste S_imple +action.EditorPasteSimple.description=Paste without formatting, autoimport, literal escaping etc. +action.EditorPasteFromX11.text=Paste from X clipboard +action.EditorDelete.text=Delete +action.EditorBackSpace.text=Backspace +action.EditorMoveToPageTop.text=Move Caret to Page Top +action.EditorMoveToPageBottom.text=Move Caret to Page Bottom +action.EditorMoveToPageTopWithSelection.text=Move Caret to Page Top with Selection +action.EditorMoveToPageBottomWithSelection.text=Move Caret to Page Bottom with Selection +action.EditorEnter.text=Enter +action.EditorEscape.text=Escape +action.EditorUp.text=Up +action.EditorDown.text=Down +action.EditorLeft.text=Left +action.EditorRight.text=Right +action.EditorUpWithSelection.text=Up with Selection +action.EditorDownWithSelection.text=Down with Selection +action.EditorLeftWithSelection.text=Left with Selection +action.EditorRightWithSelection.text=Right with Selection +action.EditorIndentSelection.text=Indent Selection +action.EditorUnindentSelection.text=Unindent Selection +action.EditorTab.text=Tab +action.EmacsStyleIndent.text=Emacs Tab +action.EditorScrollUp.text=Scroll Up +action.EditorScrollDown.text=Scroll Down +action.EditorMoveUpAndScroll.text=Move Up and Scroll +action.EditorMoveDownAndScroll.text=Move Down and Scroll +action.EditorMoveUpAndScrollWithSelection.text=Move Up and Scroll with Selection +action.EditorMoveDownAndScrollWithSelection.text=Move Down and Scroll with Selection +action.EditorLineStart.text=Move Caret to Line Start +action.EditorLineEnd.text=Move Caret to Line End +action.EditorCutLineEnd.text=Cut up to Line End +action.EditorTextStart.text=Move Caret to Text Start +action.EditorTextEnd.text=Move Caret to Text End +action.EditorLineStartWithSelection.text=Move Caret to Line Start with Selection +action.EditorLineEndWithSelection.text=Move Caret to Line End with Selection +action.EditorTextStartWithSelection.text=Move Caret to Text Start with Selection +action.EditorTextEndWithSelection.text=Move Caret to Text End with Selection +action.EditorNextWord.text=Move Caret to Next Word +action.EditorPreviousWord.text=Move Caret to Previous Word +action.EditorNextWordWithSelection.text=Move Caret to Next Word with Selection +action.EditorPreviousWordWithSelection.text=Move Caret to Previous Word with Selection +action.EditorCodeBlockStart.text=Move Caret to Code Block Start +action.EditorCodeBlockEnd.text=Move Caret to Code Block End +action.EditorCodeBlockStartWithSelection.text=Move Caret to Code Block Start with Selection +action.EditorCodeBlockEndWithSelection.text=Move Caret to Code Block End with Selection +action.EditorDeleteToWordStart.text=Delete to Word Start +action.EditorDeleteToWordEnd.text=Delete to Word End +action.EditorDeleteLine.text=Delete Line +action.EditorDuplicate.text=Duplicate Line or Block +action.EditorSelectWord.text=Select Word at Caret +action.EditorUnSelectWord.text=Unselect Word at Caret +action.EditorToggleInsertState.text=Toggle Insert/Overwrite +action.EditorToggleColumnMode.text=Column _Mode +action.EditorToggleColumnMode.description=Toggle editor column mode +action.EditorToggleShowWhitespaces.text=Sh_ow Whitespaces +action.EditorToggleShowWhitespaces.description=Toggle display whitespaces in current editor +action.EditorToggleShowIndentLines.text=Sh_ow Indent Guides +action.EditorToggleShowIndentLines.description=Toggle display indent guides in current editor +action.EditorToggleShowLineNumbers.text=Show L_ine Numbers +action.EditorToggleShowLineNumbers.description=Toggle display line numbers in current editor +action.EditorToggleUseSoftWraps.text=Use Soft Wraps +action.EditorToggleUseSoftWraps.description=Toggle using soft wraps in current editor +action.EditorConsoleScrollToTheEnd.text=Scroll to the end +action.ViewImportPopups.text=Show Import Popups +action.ViewImportPopups.description=Toggle display import popups in current file +action.EditorScrollToCenter.text=Scroll to Center +action.EditorToggleCase.text=Toggle Case +action.EditorJoinLines.text=Join Lines +action.EditorSelectLine.text=Select Line at Caret +action.EditorSplitLine.text=Split Line +action.EditorStartNewLine.text=Start New Line +action.EditorCompleteStatement.text=Complete Current Statement +action.ExpandAll.text=Expand All +action.CollapseAll.text=Collapse All +action.ExportToTextFile.text=Export to Text File +action.ExpandTreeNode.text=Expand Tree Node +action.CollapseTreeNode.text=Collapse Tree Node +action.FullyExpandTreeNode.text=Fully Expand Tree Node +group.GenerateGroup.text=_Generate +action.GenerateTestMethod.text=Test Method +action.GenerateSetUpMethod.text=SetUp Method +action.GenerateTearDownMethod.text=TearDown Method +action.GenerateConstructor.text=Constructor +action.GenerateGetter.text=Getter +action.GenerateSetter.text=Setter +action.GenerateGetterAndSetter.text=Getter and Setter +action.GenerateEquals.text=equals() and hashCode() +action.OverrideMethods.text=_Override Methods... +action.OverrideMethods.description=Override base class methods in the current class +action.ImplementMethods.text=_Implement Methods... +action.ImplementMethods.description=Implement base interface/class methods in the current class +action.DelegateMethods.text=_Delegate Methods... +action.DelegateMethods.description=Generate delegate method to a field/getter +action.GenerateSuperMethodCall.text=super method call +action.NextDiff.text=Move to the next difference +action.NextDiff.description=Move to the Next Difference +action.PreviousDiff.text=Move to the Previous Difference +action.PreviousDiff.description=Move to the previous difference +action.Refresh.text=R_efresh +action.CheckinProject.text=Check _In Project +action.CheckinFiles.text=Check _In +action.UpdateFiles.text=_Update +action.CheckStatusForFiles.text=Chec_k Status +action.Annotate.text=A_nnotate +action.Annotate.description=Show information about last modification date and author for each line +action.Compare.SameVersion.text=Compare with the Same Repositor_y Version +action.Compare.LastVersion.text=Compare with Latest Repository _Version +action.Compare.Selected.text=_Compare with... +group.NewGroup.text=_New +group.WeighingNewGroup.text=_New +action.NewClass.text=Java Class +action.NewFile.text=File +action.NewDir.text=Directory/Package +action.NewFromTemplate.text=From Template +action.CompareTwoFiles.text=Compare Two _Files +action.CompareTwoFiles.description=Compare two selected files +action.CompareFileWithEditor.text=Co_mpare File with Editor +action.CompareFileWithEditor.description=Compare selected file with editor + +group.LocalHistory.text=Local _History +action.LocalHistory.ShowHistory.text=Show _History +action.LocalHistory.ShowSelectionHistory.text=Show History for Selection +action.LocalHistory.PutLabel.text=Put _Label... + +group.VcsGroups.text=Version _Control +group.VcsGlobalGroup.text=Version Control Group +action.Vcs.FileView.text=Show Fi_le View +action.Vcs.FileView.description=Show version control related information for all local files +action.Vcs.ChangesView.text=Show Cha_nges View +action.Vcs.ChangesView.description=Show window for managing changes and changelists +action.Vcs.UpdateProject.text=_Update Project +action.Vcs.ProjectStatus.text=C_heck Project Status +group.VcsGroup.text=Version Control +group.VcsFileGroupPopup.text=Version Control Group +group.FileMenu.text=_File +group.ProjectImport.text=Import _Projects +action.NewProject.text=_New Project... +action.NewProject.description=Create a new project +action.NewModule.text=New _Module... +action.NewModule.description=Add new module to the project +action.OpenProject.text=_Open Project... +action.OpenProject.description=Open an existing project +action.OpenFile.text=Open Fi_le... +action.OpenFile.description=Open file in editor +group.reopen.win.text=_Reopen +group.reopen.mac.text=Open _Recent +group.$LRU.description=List of recently opened projects +action.CloseProject.text=Close Pro_ject +action.CloseProject.description=Close current project +action.ShowSettings.text=Se_ttings +action.ShowSettings.description=Configure application settings +action.ShowProjectStructureSettings.text=Pro_ject Structure +action.ShowProjectStructureSettings.description=Configure project structure +action.TemplateProjectProperties.text=Templ_ate Settings +action.TemplateProjectProperties.description=Configure template settings for all new projects +action.TemplateProjectStructure.text=Te_mplate Project Structure +action.TemplateProjectStructure.description=Configure template structure for all new projects +action.SaveAll.text=_Save All +action.SaveAll.description=Save all files and settings +action.ExportSettings.text=_Export Settings... +action.ExportSettings.description=Export global settings +action.ImportSettings.text=_Import Settings... +action.ImportSettings.description=Import global settings +action.ImportFromJBuilder.text=Import _From JBuilder... +action.ImportFromJBuilder.description=Load Modules From JBuilder Project +action.ImportFromEclipse.text=Import From Eclipse... +action.ImportFromEclipse.description=Create project from Eclipse projects +action.ExportToEclipse.text=Export To Eclipse... +action.ExportToEclipse.description=Create Eclipse projects for each module +action.Synchronize.text=S_ynchronize +action.Synchronize.description=Detect all externally changed files and reload them from disk +action.ReloadFromDisk.text=Reload from Dis_k +action.ReloadFromDisk.description=Reload current file from disk and lose all changes +group.Vcs.Checkout.text=Check_out from Version Control +action.ExportToHTML.text=Export to _HTML... +action.ExportToHTML.description=Save file content in HTML format with syntax coloring +action.Print.text=_Print... +action.Print.description=Print file +action.Exit.text=E_xit +action.Exit.description=Quit IDE +group.EditMenu.text=_Edit +action.undo.text=_Undo {0} +action.undo.description=Undo {0} +action.undo.description.empty=last operation +action.redo.text=_Redo {0} +action.redo.description=Redo {0} +action.redo.description.empty=last undone operation +action.$Cut.text=Cu_t +action.$Cut.description=Cut to clipboard +action.$Copy.text=_Copy +action.$Copy.description=Copy to clipboard +action.CopyPaths.text=C_opy Paths +action.CopyPaths.description=Copy paths corresponding to selected files or directories to clipboard +action.CopyReference.text=Copy Re_ference +action.CopyReference.description=Copy Java class/method/field reference +action.$Paste.text=_Paste +action.$Paste.description=Paste from clipboard +action.PasteMultiple.text=Past_e from History... +action.PasteMultiple.description=Paste from recent clipboards +action.$SelectAll.text=Select _All +action.$SelectAll.description=Select all +action.NewElement.text=_New... +action.NewElement.description=Create new class, interface, file or directory +action.NewElementSamePlace.text=_New... +action.NewElementSamePlace.description=Create new class, interface, file or directory +action.$Delete.text=_Delete +action.$Delete.description=Delete selected item +action.ToggleBookmark.text=Toggle _Bookmark +action.ToggleBookmark.description=Toggle bookmark at the current location +action.ShowBookmarks.text=_Show Bookmarks +action.ShowBookmarks.description=Show list of all bookmarks +group.SearchMenu.text=_Search +action.Find.text=_Find... +action.Find.description=Find a string in active editor +action.FindModal.text=_Find... +action.FindModal.description=Find a string in active editor, shows modal dialog +action.Replace.text=_Replace... +action.Replace.description=Replace a string in active editor with another string +action.FindNext.text=Find _Next +action.FindNext.description=Repeat the last Find/Replace operation +action.FindPrevious.text=Find Pre_vious +action.FindPrevious.description=Repeat the last Find/Replace operation in reverse direction +action.FindWordAtCaret.text=Find _Word at Caret +action.FindWordAtCaret.description=Find next occurrence of the word at caret +action.IncrementalSearch.text=Incrementa_l Search +action.IncrementalSearch.description=Start incremental search (searches string as you type), IntelliJ IDEA 6.0 implementation. +action.FindUsages.text=Find _Usages... +action.FindUsages.description=Find usages of the symbol at caret +action.ShowUsages.text=_Show Usages +action.ShowUsages.description=Show usages of the symbol at caret +action.ShowUsagesSettings.text=_Show Usages Settings +action.ShowUsagesSettings.description=Choose scope and edit options for the Show Usages action +action.FindUsagesInFile.text=F_ind Usages in File +action.FindUsagesInFile.description=Find usages of the symbol at caret in active editor +action.HighlightUsagesInFile.text=_Highlight Usages in File +action.HighlightUsagesInFile.description=Highlight usages of the symbol at caret in active editor +action.FindJoinpointsByPointcutAction.text=Find _Join Point Identified by Pointcut +action.FindJoinpointsByPointcutAction.description=Find join point identified by pointcut +action.FindInPath.text=Find in _Path... +action.FindInPath.description=Find a string in multiple files +action.ReplaceInPath.text=Repl_ace in Path... +action.ReplaceInPath.description=Replace a string in multiple files with another string +group.ViewMenu.text=_View +action.ViewToolBar.text=_Toolbar +action.ViewToolBar.description=Show/hide the main toolbar +action.ViewStatusBar.text=_Status Bar +action.ViewStatusBar.description=Show/hide the status bar +action.ViewToolButtons.text=_Tool Buttons +action.ViewToolButtons.description=Show/hide tool window buttons +action.ViewNavigationBar.text=Na_vigation Bar +action.ViewNavigationBar.description=Show/hide the navigation bar +action.FileStructurePopup.text=_File Structure Popup +action.FileStructurePopup.description=Popup structure of the current file for quick navigation +action.ShowFilePath.text=File _Path +action.ShowFilePath.description=Popup path of the current file for opening in external file manager +action.SelectIn.text=Se_lect In... +action.SelectIn.description=Select the current class or method in any view +action.QuickJavaDoc.text=Quick _Documentation Lookup +action.QuickJavaDoc.description=Show a popup window with documentation for the symbol at caret +action.QuickImplementations.text=Quick Definition Loo_kup +action.QuickImplementations.description=Show a popup window with the symbol (and its implementations) content +action.ParameterInfo.text=_Parameter Info +action.ParameterInfo.description=Show parameters of the method call at caret +action.EditorContextInfo.text=_Context Info +action.EditorContextInfo.description=Show the current method or class declaration when it is not visible +action.ShowErrorDescription.text=E_rror Description +action.ShowErrorDescription.description=Show description of error or warning at caret +action.ProjectViewChangeView.text=Change View +action.ProjectViewChangeView.description=Choose Project, Sourcepath or Classpath tree in the Project View window +action.EditSource.text=_Jump to Source +action.EditSource.description=Open editor for the selected item and give focus to it +action.ViewSource.text=Sho_w Source +action.ViewSource.description=Open editor for the selected item +action.ExternalJavaDoc.text=E_xternal Documentation +action.ExternalJavaDoc.description=Open browser with documentation for the selected item +action.RecentFiles.text=Rece_nt Files +action.RecentFiles.description=Show list of recently viewed files +action.RecentChangedFiles.text=Recently Changed Files +action.RecentChangedFiles.description=Show list of recently changed files +action.QuickChangeScheme.text=_Quick Switch Scheme +action.QuickChangeScheme.description=Change current code style scheme, color\\&fonts scheme, keymap or Look\\&Feel +action.TypeHierarchy.text=Type _Hierarchy +action.TypeHierarchy.description=Browse hierarchy for the selected class +action.MethodHierarchy.text=_Method Hierarchy +action.MethodHierarchy.description=Browse method hierarchy for the selected method +action.CallHierarchy.text=Call Hierarch_y +action.CallHierarchy.description=Browse call hierarchy for the selected method +group.FoldingGroup.text=Foldin_g +action.ExpandRegion.text=E_xpand +action.ExpandRegion.description=Expand folding region at caret +action.CollapseRegion.text=_Collapse +action.CollapseRegion.description=Collapse folding region at caret +action.ExpandAllRegions.text=_Expand All +action.ExpandAllRegions.description=Expand all folding regions +action.CollapseAllRegions.text=Collapse _All +action.CollapseAllRegions.description=Collapse all folding regions +action.ExpandJavadocs.text=Expand Java_docs +action.ExpandJavadocs.description=Expand all javadoc comments +action.CollapseJavadocs.text=Collapse _Javadocs +action.CollapseJavadocs.description=Collapse all javadoc comments +action.CollapseSelection.text=Fold _Selection / Remove region +action.CollapseSelection.description=Fold selected block +action.CollapseBlock.text=Fold Code _Block +action.CollapseBlock.description=Fold current code block +action.RecentChanges.text=R_ecent Changes +group.GoToMenu.text=_Go To +action.GotoClass.text=_Class... +action.GotoClass.description=Quickly navigate to a class by name +action.GotoFile.text=_File... +action.GotoFile.description=Quickly navigate to a file by name +action.GotoSymbol.text=_Symbol... +action.GotoSymbol.description=Quickly navigate to any symbol by name +action.GotoAction.text=Find _Action... +action.GotoAction.description=Quickly navigate to action by name +action.GotoLine.text=_Line... +action.GotoLine.description=Navigate to a line in editor by its number +action.GotoDeclaration.text=_Declaration +action.GotoDeclaration.description=Navigate to the declaration of the symbol at caret +action.GotoImplementation.text=I_mplementation(s) +action.GotoImplementation.description=Navigate to the implementation(s) of the selected class or method +action.GotoTypeDeclaration.text=_Type Declaration +action.GotoTypeDeclaration.description=Navigate to the declaration of the symbol's type at caret +action.GotoSuperMethod.text=S_uper Method +action.GotoSuperMethod.description=Navigate to the declaration of the method that current method overrides or implements +action.GotoTest.text=T_est +action.GotoTest.description=Navigate to the test of the selected class +action.GotoTestSubject.text=T_est Subject +action.GotoTestSubject.description=Navigate to the subject of the selected test +action.GotoAspect.text=_Aspect +action.GotoAspect.description=Navigate to the declaration of the advice, error or warning declaration that affects a join point at caret +action.GotoNextError.text=_Next Highlighted Error +action.GotoNextError.description=Navigate to the next highlighted error in the active editor +action.GotoPreviousError.text=_Previous Highlighted Error +action.GotoPreviousError.description=Navigate to the previous highlighted error in the active editor +action.MethodDown.text=N_ext Method +action.MethodDown.description=Navigate to the next method in the active editor +action.MethodUp.text=Prev_ious Method +action.MethodUp.description=Navigate to the previous method in the active editor +action.GotoNextBookmark.text=Ne_xt Bookmark +action.GotoNextBookmark.description=Navigate to the next bookmark in the active editor +action.GotoPreviousBookmark.text=P_revious Bookmark +action.GotoPreviousBookmark.description=Navigate to the previous bookmark in the active editor +action.Back.text=_Back +action.Back.description=Undo last navigation operation +action.Forward.text=For_ward +action.Forward.description=Redo last undone navigation operation +action.JumpToLastChange.text=Last Edit L_ocation +action.JumpToLastChange.description=Move through the most recent change points +action.PreviousOccurence.text=Previ_ous Occurrence +action.PreviousOccurence.description=Navigate to the previous occurrence +action.NextOccurence.text=Ne_xt Occurrence +action.NextOccurence.description=Navigate to the next occurrence +action.VcsShowNextChangeMarker.text=Next C_hange +action.VcsShowNextChangeMarker.description=Go to next change +action.VcsShowPrevChangeMarker.text=Prev Ch_ange +action.VcsShowPrevChangeMarker.description=Go to previous change +group.CodeMenu.text=Co_de +action.SurroundWith.text=_Surround With... +action.SurroundWith.description=Surround selected code fragment with if, while, try/catch or other construct +action.Generate.text=_Generate... +action.Generate.description=Generate constructor, getter or setter method, etc. +action.Unwrap.text=Un_wrap/Remove... +action.Unwrap.description=Safely unwraps or removes the selected scope +group.CodeCompletionGroup.text=_Complete Code +action.CodeCompletion.text=_Basic +action.CodeCompletion.description=Complete code +action.HippieCompletion.text=Cyclic _Expand Word +action.HippieCompletion.description=Cyclic Expand word +action.HippieBackwardCompletion.text=Cyclic _Expand Word (Backward) +action.HippieBackwardCompletion.description=Cyclic Expand word (Backward) +action.SmartTypeCompletion.text=_SmartType +action.SmartTypeCompletion.description=Complete code basing on expected type +action.ClassNameCompletion.text=Class _Name +action.ClassNameCompletion.description=Complete class name and add import for it +action.InsertLiveTemplate.text=Insert Live _Template... +action.InsertLiveTemplate.description=Show popup list of live templates starting with the specified prefix +action.SurroundWithLiveTemplate.text=Surround with Live Tem_plate... +action.SurroundWithLiveTemplate.description=Surrounds the selection with one of the template +action.CommentByLineComment.text=Comment with _Line Comment +action.CommentByLineComment.description=Comment/uncomment current line or selected block with line comments +action.CommentByBlockComment.text=Comment with _Block Comment +action.CommentByBlockComment.description=Comment/uncomment code with block comments +action.ReformatCode.text=_Reformat Code... +action.ReformatCode.description=Reformat code +action.AutoIndentLines.text=_Auto-Indent Lines +action.AutoIndentLines.description=Indent current line or selected block according to the code style settings +action.OptimizeImports.text=Optimi_ze Imports... +action.OptimizeImports.description=Remove unused imports and reorder/reorganize imports +action.MoveStatementDown.text=Move Statement Do_wn +action.MoveStatementDown.description=Move selected statements one line down +action.MoveStatementUp.text=Move Statement _Up +action.MoveStatementUp.description=Move selected statements one line up +group.AnalyzeMenu.text=Analy_ze +action.InspectCode.text=_Inspect Code... +action.InspectCode.description=Inspect code +action.InferNullity.text=Infer _Nullity... +action.InferNullity.description=Infer nullity +action.ViewOfflineInspection.text=View _Offline Inspection Results... +action.ViewOfflineInspection.description=Load offline inspection results +action.ShowPackageDeps.text=Analyze _Dependencies... +action.ShowPackageDeps.description=Browse code choosen analysis item depends on +action.ShowBackwardPackageDeps.text=Analyze _Backward Dependencies... +action.ShowBackwardPackageDeps.description=Browse code choosen analysis item used in +action.ShowPackageCycles.text=Analyze _Cyclic Dependencies... +action.ShowPackageCycles.description=Browse code choosen analysis item used in cycles +action.ShowModulesDependencies.text=Analyze _Module Dependencies... +action.ShowModulesDependencies.description=Show dependencies between modules in project +action.Unscramble.text=Analyze _Stacktrace... +action.Unscramble.description=Open console with the navigatable stacktrace +group.RefactoringMenu.text=_Refactor +action.RenameElement.text=_Rename... +action.RenameElement.description=Rename the selected symbol and correct all references +action.ChangeSignature.text=Change Si_gnature... +action.ChangeSignature.description=Change signature of the selected method or class and correct all references +action.MoveToTheLeft.text=Move parameter left +action.MoveToTheLeft.description=Move parameter left +action.MoveToTheRight.text=Move parameter right +action.MoveToTheRight.description=Move parameter right +action.ChangeTypeSignature.text=T_ype Migration... +action.ChangeTypeSignature.description=Change type of the return type of the method, field, parameter, variable or class type argumeants and correct all references +action.MakeStatic.text=Make S_tatic... +action.MakeStatic.description=Convert method or inner class to static and correct all references +action.ConvertToInstanceMethod.text=C_onvert To Instance Method... +action.ConvertToInstanceMethod.description=Convert static method to instance method and correct all references +action.Move.text=_Move... +action.Move.description=Move the selected class, method, package or static member to another package or class and correct all references +action.CopyElement.text=C_opy... +action.CopyElement.description=Create a copy of the selected class, file(s) or directory(es) +action.CloneElement.text=Clo_ne... +action.CloneElement.description=Create a copy of the selected class, file or directory in the same package/directory +action.SafeDelete.text=Safe _Delete... +action.SafeDelete.description=Delete the selected class, method or field, checking for usages +action.ExtractMethod.text=E_xtract Method... +action.ExtractMethod.description=Turn the selected code fragment into a method +action.RemoveMiddleman.text=Remove _Middleman +action.RemoveMiddleman.description=Get the client to call the delegate directly +action.MethodDuplicates.text=Replace Met_hod Code Duplicates... +action.MethodDuplicates.description=Finds code in current file that can be transformed into a call of selected method +action.InvertBoolean.text=Invert _Boolean... +action.InvertBoolean.description=Makes the method return or variable contain the opposite value and corrects the references +action.IntroduceParameterObject.text=Introduce Parameter Ob_ject... +action.IntroduceParameterObject.description=Replaces method parameters list with object +action.ExtractClass.text=E_xtract Delegate... +action.ExtractClass.description=Extract Delegate +action.IntroduceVariable.text=Introduce _Variable... +action.IntroduceVariable.description=Put a result of the selected expression into a variable +action.IntroduceField.text=Introduce _Field... +action.IntroduceField.description=Put a result of the selected expression into a field +action.IntroduceConstant.text=Introduce _Constant... +action.IntroduceConstant.description=Replace selected expression with a constant (static final field) +action.IntroduceParameter.text=Introduce _Parameter... +action.IntroduceParameter.description=Turn the selected expression into method parameter +action.ExtractInterface.text=Extract _Interface... +action.ExtractInterface.description=Extract interface from the selected class +action.ExtractModule.text=Extract _Module... +action.ExtractModule.description=Extract module from the selected class +action.ExtractSuperclass.text=Extract S_uperclass... +action.ExtractSuperclass.description=Extract superclass from the selected class +action.TurnRefsToSuper.text=Use Interface _Where Possible... +action.TurnRefsToSuper.description=Change usages of a class to those of its superclass or interface +action.MembersPullUp.text=Pu_ll Members Up... +action.MembersPullUp.description=Pull class members to superclass or implemented interface +action.MemberPushDown.text=Pu_sh Members Down... +action.MemberPushDown.description=Push class members down to directly inheriting subclasses +action.InheritanceToDelegation.text=Replace _Inheritance with Delegation... +action.InheritanceToDelegation.description=Replace inheritance with delegation +action.Inline.text=I_nline... +action.Inline.description=Inline the selected method or variable +action.AnonymousToInner.text=Convert Anon_ymous to Inner... +action.AnonymousToInner.description=Convert anonymous class into an inner class +action.EncapsulateFields.text=_Encapsulate Fields... +action.EncapsulateFields.description=Replace direct access to fields in the selected class with use of accessor methods +action.ReplaceTempWithQuery.text=Replace Temp with _Query... +action.ReplaceTempWithQuery.description=Turn the selected variable into a method +action.ReplaceConstructorWithFactory.text=Replace Constructor with F_actory Method... +action.ReplaceConstructorWithFactory.description=Create a static factory method and use it instead of a constructor +action.ReplaceMethodWithMethodObject.text=Extract Method Ob_ject... +action.ReplaceMethodWithMethodObject.description=Turn the method into its own object so that all the parameters become fields on that object +action.Generify.text=Ge_nerify... +action.Generify.description=Convert your code to use generic types +action.Migrate.text=_Migrate... +action.Migrate.description=Open migration dialog +action.Type\ Migration.text=Type Migration... +action.Type\ Migration.description=Migrate one type to another +action.ExtractInclude.text=E_xtract Include File... +action.ExtractInclude.description=Turn the selected code fragment into included file +group.BuildMenu.text=_Build +action.CompileProject.text=_Rebuild Project +action.CompileProject.description=Recompile all files in the project +action.CompileDirty.text=Make _Project +action.CompileDirty.description=Compile all modified and dependent files in the project +action.MakeModule.text=Make _Module +action.MakeModule.description=Compile all modified and dependent files in the module +action.Compile.text=Compil_e +action.Compile.description=Compile the selected file or package +action.RunAPT.text=Process _Annotations +action.RunAPT.1.text=Process {0} _Annotations +action.RunAPT.description=Run Annotation Processors on the selected scope +action.GenerateAntBuild.text=_Generate Ant Build... +action.GenerateAntBuild.description=Generate Ant Build File from the Project +action.BuildAllArtifacts.text=Build All Artifacts +group.BuildArtifactsGroup.text=Build Artifact +group.RunMenu.text=R_un +group.RunnerActions.text=Run/Debug +action.editRunConfigurations.text=Edit Configu_rations +action.editRunConfigurations.description=Open edit Run/Debug configurations dialog +action.Stop.text=Stop +action.Stop.description=Stop process +action.Hotswap.text=Relo_ad Changed Classes +action.Hotswap.description=Reload all changed classes into application being debugged (HotSwap) +action.StepOver.text=Step _Over +action.StepOver.description=Step to the next line in this file +action.StepInto.text=Step _Into +action.StepInto.description=Step to the next line executed +action.SmartStepInto.text=Smart Ste_p Into +action.SmartStepInto.description=Step into the particular method +action.StepOut.text=Step Ou_t +action.StepOut.description=Step to the first line executed after returning from this method +action.ForceStepOver.text=Force Step O_ver +action.ForceStepOver.description=Step over, disable all breakpoints before stepping. +action.ForceStepInto.text=Force Step I_nto +action.ForceStepInto.description=Step into, ignore stepping filters for libraries, constructors, etc. +action.RunToCursor.text=Run to _Cursor +action.RunToCursor.description=Run to the line where the caret is +action.ForceRunToCursor.text=Force Run to Cur_sor +action.ForceRunToCursor.description=Run to the line where the caret is, ignoring any breakpoints +action.Debugger.PopFrame.text=Drop _Frame +action.Debugger.PopFrame.description=Moves execution point back to the method call dropping current method frames from the stack +action.Pause.text=_Pause Program +action.Pause.description=Suspend program execution and enable debugging +action.Resume.text=Resume Pro_gram +action.Resume.description=Resume program execution +action.EvaluateExpression.text=_Evaluate Expression... +action.EvaluateExpression.description=Evaluate arbitrary expression +action.QuickEvaluateExpression.text=_Quick Evaluate Expression +action.QuickEvaluateExpression.description=Evaluate selected expression +action.ShowExecutionPoint.text=Show E_xecution Point +action.ShowExecutionPoint.description=Show the current program execution point +action.ToggleLineBreakpoint.text=Toggle _Line Breakpoint +action.ToggleLineBreakpoint.description=Toggle line breakpoint at caret +action.ToggleMethodBreakpoint.text=Toggle _Method Breakpoint +action.ToggleMethodBreakpoint.description=Toggle method breakpoint for the method at caret +action.ToggleFieldBreakpoint.text=Toggle _Field Watchpoint +action.ToggleFieldBreakpoint.description=Toggle field watchpoint for the field at caret +action.ToggleBreakpointEnabled.text=Toggle _Breakpoint Enabled +action.ToggleBreakpointEnabled.description=Enable/disable breakpoint at the current line +action.ViewBreakpoints.text=View Brea_kpoints +action.ViewBreakpoints.description=View and manage all breakpoints and watchpoints +action.ExportThreads.text=Export T_hreads... +action.ExportThreads.description=Save all threads information to a text file or clipboard +action.Runner.RestoreLayout.text=Restore Layout +action.Runner.RestoreLayout.description=Restore visual elements of debugger session to default state +action.Runner.ToggleToolbarLayout.text=Horizontal Toolbars +action.Runner.ToggleToolbarLayout.description=Toggles horizontal/vertical toolbar orientation +group.Runner.Layout.text=Layout +group.Runner.Layout.description=Layout configuration +action.Runner.MinimizeView.text=Hide +action.Runner.MinimizeView.description=Hide view +action.Runner.CloseView.text=Close +action.Runner.CloseView.description=Close view +action.Runner.MoveViewToGrid.text=Move To Grid +action.Runner.MoveViewToGrid.description=Move view to the debugger grid container +action.Runner.MoveViewToTab.text=Move To Tab +action.Runner.MoveViewToTab.description=Move view to the debugger tab container +action.Runner.DetachCell.text=Detach +action.Runner.DetachCells.text=Detach {0} Views +action.Runner.DetachCell.description=Detach selected views to a separate window +action.Runner.AttachCell.text=Attach +action.Runner.AttachCells.text=Attach {0} Views +action.Runner.AttachCell.description=Attach selected views to the debugger tool window +action.Runner.RestoreView.text=Restore ''{0}'' View +action.Runner.RestoreView.description=Restore view in the debugger grid +action.Runner.FocusOnStartup.text=Focus On Startup +action.Runner.FocusOnStartup.description=Focus this view by default when UI is shown +action.Debugger.FocusOnBreakpoint.text=Focus On Breakpoint +action.Debugger.FocusOnBreakpoint.description=Focus this view by default when breakpont is reached +group.ToolsMenu.text=_Tools +action.SaveAsTemplate.text=Save as L_ive Template... +action.SaveAsTemplate.description=Save the selected text as live template +action.SaveFileAsTemplate.text=Save File as Temp_late... +action.SaveFileAsTemplate.description=Save current file as template +action.GenerateJavadoc.text=Generate Java_Doc... +action.GenerateJavadoc.description=Run the JavaDoc tool +group.Macros.text=_Macros +group.Macros.description=View, Change, Record, Play Macros +group.StanardMacroActions.text=Standard Macro Actions +action.PlaybackLastMacro.text=Pla_yback Last Macro +action.PlaybackLastMacro.description=Playback last used macro +action.StartStopMacroRecording.text=Start/Stop Macro _Recording +action.StartStopMacroRecording.description=Start/Stop recording of a new macro +action.EditMacros.text=_Edit Macros +action.EditMacros.description=Configure existing macros +action.ValidateXml.text=_Validate +action.ValidateXml.description=Validate the current xml file +action.GenerateDTD.text=Generate DTD from _XML file +group.ExternalToolsGroup.text=External Tools +action.PsiViewer.text=_View PSI Structure... +action.DebugListen.text=Throw Debug Exception +action.VirtualFileInfo.text=Virtual File Info +action.Merge3Files.text=Merge +action.LocalizePlugin.text=Localize Plugin +group.MemoryManagement.text=Memory Management +action.DumpPsiMemoryCaches.text=Dump what is cached in PSI +action.DecodeBytesAction.text=Decode Bytes +action.ReloadProjectAction.text=Reload project +action.LocalVcs.ShowStatistics.text=Show Local VCS Statistics... +action.LocalVcs.ShowStatistics.description=Show Local VCS statistics +action.LocalVcs.ShowRepHistory.text=Repository History +action.DropAnError.text=Drop an error +action.DropAnOutOfMemoryError.text=Drop an OutOfMemoryError +action.DropAnOutOfPermGenMemoryError.text=Drop an perm gen OutOfMemoryError +action.DumpInspectionDescriptions.text=Dump inspection descriptions +action.DumpIntentionsDescriptions.text=Dump intentions descriptions +action.ShowUserActivities.text=Show Activities +group.WindowMenu.text=_Window +group.ToolWindowsGroup.text=_Tool Windows +group.BackgroundTasks.text=Background Tasks +action.ShowProcessWindow.text=Show +action.ShowProcessWindow.description=Show/Hide background tasks window +action.ShowProcessWindow.double.click=Click to show/hide background tasks window +action.AutoShowProcessWindow.text=Auto Show +action.AutoShowProcessWindow.description=Show background tasks window on starting of any progress +action.StoreDefaultLayout.text=Store Current La_yout as Default +action.StoreDefaultLayout.description=Store current layout as default +action.RestoreDefaultLayout.text=Restore De_fault Layout +action.RestoreDefaultLayout.description=Restore default layout +action.HideActiveWindow.text=Hi_de Active Tool Window +action.HideActiveWindow.description=Hide active tool window +action.HideSideWindows.text=Hide Side Tool Windows +action.HideSideWindows.description=Hide all windows on this side +action.HideAllWindows.text=Hide All _Tool Windows +action.HideAllWindows.description=Hide all tool windows +action.JumpToLastWindow.text=_Jump to Last Tool Window +action.JumpToLastWindow.description=Activate last focused tool window +action.TogglePinnedMode.text=_Pinned Mode +action.TogglePinnedMode.description=Pin/unpin active tool window +action.ToggleDockMode.text=Dock_ed Mode +action.ToggleDockMode.description=Dock/undock active tool window +action.ToggleFloatingMode.text=Floating _Mode +action.ToggleFloatingMode.description=Float/unfloat active tool window +action.ToggleSideMode.text=Split Mode +action.ToggleSideMode.description=Tool windows split mode on/off +action.ToggleContentUiTypeMode.text=Tabbed Content +action.ToggleContentUiTypeMode.description=Toggle between tabbed/combo presentation of contents +action.ShowContent.text=Show Content +action.ShowContent.description=Show tool window content popup +group.ResizeToolWindowGroup.text=Resize +action.ResizeToolWindowLeft.text=Stretch to Left +action.ResizeToolWindowLeft.description=Resize active tool window to the left +action.ResizeToolWindowRight.text=Stretch to Right +action.ResizeToolWindowRight.description=Resize active tool window to the right +action.ResizeToolWindowUp.text=Stretch to Top +action.ResizeToolWindowUp.description=Resize active tool window to the top +action.ResizeToolWindowDown.text=Stretch to Bottom +action.ResizeToolWindowDown.description=Resize active tool window to the bottom +action.IncrementWindowWidth.text=Increment Width +action.DecrementWindowWidth.text=Decrement Width +action.IncrementWindowHeight.text=Increment Height +action.DecrementWindowHeight.text=Decrement Height +action.NextTab.text=Select Ne_xt Tab +action.NextTab.description=Activate next tab +action.PreviousTab.text=Se_lect Previous Tab +action.PreviousTab.description=Activate previous tab +action.NextEditorTab.text=Select Next Tab in multi-editor file +action.PreviousEditorTab.text=Select Previous Tab in multi-editor file +action.CloseActiveTab.text=Cl_ose Active Tab +action.CloseActiveTab.description=Close active tool window tab +action.PinActiveTab.text=P_in Active Tab +action.PinActiveTab.description=Pin active tool window or editor tab +action.CloseEditor.text=_Close Active Editor +action.CloseEditor.description=Close active editor +action.CloseAllEditors.text=Close _All +action.CloseAllEditors.description=Close all editors in active tab group +action.CloseAllEditorsButActive.text=Close _Others +action.CloseAllEditorsButActive.description=Close all open editors except the active one +action.CloseAllUnmodifiedEditors.text=Close _Unmodified +action.CloseAllUnmodifiedEditors.description=Close all nonmodified editors +action.CloseAllUnpinnedEditors.text=Close All But Pinned +action.CloseAllUnpinnedEditors.description=Close all editors which are not pinned +action.CloseContent.text=Close +action.CloseContent.description=Close currently focused content +action.SplitVertically.text=Split _Vertically +action.SplitVertically.description=Split editor area into 2 tab groups and move the current tab to the right group +action.SplitHorizontally.text=Split _Horizontally +action.SplitHorizontally.description=Split editor area into 2 tab groups and move the current tab to the bottom group +action.ChangeSplitOrientation.text=Change Splitter O_rientation +action.ChangeSplitOrientation.description=Changes orientation of splitter +action.Unsplit.text=Un_split +action.Unsplit.description=Unsplit Window +action.UnsplitAll.text=U_nsplit All +action.UnsplitAll.description=Remove all splitters +action.NextSplitter.text=_Goto Next Splitter +action.NextSplitter.description=Goto next splitter window +action.PrevSplitter.text=Goto Previous Splitter +action.PrevSplitter.description=Goto previous splitter window +group.HelpMenu.text=_Help +action.HelpTopics.text=Help _Topics +action.HelpTopics.description=Show help contents +action.ShowTips.text=Tip of the _Day +action.ShowTips.description=Show Tips of the Day +action.ProductivityGude.text=_Productivity Guide +action.ProductivityGude.description=Show productivity features usages statistics +action.OnlineDocAction.text=_Online Documentation +action.OnlineDocAction.description=Show online documentation +action.WhatsNewAction.text=What's _New in IntelliJ IDEA +action.WhatsNewAction.description=Find out about the new features in this version of IntelliJ IDEA +action.Help.KeymapReference.text=Default _Keymap Reference +action.Help.KeymapReference.description=Open PDF file with the default keymap reference card. +action.CheckForUpdate.text=_Check for Update... +action.CheckForUpdate.description=Check for available update +action.About.text=_About +action.About.description=Show information about IntelliJ IDEA +group.OtherMenu.text=Other +group.OtherMenu.description=Actions that are not properly registered +group.MainToolBar.text=Main Toolbar +group.Bookmarks.text=Bookmarks +action.ToggleBookmark0.text=Toggle Bookmark 0 +action.ToggleBookmark1.text=Toggle Bookmark 1 +action.ToggleBookmark2.text=Toggle Bookmark 2 +action.ToggleBookmark3.text=Toggle Bookmark 3 +action.ToggleBookmark4.text=Toggle Bookmark 4 +action.ToggleBookmark5.text=Toggle Bookmark 5 +action.ToggleBookmark6.text=Toggle Bookmark 6 +action.ToggleBookmark7.text=Toggle Bookmark 7 +action.ToggleBookmark8.text=Toggle Bookmark 8 +action.ToggleBookmark9.text=Toggle Bookmark 9 +action.GotoBookmark0.text=Go to Bookmark 0 +action.GotoBookmark1.text=Go to Bookmark 1 +action.GotoBookmark2.text=Go to Bookmark 2 +action.GotoBookmark3.text=Go to Bookmark 3 +action.GotoBookmark4.text=Go to Bookmark 4 +action.GotoBookmark5.text=Go to Bookmark 5 +action.GotoBookmark6.text=Go to Bookmark 6 +action.GotoBookmark7.text=Go to Bookmark 7 +action.GotoBookmark8.text=Go to Bookmark 8 +action.GotoBookmark9.text=Go to Bookmark 9 + +action.XDebugger.SetValue.text=Set Value... +action.XDebugger.SetValue.description=Modify value of selected node +action.XDebugger.CopyValue.text=Copy Value +action.XDebugger.CopyValue.description=Copy value of selected node to clipboard +action.XDebugger.JumpToSource.text=Jump To Source +action.XDebugger.JumpToSource.description=Open for the selected item +action.XDebugger.Inspect.text=Inspect +action.XDebugger.AddToWatches.text=Add to Watches +action.XDebugger.RemoveWatch.text=Remove Watch +action.XDebugger.NewWatch.text=New Watch... +action.XDebugger.EditWatch.text=Edit... +action.XDebugger.AutoTooltip.text=Value Auto Tooltip +action.XDebugger.AutoTooltip.description=Toggles if value tooltip is shown automatically on mouse over +group.XDebugger.settings.text=Settings + +action.Debugger.AdjustArrayRange.text=Adjust Range... +action.Debugger.Inspect.text=Inspect +action.Debugger.CopyValue.text=Copy Value +action.Debugger.CompareValueWithClipboard.text=Compare Value with Clipboard +action.Debugger.CustomizeContextView.text=Customize Data Views... +action.Debugger.CustomizeThreadsView.text=Customize Threads View... +action.Debugger.NewWatch.text=New Watch... +action.Debugger.EditWatch.text=Edit Watch +action.Debugger.EditFrameSource.text=EditFrameSource +action.Debugger.EditNodeSource.text=EditNodeSource +action.Debugger.EditTypeSource.text=Jump to Object Source +action.Debugger.RemoveAllWatches.text=Remove All Watches +action.Debugger.RemoveWatch.text=Remove Watch +action.Debugger.ViewAsGroup.text=View as +action.Debugger.SetValue.text=Set Value... +action.Debugger.ShowFrame.text=Show Frame +action.Debugger.ResumeThread.text=Resume +action.Debugger.FreezeThread.text=Freeze +action.Debugger.InterruptThread.text=Interrupt +action.Debugger.MuteBreakpoints.text=Mute Breakpoints +action.Debugger.MuteBreakpoints.description=Mute/unmute all breakpoints in a debug session. +action.Debugger.MarkObject.text=Mark Object... +action.Debugger.MarkObject.unmark.text=Unmark Object +action.Debugger.MarkObject.description=Mark/unmark the object so that it can be visually distinguished in in debugger views +action.Debugger.AddToWatch.text=Add to Watches +action.Debugger.AutoRenderer.text=Auto +group.EditorPopupMenu.text=Editor Popup Menu +group.EditorPopupMenu.description=Editor Popup Menu +action.Add\ to\ migration\ roots.text=_Add to migration roots +action.Add\ to\ migration\ roots.description=Add selected element to migration roots +group.EditorPopupMenu.GoTo.text=_Go To +group.EditorPopupMenu.GoTo.description=Go To Editor Popup Menu Group +action.CompareClipboardWithSelection.text=Compare with Clip_board +action.CompareClipboardWithSelection.description=Compare current selection with clipboard +action.ChangeColorScheme.text=Switch Color Scheme +action.ChangeColorScheme.description=Change current color scheme +action.ChangeCodeStyleScheme.text=Switch Code Style Scheme +action.ChangeCodeStyleScheme.description=Change current code style scheme +action.ChangeKeymap.text=Switch Keymap +action.ChangeKeymap.description=Change current keymap +action.ChangeLaf.text=Switch Look\\&\\&Feel +action.ChangeLaf.description=Change current Look\\&Feel +action.ChangeInspectionProfile.text=Switch Error Highlighting +action.ChangeInspectionProfile.description=Change current editor inspection profile +action.ChangeCustomizationSchema.text=Switch Customization Scheme +action.ChangeCustomizationSchema.description=Change Current Customization Scheme +action.MoveEditorToOppositeTabGroup.text=_Move To Opposite Group +action.MoveEditorToOppositeTabGroup.description=Move Editor To Opposite Tab Group +action.ModuleSettings.text=Module _Settings +action.ModuleSettings.description=Open settings dialog for selected module +group.MoveModuleToGroup.text=Move Module to Group +group.MoveModuleToGroup.description=_Move module +action.AssociateWithFileType.text=Associate with File Type +action.AssociateWithFileType.description=Associate selected file with one of file types +action.SynchronizeCurrentFile.text=Synchronize selected file +action.TypeHierarchy.BaseOnThisType.text=Base On This Type +action.TypeHierarchy.Class.text=Class Hierarchy +action.TypeHierarchy.Subtypes.text=Subtypes Hierarchy +action.TypeHierarchy.Supertypes.text=Supertypes Hierarchy +action.MethodHierarchy.ImplementMethodAction.text=Implement Method +action.MethodHierarchy.ImplementMethodAction.description=Implement Method (from Method Hierarchy) +action.MethodHierarchy.OverrideMethodAction.text=Override Method +action.MethodHierarchy.OverrideMethodAction.description=Override Method (from Method Hierarchy) +action.MethodHierarchy.BaseOnThisType.text=Base On This Type +action.MethodHierarchy.BaseOnThisType.description=Show hierarchy for method of selected class/interface +action.CallHierarchy.BaseOnThisType.text=Base On This Method +action.CallHierarchy.BaseOnThisType.description=Show call hierarchy for selected method +action.Lvcs.ShowLvcsDiffAction.text=Compare Versions +action.Lvcs.ShowLvcsDiffAction.description=Show changes +action.Lvcs.RollbackFileAction.text=Rollback File +action.Lvcs.RollbackFileAction.description=Rollback +action.Vcs.ShowDiffAction.text=Show Changes +action.Vcs.ShowDiffAction.description=Show changes +action.Vcs.RollbackChanges.text=Rollback Changes +action.Vcs.RollbackChanges.description=Rollback changes +action.Vcs.EditSourceAction.text=Edit Source +action.Vcs.EditSourceAction.description=Edit source +action.Vcs.ExcludeAction.text=Exclude from Commit +action.Vcs.ExcludeAction.description=Exclude from commit +action.Vcs.IncludeAction.text=Include into Commit +action.Vcs.IncludeAction.description=Include into commit +action.openAssertEqualsDiff.text=View assertEquals Difference +action.UsageView.Include.text=Include +action.UsageView.Include.description=Include this usage(s) into processing +action.UsageView.Exclude.text=Exclude +action.UsageView.Exclude.description=Exlude this usage(s) from processing +action.UsageView.ShowRecentFindUsages.text=R_ecent Find Usages +action.UsageView.ShowRecentFindUsages.description=Choose and re-run recent find usages +group.ShowRecentFindUsagesGroup.text=Recent Find Usages +group.ShowRecentFindUsagesGroup.description=Choose and re-run recent find usages + +stop.capture.cpu.usage.data.action.name=Capture CPU usage data, now capturing +stop.capture.cpu.usage.data.action.description=Stop capturing CPU usage data +stop.capture.allocations.usage.data.action.name=Capture allocations data, now capturing +stop.capture.allocations.usage.data.action.description=Stop capturing allocations data + +action.IntegrateFiles.text=Integrate +action.IntegrateFiles.description=Integrate selected files or directories +action.Vcs.IntegrateProject.text=Inte_grate Project +action.Vcs.IntegrateProject.description=Integrate project + +action.Vcs.RefreshStatuses.text=R_efresh File Status +action.Vcs.RefreshStatuses.description=Refresh VCS status for all files in project + +action.ShowNavBar.text=Show Na_vigation Bar + +action.ScopeView.EditScopes.text=Ed_it Scopes +action.ScopeView.EditScopes.description=Edit Scopes + +action.CodeInspection.OnEditor.text=Inspect Code With _Editor Settings +action.CodeInspection.OnEditor.description=Inspect Code With _Editor Settings + +action.ShowCoverageInfo.text=Show Co&verage Info +action.HideCoverageInfo.text=Hide Co&verage Info + +action.Graph.layout.circular=Circular Layout +action.Graph.layout.balloon=Balloon Layout +action.Graph.layout.directed.orthogonal=Directed Orthogonal Layout +action.Graph.layout.orthogonal=Orthogonal Layout +action.Graph.layout.organic=Organic Layout +action.Graph.layout.hierarchic.group=Hierarchic Group Layout +action.Graph.layout.tree=Tree Layout +action.Graph.layout.apply.current=Apply current layout +action.Graph.layout.fit.content=Fit Content After Layout + +action.Graph.zoom.actual=Actual size +action.Graph.zoom.in=Zoom in +action.Graph.zoom.out=Zoom out + +action.Graph.grid.hide=Hide Grid +action.Graph.grid.show=Show Grid +action.Graph.grid.snap=Snap To Grid +action.Graph.show.edge.label=Show Edge Labels +action.Graph.show.selected.node.dependencies.label=Show selected nodes with dependencies +action.Graph.export.to.file=Export to file... +action.Graph.export.to.file.dialog.caption=Export to file +action.Graph.choose.directory=Choose Directory +action.Graph.fit.content=Fit Content + +action.Graph.print=Print Graph +action.Graph.print.preview=Print Preview + +action.SendFeedback.text=Submit _Feedback +action.SendFeedback.description=Submit feedback to the JetBrains Web site +action.TechnicalSupport.text=Contact _Support Team +action.TechnicalSupport.description=Access technical support on JetBrains Web site + +action.MoveToChangeList.text=Move to Changelist... +action.MoveToChangeList.description=Move selected file(s) to a different changelist + +action.TextComponent.ClearAction.text=Clear text +action.TextComponent.ClearAction.description=Clear text in text components + +action.ConvertProject.text=&Convert Project +action.ConvertProject.description=Convert project to the new format. You will not be able to open the project by earlier versions of IDEA. + +action.ChangesView.Refresh.text=Refresh +action.ChangesView.Refresh.description=Refresh VCS changes +action.ChangesView.NewChangeList.text=New Changelist +action.ChangesView.NewChangeList.description=Create new changelist +action.ChangesView.Commit.text=Comm_it Changes +action.ChangesView.Commit.description=Commit the changes in selected changelist +action.ChangesView.Rollback.text=Rollback +action.ChangesView.Rollback.description=Rollback selected changes +action.ChangesView.RemoveChangeList.text=Delete Changelist +action.ChangesView.RemoveChangeList.description=Remove changelist and move all changes to default +action.ChangesView.SetDefault.text=Set Active Changelist +action.ChangesView.SetDefault.description=Set changelist to which new changes are placed by default +action.ChangesView.Move.text=Move to Another Changelist +action.ChangesView.Move.description=Move selected changes to another changelist +action.ChangesView.Diff.text=Show Diff +action.ChangesView.Diff.description=Show diff for selected change +action.ChangesView.DeleteUnversioned.text=Delete +action.ChangesView.DeleteUnversioned.description= +action.ChangesView.AddUnversioned.text=Add to VCS +action.ChangesView.AddUnversioned.description=Schedule selected files to be added to VCS +action.ChangesView.RemoveDeleted.text=Remove from VCS +action.ChangesView.RemoveDeleted.description=Schedule selected files to be removed from VCS +action.ChangesView.Rename.text=Edit Changelist... +action.ChangesView.Rename.description=Edit name and description of selected changelist +action.ChangesView.CreatePatch.text=Create Patch... +action.ChangesView.CreatePatch.description=Create a patch from the selected changes +action.ChangesView.ApplyPatch.text=Apply Patch... +action.ChangesView.ApplyPatch.description=Apply a patch to the sources of the project +action.ChangesView.Shelve.text=Shelve Changes... +action.ChangesView.Shelve.description=Save changes to an external patch file and remove them from the code +action.ShelvedChanges.Unshelve.text=Unshelve Changes +action.ShelvedChanges.Unshelve.description=Apply saved changes to current codebase +action.ShelvedChanges.Rename.text=Rename... +action.ShelvedChanges.Rename.description=Rename shelved changelist +action.ChangesView.Browse.text=Browse Changes... +action.ChangesView.Browse.description=View history of committed changes +action.ChangesView.Edit.text=Check Out +action.ChangesView.Edit.description=Check out selected files for editing +action.ChangesView.Ignore.text=Ignore... +action.ChangesView.Ignore.description=Do not show selected files as unversioned +action.ChangesView.CreatePatchFromChanges.text=Create Patch... +action.ChangesView.CreatePatchFromChanges.description=Create a patch from the selected changes +action.CommittedChanges.Refresh.text=Refresh +action.CommittedChanges.Refresh.description=Refresh the list of committed changes +action.CommittedChanges.Filter.text=Filter +action.CommittedChanges.Filter.description=Change filtering criteria +action.CommittedChanges.Details.text=Show Details +action.CommittedChanges.Details.description=View complete information about the selected changelist +action.IncomingChanges.Refresh.text=Refresh +action.IncomingChanges.Refresh.description=Refresh the list of incoming changes +action.IncomingChanges.Get.text=Get Changelist +action.IncomingChanges.Get.description=Get latest version of files modified in selected changelist +action.FileChooser.TogglePathShowing.text=Show/Hide path text +action.ChangeFileEncoding.text=Change File Encoding +action.ChangeFileEncoding.description=Change file encoding +action.ConfigureFileDefaultEncoding.text=Configure Default Encoding... +action.ConfigureFileDefaultEncoding.description=Configure default encoding for files, files in directories or in the whole project +action.ChangeFileEncodingGroupAction.text=Change File Encoding +action.ChangeFileEncodingGroupAction.description=Change File Encoding + +action.AddFrameworkSupport.text=Add Framework Support... + +action.Diff.FocusOppositePane.text=Select opposite pane in diff view +action.Diff.PrevChange.text=Compare Previous File +action.Diff.NextChange.text=Compare Next File + +action.FileChooser.ShowHiddens.text=Show Hidden Files and Directories +action.FileChooser.ShowHiddens.description=Show hidden files and directories +action.FileChooser.Refresh.text=Refresh +action.FileChooser.Refresh.description=Refresh the file system tree +action.FileChooser.Delete.text=Delete... +action.FileChooser.Delete.text.description=Delete +action.FileChooser.NewFolder.text=New Folder... +action.FileChooser.NewFolder.description=Create new folder +action.FileChooser.NewFile.text=New File... +action.FileChooser.NewFile.description=Create new file +action.FileChooser.GotoHome.text=Home +action.FileChooser.GotoHome.description=Go to home directory +action.FileChooser.GotoProject.text=Project +action.FileChooser.GotoProject.description=Go to project directory +action.FileChooser.GotoModule.text=Module +action.FileChooser.GotoModule.description=Go to module directory + +action.WelcomeScreen.DevelopPlugins.text=Plugin Development +action.WelcomeScreen.ReadHelp.text=Read Help +action.WelcomeScreen.GetFromVcs.text=Check out from Version Control +action.WelcomeScreen.GetFromVcs.description=You can check out an entire project from a Version Control System. Click the icon or link to select your VCS. +action.WelcomeScreen.ReopenRecent.text=Reopen Recent Project +action.WelcomeScreen.ReopenRecent.description=You can open one of the most recent projects you were working with. Click the icon or link to select a project from the list. +action.WelcomeScreen.OpenProject.text=Open Project +action.WelcomeScreen.CreateNewProject.text=Create New Project +action.WelcomeScreen.CreateNewProject.description=Start the \"New Project\" Wizard that will guide you through the steps necessary for creating a new or import from existent project. + +action.CommittedChanges.Revert.text=Revert Changes +action.CommittedChanges.Revert.description=Apply the reverse of the selected changes to the working copy + +action.OpenDirectoryProject.text=Open Directory... +action.OpenDirectoryProject.description=Start editing code in the specified directory +action.WelcomeScreen.OpenDirectoryProject.text=Open Directory +action.WelcomeScreen.OpenDirectoryProject.description=Start editing code in the specified directory +action.WelcomeScreen.CreateDirectoryProject.text=Create New Project +action.WelcomeScreen.CreateDirectoryProject.description=Create a new project +action.NewDirectoryProject.text=New Project... +action.NewDirectoryProject.description=Create a new project + +action.SliceBackward.text=Analyze Dataflow _to here +action.SliceBackward.description=Search for values which can be assigned to selected expression + +action.SliceForward.text=Analyze Dataflow _from here +action.SliceSliceForward.description=Search for values escaped from this location + +action.ShelvedChanges.Diff.text=Show Diff +action.ShelvedChanges.Diff.description=Show diff for selected change + +action.ChangeTemplateDataLanguage.text=Change Template Data Language + +group.FileOpenGroup.text=File Open Actions +group.FileOtherSettingsGroup.text=Other Settings +group.FileMainSettingsGroup.text=Settings Actions +group.FilSettingsGroup.text=Settings Actions +group.ExportImportGroup.text=Export/Import Actions +group.PrintExportGroup.text=Print/Export Actions +group.EditSelectGroup.text=Editor Select Actions +group.EditCreateDeleteGroup.text=Editor Create/Delete Actions +group.CutCopyPasteGroup.text=Cut/Copy/Paste Actions +group.EditSelectWordGroup.text=Select Word Actions +group.EditBookmarksGroup.text=Bookmark Actions +group.OpenProjectGroup.text=Open Project Actions +group.EditSmartGroup.text=Edit Code Actions +group.ProjectViewGroup.text=Project View Actions +group.CodeEditorViewGroup.text=Code View Actions +group.CodeEditorBaseGroup.text=Code Editor View Actions +group.HierarchyGroup.text=Hierarchy Actions +group.DebugMainMenu.text=Debugger Actions +group.ToggleBreakpointAction.text=Breakpoint Actions +group.CommentGroup.text=Comment Actions +group.CodeFormatGroup.text=Code Formatting Actions +group.GoToTargetEx.text=Goto by Name Actions +group.GoToErrorGroup.text=Goto Error/Bookmark Actions +group.GoToMenuEx.text=Method Navigation Actions +group.GoToCodeGroup.text=Goto by Reference Actions +group.GoToChangeMarkerGroup.text=Change Navigation Actions +group.CoverageMenu.text=Code Coverage Actions +group.ToolsXmlGroup.text=XML Actions +group.XmlGenerateToolsGroup.text=XML Generate Actions +group.EditorPopupMenuDebug.text=Debug Actions +group.VersionControlsGroup.text=VCS/LVCS Actions +group.EditorPopupMenu.Run.text=Compile/Run Actions +group.ProjectViewCompileGroup.text=Compile/Debug Actions +group.ProjectViewAnalysisGroup.text=Analysis Actions +action.PerformanceReport.text=Submit Performance Report +group.MacrosGroup.text=Macro Actions +group.PairFileActions.text=Actions on Pairs of Files +group.RefactoringMenu1.text=Refactoring Actions (1) +group.RefactoringMenu2.text=Refactoring Actions (2) +group.RefactoringMenu3.text=Refactoring Actions (3) +group.ToolbarFindGroup.text=Toolbar Find Actions +group.ToolbarRunGroup.text=Toolbar Run Actions +group.ToolbarMakeGroup.text=Toolbar Make Actions +group.ChangeFileEncodingGroup.text=File Encoding Actions +group.EditorPopupMenu1.text=Editor Popup Menu Actions (1) +group.EditorLangPopupMenu.text=Editor Popup Menu Actions (2) +group.CloseEditorsGroup.text=Editor Close Actions +group.EditorTabPopupMenuEx.text=Editor Tab Popup Menu Actions (1) +group.ProjectViewPopupMenuModifyGroup.text=Project View Popup Menu Modify Group +group.ProjectViewPopupMenuRunGroup.text=Project View Popup Menu Run Group +group.ProjectViewPopupMenuSettingsGroup.text=Project View Popup Menu Settings Group +group.ProjectViewPopupMenuRefactoringGroup.text = Project View Popup Refactoring Group +group.ViewRecentActions.text=View Recent Actions Group +group.NewGroup1.text=New Group (1) +group.NewGroup2.text=New Group (2) +action.NewModuleInGroup.text=Module +action.WrapReturnValue.text=Wr_ap Return Value... +action.WrapReturnValue.description=Wrap return value of the specified method with object +action.ReplaceConstructorWithBuilder.text=Replace Constructor with Builder... +action.ReplaceConstructorWithBuilder.description=Replace Constructor with Builder +action.WelcomeScreen.ShowTips.text=Tips of the Day +action.WelcomeScreen.ShowTips.description=Learn how to increase your coding productivity. +action.ShelvedChanges.ShowHideDeleted.text=Show/Hide Applied Shelved Changes +action.XDebugger.MuteBreakpoints.text=Mute Breakpoints +action.ShelvedChanges.DeleteDeleted.text=Delete Applied Shelved Changes +action.Diff.IgnoreWhitespace.text=Toggle Diff Ignore Whitespace +action.ShelvedChanges.Restore.text=Restore Applied Shelved Change +action.Graph.Current.Node.Dependencies.Filter.text=Show selected nodes with dependencies +group.VcsToobarActions.text=VCS Actions +action.StartupWizard.text=Configure Plugins +action.StartupWizard.description=Run a wizard for selecting the set of enabled plugins +action.PopupHector.text=Popup Per File Highlighting Settings +action.PopupHector.description=Show panel to configure highlighting mode for the current file +group.EditorTabPopupMenu.text=Editor Tab Popup Menu +group.MainMenu.text=Main menu +group.ProjectViewPopupMenu.text=Project View Popup Menu +group.FavoritesViewPopupMenu.text=Favorites View Popup Menu +group.CommanderViewPopupMenu.text=Commander View Popup Menu +group.NavbarPopupMenu.text=Navigation Bar +action.NewInspectionsInLatestVersion.text=What's new in IDEA 9 +action.NewInspectionsInLatestVersion.description=Show inspections new in IDEA 9 +action.RerunFailedTests.text=Rerun Failed Tests +action.RerunFailedTests.description=Run only tests that failed/crashed after last run +group.AnalyzeJavaMenu.text=Analyze Java Menu +group.AnalyzeJavaMenu.description=Analyze Java Menu +action.InvalidateCaches.text=Invalidate Caches... +action.InvalidateCaches.description=Force rebuild of all caches and indices on next startup +group.PlaySavedMacroActions.text=Play Saved Macros +group.PlaySavedMacroActions.description=Play Saved Macros +group.ToolsBasicGroup.text=Tools Basic Group +group.ToolsBasicGroup.description=Tools Basic Group +group.ToolbarNewElement.text=Toolbar New Element Group +action.NewElementToolbarAction.text=Create New File +action.ShowRegistry.text=Registry +action.ExportTestResults.text=Export Test Results... +action.ExportTestResults.description=Export test results to the file +action.TogglePowerSave.text=Power Save Mode +action.TogglePowerSave.description=Power save mode disables background code analysis and other background operations +action.ConvertIndentsToSpaces.text=Convert Indents to Spaces +action.ConvertIndentsToSpaces.description=Convert the indentation in the file or selected block to use spaces instead of tabs +action.ConvertIndentsToTabs.text=Convert Indents to Tabs +action.ConvertIndentsToTabs.description=Convert the indentation in the file or selected block to use tabs instead of spaces +group.Vcs.Import.text=Import into Version Control +group.Vcs.Browse.text=Browse VCS Repository +action.Graph.print.reset=Reset +action.MarkSourceRoot.text=Source Root +action.MarkTestSourceRoot.text=Test Source Root +action.MarkExcludeRoot.text=Excluded +group.MarkRootGroup.text=Mark Directory As diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index 05da502e54e3..cc8ae5dda7d5 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -1,304 +1,304 @@ -no.module.defined.error.message=No module defined -module.does.not.exist.error.message=Module ''{0}'' does not exist -no.jdk.for.module.error.message=No jdk for module ''{0}'' -jdk.is.bad.configured.error.message=''{0}'' is bad configured -class.not.found.in.module.error.message=Class ''{0}'' not found in module ''{1}'' -package.not.found.error.message=Package ''{0}'' not found -jdk.not.configured.error.message=Jdk ''{0}'' not configured -project.has.no.jdk.error.message=Project has no JDK -some.modules.has.circular.dependency.error.message=Some modules has circular dependency. -project.has.no.jdk.configured.error.message=Project has no JDK configured. -run.configuration.stop.action.name=Stop -warning.common.title=Warning -run.configuration.error.dialog.title=Run Configuration Error -no.jdk.specified..error.message=No JDK specified -home.directory.not.specified.for.jdk.error.message=Home directory is not specified for JDK -run.configuration.pause.output.action.name=Pause Output -main.class.is.not.specified.error.message=Main class is not specified -close.tab.action.name=Close -run.configuration.show.command.line.action.name=Show command line - -#--- -create.run.configuration.action.name=Create Run Configuration -create.run.configuration.for.item.action.name=Create {0} -create.run.configuration.for.item.dialog.title=Create Run/Debug Configuration: {0} -edit.configuration.action=&Edit Configurations -save.temporary.run.configuration.action.name=&Save ''{0}'' Configuration -choose.run.configuration.action.description=Open run/debug configurations dropdown - -#action - run. debug,profile etc -perform.action.with.context.configuration.action.name={0} context configuration - -error.common.title=Error -error.running.configuration.with.error.error.message=Error running {0}:
{1} - -select.applet.policy.file.dialog.title=Select applet policy file -choose.html.file.dialog.title=Choose HTML File -html.file.not.specified.error.message=Html file not specified -jre.not.valid.error.message=''{0}'' is not valid JRE home - -applet.configuration.description=Applet configuration -applet.configuration.name=Applet - -application.configuration.description=Application configuration -application.configuration.name=Application - -run.configuration.norunner.selected.label=No runner selected -run.configuration.configuration.tab.title=Configuration -run.configuration.startup.connection.rab.title=Startup/Connection - -add.new.run.configuration.acrtion.name=Add New Configuration -add.new.run.configuration.action.name=Add New ''{0}'' Configuration - -remove.run.configuration.action.name=Remove Configuration - -run.configuration.edit.default.configuration.settings.button=Edit De&faults -default.settings.editor.dialog.title=Default Settings -clear.all.from.console.action.name=Clear All -copy.selected.content.action.name=Copy Selected Content -copy.content.action.name=Copy Content -run.debug.dialog.title=Run/Debug Configurations -run.configuration.display.settings.checkbox=Display settings &before launching -run.configurable.display.name=Run -apply.action.name=&Apply -invalid.data.dialog.title=Invalid Data -template.settings.configurable.display.name=Template Settings -default.run.configuration.name= - -process.is.running.dialog.title=Process ''{0}'' is running -button.disconnect=Disconnect -disconnect.process.confirmation.text=Disconnect from the process ''{0}''? -terminate.after.disconnect.checkbox=Terminate the process after disconnect -copy.configuration.action.name=Copy Configuration -junit.configuration.display.name=JUnit -junit.configuration.description=JUnit test configuration -no.junit.error.message=No junit.jar -no.junit.in.scope.error.message=No junit.jar: {0} -junit.not.found.in.module.error.message=JUnit not found in module ''{0}'' -cannot.browse.test.inheritors.dialog.title=Can't Browse TestCase Inheritors -seaching.test.progress.title=Searching For Tests... -configuration.not.speficied.message=Configuration test type not specified -test.in.scope.presentable.text=Tests in ''{0}'' -all.tests.scope.presentable.text=All Tests -module.does.not.exists=Module ''{0}'' does not exist in project ''{1}'' -select.working.directory.message=Select working directory -set.class.name.message=Set class name first -cannot.browse.method.dialog.title=Cannot Browse Methods -class.does.not.exists.error.message=Class {0} does not exist -choose.package.dialog.title=Choose Package -choose.test.class.dialog.title=Choose Test Class -choose.test.method.dialog.title=Choose Test Method -test.cases.count.message={0} test case(s) -diff.content.expected.title=Expected -diff.content.expected.for.file.title=Expected : -diff.content.actual.title=Actual -junit.actual.text.label=Actual : -junit.click.to.see.diff.link= -output.tab.title=Output -statistics.tab.title=Statistics -test.not.started.progress.text=Tests were not started -starting.jvm.progress.text=Starting JVM... -instantiating.tests.progress.text=Instantiating tests... -next.faled.test.action.name=Next Failed Test -prev.faled.test.action.name=Previous Failed Test -junit.runing.info.memory.available.kb.message={0} kb. -junit.runing.info.memory.available.mb.message={0} Mb. -junit.runing.info.time.sec.message={0} s -junit.runing.info.total.label=Total: -junit.runing.info.starting.label=Starting... -junit.runing.info.running.label=Running -junit.runing.info.passed.label=Passed -junit.runing.info.terminated.label=Terminated -junit.runing.info.assertion.tree.node=Assertion -junit.runing.info.error.tree.node=Error -junit.runing.info.ignored.label=Ignored -junit.runing.info.ignored.console.message=Test ''{0}.{1}'' ignored -junit.runing.info.left.to.run.count.tree.node=Left: {0} -junit.runing.info.failed.count.message=F:{0} -junit.runing.info.errors.count.message=E:{0} -junit.runing.info.passed.count.message=P:{0} -junit.runing.info.ignored.count.message=I:{0} - -junit.runing.info.status.completed.from.total.failed={0} of {1} Failed: {2} -junit.runing.info.status.completed.from.total={0} of {1} -junit.runing.info.status.running.number.with.name=Running: {0} {1} -junit.runing.info.failed.to.start.error.message=Failed to start -junit.runing.info.tests.failed.label=Tests Failed -junit.runing.info.tests.passed.label=Tests Passed -tests.passed.with.warnings.message=Tests passed (with warnings) -junit.run.hide.passed.action.name=Hide Passed -junit.run.hide.passed.action.description=Hide passed tests -junit.runing.info.track.test.action.name=Track Running Test -junit.runing.info.track.test.action.description=Select currently running test in tree -junit.runing.info.collapse.test.action.name=Collapse all test suites -junit.runing.info.expand.test.action.name=Expand all test suites -junit.runing.info.select.first.failed.action.name=Select First Failed Test When Finished -junit.runing.info.scroll.to.stacktrace.action.name=Scroll to Stacktrace -junit.runing.info.scroll.to.stacktrace.action.description=Scroll console to beginning of assertion or exception stacktrace -junit.runing.info.open.source.at.exception.action.name=Open Source at Exception -junit.runing.info.open.source.at.exception.action.description=Go to line which caused exception when opening test source -junit.all.tests.passed.label=All Tests Passed -junit.tests.in.progress.label=Tests in Progress -junit.auto.scroll.to.source.action.name=Auto Scroll to Source -junit.open.text.in.editor.action.name=Open selected test in editor - -run.configuration.java.vm.parameters.label=&VM parameters: -run.configuration.program.parameters=Program pa&rameters: -run.configuration.working.directory.label=&Working directory: -run.configuration.use.alternate.jre.checkbox=Use alternative &JRE: -run.configuration.select.alternate.jre.label=Select Alternative JRE -run.configuration.select.jre.dir.label=Select directory with JRE to run with -run.configuration.arguments.help.panel.copy.action.name=Copy -terminating.process.progress.title=Terminating ''{0}'' -waiting.for.vm.detach.progress.text=Waiting for process detach -restart.error.message.title=Restart Error -rerun.configuration.action.name=Rerun {0} -run.configuration.dump.threads.action.name=Dump Threads -run.configuration.exit.action.name=Exit -run.error.message.title=Run Error -default.runner.start.action.text=R&un -remote.debug.configuration.description=Remote debug configuration -remote.debug.configuration.display.name=Remote - -applet.configuration.url.label=&URL -applet.configuration.applet.class.border=Applet Class -applet.configuration.applet.parameters.label=Applet Parameters -applet.configuration.applet.class.label=Applet &class: -button.remove=&Remove -button.add=&Add -applet.configuration.height.label= &Height: -applet.configuration.width.label= &Width: -applet.configuration.url.html.file.label=URL/HTML &file: -applet.configuration.url.border=URL -applet.configuration.use.classpath.and.jdk.of.module.label=Use classpath and JDK of m&odule: -applet.configuration.vm.parameters.for.appletviewer.label=&VM parameters for appletviewer: -applet.configuration.policy.file.label=&Policy file: -applet.configuration.parameter.name.column=Name -applet.configuration.parameter.value.column=Value -class.not.specified.error.message=Class not specified. -failed.to.generate.wrapper.error.message=Failed to generate temporary html wrapper for applet class - -application.configuration.use.classpath.and.jdk.of.module.label=Use classpath and JDK of m&odule: -application.configuration.main.class.label=Main &class: -jre.path.is.not.valid.jre.home.error.mesage=''{0}'' is not valid JRE home -main.method.not.found.in.class.error.message=Main method not found in class {0} -no.user.process.input.error.message=No process input -fix.run.configuration.problem.button=Fix -class.isnt.test.class.error.message={0} isn''t test class -class.isnt.inheritor.of.testcase.error.message={0} isn''t inheritor of TestCase -junit.jar.not.found.in.module.class.path.error.message=junit.jar not found in module ''{0}'' class path. -method.name.not.specified.error.message=Method name not specified -test.method.doesnt.exist.error.message=Test method ''{0}'' doesn''t exist -no.tests.found.in.package.error.message=No tests found in the package ''{0}'' -package.does.not.exist.error.message=Package ''{0}'' does not exist -choose.main.class.dialog.title=Choose Main Class -choose.applet.class.dialog.title=Choose Applet Class -junit.configuration.test.runner.parameters.label=Test runner pa&rameters: -junit.configuration.use.classpath.and.jdk.of.module.label=Use classpath and JDK of m&odule: -junit.configuration.test.border=Test -junit.configuration.across.module.dependencies.radio=Across modu&le dependencies -junit.configuration.in.single.module.radio=In s&ingle module -junit.configuration.in.whole.project.radio=In &whole project -junit.configuration.search.for.tests.label=Search for tests: -junit.configuration.package.label=Packa&ge: -junit.configuration.method.label=Mðod: -junit.configuration.class.label=&Class: -junit.configuration.configure.junit.test.label=Test: -junit.configuration.test.method.radio=Test Method -junit.configuration.test.class.radio=Test Class -junit.configuration.class.radio=C&lass -junit.configuration.method.radio=Me&thod -jnit.configuration.all.tests.in.package.radio=All Tests in Package -junit.configuration.all.in.package.radio=All in &Package -no.jdk.specified.for.module.warning.text=No JDK specified for module ''{0}'' -module.not.specified.error.text=Module not specified -module.doesn.t.exist.in.project.error.text=Module ''{0}'' doesn''t exist in project -run.configuration.unnamed.name.prefix=Unnamed -no.applet.class.specified.error.message=No applet class specified -no.main.class.specified.error.text=No main class specified -action.name.save.configuration=Save Configuration -#2 - configuration type description -empty.run.configuration.panel.text.label=

Press the \\  button \ - to create a new {3} based on default settings. -default.package.presentable.name= -default.package.configuration.name=default package -no.test.class.specified.error.text=No test class specified -edit.run.configuration.run.configuration.name.label=&Name: -default.junit.configuration.name= -strings.equal.failed.dialog.title=Comparison failure -junit.runing.info.test.column.name=Test -junit.runing.info.time.elapsed.column.name=Time elapsed -junit.runing.info.usage.delta.column.name=Usage Delta -junit.runing.info.usage.before.column.name=Usage Before -junit.runing.info.usage.after.column.name=Usage After -junit.runing.info.results.column.name=Results -junit.runing.info.loading.tree.node.text=loading -remote.configuration.settings.border=Settings -remote.configuration.transport.label=Transport: -remote.configuration.listen.radio=Listen -remote.configuration.attach.radio=Attach -remote.configuration.debugger.mode.label=Debugger mode: -remote.configuration.shared.memory.radio=Shared memory -remote.configuration.socket.radio=Socket -remote.configuration.shared.memory.address.label=Shared memory address: -remote.configuration.port.label=Port: -remote.configuration.host.label=Host: -remote.configuration.remote.debugging.allows.you.to.connect.idea.to.a.running.jvm.label=Remote debugging allows you to connect IDEA to a running JVM. -standard.runner.description=Run selected configuration -environment.variables.helper.use.arguments.label=Use the following command line arguments for running remote JVM (you may copy and paste them) -environment.variables.helper.use.arguments.jdk13.label=If the application runs on JDK 1.3.x or earlier, use following arguments -select.run.configuration.for.item.action.name=Select {0} -save.run.configuration.for.item.action.name=Save {0} -junit.runing.info.status.done.count=Done: {0} -junit.runing.info.status.terminated.count=Terminated: {0} -junit.runing.info.tests.in.progress.done.tree.node=Tests in Progress: Done -junit.runing.info.tests.in.progress.terminated.tre.node=Tests in Progress: Terminated -delete.confirmation.dialog.title=Delete Confirmation -move.up.action.name=Move Up -move.down.action.name=Move Down -memory.available.message={0} Kb - -#code coverage -enable.coverage.with.emma=Record code &coverage information -merge.coverage.data=&Merge gathered coverage with suite chosen below -record.coverage.filters.title=Packages and classes to record coverage data -coverage.tab.title=Code Coverage -show.swing.inspector=&Enable capturing form snapshots -show.swing.inspector.disabled=&Enable capturing form snapshots (requires JRE 5.0 or higher) -before.run.property.make=Make -run.configuration.store.place.option=&Share configuration -run.configuration.default.type.description=configuration - -#GeneralCommandLine -run.configuration.error.no.jdk.specified=No JDK specified -run.configuration.cannot.find.vm.executable=Cannot find VM executable - -logs.tab.title=Logs -before.launch.panel.title=Before launch -action.name.save.as.configuration=Save As -default.junit.config.name.all.in.module=All in {0} -default.junit.config.name.all.in.package.in.module={0} in {1} -environment.variables.dialog.title=Environment Variables -environment.variables.component.title=&Environment Variables -down.the.stack.trace=Down the stack trace -up.the.stack.trace=Up the stack trace -configuration.action.chooser.title=Choose configuration type to run -env.vars.checkbox.title=&Include parent environment variables - -before.launch.compile.step=Make -execute.before.run.debug.dialog.title=Execute {0} Before Run/Debug - -export.test.results.filename=Test Results - {0} -export.test.results.succeeded=Test results exported successfully to {0} -export.test.results.failed=Test results export failed: {0} -export.test.results.custom.template.chooser.title=Choose Custom Template -export.test.results.output.folder.chooser.title=Choose Output Folder -export.test.results.custom.template.path.empty=User-defined tempate path is empty -export.test.results.custom.template.not.found=User-defined tempate file ''{0}'' is not found -export.test.results.task.name=Exporting test results -export.test.results.open.editor=O&pen exported file in editor -export.test.results.open.browser=O&pen exported file in browser -export.test.results.dialog.title=Export Test Results -export.test.results.output.path.empty=Output path is empty -export.test.results.output.filename.empty=Output file name is empty -configuration.edit.before.run=Show settings +no.module.defined.error.message=No module defined +module.does.not.exist.error.message=Module ''{0}'' does not exist +no.jdk.for.module.error.message=No jdk for module ''{0}'' +jdk.is.bad.configured.error.message=''{0}'' is bad configured +class.not.found.in.module.error.message=Class ''{0}'' not found in module ''{1}'' +package.not.found.error.message=Package ''{0}'' not found +jdk.not.configured.error.message=Jdk ''{0}'' not configured +project.has.no.jdk.error.message=Project has no JDK +some.modules.has.circular.dependency.error.message=Some modules has circular dependency. +project.has.no.jdk.configured.error.message=Project has no JDK configured. +run.configuration.stop.action.name=Stop +warning.common.title=Warning +run.configuration.error.dialog.title=Run Configuration Error +no.jdk.specified..error.message=No JDK specified +home.directory.not.specified.for.jdk.error.message=Home directory is not specified for JDK +run.configuration.pause.output.action.name=Pause Output +main.class.is.not.specified.error.message=Main class is not specified +close.tab.action.name=Close +run.configuration.show.command.line.action.name=Show command line + +#--- +create.run.configuration.action.name=Create Run Configuration +create.run.configuration.for.item.action.name=Create {0} +create.run.configuration.for.item.dialog.title=Create Run/Debug Configuration: {0} +edit.configuration.action=&Edit Configurations +save.temporary.run.configuration.action.name=&Save ''{0}'' Configuration +choose.run.configuration.action.description=Open run/debug configurations dropdown + +#action - run. debug,profile etc +perform.action.with.context.configuration.action.name={0} context configuration + +error.common.title=Error +error.running.configuration.with.error.error.message=Error running {0}:
{1} + +select.applet.policy.file.dialog.title=Select applet policy file +choose.html.file.dialog.title=Choose HTML File +html.file.not.specified.error.message=Html file not specified +jre.not.valid.error.message=''{0}'' is not valid JRE home + +applet.configuration.description=Applet configuration +applet.configuration.name=Applet + +application.configuration.description=Application configuration +application.configuration.name=Application + +run.configuration.norunner.selected.label=No runner selected +run.configuration.configuration.tab.title=Configuration +run.configuration.startup.connection.rab.title=Startup/Connection + +add.new.run.configuration.acrtion.name=Add New Configuration +add.new.run.configuration.action.name=Add New ''{0}'' Configuration + +remove.run.configuration.action.name=Remove Configuration + +run.configuration.edit.default.configuration.settings.button=Edit De&faults +default.settings.editor.dialog.title=Default Settings +clear.all.from.console.action.name=Clear All +copy.selected.content.action.name=Copy Selected Content +copy.content.action.name=Copy Content +run.debug.dialog.title=Run/Debug Configurations +run.configuration.display.settings.checkbox=Display settings &before launching +run.configurable.display.name=Run +apply.action.name=&Apply +invalid.data.dialog.title=Invalid Data +template.settings.configurable.display.name=Template Settings +default.run.configuration.name= + +process.is.running.dialog.title=Process ''{0}'' is running +button.disconnect=Disconnect +disconnect.process.confirmation.text=Disconnect from the process ''{0}''? +terminate.after.disconnect.checkbox=Terminate the process after disconnect +copy.configuration.action.name=Copy Configuration +junit.configuration.display.name=JUnit +junit.configuration.description=JUnit test configuration +no.junit.error.message=No junit.jar +no.junit.in.scope.error.message=No junit.jar: {0} +junit.not.found.in.module.error.message=JUnit not found in module ''{0}'' +cannot.browse.test.inheritors.dialog.title=Can't Browse TestCase Inheritors +seaching.test.progress.title=Searching For Tests... +configuration.not.speficied.message=Configuration test type not specified +test.in.scope.presentable.text=Tests in ''{0}'' +all.tests.scope.presentable.text=All Tests +module.does.not.exists=Module ''{0}'' does not exist in project ''{1}'' +select.working.directory.message=Select working directory +set.class.name.message=Set class name first +cannot.browse.method.dialog.title=Cannot Browse Methods +class.does.not.exists.error.message=Class {0} does not exist +choose.package.dialog.title=Choose Package +choose.test.class.dialog.title=Choose Test Class +choose.test.method.dialog.title=Choose Test Method +test.cases.count.message={0} test case(s) +diff.content.expected.title=Expected +diff.content.expected.for.file.title=Expected : +diff.content.actual.title=Actual +junit.actual.text.label=Actual : +junit.click.to.see.diff.link= +output.tab.title=Output +statistics.tab.title=Statistics +test.not.started.progress.text=Tests were not started +starting.jvm.progress.text=Starting JVM... +instantiating.tests.progress.text=Instantiating tests... +next.faled.test.action.name=Next Failed Test +prev.faled.test.action.name=Previous Failed Test +junit.runing.info.memory.available.kb.message={0} kb. +junit.runing.info.memory.available.mb.message={0} Mb. +junit.runing.info.time.sec.message={0} s +junit.runing.info.total.label=Total: +junit.runing.info.starting.label=Starting... +junit.runing.info.running.label=Running +junit.runing.info.passed.label=Passed +junit.runing.info.terminated.label=Terminated +junit.runing.info.assertion.tree.node=Assertion +junit.runing.info.error.tree.node=Error +junit.runing.info.ignored.label=Ignored +junit.runing.info.ignored.console.message=Test ''{0}.{1}'' ignored +junit.runing.info.left.to.run.count.tree.node=Left: {0} +junit.runing.info.failed.count.message=F:{0} +junit.runing.info.errors.count.message=E:{0} +junit.runing.info.passed.count.message=P:{0} +junit.runing.info.ignored.count.message=I:{0} + +junit.runing.info.status.completed.from.total.failed={0} of {1} Failed: {2} +junit.runing.info.status.completed.from.total={0} of {1} +junit.runing.info.status.running.number.with.name=Running: {0} {1} +junit.runing.info.failed.to.start.error.message=Failed to start +junit.runing.info.tests.failed.label=Tests Failed +junit.runing.info.tests.passed.label=Tests Passed +tests.passed.with.warnings.message=Tests passed (with warnings) +junit.run.hide.passed.action.name=Hide Passed +junit.run.hide.passed.action.description=Hide passed tests +junit.runing.info.track.test.action.name=Track Running Test +junit.runing.info.track.test.action.description=Select currently running test in tree +junit.runing.info.collapse.test.action.name=Collapse all test suites +junit.runing.info.expand.test.action.name=Expand all test suites +junit.runing.info.select.first.failed.action.name=Select First Failed Test When Finished +junit.runing.info.scroll.to.stacktrace.action.name=Scroll to Stacktrace +junit.runing.info.scroll.to.stacktrace.action.description=Scroll console to beginning of assertion or exception stacktrace +junit.runing.info.open.source.at.exception.action.name=Open Source at Exception +junit.runing.info.open.source.at.exception.action.description=Go to line which caused exception when opening test source +junit.all.tests.passed.label=All Tests Passed +junit.tests.in.progress.label=Tests in Progress +junit.auto.scroll.to.source.action.name=Auto Scroll to Source +junit.open.text.in.editor.action.name=Open selected test in editor + +run.configuration.java.vm.parameters.label=&VM parameters: +run.configuration.program.parameters=Program pa&rameters: +run.configuration.working.directory.label=&Working directory: +run.configuration.use.alternate.jre.checkbox=Use alternative &JRE: +run.configuration.select.alternate.jre.label=Select Alternative JRE +run.configuration.select.jre.dir.label=Select directory with JRE to run with +run.configuration.arguments.help.panel.copy.action.name=Copy +terminating.process.progress.title=Terminating ''{0}'' +waiting.for.vm.detach.progress.text=Waiting for process detach +restart.error.message.title=Restart Error +rerun.configuration.action.name=Rerun {0} +run.configuration.dump.threads.action.name=Dump Threads +run.configuration.exit.action.name=Exit +run.error.message.title=Run Error +default.runner.start.action.text=R&un +remote.debug.configuration.description=Remote debug configuration +remote.debug.configuration.display.name=Remote + +applet.configuration.url.label=&URL +applet.configuration.applet.class.border=Applet Class +applet.configuration.applet.parameters.label=Applet Parameters +applet.configuration.applet.class.label=Applet &class: +button.remove=&Remove +button.add=&Add +applet.configuration.height.label= &Height: +applet.configuration.width.label= &Width: +applet.configuration.url.html.file.label=URL/HTML &file: +applet.configuration.url.border=URL +applet.configuration.use.classpath.and.jdk.of.module.label=Use classpath and JDK of m&odule: +applet.configuration.vm.parameters.for.appletviewer.label=&VM parameters for appletviewer: +applet.configuration.policy.file.label=&Policy file: +applet.configuration.parameter.name.column=Name +applet.configuration.parameter.value.column=Value +class.not.specified.error.message=Class not specified. +failed.to.generate.wrapper.error.message=Failed to generate temporary html wrapper for applet class + +application.configuration.use.classpath.and.jdk.of.module.label=Use classpath and JDK of m&odule: +application.configuration.main.class.label=Main &class: +jre.path.is.not.valid.jre.home.error.mesage=''{0}'' is not valid JRE home +main.method.not.found.in.class.error.message=Main method not found in class {0} +no.user.process.input.error.message=No process input +fix.run.configuration.problem.button=Fix +class.isnt.test.class.error.message={0} isn''t test class +class.isnt.inheritor.of.testcase.error.message={0} isn''t inheritor of TestCase +junit.jar.not.found.in.module.class.path.error.message=junit.jar not found in module ''{0}'' class path. +method.name.not.specified.error.message=Method name not specified +test.method.doesnt.exist.error.message=Test method ''{0}'' doesn''t exist +no.tests.found.in.package.error.message=No tests found in the package ''{0}'' +package.does.not.exist.error.message=Package ''{0}'' does not exist +choose.main.class.dialog.title=Choose Main Class +choose.applet.class.dialog.title=Choose Applet Class +junit.configuration.test.runner.parameters.label=Test runner pa&rameters: +junit.configuration.use.classpath.and.jdk.of.module.label=Use classpath and JDK of m&odule: +junit.configuration.test.border=Test +junit.configuration.across.module.dependencies.radio=Across modu&le dependencies +junit.configuration.in.single.module.radio=In s&ingle module +junit.configuration.in.whole.project.radio=In &whole project +junit.configuration.search.for.tests.label=Search for tests: +junit.configuration.package.label=Packa&ge: +junit.configuration.method.label=Mðod: +junit.configuration.class.label=&Class: +junit.configuration.configure.junit.test.label=Test: +junit.configuration.test.method.radio=Test Method +junit.configuration.test.class.radio=Test Class +junit.configuration.class.radio=C&lass +junit.configuration.method.radio=Me&thod +jnit.configuration.all.tests.in.package.radio=All Tests in Package +junit.configuration.all.in.package.radio=All in &Package +no.jdk.specified.for.module.warning.text=No JDK specified for module ''{0}'' +module.not.specified.error.text=Module not specified +module.doesn.t.exist.in.project.error.text=Module ''{0}'' doesn''t exist in project +run.configuration.unnamed.name.prefix=Unnamed +no.applet.class.specified.error.message=No applet class specified +no.main.class.specified.error.text=No main class specified +action.name.save.configuration=Save Configuration +#2 - configuration type description +empty.run.configuration.panel.text.label=

Press the \\  button \ + to create a new {3} based on default settings. +default.package.presentable.name= +default.package.configuration.name=default package +no.test.class.specified.error.text=No test class specified +edit.run.configuration.run.configuration.name.label=&Name: +default.junit.configuration.name= +strings.equal.failed.dialog.title=Comparison failure +junit.runing.info.test.column.name=Test +junit.runing.info.time.elapsed.column.name=Time elapsed +junit.runing.info.usage.delta.column.name=Usage Delta +junit.runing.info.usage.before.column.name=Usage Before +junit.runing.info.usage.after.column.name=Usage After +junit.runing.info.results.column.name=Results +junit.runing.info.loading.tree.node.text=loading +remote.configuration.settings.border=Settings +remote.configuration.transport.label=Transport: +remote.configuration.listen.radio=Listen +remote.configuration.attach.radio=Attach +remote.configuration.debugger.mode.label=Debugger mode: +remote.configuration.shared.memory.radio=Shared memory +remote.configuration.socket.radio=Socket +remote.configuration.shared.memory.address.label=Shared memory address: +remote.configuration.port.label=Port: +remote.configuration.host.label=Host: +remote.configuration.remote.debugging.allows.you.to.connect.idea.to.a.running.jvm.label=Remote debugging allows you to connect IDEA to a running JVM. +standard.runner.description=Run selected configuration +environment.variables.helper.use.arguments.label=Use the following command line arguments for running remote JVM (you may copy and paste them) +environment.variables.helper.use.arguments.jdk13.label=If the application runs on JDK 1.3.x or earlier, use following arguments +select.run.configuration.for.item.action.name=Select {0} +save.run.configuration.for.item.action.name=Save {0} +junit.runing.info.status.done.count=Done: {0} +junit.runing.info.status.terminated.count=Terminated: {0} +junit.runing.info.tests.in.progress.done.tree.node=Tests in Progress: Done +junit.runing.info.tests.in.progress.terminated.tre.node=Tests in Progress: Terminated +delete.confirmation.dialog.title=Delete Confirmation +move.up.action.name=Move Up +move.down.action.name=Move Down +memory.available.message={0} Kb + +#code coverage +enable.coverage.with.emma=Record code &coverage information +merge.coverage.data=&Merge gathered coverage with suite chosen below +record.coverage.filters.title=Packages and classes to record coverage data +coverage.tab.title=Code Coverage +show.swing.inspector=&Enable capturing form snapshots +show.swing.inspector.disabled=&Enable capturing form snapshots (requires JRE 5.0 or higher) +before.run.property.make=Make +run.configuration.store.place.option=&Share configuration +run.configuration.default.type.description=configuration + +#GeneralCommandLine +run.configuration.error.no.jdk.specified=No JDK specified +run.configuration.cannot.find.vm.executable=Cannot find VM executable + +logs.tab.title=Logs +before.launch.panel.title=Before launch +action.name.save.as.configuration=Save As +default.junit.config.name.all.in.module=All in {0} +default.junit.config.name.all.in.package.in.module={0} in {1} +environment.variables.dialog.title=Environment Variables +environment.variables.component.title=&Environment Variables +down.the.stack.trace=Down the stack trace +up.the.stack.trace=Up the stack trace +configuration.action.chooser.title=Choose configuration type to run +env.vars.checkbox.title=&Include parent environment variables + +before.launch.compile.step=Make +execute.before.run.debug.dialog.title=Execute {0} Before Run/Debug + +export.test.results.filename=Test Results - {0} +export.test.results.succeeded=Test results exported successfully to {0} +export.test.results.failed=Test results export failed: {0} +export.test.results.custom.template.chooser.title=Choose Custom Template +export.test.results.output.folder.chooser.title=Choose Output Folder +export.test.results.custom.template.path.empty=User-defined tempate path is empty +export.test.results.custom.template.not.found=User-defined tempate file ''{0}'' is not found +export.test.results.task.name=Exporting test results +export.test.results.open.editor=O&pen exported file in editor +export.test.results.open.browser=O&pen exported file in browser +export.test.results.dialog.title=Export Test Results +export.test.results.output.path.empty=Output path is empty +export.test.results.output.filename.empty=Output file name is empty +configuration.edit.before.run=Show settings diff --git a/platform/platform-resources-en/src/messages/UIBundle.properties b/platform/platform-resources-en/src/messages/UIBundle.properties index 64ba7df8b521..2557467da05b 100644 --- a/platform/platform-resources-en/src/messages/UIBundle.properties +++ b/platform/platform-resources-en/src/messages/UIBundle.properties @@ -1,166 +1,166 @@ -color.panel.select.color.dialog.description=Select a color -color.panel.right.click.to.customize.tooltip.suffix=Right-click to customize -insert.file.path.to.text.action.name=Insert Path -order.entries.panel.export.column.name=Export -tree.view.expand.all.action.name=Expand All -tree.view.collapse.all.action.name=Collapse All -component.with.browse.button.browse.button.tooltip.text=Click or press Alt-Enter -there.is.no.help.for.this.dialog.error.message=There is no help for this dialog -no.help.available.dialog.title=No Help Available -invalid.user.input.dialog.title=Input Error - -splitter.down.tooltip.text=Down -splitter.right.tooltip.text=Right -splitter.center.tooltip.text=Center -splitter.up.tooltip.text=Up -splitter.left.tooltip.text=Left -autoscroll.from.source.action.name=Autoscroll from Source -autoscroll.from.source.action.description=Autoscroll from Source -autoscroll.to.source.action.name=Autoscroll to Source -autoscroll.to.source.action.description=Autoscroll to Source -collapsible.panel.collapsed.state.tooltip.text=Expand Panel -collapsible.panel.expanded.state.tooltip.text=Collapse Panel -idea.blue.metal.theme.name=beg blue -replace.prompt.replace.button=&Replace -replace.prompt.skip.button=&Skip -replace.prompt.all.in.this.file.button=All in This &File -replace.prompt.all.files.action=&All Files -replace.prompt.all.button=&All -replace.propmt.replace.occurrence.label=Do you want to replace this occurrence? -search.popup.search.for.label=Search for: -tabbed.pane.close.all.action.name=Close All -tabbed.pane.close.all.but.this.action.name=Close All But This -tabbed.pane.pin.tab.action.name=Pin Tab -tabbed.pane.pin.tab.action.description=Pin tool window tab -tabbed.pane.close.tab.action.name=Close Tab -tabbed.pane.close.all.tabs.but.this.action.name=Close All Tabs But This -file.is.read.only.message.text=File ''{0} is read-only. -files.are.read.only.message.text=Files {0} are read-only -error.dialog.title=Error -tool.window.name.commander=Commander -tool.window.name.messages=Messages -tool.window.name.project=Project -tool.window.name.structure=Structure -tool.window.name.favorites=Favorites -tool.window.name.ant.build=Ant Build -tool.window.name.debug=Debug -tool.window.name.run=Run -tool.window.name.find=Find -tool.window.name.cvs=CVS -tool.window.name.hierarchy=Hierarchy -tool.window.name.inspection=Inspection -tool.window.name.todo=TODO -tool.window.name.dependency.viewer=Dependency Viewer -tool.window.name.version.control=Version Control -tool.window.name.module.dependencies=Module Dependencies -tool.window.move.to.action.group.name=Move to -tool.window.move.to.top.action.name=Top -tool.window.move.to.left.action.name=Left -tool.window.move.to.bottom.action.name=Bottom -tool.window.move.to.right.action.name=Right -tool.window.hide.action.name=Hide -tool.window.hideSide.action.name=Hide Side -tool.wondow.undock.action.name=Undock -tool.wondow.dock.action.name=Dock -tool.wondow.fix.action.name=Fix -tool.wondow.float.action.name=Float -tool.wondow.pin.action.name=Pin -tool.wondow.unpin.action.name=Unpin - -#0 - total, 1 - used -memory.usage.panel.statistics.message=Allocated heap size: {0}M Used: {1}M -memory.usage.panel.run.garbage.collector.action.name=Run Garbage Collector -memory.usage.panel.run.garbage.collector.action.description=Run Garbage Collector -#0 - used, 1 - total -memory.usage.panel.message.text={0}M of {1}M -go.to.line.command.name=Go to Line -go.to.line.command.double.click=Double-click to go to line -status.bar.insert.status.text=Insert -status.bar.overwrite.status.text=Overwrite -popup.hints.panel.click.to.configure.highlighting.tooltip.text=Click to configure highlighting for this file -popup.hints.panel.click.to.configure.profile.text=Click to configure inspection profiles -read.only.attr.panel.double.click.to.toggle.attr.tooltip.text=Click to toggle the read-only attribute -welcome.screen.get.from.vcs.action.no.vcs.plugins.with.check.out.action.installed.action.name=No VCS plugins with Check-out action installed. -welcome.screen.get.from.vcs.action.checkout.from.list.popup.title=Checkout from -welcome.screen.recent.projects.action.no.recent.projects.to.display.action.name=No recent projects to display. -welcome.screen.quick.start.action.group.name=Quick Start -welcome.screen.documentation.action.group.name=Documentation -welcome.screen.read.help.action.description=Open {0} "Help Topics" in a new window. -welcome.screen.plugin.development.action.description=Get started developing plugins for {0}. -welcome.screen.open.project.action.description=You can open any arbitrary {0} project. Click the icon or link to select the project file from the File Chooser. -welcome.screen.plugins.panel.plugins.label=Plugins -welcome.screen.plugins.panel.my.plugins.label=My Plugins: -welcome.screen.plugins.panel.bundled.plugins.label=Bundled Plugins: -welcome.screen.plugins.panel.no.plugins.currently.installed.message.text=No plugins currently installed. -welcome.screen.plugins.panel.all.bundled.plugins.were.uninstalled.message.text=All bundled plugins were uninstalled. -welcome.screen.plugins.panel.learn.more.tooltip.text=Learn more... -welcome.script.text.not.specified.message=Not specified - -file.chooser.default.title=Select Path -file.chooser.save.dialog.default.title=Select File to Save -file.chooser.save.dialog.confirmation={0} is already exists.\nDo you want to replace it? -file.chooser.save.dialog.confirmation.title=Confirm Save as -file.chooser.select.object.title=Select {0} -delete.dialog.title=Delete -file.chooser.could.not.erase.file.or.folder.error.messabe=Could not erase file or folder: {0} -are.you.sure.you.want.to.delete.selected.folder.confirmation.message=Are you sure you want to delete selected folder? -are.you.sure.you.want.to.delete.selected.file.confirmation.message=Are you sure you want to delete selected file? -are.you.sure.you.want.to.delete.selected.files.and.directories.confirmation.message=Are you sure you want to delete selected files and directories? -are.you.sure.you.want.to.delete.selected.folders.confirmation.message=Are you sure you want to delete selected directories? -are.you.sure.you.want.to.delete.selected.files.and.files.confirmation.message=Are you sure you want to delete selected files? -create.new.folder.enter.new.folder.name.prompt.text=Enter a new folder name: -create.new.folder.folder.name.cannot.be.empty.error.message=Folder name cannot be empty -create.new.folder.could.not.create.folder.error.message=Could not create folder ''{0}'' -new.folder.dialog.title=New Folder -create.new.file.enter.new.file.name.prompt.text=Enter a new file name: -create.new.file.file.name.cannot.be.empty.error.message=File name cannot be empty -create.new.file.could.not.create.file.error.message=Could not create file ''{0}'' -new.file.dialog.title=New File -file.chooser.create.new.folder.command.name=Create New Folder -file.chooser.create.new.file.command.name=Create New File -file.cache.conflict.action=Reload From Disk -file.cache.conflict.message.text=Changes have been made to \"{0}\"\nin memory and on disk. -file.cache.conflict.load.fs.changes.button=&Load File System Changes -file.cache.conflict.keep.memory.changes.button=&Keep Memory Changes -file.cache.conflict.show.difference.button=&Show difference -file.cache.conflict.for.file.dialog.title=File Cache Conflict {0} -file.cache.conflict.dialog.title=File Cache Conflict -file.cache.conflict.diff.content.file.system.content=File system content -file.cache.conflict.diff.content.memory.content=Memory content -file.cache.conflict.save.changes.button=Save memory content -cannot.save.file.with.error.error.message=Cannot save file:\n{0} -cannot.save.file.dialog.title=Cannot Save File -status.bar.column.status.text=Column -choose.content.to.paste.dialog.title=Choose Content to Paste - -downloading.file.try.again.button=Tr&y Again -downloading.file.change.http.proxy.settings=Change HTTP &Proxy Settings... - -remove.field.initializer.quick.fix=Remove field initializer -button.add.class=Add Class... -button.add.pattern=Add Pattern... -button.remove=&Remove -class.filter.editor.choose.class.title=Choose Class -class.filter.editor.add.dialog.title=New Filter -label.class.filter.editor.add.dialog.filter.pattern=Enter the filter pattern: - -choose.class=Choose class -big.text.control.window.title=Text -tool.window.name.module.duplicates=Duplicates -welcome.screen.disabled.plugins.description=(disabled) -welcome.screen.incompatible.plugins.description=(incompatible) - -row.add=&Add -row.remove=R&emove -row.move.up=Move &Up -row.move.down=Move &Down -row.add.without.mnemonic=Add -row.remove.without.mnemonic=Remove -row.move.up.without.mnemonic=Move Up -row.move.down.without.mnemonic=Move Down -move.up.action.name=Move Up -move.down.action.name=Move Down -file.chooser.save.dialog.file.name=File name: -tool.window.name.documentation=Documentation - +color.panel.select.color.dialog.description=Select a color +color.panel.right.click.to.customize.tooltip.suffix=Right-click to customize +insert.file.path.to.text.action.name=Insert Path +order.entries.panel.export.column.name=Export +tree.view.expand.all.action.name=Expand All +tree.view.collapse.all.action.name=Collapse All +component.with.browse.button.browse.button.tooltip.text=Click or press Alt-Enter +there.is.no.help.for.this.dialog.error.message=There is no help for this dialog +no.help.available.dialog.title=No Help Available +invalid.user.input.dialog.title=Input Error + +splitter.down.tooltip.text=Down +splitter.right.tooltip.text=Right +splitter.center.tooltip.text=Center +splitter.up.tooltip.text=Up +splitter.left.tooltip.text=Left +autoscroll.from.source.action.name=Autoscroll from Source +autoscroll.from.source.action.description=Autoscroll from Source +autoscroll.to.source.action.name=Autoscroll to Source +autoscroll.to.source.action.description=Autoscroll to Source +collapsible.panel.collapsed.state.tooltip.text=Expand Panel +collapsible.panel.expanded.state.tooltip.text=Collapse Panel +idea.blue.metal.theme.name=beg blue +replace.prompt.replace.button=&Replace +replace.prompt.skip.button=&Skip +replace.prompt.all.in.this.file.button=All in This &File +replace.prompt.all.files.action=&All Files +replace.prompt.all.button=&All +replace.propmt.replace.occurrence.label=Do you want to replace this occurrence? +search.popup.search.for.label=Search for: +tabbed.pane.close.all.action.name=Close All +tabbed.pane.close.all.but.this.action.name=Close All But This +tabbed.pane.pin.tab.action.name=Pin Tab +tabbed.pane.pin.tab.action.description=Pin tool window tab +tabbed.pane.close.tab.action.name=Close Tab +tabbed.pane.close.all.tabs.but.this.action.name=Close All Tabs But This +file.is.read.only.message.text=File ''{0} is read-only. +files.are.read.only.message.text=Files {0} are read-only +error.dialog.title=Error +tool.window.name.commander=Commander +tool.window.name.messages=Messages +tool.window.name.project=Project +tool.window.name.structure=Structure +tool.window.name.favorites=Favorites +tool.window.name.ant.build=Ant Build +tool.window.name.debug=Debug +tool.window.name.run=Run +tool.window.name.find=Find +tool.window.name.cvs=CVS +tool.window.name.hierarchy=Hierarchy +tool.window.name.inspection=Inspection +tool.window.name.todo=TODO +tool.window.name.dependency.viewer=Dependency Viewer +tool.window.name.version.control=Version Control +tool.window.name.module.dependencies=Module Dependencies +tool.window.move.to.action.group.name=Move to +tool.window.move.to.top.action.name=Top +tool.window.move.to.left.action.name=Left +tool.window.move.to.bottom.action.name=Bottom +tool.window.move.to.right.action.name=Right +tool.window.hide.action.name=Hide +tool.window.hideSide.action.name=Hide Side +tool.wondow.undock.action.name=Undock +tool.wondow.dock.action.name=Dock +tool.wondow.fix.action.name=Fix +tool.wondow.float.action.name=Float +tool.wondow.pin.action.name=Pin +tool.wondow.unpin.action.name=Unpin + +#0 - total, 1 - used +memory.usage.panel.statistics.message=Allocated heap size: {0}M Used: {1}M +memory.usage.panel.run.garbage.collector.action.name=Run Garbage Collector +memory.usage.panel.run.garbage.collector.action.description=Run Garbage Collector +#0 - used, 1 - total +memory.usage.panel.message.text={0}M of {1}M +go.to.line.command.name=Go to Line +go.to.line.command.double.click=Double-click to go to line +status.bar.insert.status.text=Insert +status.bar.overwrite.status.text=Overwrite +popup.hints.panel.click.to.configure.highlighting.tooltip.text=Click to configure highlighting for this file +popup.hints.panel.click.to.configure.profile.text=Click to configure inspection profiles +read.only.attr.panel.double.click.to.toggle.attr.tooltip.text=Click to toggle the read-only attribute +welcome.screen.get.from.vcs.action.no.vcs.plugins.with.check.out.action.installed.action.name=No VCS plugins with Check-out action installed. +welcome.screen.get.from.vcs.action.checkout.from.list.popup.title=Checkout from +welcome.screen.recent.projects.action.no.recent.projects.to.display.action.name=No recent projects to display. +welcome.screen.quick.start.action.group.name=Quick Start +welcome.screen.documentation.action.group.name=Documentation +welcome.screen.read.help.action.description=Open {0} "Help Topics" in a new window. +welcome.screen.plugin.development.action.description=Get started developing plugins for {0}. +welcome.screen.open.project.action.description=You can open any arbitrary {0} project. Click the icon or link to select the project file from the File Chooser. +welcome.screen.plugins.panel.plugins.label=Plugins +welcome.screen.plugins.panel.my.plugins.label=My Plugins: +welcome.screen.plugins.panel.bundled.plugins.label=Bundled Plugins: +welcome.screen.plugins.panel.no.plugins.currently.installed.message.text=No plugins currently installed. +welcome.screen.plugins.panel.all.bundled.plugins.were.uninstalled.message.text=All bundled plugins were uninstalled. +welcome.screen.plugins.panel.learn.more.tooltip.text=Learn more... +welcome.script.text.not.specified.message=Not specified + +file.chooser.default.title=Select Path +file.chooser.save.dialog.default.title=Select File to Save +file.chooser.save.dialog.confirmation={0} is already exists.\nDo you want to replace it? +file.chooser.save.dialog.confirmation.title=Confirm Save as +file.chooser.select.object.title=Select {0} +delete.dialog.title=Delete +file.chooser.could.not.erase.file.or.folder.error.messabe=Could not erase file or folder: {0} +are.you.sure.you.want.to.delete.selected.folder.confirmation.message=Are you sure you want to delete selected folder? +are.you.sure.you.want.to.delete.selected.file.confirmation.message=Are you sure you want to delete selected file? +are.you.sure.you.want.to.delete.selected.files.and.directories.confirmation.message=Are you sure you want to delete selected files and directories? +are.you.sure.you.want.to.delete.selected.folders.confirmation.message=Are you sure you want to delete selected directories? +are.you.sure.you.want.to.delete.selected.files.and.files.confirmation.message=Are you sure you want to delete selected files? +create.new.folder.enter.new.folder.name.prompt.text=Enter a new folder name: +create.new.folder.folder.name.cannot.be.empty.error.message=Folder name cannot be empty +create.new.folder.could.not.create.folder.error.message=Could not create folder ''{0}'' +new.folder.dialog.title=New Folder +create.new.file.enter.new.file.name.prompt.text=Enter a new file name: +create.new.file.file.name.cannot.be.empty.error.message=File name cannot be empty +create.new.file.could.not.create.file.error.message=Could not create file ''{0}'' +new.file.dialog.title=New File +file.chooser.create.new.folder.command.name=Create New Folder +file.chooser.create.new.file.command.name=Create New File +file.cache.conflict.action=Reload From Disk +file.cache.conflict.message.text=Changes have been made to \"{0}\"\nin memory and on disk. +file.cache.conflict.load.fs.changes.button=&Load File System Changes +file.cache.conflict.keep.memory.changes.button=&Keep Memory Changes +file.cache.conflict.show.difference.button=&Show difference +file.cache.conflict.for.file.dialog.title=File Cache Conflict {0} +file.cache.conflict.dialog.title=File Cache Conflict +file.cache.conflict.diff.content.file.system.content=File system content +file.cache.conflict.diff.content.memory.content=Memory content +file.cache.conflict.save.changes.button=Save memory content +cannot.save.file.with.error.error.message=Cannot save file:\n{0} +cannot.save.file.dialog.title=Cannot Save File +status.bar.column.status.text=Column +choose.content.to.paste.dialog.title=Choose Content to Paste + +downloading.file.try.again.button=Tr&y Again +downloading.file.change.http.proxy.settings=Change HTTP &Proxy Settings... + +remove.field.initializer.quick.fix=Remove field initializer +button.add.class=Add Class... +button.add.pattern=Add Pattern... +button.remove=&Remove +class.filter.editor.choose.class.title=Choose Class +class.filter.editor.add.dialog.title=New Filter +label.class.filter.editor.add.dialog.filter.pattern=Enter the filter pattern: + +choose.class=Choose class +big.text.control.window.title=Text +tool.window.name.module.duplicates=Duplicates +welcome.screen.disabled.plugins.description=(disabled) +welcome.screen.incompatible.plugins.description=(incompatible) + +row.add=&Add +row.remove=R&emove +row.move.up=Move &Up +row.move.down=Move &Down +row.add.without.mnemonic=Add +row.remove.without.mnemonic=Remove +row.move.up.without.mnemonic=Move Up +row.move.down.without.mnemonic=Move Down +move.up.action.name=Move Up +move.down.action.name=Move Down +file.chooser.save.dialog.file.name=File name: +tool.window.name.documentation=Documentation + message.nothingToShow=Nothing to show \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/XmlBundle.properties b/platform/platform-resources-en/src/messages/XmlBundle.properties index 859d45b777a7..9b2e1b480bb7 100644 --- a/platform/platform-resources-en/src/messages/XmlBundle.properties +++ b/platform/platform-resources-en/src/messages/XmlBundle.properties @@ -1,241 +1,241 @@ -xml.validate.tab.content.title=Validate -xml.validate.no.errors.detected.status.message=No errors detected -validate.xml.open.message.view.command.name=Open message view -xml.validate.validation.is.running.terminate.confirmation.text=Validation is running. Terminate it? -xml.validate.validation.is.running.terminate.confirmation.title=Validation Is Running - -#Docs generation -xml.javadoc.tag.name.message=Tag name -xml.javadoc.attribute.name.message=Attribute name -xml.javadoc.description.message=Description -xml.javadoc.start.tag.could.be.omitted.message=Start tag:\\ could be omitted -xml.javadoc.end.tag.could.be.omitted.message=End tag:\\ could be omitted -xml.javadoc.is.empty.message=Is empty:\\ true -xml.javadoc.attr.type.message=Attr type:\\ {0} -xml.javadoc.attr.default.required.message=Attr default:\\ required -html.quickdoc.additional.template=More info on W3C website, SitePoint Reference website. -#0 - boolean (is deprecated) -xml.javadoc.deprecated.message=Deprecated :\\ {0} -xml.javadoc.defined.in.loose.dtd.message=Defined in:\\ loose dtd -xml.javadoc.defined.in.frameset.dtd.message=Defined in:\\ frameset dtd -xml.javadoc.defined.in.any.dtd.message=Defined in:\\ any dtd -xml.javadoc.complex.type.message=Complex type -#Check Image Size inspection -html.inspections.group.name=HTML -html.inspections.check.image.size=Image size mismatch -html.inspections.check.empty.tag=Empty tag -html.inspections.check.valid.script.tag=Malformed content of