diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
index 05e4701795fa..5113c7f3593d 100644
--- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
+++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
@@ -638,4 +638,6 @@ inspection.extract.method.message=Extract method returning ''{0}''
inspection.extract.method.option.min.length=Minimum length of code to extract:|symbols
inspection.extract.method.option.max.parameters=Maximum number of parameters:
inspection.extract.method.dont.suggest.parameters=Don''t suggest to extract method with {0} or more parameters
-inspection.extract.method.dont.suggest.length=Don\u2019t suggest to extract methods as short as this
\ No newline at end of file
+inspection.extract.method.dont.suggest.length=Don\u2019t suggest to extract methods as short as this
+notification.file.system.issue=File Operation Issue
+notification.content.cannot.move.file=Cannot move ''{0}'' into ''{1}'': {2}
diff --git a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml
index f73c88a6cdb7..2b594f9f9201 100644
--- a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml
+++ b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml
@@ -320,5 +320,6 @@
+
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java
index 6a4d12de62f5..0a15c403df50 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java
@@ -1,24 +1,16 @@
-/*
- * Copyright 2000-2016 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInspection.util.IntentionName;
+import com.intellij.java.analysis.JavaAnalysisBundle;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationType;
import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
@@ -63,14 +55,26 @@ public class MoveFileFix implements IntentionAction {
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
+ int offset = -1;
+ if (editor.getVirtualFile().equals(myFile)) {
+ offset = editor.getCaretModel().getOffset();
+ }
if (myFile.isValid() && myTarget.isValid()) {
try {
myFile.move(this, myTarget);
}
catch (IOException e) {
- throw new IncorrectOperationException("Cannot move '" + myFile.getPath() + "' into '" + myTarget.getPath() + "'", (Throwable)e);
+ new Notification("FileSystemIssue",
+ JavaAnalysisBundle.message("notification.content.cannot.move.file", myFile.getPath(), myTarget.getPath(), e.getMessage()),
+ NotificationType.ERROR)
+ .notify(project);
}
}
+ FileEditorManager manager = FileEditorManager.getInstance(project);
+ manager.closeFile(myFile);
+ if (offset >= 0) {
+ manager.openTextEditor(new OpenFileDescriptor(project, myFile, offset), true);
+ }
}
@Override
diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml
index 13f532f0b135..6f02e240f283 100644
--- a/java/java-impl/src/META-INF/JavaPlugin.xml
+++ b/java/java-impl/src/META-INF/JavaPlugin.xml
@@ -2398,6 +2398,7 @@
+
diff --git a/java/java-impl/src/com/intellij/ide/FileNotInSourceRootChecker.kt b/java/java-impl/src/com/intellij/ide/FileNotInSourceRootChecker.kt
new file mode 100644
index 000000000000..8c272b7f4120
--- /dev/null
+++ b/java/java-impl/src/com/intellij/ide/FileNotInSourceRootChecker.kt
@@ -0,0 +1,120 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.ide
+
+import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
+import com.intellij.codeInsight.daemon.impl.HighlightInfo
+import com.intellij.codeInsight.daemon.impl.HighlightInfoType
+import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
+import com.intellij.codeInsight.daemon.impl.quickfix.MoveFileFix
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.ide.util.PropertiesComponent
+import com.intellij.java.JavaBundle
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ReadAction
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.EditorFactory
+import com.intellij.openapi.editor.event.EditorFactoryEvent
+import com.intellij.openapi.editor.event.EditorFactoryListener
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ProjectFileIndex
+import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider
+import com.intellij.openapi.startup.ProjectActivity
+import com.intellij.openapi.vfs.VfsUtil
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiJavaFile
+
+class FileNotInSourceRootChecker : ProjectActivity {
+ override suspend fun execute(project: Project) {
+ project.getService(FileNotInSourceRootService::class.java).init()
+ }
+}
+
+private const val GROUP : Int = 1234
+private const val JAVA_DONT_CHECK_OUT_OF_SOURCE_FILES : String = "com.intellij.ide.FileNotInSourceRootChecker.no.check"
+
+@Service(Service.Level.PROJECT)
+class FileNotInSourceRootService(val project: Project) : Disposable {
+
+ fun init() {
+ if (PropertiesComponent.getInstance(project).getBoolean(JAVA_DONT_CHECK_OUT_OF_SOURCE_FILES, false)) return
+ val editorFactory = EditorFactory.getInstance()
+ editorFactory.addEditorFactoryListener(object : EditorFactoryListener {
+ override fun editorCreated(event: EditorFactoryEvent) {
+ checkEditor(event.editor)
+ }
+ }, this)
+ editorFactory.allEditors.forEach { editor -> checkEditor(editor) }
+ }
+
+ private fun checkEditor(editor: Editor) {
+ if (editor.project !== project) return
+ if (PropertiesComponent.getInstance(project).getBoolean(JAVA_DONT_CHECK_OUT_OF_SOURCE_FILES, false)) return
+ val virtualFile = editor.virtualFile
+ if (virtualFile == null) return
+ ReadAction.run {
+ val fileIndex = ProjectFileIndex.getInstance(project)
+ if (fileIndex.isInSource(virtualFile) || fileIndex.isExcluded(virtualFile) || fileIndex.isUnderIgnored(virtualFile)) return@run
+ if (!fileIndex.getOrderEntriesForFile(virtualFile).isEmpty()) return@run
+ val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) as? PsiJavaFile ?: return@run
+ val packageName = psiFile.packageName
+ val module = fileIndex.getModuleForFile(virtualFile) ?: return@run
+ val rootModel = DefaultModulesProvider.createForProject(project).getRootModel(module)
+ val roots = rootModel.sourceRoots
+ if (roots.isEmpty()) return@run
+ var root = roots[0]
+ if (packageName.isNotEmpty()) {
+ root = VfsUtil.findRelativeFile(root, *packageName.split('.').toTypedArray()) ?: root
+ }
+ if (root.findChild(virtualFile.name) != null) return@run
+ val moveFileFix = MoveFileFix(virtualFile, root, JavaBundle.message("fix.move.to.source.root"))
+ val info = HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING)
+ .range(psiFile)
+ .description(JavaBundle.message("warning.java.file.outside.source.root"))
+ .fileLevelAnnotation()
+ .group(GROUP)
+ .registerFix(moveFileFix, listOf(DismissFix(), IgnoreForThisProjectFix()), null, null, null)
+ ApplicationManager.getApplication().invokeLater {
+ UpdateHighlightersUtil.setHighlightersToSingleEditor(project, editor, 0, editor.document.textLength,
+ listOf(info.create()), null, GROUP)
+ }
+ }
+ }
+
+ override fun dispose() {
+ }
+
+ class DismissFix: IntentionAction {
+ override fun startInWriteAction(): Boolean = false
+ override fun getText(): String = familyName
+ override fun getFamilyName(): String = JavaBundle.message("intention.family.name.dismiss")
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean = true
+
+ override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
+ file ?: return
+ DaemonCodeAnalyzerEx.getInstanceEx(project).cleanFileLevelHighlights(GROUP, file)
+ }
+ }
+
+ class IgnoreForThisProjectFix: IntentionAction {
+ override fun startInWriteAction(): Boolean = false
+ override fun getText(): String = familyName
+ override fun getFamilyName(): String = JavaBundle.message("intention.family.name.ignore.project")
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean = true
+
+ override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
+ file ?: return
+ val codeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(project)
+ PropertiesComponent.getInstance(project).setValue(JAVA_DONT_CHECK_OUT_OF_SOURCE_FILES, true)
+ codeAnalyzer.cleanFileLevelHighlights(GROUP, file)
+ for (ed in EditorFactory.getInstance().allEditors) {
+ if (ed.project != project) continue
+ val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(ed.document) as? PsiJavaFile ?: continue
+ if (psiFile == file) continue
+ codeAnalyzer.cleanFileLevelHighlights(GROUP, psiFile)
+ }
+ }
+ }
+}
diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties
index 1e2e016fcfc0..03e961c4770f 100644
--- a/java/openapi/resources/messages/JavaBundle.properties
+++ b/java/openapi/resources/messages/JavaBundle.properties
@@ -1813,3 +1813,7 @@ hint.text.not.valid.java.identifier=Not a valid Java identifier
command.name.replace.type=Replace Type
java.completion.tag=Tag:
java.completion.tags=Tags:
+intention.family.name.dismiss=Dismiss
+intention.family.name.ignore.project=Do not show again in this project
+fix.move.to.source.root=Move to source root
+warning.java.file.outside.source.root=Java file is located outside of the module source root, so it won't be compiled