diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileNotificationPanel.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileNotificationPanel.java deleted file mode 100644 index 0e0efa41bd9b..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileNotificationPanel.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.fileEditor.impl; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.EditorNotificationPanel; -import com.intellij.ui.HyperlinkLabel; -import org.jetbrains.annotations.NotNull; - -public class NonProjectFileNotificationPanel extends EditorNotificationPanel { - private final HyperlinkLabel myUnlockAction; - private final HyperlinkLabel myUnlockAllLabel; - - public NonProjectFileNotificationPanel(@NotNull final Project project, @NotNull final VirtualFile file) { - setText("This file is not in the project, please unlock it to continue editing"); - - myUnlockAction = createActionLabel("Unlock", new Runnable() { - @Override - public void run() { - NonProjectFileWritingAccessProvider.allowAccess(project, file); - } - }); - myUnlockAllLabel = createActionLabel("Unlock all in current session", new Runnable() { - @Override - public void run() { - NonProjectFileWritingAccessProvider.allowAccessForAll(project, file); - } - }); - } - - @NotNull - public HyperlinkLabel getUnlockAction() { - return myUnlockAction; - } - - @NotNull - public HyperlinkLabel getUnlockAllLabel() { - return myUnlockAllLabel; - } -} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileNotificationProvider.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileNotificationProvider.java deleted file mode 100644 index 16e5b8343df2..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileNotificationProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.fileEditor.impl; - -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.EditorNotifications; -import org.jetbrains.annotations.NotNull; - -public class NonProjectFileNotificationProvider extends EditorNotifications.Provider { - private static final Key KEY = Key.create("NonProjectFileNotificationPanel"); - - @NotNull private final Project myProject; - - public NonProjectFileNotificationProvider(@NotNull Project project) { - myProject = project; - } - - public Key getKey() { - return KEY; - } - - public NonProjectFileNotificationPanel createNotificationPanel(VirtualFile file, FileEditor fileEditor) { - NonProjectFileWritingAccessProvider.AccessStatus status = NonProjectFileWritingAccessProvider.getAccessStatus(myProject, file); - if (status != NonProjectFileWritingAccessProvider.AccessStatus.REQUESTED) return null; - - return new NonProjectFileNotificationPanel(myProject, file); - } -} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessDialog.form b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessDialog.form new file mode 100644 index 000000000000..33a20bc8b164 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessDialog.form @@ -0,0 +1,69 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessDialog.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessDialog.java new file mode 100644 index 000000000000..f85a0bf52ab1 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessDialog.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.fileEditor.impl; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vcs.readOnlyHandler.FileListRenderer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.CollectionListModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.List; + +public class NonProjectFileWritingAccessDialog extends DialogWrapper { + private JPanel myPanel; + private JList myFileList; + private JRadioButton myUnlockAllButton; + + protected NonProjectFileWritingAccessDialog(@NotNull Project project, @NotNull List nonProjectFiles) { + super(project); + setTitle("Non-Project Files Access"); + + myFileList.setCellRenderer(new FileListRenderer()); + myFileList.setModel(new CollectionListModel(nonProjectFiles)); + + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myPanel; + } + + @NotNull + public NonProjectFileWritingAccessProvider.UnlockOption getUnlockOption() { + return myUnlockAllButton.isSelected() ? NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK_ALL + : NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK; + } + + protected String getHelpId() { + return "readOnlyHandler.nonProjectFilesDialog"; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java index 176a4cccdd58..75432f4c2a2f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java @@ -30,8 +30,8 @@ import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NotNullLazyKey; import com.intellij.openapi.vfs.*; -import com.intellij.ui.EditorNotifications; import com.intellij.util.NotNullFunction; +import com.intellij.util.NullableFunction; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,6 +54,12 @@ public class NonProjectFileWritingAccessProvider extends WritingAccessProvider { }); @NotNull private final Project myProject; + @Nullable private static NullableFunction, UnlockOption> ourCustomUnlocker; + + @TestOnly + public static void setCustomUnlocker(@Nullable NullableFunction, UnlockOption> unlocker) { + ourCustomUnlocker = unlocker; + } public NonProjectFileWritingAccessProvider(@NotNull final Project project) { myProject = project; @@ -73,7 +79,6 @@ public class NonProjectFileWritingAccessProvider extends WritingAccessProvider { for (VirtualFile each : new ArrayList(files.keySet())) { if (isProjectFile(each)) { files.remove(each); - EditorNotifications.getInstance(myProject).updateNotifications(each); } } } @@ -98,18 +103,40 @@ public class NonProjectFileWritingAccessProvider extends WritingAccessProvider { if (!(each.getFileSystem() instanceof LocalFileSystem)) continue; // do not block e.g., HttpFileSystem, LightFileSystem etc. if (isProjectFile(each)) { - if (statuses.remove(each) != null) { - EditorNotifications.getInstance(myProject).updateNotifications(each); - } + statuses.remove(each); continue; } statuses.put(each, AccessStatus.REQUESTED); deniedFiles.add(each); - EditorNotifications.getInstance(myProject).updateNotifications(each); } - return deniedFiles; + if (deniedFiles.isEmpty()) return Collections.emptyList(); + + UnlockOption unlockOption = askToUnlock(deniedFiles); + if (unlockOption == null) return deniedFiles; + + switch (unlockOption) { + case UNLOCK: + for (VirtualFile eachAllowed : deniedFiles) { + statuses.put(eachAllowed, AccessStatus.ALLOWED); + } + break; + case UNLOCK_ALL: + myProject.putUserData(ALL_ACCESS_ALLOWED, Boolean.TRUE); + break; + } + + return Collections.emptyList(); + } + + @Nullable + private UnlockOption askToUnlock(@NotNull List files) { + if (ourCustomUnlocker != null) return ourCustomUnlocker.fun(files); + + NonProjectFileWritingAccessDialog dialog = new NonProjectFileWritingAccessDialog(myProject, files); + if (!dialog.showAndGet()) return null; + return dialog.getUnlockOption(); } private boolean isProjectFile(@NotNull VirtualFile file) { @@ -156,28 +183,10 @@ public class NonProjectFileWritingAccessProvider extends WritingAccessProvider { return allAccessAllowed(project) ? AccessStatus.ALLOWED : getRegisteredFiles(project).get(file); } - public static void allowAccessForAll(@NotNull Project project, @NotNull VirtualFile file) { - project.putUserData(ALL_ACCESS_ALLOWED, Boolean.TRUE); - // unlock the current file as well - ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(file); - - EditorNotifications.getInstance(project).updateAllNotifications(); - } - - public static void allowAccess(@NotNull Project project, @NotNull VirtualFile file) { - // Ask other access provides to remove read-only status - Map statuses = getRegisteredFiles(project); - statuses.put(file, AccessStatus.ALLOWED); - if (ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(file).hasReadonlyFiles()) { - // If failed, do not change the status - statuses.put(file, AccessStatus.REQUESTED); - } - - EditorNotifications.getInstance(project).updateNotifications(file); - } - @NotNull private static Map getRegisteredFiles(@NotNull Project project) { return ACCESS_STATUS.getValue(project); } + + public enum UnlockOption {UNLOCK, UNLOCK_ALL} } diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index d7e8c00cfa7b..53fcc0bad0ca 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -285,7 +285,6 @@ - diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/NonProjectFileAccessTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/NonProjectFileAccessTest.java index 93d5a8e16a4c..e517799165e2 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/NonProjectFileAccessTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/NonProjectFileAccessTest.java @@ -26,11 +26,8 @@ import com.intellij.openapi.components.impl.ComponentManagerImpl; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionManager; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl; -import com.intellij.openapi.fileEditor.impl.NonProjectFileNotificationPanel; import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessExtension; import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider; -import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.module.EmptyModuleType; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; @@ -47,9 +44,10 @@ import com.intellij.testFramework.PlatformTestUtil; import com.intellij.testFramework.PsiTestUtil; import com.intellij.ui.EditorNotifications; import com.intellij.ui.EditorNotificationsImpl; +import com.intellij.util.NullableFunction; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.swing.*; import java.util.*; public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { @@ -64,6 +62,7 @@ public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { @Override protected void tearDown() throws Exception { + NonProjectFileWritingAccessProvider.setCustomUnlocker(null); NonProjectFileWritingAccessProvider.enableChecksInTests(getProject(), false); super.tearDown(); } @@ -80,15 +79,10 @@ public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { VirtualFile nonProjectFile = createNonProjectFile(); typeAndCheck(nonProjectFile, false); - NonProjectFileNotificationPanel panel = typeAndCheck(nonProjectFile, false);// still not allowed + typeAndCheck(nonProjectFile, false);// still not allowed - panel.getUnlockAction().doClick(); - assertNull(getNotificationPanel(projectFile)); - assertNull(getNotificationPanel(nonProjectFile)); - - typeAndCheck(nonProjectFile, true); - assertNull(getNotificationPanel(projectFile)); - assertNull(getNotificationPanel(nonProjectFile)); + typeAndCheck(nonProjectFile, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); + typeAndCheck(nonProjectFile, null, true); // still allowed after previous Unlock } public void testAccessToProjectSystemFiles() throws Exception { @@ -158,77 +152,59 @@ public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { VirtualFile nonProjectFile1 = createNonProjectFile(); VirtualFile nonProjectFile2 = createNonProjectFile(); - NonProjectFileNotificationPanel panel1 = typeAndCheck(nonProjectFile1, false); - NonProjectFileNotificationPanel panel2 = typeAndCheck(nonProjectFile2, false); + typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); + typeAndCheck(nonProjectFile2, null, false); - panel1.getUnlockAction().doClick(); - assertNull(getNotificationPanel(nonProjectFile1)); - assertNotNull(getNotificationPanel(nonProjectFile2)); + typeAndCheck(nonProjectFile1, null, true); + typeAndCheck(nonProjectFile2, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); - typeAndCheck(nonProjectFile1, true); - typeAndCheck(nonProjectFile2, false); - - panel2.getUnlockAction().doClick(); - assertNull(getNotificationPanel(nonProjectFile1)); - assertNull(getNotificationPanel(nonProjectFile2)); - - typeAndCheck(nonProjectFile1, true); - typeAndCheck(nonProjectFile2, true); + // let's check both files one more time to make sure unlock option doesn't have eny unexpected effect + typeAndCheck(nonProjectFile1, null, true); + typeAndCheck(nonProjectFile2, null, true); } public void testAllowEditingInAllFiles() throws Exception { VirtualFile nonProjectFile1 = createNonProjectFile(); VirtualFile nonProjectFile2 = createNonProjectFile(); + VirtualFile nonProjectFile3 = createNonProjectFile(); - NonProjectFileNotificationPanel panel1 = typeAndCheck(nonProjectFile1, false); + typeAndCheck(nonProjectFile1, false); typeAndCheck(nonProjectFile2, false); + typeAndCheck(nonProjectFile3, false); - assertNotNull(getNotificationPanel(nonProjectFile1)); - assertNotNull(getNotificationPanel(nonProjectFile2)); - - panel1.getUnlockAllLabel().doClick(); - assertNull(getNotificationPanel(nonProjectFile1)); - assertNull(getNotificationPanel(nonProjectFile2)); - - typeAndCheck(nonProjectFile1, true); + typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK_ALL, true); + // affects other files typeAndCheck(nonProjectFile2, true); + typeAndCheck(nonProjectFile3, true); } public void testCheckingOtherWriteAccessProvidersOnUnlock() throws Exception { final VirtualFile nonProjectFile1 = createNonProjectFile(); final VirtualFile nonProjectFile2 = createNonProjectFile(); - final List requested = registerWriteAccessProvider(nonProjectFile1); + final Set requested = registerWriteAccessProvider(nonProjectFile1); - NonProjectFileNotificationPanel panel1 = typeAndCheck(nonProjectFile1, false); - panel1.getUnlockAction().doClick(); + typeAndCheck(nonProjectFile1, false); + assertSameElements(requested); // not called since non-project file access is denied + + typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, false); assertSameElements(requested, nonProjectFile1); + typeAndCheck(nonProjectFile1, false); // leave file locked if other provides denied access - requested.clear(); - NonProjectFileNotificationPanel panel2 = typeAndCheck(nonProjectFile2, false); - panel2.getUnlockAction().doClick(); + typeAndCheck(nonProjectFile2, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); assertSameElements(requested, nonProjectFile2); - typeAndCheck(nonProjectFile2, true); } public void testCheckingOtherWriteAccessProvidersOnUnlockAll() throws Exception { final VirtualFile nonProjectFile1 = createNonProjectFile(); final VirtualFile nonProjectFile2 = createNonProjectFile(); - final List requested = registerWriteAccessProvider(nonProjectFile1); + registerWriteAccessProvider(nonProjectFile1); - NonProjectFileNotificationPanel panel = typeAndCheck(nonProjectFile1, false); - typeAndCheck(nonProjectFile2, false); - - assertNotNull(getNotificationPanel(nonProjectFile1)); - assertNotNull(getNotificationPanel(nonProjectFile2)); - - panel.getUnlockAllLabel().doClick(); - assertSameElements(requested, nonProjectFile1); - - typeAndCheck(nonProjectFile1, false, false); // can't write, but access panel is not shown + typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK_ALL, + false); // can't write since denied by another write-access provider typeAndCheck(nonProjectFile2, true); } @@ -260,8 +236,8 @@ public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { typeAndCheck(nonProjectFile1, false); typeAndCheck(nonProjectFile2, false); - assertNotNull(getNotificationPanel(nonProjectFile1)); - assertNotNull(getNotificationPanel(nonProjectFile2)); + assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); + assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); ContentEntry contextRoot = PsiTestUtil.addContentRoot(myModule, nonProjectFile2.getParent()); @@ -309,8 +285,8 @@ public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { typeAndCheck(nonProjectFile2, false); } - private List registerWriteAccessProvider(final VirtualFile... filesToDeny) { - final List requested = new ArrayList(); + private Set registerWriteAccessProvider(final VirtualFile... filesToDeny) { + final Set requested = new LinkedHashSet(); PlatformTestUtil.registerExtension(Extensions.getArea(getProject()), WritingAccessProvider.EP_NAME, new WritingAccessProvider() { @NotNull @Override @@ -361,41 +337,34 @@ public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { }.execute().getResultObject(); } - private NonProjectFileNotificationPanel typeAndCheck(VirtualFile file, boolean fileHasBeenChanged) { - return typeAndCheck(file, fileHasBeenChanged, !fileHasBeenChanged); + private void typeAndCheck(VirtualFile file, boolean fileHasBeenChanged) { + typeAndCheck(file, null, fileHasBeenChanged); } - private NonProjectFileNotificationPanel typeAndCheck(VirtualFile file, boolean changed, boolean hasWarningPanel) { + private void typeAndCheck(VirtualFile file, + @Nullable final NonProjectFileWritingAccessProvider.UnlockOption option, + boolean fileHasBeenChanged) { Editor editor = getEditor(file); + NullableFunction, NonProjectFileWritingAccessProvider.UnlockOption> unlocker = + new NullableFunction, NonProjectFileWritingAccessProvider.UnlockOption>() { + @Nullable + @Override + public NonProjectFileWritingAccessProvider.UnlockOption fun(List files) { + return option; + } + }; + NonProjectFileWritingAccessProvider.setCustomUnlocker(unlocker); + String before = editor.getDocument().getText(); typeInChar(editor, 'a'); - NonProjectFileNotificationPanel panel = getNotificationPanel(file); - if (changed) { + if (fileHasBeenChanged) { assertEquals("Text should be changed", 'a' + before, editor.getDocument().getText()); } else { assertEquals("Text should not be changed", before, editor.getDocument().getText()); } - assertEquals(hasWarningPanel, panel != null); - return panel; - } - - private NonProjectFileNotificationPanel getNotificationPanel(VirtualFile file) { - Editor editor = getEditor(file); - - FileEditorManagerImpl manager = (FileEditorManagerImpl)FileEditorManager.getInstance(getProject()); - List topComponents = manager.getTopComponents(getFileEditor(editor)); - if (topComponents.isEmpty()) return null; - - JComponent panel = topComponents.get(0); - assertTrue(panel instanceof NonProjectFileNotificationPanel); - return (NonProjectFileNotificationPanel)panel; - } - - protected FileEditor getFileEditor(Editor e) { - return e == null ? null : TextEditorProvider.getInstance().getTextEditor(e); } private Editor getEditor(VirtualFile file) {