diff --git a/lib/org/hamcrest/annotations.xml b/lib/org/hamcrest/annotations.xml new file mode 100644 index 000000000000..36d1cf4640d1 --- /dev/null +++ b/lib/org/hamcrest/annotations.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/platform/built-in-server/testSrc/BinaryRequestHandlerTest.kt b/platform/built-in-server/testSrc/BinaryRequestHandlerTest.kt index 0c8c4dc5e13f..1d3b65cc977f 100644 --- a/platform/built-in-server/testSrc/BinaryRequestHandlerTest.kt +++ b/platform/built-in-server/testSrc/BinaryRequestHandlerTest.kt @@ -17,6 +17,7 @@ import org.jetbrains.io.ChannelExceptionHandler import org.jetbrains.io.Decoder import org.jetbrains.io.MessageDecoder import org.jetbrains.io.NettyUtil +import org.jetbrains.testFramework.FixtureRule import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain diff --git a/platform/built-in-server/testSrc/RestApiTest.kt b/platform/built-in-server/testSrc/RestApiTest.kt index 2f9586857102..b4716ebfac5e 100644 --- a/platform/built-in-server/testSrc/RestApiTest.kt +++ b/platform/built-in-server/testSrc/RestApiTest.kt @@ -1,19 +1,19 @@ package org.jetbrains.ide +import com.google.gson.stream.JsonWriter +import com.intellij.openapi.vfs.CharsetToolkit import io.netty.handler.codec.http.HttpResponseStatus +import org.hamcrest.CoreMatchers.equalTo +import org.jetbrains.ide.TestManager.TestDescriptor +import org.jetbrains.testFramework.FixtureRule +import org.junit.Assert.assertThat import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain - -import org.hamcrest.CoreMatchers.equalTo -import org.jetbrains.ide.TestManager.TestDescriptor -import org.junit.Assert.assertThat -import java.net.URL -import java.net.HttpURLConnection -import com.google.gson.stream.JsonWriter -import java.io.OutputStreamWriter -import com.intellij.openapi.vfs.CharsetToolkit import java.io.BufferedOutputStream +import java.io.OutputStreamWriter +import java.net.HttpURLConnection +import java.net.URL public class RestApiTest { private val fixtureManager = FixtureRule() diff --git a/platform/built-in-server/testSrc/TestManager.kt b/platform/built-in-server/testSrc/TestManager.kt index 50fa6cd3fe84..be3fc2ef57e3 100644 --- a/platform/built-in-server/testSrc/TestManager.kt +++ b/platform/built-in-server/testSrc/TestManager.kt @@ -1,5 +1,7 @@ package org.jetbrains.ide +import com.intellij.openapi.application.invokeAndWaitIfNeed +import com.intellij.openapi.application.writeAction import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.openapi.util.io.FileUtilRt @@ -63,10 +65,10 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() { } invokeAndWaitIfNeed { - val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath) + val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath!!) if (annotation!!.relativeToProject) { val root = projectFixture.getProject().getBaseDir() - runWriteAction { + writeAction { fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath) } } @@ -75,7 +77,7 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() { ModuleRootModificationUtil.updateModel(projectFixture.getModule()) { model -> val contentEntry = model.getContentEntries()[0] val contentRoot = contentEntry.getFile()!! - runWriteAction { + writeAction { contentRoot.findChild(EXCLUDED_DIR_NAME)?.delete(this@TestManager) fileToDelete = contentRoot.createChildDirectory(this@TestManager, EXCLUDED_DIR_NAME) fileToDelete!!.createChildData(this@TestManager, normalizedFilePath) @@ -87,7 +89,7 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() { } else { val root = ModuleRootManager.getInstance(projectFixture.getModule()).getSourceRoots()[0] - runWriteAction { + writeAction { fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath) } } @@ -101,11 +103,11 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() { } if (fileToDelete != null) { - invokeAndWaitIfNeed { runWriteAction { fileToDelete?.delete(this@TestManager) } } + invokeAndWaitIfNeed { writeAction { fileToDelete?.delete(this@TestManager) } } fileToDelete = null } - if (ioFileToDelete != null && !FileUtilRt.delete(ioFileToDelete)) { + if (ioFileToDelete != null && !FileUtilRt.delete(ioFileToDelete!!)) { ioFileToDelete!!.deleteOnExit() } diff --git a/platform/core-api/src/com/intellij/openapi/options/SchemesManager.java b/platform/core-api/src/com/intellij/openapi/options/SchemesManager.java index 88f6e37ce838..28844ab00844 100644 --- a/platform/core-api/src/com/intellij/openapi/options/SchemesManager.java +++ b/platform/core-api/src/com/intellij/openapi/options/SchemesManager.java @@ -46,8 +46,6 @@ public abstract class SchemesManager convertor) { } @@ -69,4 +70,11 @@ public abstract class SchemesManager schemes, @Nullable Condition removeCondition) { } + + /** + * Bundled / read-only (or overriding) scheme cannot be renamed or deleted. + */ + public boolean isMetadataEditable(@NotNull E scheme) { + return true; + } } diff --git a/platform/core-impl/core-impl.iml b/platform/core-impl/core-impl.iml index 6d93fd99a32c..e57a2b6e112a 100644 --- a/platform/core-impl/core-impl.iml +++ b/platform/core-impl/core-impl.iml @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/openapi/application/actions.kt b/platform/core-impl/src/com/intellij/openapi/application/actions.kt new file mode 100644 index 000000000000..4be236085b50 --- /dev/null +++ b/platform/core-impl/src/com/intellij/openapi/application/actions.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.application + +import javax.swing.SwingUtilities + +public inline fun writeAction(runnable: () -> Unit) { + val token = WriteAction.start() + try { + runnable() + } + finally { + token.finish() + } +} + +public fun invokeAndWaitIfNeed(runnable: () -> Unit) { + val app = ApplicationManager.getApplication() + if (app == null) { + if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable) + } + else { + if (app.isDispatchThread()) runnable() else app.invokeAndWait(runnable, ModalityState.any()) + } +} diff --git a/platform/core-impl/src/com/intellij/openapi/options/EmptySchemesManager.java b/platform/core-impl/src/com/intellij/openapi/options/EmptySchemesManager.java index 55a74d08ebd1..f5bc6cee0c35 100644 --- a/platform/core-impl/src/com/intellij/openapi/options/EmptySchemesManager.java +++ b/platform/core-impl/src/com/intellij/openapi/options/EmptySchemesManager.java @@ -48,10 +48,6 @@ public class EmptySchemesManager extends SchemesManager { return null; } - @Override - public void save() { - } - @Override public void setCurrentSchemeName(String schemeName) { } diff --git a/platform/platform-api/src/com/intellij/ui/AbstractCollectionComboBoxModel.java b/platform/platform-api/src/com/intellij/ui/AbstractCollectionComboBoxModel.java index 92329b428aa3..996ae90ea7b4 100644 --- a/platform/platform-api/src/com/intellij/ui/AbstractCollectionComboBoxModel.java +++ b/platform/platform-api/src/com/intellij/ui/AbstractCollectionComboBoxModel.java @@ -25,7 +25,7 @@ import java.util.Collection; * @author traff */ public abstract class AbstractCollectionComboBoxModel extends CollectionListModel implements ComboBoxModel { - private T mySelection; + protected T mySelection; public AbstractCollectionComboBoxModel(@Nullable T selection) { mySelection = selection; diff --git a/platform/platform-impl/platform-impl.iml b/platform/platform-impl/platform-impl.iml index 64dc847f675a..b6c974e918c2 100644 --- a/platform/platform-impl/platform-impl.iml +++ b/platform/platform-impl/platform-impl.iml @@ -42,5 +42,6 @@ + \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/QuickListsManager.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/QuickListsManager.java index 8f8b0789f240..7cd47436a47b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/QuickListsManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/ex/QuickListsManager.java @@ -43,11 +43,11 @@ public class QuickListsManager implements ExportableApplicationComponent { private static final String LIST_TAG = "list"; private final ActionManager myActionManager; - private final SchemesManager mySchemesManager; + private final SchemesManager mySchemeManager; public QuickListsManager(@NotNull ActionManager actionManager, @NotNull SchemesManagerFactory schemesManagerFactory) { myActionManager = actionManager; - mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, + mySchemeManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, new BaseSchemeProcessor() { @NotNull @Override @@ -73,7 +73,7 @@ public class QuickListsManager implements ExportableApplicationComponent { @Override @NotNull public File[] getExportFiles() { - return new File[]{mySchemesManager.getRootDirectory()}; + return new File[]{mySchemeManager.getRootDirectory()}; } @NotNull @@ -99,7 +99,7 @@ public class QuickListsManager implements ExportableApplicationComponent { public void initComponent() { for (BundledQuickListsProvider provider : BundledQuickListsProvider.EP_NAME.getExtensions()) { for (final String path : provider.getBundledListsRelativePaths()) { - mySchemesManager.loadBundledScheme(path, provider, new ThrowableConvertor() { + mySchemeManager.loadBundledScheme(path, provider, new ThrowableConvertor() { @Override public QuickList convert(Element element) throws Throwable { return createItem(element); @@ -107,7 +107,7 @@ public class QuickListsManager implements ExportableApplicationComponent { }); } } - mySchemesManager.loadSchemes(); + mySchemeManager.loadSchemes(); registerActions(); } @@ -116,20 +116,20 @@ public class QuickListsManager implements ExportableApplicationComponent { } @NotNull - public List getQuickLists() { - return mySchemesManager.getAllSchemes(); + public SchemesManager getSchemeManager() { + return mySchemeManager; } @NotNull public QuickList[] getAllQuickLists() { - Collection lists = mySchemesManager.getAllSchemes(); + Collection lists = mySchemeManager.getAllSchemes(); return lists.toArray(new QuickList[lists.size()]); } private void registerActions() { // to prevent exception if 2 or more targets have the same name Set registeredIds = new THashSet(); - for (QuickList list : mySchemesManager.getAllSchemes()) { + for (QuickList list : mySchemeManager.getAllSchemes()) { String actionId = list.getActionId(); if (registeredIds.add(actionId)) { myActionManager.registerAction(actionId, new InvokeQuickListAction(list)); @@ -145,7 +145,7 @@ public class QuickListsManager implements ExportableApplicationComponent { public void setQuickLists(@NotNull List quickLists) { unregisterActions(); - mySchemesManager.setSchemes(quickLists); + mySchemeManager.setSchemes(quickLists); registerActions(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StreamProvider.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StreamProvider.java deleted file mode 100644 index 4b455372bbf1..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StreamProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.components.impl.stores; - -import com.intellij.openapi.components.RoamingType; -import com.intellij.openapi.util.Condition; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; - -public abstract class StreamProvider { - public static final StreamProvider[] EMPTY_ARRAY = new StreamProvider[0]; - - public boolean isEnabled() { - return true; - } - - /** - * fileSpec Only main fileSpec, not version - */ - public boolean isApplicable(@NotNull String fileSpec, @NotNull RoamingType roamingType) { - return true; - } - - /** - * @param fileSpec - * @param content bytes of content, size of array is not actual size of data, you must use {@code size} - * @param size actual size of data - */ - public abstract void saveContent(@NotNull String fileSpec, @NotNull byte[] content, int size, @NotNull RoamingType roamingType) throws IOException; - - @Nullable - public abstract InputStream loadContent(@NotNull String fileSpec, @NotNull RoamingType roamingType) throws IOException; - - @NotNull - public Collection listSubFiles(@NotNull String fileSpec, @NotNull RoamingType roamingType) { - return Collections.emptyList(); - } - - /** - * You must close passed input stream. - */ - public void processChildren(@NotNull String path, @NotNull RoamingType roamingType, @NotNull Condition filter, @NotNull ChildrenProcessor processor) { - for (String name : listSubFiles(path, roamingType)) { - if (!filter.value(name)) { - continue; - } - - InputStream input; - try { - input = loadContent(path + '/' + name, roamingType); - } - catch (IOException e) { - StorageUtil.LOG.error(e); - continue; - } - - if (input != null && !processor.process(name, input)) { - break; - } - } - } - - public abstract static class ChildrenProcessor { - public abstract boolean process(@NotNull String name, @NotNull InputStream input); - } - - /** - * Delete file or directory - */ - public abstract void delete(@NotNull String fileSpec, @NotNull RoamingType roamingType); -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StreamProvider.kt b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StreamProvider.kt new file mode 100644 index 000000000000..df166e62a043 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StreamProvider.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.components.impl.stores + +import com.intellij.openapi.components.RoamingType +import java.io.IOException +import java.io.InputStream + +public interface StreamProvider { + public open fun isEnabled(): Boolean = true + + /** + * fileSpec Only main fileSpec, not version + */ + public open fun isApplicable(fileSpec: String, roamingType: RoamingType): Boolean = true + + /** + * @param fileSpec + * * + * @param content bytes of content, size of array is not actual size of data, you must use `size` + * * + * @param size actual size of data + */ + public fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) + + public fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream? + + public open fun listSubFiles(fileSpec: String, roamingType: RoamingType): Collection = emptyList() + + /** + * You must close passed input stream. + */ + public open fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean) { + for (name in listSubFiles(path, roamingType)) { + if (!filter(name)) { + continue + } + + val input: InputStream? + try { + input = loadContent("$path/$name", roamingType) + } + catch (e: IOException) { + StorageUtil.LOG.error(e) + continue + } + + + if (input != null && !processor(name, input, false)) { + break + } + } + } + + /** + * Delete file or directory + */ + public fun delete(fileSpec: String, roamingType: RoamingType) +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.java index 20b8af578520..2f69a039db5c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.java @@ -30,7 +30,9 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.OrderedSet; import gnu.trove.THashMap; +import gnu.trove.TObjectObjectProcedure; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -39,7 +41,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; import java.lang.reflect.Field; import java.util.*; @@ -77,7 +78,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { private KeymapImpl myParent; private boolean myCanModify = true; - private final Map> myActionId2ListOfShortcuts = new THashMap>(); + private final THashMap> myActionId2ListOfShortcuts = new THashMap>(); /** * Don't use this field directly! Use it only through getKeystroke2ListOfIds. @@ -134,21 +135,26 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { @NotNull public KeymapImpl copy() { - KeymapImpl newKeymap = new KeymapImpl(); - return copyTo(newKeymap); + return copyTo(new KeymapImpl()); } @NotNull - public KeymapImpl copyTo(@NotNull KeymapImpl otherKeymap) { + public KeymapImpl copyTo(@NotNull final KeymapImpl otherKeymap) { otherKeymap.myParent = myParent; otherKeymap.myName = myName; otherKeymap.myCanModify = canModify(); otherKeymap.cleanShortcutsCache(); - for (Map.Entry> entry : myActionId2ListOfShortcuts.entrySet()) { - otherKeymap.myActionId2ListOfShortcuts.put(entry.getKey(), new LinkedHashSet(entry.getValue())); - } + otherKeymap.myActionId2ListOfShortcuts.clear(); + otherKeymap.myActionId2ListOfShortcuts.ensureCapacity(myActionId2ListOfShortcuts.size()); + myActionId2ListOfShortcuts.forEachEntry(new TObjectObjectProcedure>() { + @Override + public boolean execute(String actionId, OrderedSet shortcuts) { + otherKeymap.myActionId2ListOfShortcuts.put(actionId, new OrderedSet(shortcuts)); + return true; + } + }); return otherKeymap; } @@ -195,9 +201,9 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { } private void addShortcutSilently(String actionId, Shortcut shortcut, final boolean checkParentShortcut) { - LinkedHashSet list = myActionId2ListOfShortcuts.get(actionId); + OrderedSet list = myActionId2ListOfShortcuts.get(actionId); if (list == null) { - list = new LinkedHashSet(); + list = new OrderedSet(); myActionId2ListOfShortcuts.put(actionId, list); Shortcut[] boundShortcuts = getBoundShortcuts(actionId); if (boundShortcuts != null) { @@ -230,7 +236,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { @Override public void removeShortcut(String actionId, Shortcut toDelete) { - LinkedHashSet list = myActionId2ListOfShortcuts.get(actionId); + OrderedSet list = myActionId2ListOfShortcuts.get(actionId); if (list != null) { Iterator it = list.iterator(); while (it.hasNext()) { @@ -253,7 +259,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { if (inherited != null) { boolean affected = false; - LinkedHashSet newShortcuts = new LinkedHashSet(inherited.length); + OrderedSet newShortcuts = new OrderedSet(inherited.length); for (Shortcut eachInherited : inherited) { if (toDelete.equals(eachInherited)) { // skip this one @@ -306,7 +312,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { } private void addAction2ShortcutsMap(final String actionId, final Map> strokesMap, final Class shortcutClass) { - LinkedHashSet listOfShortcuts = _getShortcuts(actionId); + OrderedSet listOfShortcuts = _getShortcuts(actionId); for (Shortcut shortcut : listOfShortcuts) { if (!shortcutClass.isAssignableFrom(shortcut.getClass())) { continue; @@ -328,7 +334,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { } private void addKeystrokesMap(final String actionId, final Map> strokesMap) { - LinkedHashSet listOfShortcuts = _getShortcuts(actionId); + OrderedSet listOfShortcuts = _getShortcuts(actionId); for (Shortcut shortcut : listOfShortcuts) { if (!(shortcut instanceof KeyboardShortcut)) { continue; @@ -347,14 +353,14 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { } } - private LinkedHashSet _getShortcuts(final String actionId) { + private OrderedSet _getShortcuts(final String actionId) { KeymapManagerEx keymapManager = getKeymapManager(); - LinkedHashSet listOfShortcuts = myActionId2ListOfShortcuts.get(actionId); + OrderedSet listOfShortcuts = myActionId2ListOfShortcuts.get(actionId); if (listOfShortcuts != null) { return listOfShortcuts; } else { - listOfShortcuts = new LinkedHashSet(); + listOfShortcuts = new OrderedSet(); } final String actionBinding = keymapManager.getActionBinding(actionId); @@ -519,7 +525,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { @NotNull @Override public Shortcut[] getShortcuts(String actionId) { - LinkedHashSet shortcuts = myActionId2ListOfShortcuts.get(actionId); + OrderedSet shortcuts = myActionId2ListOfShortcuts.get(actionId); if (shortcuts == null) { Shortcut[] boundShortcuts = getBoundShortcuts(actionId); @@ -539,7 +545,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { @Nullable private Shortcut[] getOwnShortcuts(String actionId) { - LinkedHashSet own = myActionId2ListOfShortcuts.get(actionId); + OrderedSet own = myActionId2ListOfShortcuts.get(actionId); if (own == null) return null; return own.isEmpty() ? ourEmptyShortcutsArray : own.toArray(new Shortcut[own.size()]); } @@ -685,7 +691,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { } // Add read shortcuts for (String id : id2shortcuts.keySet()) { - myActionId2ListOfShortcuts.put(id, new LinkedHashSet(2)); // It's a trick! After that parent's shortcuts are not added to the keymap + myActionId2ListOfShortcuts.put(id, new OrderedSet(2)); // It's a trick! After that parent's shortcuts are not added to the keymap ArrayList shortcuts = id2shortcuts.get(id); for (Shortcut shortcut : shortcuts) { addShortcutSilently(id, shortcut, false); @@ -711,7 +717,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { for (String actionId : ownActionIds) { Element actionElement = new Element(ACTION); actionElement.setAttribute(ID_ATTRIBUTE, actionId); - // Save keyboad shortcuts + // Save keyboard shortcuts Shortcut[] shortcuts = getShortcuts(actionId); for (Shortcut shortcut : shortcuts) { if (shortcut instanceof KeyboardShortcut) { @@ -805,23 +811,23 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { // modifiers int modifiers = shortcut.getModifiers(); - if ((MouseEvent.SHIFT_DOWN_MASK & modifiers) > 0) { + if ((InputEvent.SHIFT_DOWN_MASK & modifiers) > 0) { buffer.append(SHIFT); buffer.append(' '); } - if ((MouseEvent.CTRL_DOWN_MASK & modifiers) > 0) { + if ((InputEvent.CTRL_DOWN_MASK & modifiers) > 0) { buffer.append(CONTROL); buffer.append(' '); } - if ((MouseEvent.META_DOWN_MASK & modifiers) > 0) { + if ((InputEvent.META_DOWN_MASK & modifiers) > 0) { buffer.append(META); buffer.append(' '); } - if ((MouseEvent.ALT_DOWN_MASK & modifiers) > 0) { + if ((InputEvent.ALT_DOWN_MASK & modifiers) > 0) { buffer.append(ALT); buffer.append(' '); } - if ((MouseEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) { + if ((InputEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) { buffer.append(ALT_GRAPH); buffer.append(' '); } @@ -849,7 +855,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap { cleanShortcutsCache(); } - public boolean hasOwnActionId(String actionId) { + public boolean hasOwnActionId(@NotNull String actionId) { return myActionId2ListOfShortcuts.containsKey(actionId); } diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/ActionsTree.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/ActionsTree.java index a10e6360d3c9..fdd72694b8bc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/ActionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/ActionsTree.java @@ -86,7 +86,6 @@ public class ActionsTree { if (rowBounds.intersects(clip)) { Object node = getPathForRow(row).getLastPathComponent(); - if (node instanceof DefaultMutableTreeNode) { Object data = ((DefaultMutableTreeNode)node).getUserObject(); Rectangle fullRowRect = new Rectangle(visibleRect.x, rowBounds.y, visibleRect.width, rowBounds.height); @@ -106,30 +105,20 @@ public class ActionsTree { @Override public void mouseMoved(MouseEvent e) { String description = getDescription(e); - if (description != null) { - ActionMenu.showDescriptionInStatusBar(true, myTree, description); - } - else { - ActionMenu.showDescriptionInStatusBar(false, myTree, null); - } + ActionMenu.showDescriptionInStatusBar(description != null, myTree, description); } @Nullable private String getDescription(@NotNull MouseEvent e) { TreePath path = myTree.getPathForLocation(e.getX(), e.getY()); - if (path == null) return null; + DefaultMutableTreeNode node = path == null ? null : (DefaultMutableTreeNode)path.getLastPathComponent(); + Object userObject = node == null ? null : node.getUserObject(); + if (!(userObject instanceof String)) { + return null; + } - DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - if (node == null) return null; - - Object userObject = node.getUserObject(); - if (!(userObject instanceof String)) return null; - - String actionId = (String)userObject; - AnAction action = ActionManager.getInstance().getActionOrStub(actionId); - if (action == null) return null; - - return action.getTemplatePresentation().getDescription(); + AnAction action = ActionManager.getInstance().getActionOrStub((String)userObject); + return action == null ? null : action.getTemplatePresentation().getDescription(); } }); @@ -140,6 +129,11 @@ public class ActionsTree { ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); } + // silently replace current map + void setKeymap(@NotNull Keymap keymap) { + myKeymap = keymap; + } + public JComponent getComponent() { return myComponent; } @@ -171,7 +165,7 @@ public class ActionsTree { return (QuickList)userObject; } - public void reset(Keymap keymap, final QuickList[] allQuickLists) { + public void reset(@NotNull Keymap keymap, @NotNull QuickList[] allQuickLists) { reset(keymap, allQuickLists, myFilter, null); } @@ -188,7 +182,7 @@ public class ActionsTree { reset(myKeymap, currentQuickListIds, filter, null); } - private void reset(final Keymap keymap, final QuickList[] allQuickLists, String filter, @Nullable KeyboardShortcut shortcut) { + private void reset(@NotNull Keymap keymap, @NotNull QuickList[] allQuickLists, String filter, @Nullable KeyboardShortcut shortcut) { myKeymap = keymap; final PathsKeeper pathsKeeper = new PathsKeeper(); @@ -198,11 +192,11 @@ public class ActionsTree { ActionManager actionManager = ActionManager.getInstance(); Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myComponent)); - Group mainGroup = ActionsTreeUtil.createMainGroup(project, myKeymap, allQuickLists, filter, true, - ActionsTreeUtil.isActionFiltered(actionManager, myKeymap, shortcut, filter, true)); + Group mainGroup = ActionsTreeUtil.createMainGroup(project, keymap, allQuickLists, filter, true, + ActionsTreeUtil.isActionFiltered(actionManager, keymap, shortcut, filter, true)); if ((filter != null && filter.length() > 0 || shortcut != null) && mainGroup.initIds().isEmpty()){ - mainGroup = ActionsTreeUtil.createMainGroup(project, myKeymap, allQuickLists, filter, false, - ActionsTreeUtil.isActionFiltered(actionManager, myKeymap, shortcut, filter, false)); + mainGroup = ActionsTreeUtil.createMainGroup(project, keymap, allQuickLists, filter, false, + ActionsTreeUtil.isActionFiltered(actionManager, keymap, shortcut, filter, false)); } myRoot = ActionsTreeUtil.createNode(mainGroup); myMainGroup = mainGroup; @@ -251,14 +245,7 @@ public class ActionsTree { if (userObject instanceof QuickList) { userObject = ((QuickList)userObject).getActionId(); } - - if (userObject instanceof String) { - Shortcut[] shortcuts = myKeymap.getShortcuts((String)userObject); - return KeymapUtil.getShortcutsText(shortcuts); - } - else { - return ""; - } + return userObject instanceof String ? KeymapUtil.getShortcutsText(myKeymap.getShortcuts((String)userObject)) : ""; } else { return "???"; diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeyboardShortcutDialog.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeyboardShortcutDialog.java index 0894b0dd7afc..b08cf0fdd087 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeyboardShortcutDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeyboardShortcutDialog.java @@ -34,6 +34,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -214,7 +215,8 @@ public class KeyboardShortcutDialog extends DialogWrapper { myConflictInfoArea.setForeground(JBColor.RED); if (loaded) { myConflictInfoArea.setText(KeyMapBundle.message("assigned.to.info.message", buffer.toString())); - } else { + } + else { myConflictInfoArea.setText("Assigned to " + buffer.toString() + " which is now not loaded but may be loaded later"); } } @@ -224,13 +226,10 @@ public class KeyboardShortcutDialog extends DialogWrapper { mySecondStrokePanel.setEnabled(myEnableSecondKeystroke.isSelected()); } + @Nullable public KeyboardShortcut getKeyboardShortcut() { KeyStroke firstStroke = myFirstStrokePanel.getKeyStroke(); - if (firstStroke == null) { - return null; - } - KeyStroke secondStroke = myEnableSecondKeystroke.isSelected() ? mySecondStrokePanel.getKeyStroke() : null; - return new KeyboardShortcut(firstStroke, secondStroke); + return firstStroke == null ? null : new KeyboardShortcut(firstStroke, myEnableSecondKeystroke.isSelected() ? mySecondStrokePanel.getKeyStroke() : null); } static String getTextByKeyStroke(KeyStroke keyStroke) { diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java index 36a5af720474..3df7eb33ef35 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java @@ -119,8 +119,6 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi } }); - private Keymap mySelectedKeymap; - private JButton myCopyButton; private JButton myDeleteButton; private JButton myResetToDefault; @@ -146,7 +144,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi @Override public void propertyChange(@NotNull final PropertyChangeEvent evt) { if (evt.getPropertyName().equals("ancestor") && evt.getNewValue() != null && evt.getOldValue() == null && myQuickListsModified) { - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); myQuickListsModified = false; } } @@ -185,9 +183,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi myEditor.getComboBox().addActionListener(new ActionListener() { @Override public void actionPerformed(@NotNull ActionEvent e) { - if (myEditor.getModel().getSelectedItem() != mySelectedKeymap) { - processCurrentKeymapChanged(myQuickLists); - } + currentKeymapChanged(); } }); panel.add(createKeymapNamePanel(), new GridBagConstraints(3, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 10, 0, 0), 0, 0)); @@ -207,10 +203,13 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi @Override public void processCurrentKeymapChanged(@NotNull QuickList[] ids) { myQuickLists = ids; + currentKeymapChanged(); + } + + private void currentKeymapChanged() { myResetToDefault.setEnabled(false); Keymap selectedKeymap = myEditor.getModel().getSelected(); - mySelectedKeymap = selectedKeymap; boolean editable = selectedKeymap != null && selectedKeymap.canModify(); myDeleteButton.setEnabled(editable); @@ -228,16 +227,12 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi } else { myBaseKeymapLabel.setText(KeyMapBundle.message("based.on.keymap.label", parent.getPresentableName())); - if (selectedKeymap.canModify() && ((KeymapImpl)mySelectedKeymap).getOwnActionIds().length > 0) { + if (selectedKeymap.canModify() && ((KeymapImpl)selectedKeymap).getOwnActionIds().length > 0) { myResetToDefault.setEnabled(true); } } - resetActionsTree(selectedKeymap); - } - - private void resetActionsTree(@NotNull Keymap keymap) { - myActionsTree.reset(myEditor.getMutable(keymap), myQuickLists); + myActionsTree.reset(selectedKeymap, myQuickLists); } private JPanel createKeymapButtonsPanel() { @@ -450,7 +445,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi private JPanel createFilteringPanel() { //noinspection ConstantConditions - resetActionsTree(myEditor.getModel().getSelected()); + myActionsTree.reset(myEditor.getModel().getSelected(), myQuickLists); final JLabel firstLabel = new JLabel(KeyMapBundle.message("filter.first.stroke.input")); final JCheckBox enable2Shortcut = new JCheckBox(KeyMapBundle.message("filter.second.stroke.input")); @@ -482,12 +477,11 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi }); IJSwingUtilities.adjustComponentsOnMac(firstLabel, firstShortcut); - //noinspection deprecation JPanel filterComponent = FormBuilder.createFormBuilder() .addLabeledComponent(firstLabel, firstShortcut, true) .addComponent(enable2Shortcut) .setVerticalGap(0) - .setIndent(5) + .setHorizontalGap(5) .addComponent(secondShortcut) .getPanel(); @@ -525,31 +519,21 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi myActionsTree.filter(option, myQuickLists); } - private void addKeyboardShortcut(Shortcut shortcut) { - String actionId = myActionsTree.getSelectedActionId(); - if (actionId == null) { - return; - } - - if (!createKeymapCopyIfNeeded()) return; + private void addKeyboardShortcut(@NotNull String actionId, @Nullable Shortcut shortcut) { + Keymap keymap = createKeymapCopyIfNeeded(); KeyboardShortcutDialog dialog = new KeyboardShortcutDialog(this, actionId, myQuickLists); - - - KeyboardShortcut selectedKeyboardShortcut = shortcut instanceof KeyboardShortcut ? (KeyboardShortcut)shortcut : null; - - dialog.setData(mySelectedKeymap, selectedKeyboardShortcut); + dialog.setData(keymap, shortcut instanceof KeyboardShortcut ? (KeyboardShortcut)shortcut : null); if (!dialog.showAndGet()) { return; } KeyboardShortcut keyboardShortcut = dialog.getKeyboardShortcut(); - if (keyboardShortcut == null) { return; } - Map> conflicts = mySelectedKeymap.getConflicts(actionId, keyboardShortcut); + Map> conflicts = keymap.getConflicts(actionId, keyboardShortcut); if (!conflicts.isEmpty()) { int result = Messages.showYesNoCancelDialog( this, @@ -563,7 +547,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi if (result == Messages.YES) { for (String id : conflicts.keySet()) { for (KeyboardShortcut s : conflicts.get(id)) { - mySelectedKeymap.removeShortcut(id, s); + keymap.removeShortcut(id, s); } } } @@ -573,20 +557,20 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi } // if shortcut is already registered to this action, just select it in the list - Shortcut[] shortcuts = mySelectedKeymap.getShortcuts(actionId); + Shortcut[] shortcuts = keymap.getShortcuts(actionId); for (Shortcut s : shortcuts) { if (s.equals(keyboardShortcut)) { return; } } - mySelectedKeymap.addShortcut(actionId, keyboardShortcut); + keymap.addShortcut(actionId, keyboardShortcut); if (StringUtil.startsWithChar(actionId, '$')) { - mySelectedKeymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), keyboardShortcut); + keymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), keyboardShortcut); } repaintLists(); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); } private void addMouseShortcut(Shortcut shortcut, ShortcutRestrictions restrictions) { @@ -595,14 +579,14 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi return; } - if (!createKeymapCopyIfNeeded()) return; + Keymap keymap = createKeymapCopyIfNeeded(); MouseShortcut mouseShortcut = shortcut instanceof MouseShortcut ? (MouseShortcut)shortcut : null; MouseShortcutDialog dialog = new MouseShortcutDialog( this, mouseShortcut, - mySelectedKeymap, + keymap, actionId, myActionsTree.getMainGroup(), restrictions @@ -617,7 +601,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi return; } - String[] actionIds = mySelectedKeymap.getActionIds(mouseShortcut); + String[] actionIds = keymap.getActionIds(mouseShortcut); if (actionIds.length > 1 || (actionIds.length == 1 && !actionId.equals(actionIds[0]))) { int result = Messages.showYesNoCancelDialog( this, @@ -630,7 +614,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi if (result == Messages.YES) { for (String id : actionIds) { - mySelectedKeymap.removeShortcut(id, mouseShortcut); + keymap.removeShortcut(id, mouseShortcut); } } else if (result != Messages.NO) { @@ -640,50 +624,51 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi // if shortcut is already registered to this action, just select it in the list - Shortcut[] shortcuts = mySelectedKeymap.getShortcuts(actionId); + Shortcut[] shortcuts = keymap.getShortcuts(actionId); for (Shortcut shortcut1 : shortcuts) { if (shortcut1.equals(mouseShortcut)) { return; } } - mySelectedKeymap.addShortcut(actionId, mouseShortcut); + keymap.addShortcut(actionId, mouseShortcut); if (StringUtil.startsWithChar(actionId, '$')) { - mySelectedKeymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), mouseShortcut); + keymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), mouseShortcut); } repaintLists(); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); } private void repaintLists() { myActionsTree.getComponent().repaint(); } - private boolean createKeymapCopyIfNeeded() { - if (mySelectedKeymap.canModify()) return true; - - final Keymap selectedKeymap = myEditor.getModel().getSelected(); - if (selectedKeymap == null) { - return false; + @NotNull + private Keymap createKeymapCopyIfNeeded() { + Keymap keymap = myEditor.getModel().getSelected(); + assert keymap != null; + if (keymap.canModify()) { + Keymap mutable = myEditor.getMutable(keymap); + myActionsTree.setKeymap(mutable); + return mutable; } - KeymapImpl newKeymap = ((KeymapImpl)selectedKeymap).deriveKeymap(); - - String newKeymapName = KeyMapBundle.message("new.keymap.name", selectedKeymap.getPresentableName()); + String newKeymapName = KeyMapBundle.message("new.keymap.name", keymap.getPresentableName()); if (!tryNewKeymapName(newKeymapName)) { for (int i = 0; ; i++) { - newKeymapName = KeyMapBundle.message("new.indexed.keymap.name", selectedKeymap.getPresentableName(), i); + newKeymapName = KeyMapBundle.message("new.indexed.keymap.name", keymap.getPresentableName(), i); if (tryNewKeymapName(newKeymapName)) { break; } } } + KeymapImpl newKeymap = ((KeymapImpl)keymap).deriveKeymap(); newKeymap.setName(newKeymapName); newKeymap.setCanModify(true); - final int indexOf = myEditor.getModel().getElementIndex(selectedKeymap); + int indexOf = myEditor.getModel().getElementIndex(keymap); if (indexOf >= 0) { myEditor.getModel().add(indexOf + 1, newKeymap); } @@ -692,28 +677,8 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi } myEditor.getModel().setSelectedItem(newKeymap); - processCurrentKeymapChanged(myQuickLists); - - return true; - } - - private void removeShortcut(Shortcut shortcut) { - String actionId = myActionsTree.getSelectedActionId(); - if (actionId == null) { - return; - } - - if (!createKeymapCopyIfNeeded()) return; - - if (shortcut == null) return; - - mySelectedKeymap.removeShortcut(actionId, shortcut); - if (StringUtil.startsWithChar(actionId, '$')) { - mySelectedKeymap.removeShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), shortcut); - } - - repaintLists(); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); + return newKeymap; } private void copyKeymap() { @@ -738,7 +703,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi myEditor.getModel().add(newKeymap); myEditor.getModel().setSelectedItem(newKeymap); myEditor.getComboBox().getEditor().selectAll(); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); } private boolean tryNewKeymapName(String name) { @@ -759,7 +724,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi } myEditor.getModel().remove(keymap); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); } private void resetKeymap() { @@ -768,7 +733,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi return; } ((KeymapImpl)keymap).clearOwnActionsIds(); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); } @Override @@ -784,11 +749,11 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi myNonEnglishKeyboardSupportOption.setSelected(KeyboardSettingsExternalizable.getInstance().isNonEnglishKeyboardSupportEnabled()); } - mySelectedKeymap = null; + Keymap selectedKeymap = null; List list = getManagerKeymaps(); for (Keymap keymap : list) { - if (mySelectedKeymap == null && keymap == KeymapManagerEx.getInstanceEx().getActiveKeymap()) { - mySelectedKeymap = keymap; + if (selectedKeymap == null && keymap == KeymapManagerEx.getInstanceEx().getActiveKeymap()) { + selectedKeymap = keymap; } } myEditor.reset(list); @@ -797,23 +762,23 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi KeymapImpl keymap = new KeymapImpl(); keymap.setName(KeyMapBundle.message("keymap.no.name")); myEditor.getModel().add(keymap); - mySelectedKeymap = keymap; + selectedKeymap = keymap; } - myEditor.getModel().setSelectedItem(mySelectedKeymap); + myEditor.getModel().setSelectedItem(selectedKeymap); - processCurrentKeymapChanged(myQuickLists); + currentKeymapChanged(); } @Override public void apply() throws ConfigurationException { - myEditor.ensureNonEmptyNames("Quick list should have non empty name"); + myEditor.ensureNonEmptyNames(KeyMapBundle.message("configuration.all.keymaps.should.have.non.empty.names.error.message")); ensureUniqueKeymapNames(); KeymapManagerImpl keymapManager = (KeymapManagerImpl)KeymapManager.getInstance(); // we must specify the same filter, which was used to get original items keymapManager.setKeymaps(myEditor.apply(), KEYMAP_FILTER); - keymapManager.setActiveKeymap(mySelectedKeymap); + keymapManager.setActiveKeymap(myEditor.getModel().getSelected()); ActionToolbarImpl.updateAllToolbarsImmediately(); } @@ -828,7 +793,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi @Override public boolean isModified() { - return !Comparing.equal(mySelectedKeymap, KeymapManager.getInstance().getActiveKeymap()) || myEditor.isModified(); + return !Comparing.equal(myEditor.getModel().getSelected(), KeymapManager.getInstance().getActiveKeymap()) || myEditor.isModified(); } @NotNull @@ -873,34 +838,59 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi @Nullable public Shortcut[] getCurrentShortcuts(@NotNull String actionId) { - return mySelectedKeymap == null ? null : mySelectedKeymap.getShortcuts(actionId); + Keymap keymap = myEditor.getModel().getSelected(); + return keymap == null ? null : keymap.getShortcuts(actionId); } private void editSelection(InputEvent e) { - final String actionId = myActionsTree.getSelectedActionId(); - if (actionId == null) return; + String actionId = myActionsTree.getSelectedActionId(); + if (actionId == null) { + return; + } + DefaultActionGroup group = createEditActionGroup(actionId); + if (e instanceof MouseEvent && ((MouseEvent)e).isPopupTrigger()) { + ActionManager.getInstance() + .createActionPopupMenu(ActionPlaces.UNKNOWN, group) + .getComponent() + .show(e.getComponent(), ((MouseEvent)e).getX(), ((MouseEvent)e).getY()); + } + else { + DataContext dataContext = DataManager.getInstance().getDataContext(this); + ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Edit Shortcuts", + group, + dataContext, + JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, + true); + + if (e instanceof MouseEvent) { + popup.show(new RelativePoint((MouseEvent)e)); + } + else { + popup.showInBestPositionFor(dataContext); + } + } + } + + @NotNull + private DefaultActionGroup createEditActionGroup(@NotNull final String actionId) { DefaultActionGroup group = new DefaultActionGroup(); - - final Shortcut[] shortcuts = getCurrentShortcuts(actionId); - final Set abbreviations = AbbreviationManager.getInstance().getAbbreviations(actionId); - final ShortcutRestrictions restrictions = ActionShortcutRestrictions.getInstance().getForActionId(actionId); - if (restrictions.allowKeyboardShortcut) { group.add(new DumbAwareAction("Add Keyboard Shortcut") { @Override public void actionPerformed(@NotNull AnActionEvent e) { - Shortcut firstKeyboard = null; - assert shortcuts != null; - for (Shortcut shortcut : shortcuts) { + Shortcut firstShortcut = null; + Keymap keymap = myEditor.getModel().getSelected(); + assert keymap != null; + for (Shortcut shortcut : keymap.getShortcuts(actionId)) { if (shortcut instanceof KeyboardShortcut) { - firstKeyboard = shortcut; + firstShortcut = shortcut; break; } } - addKeyboardShortcut(firstKeyboard); + addKeyboardShortcut(actionId, firstShortcut); } }); } @@ -910,8 +900,9 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi @Override public void actionPerformed(@NotNull AnActionEvent e) { Shortcut firstMouse = null; - assert shortcuts != null; - for (Shortcut shortcut : shortcuts) { + Keymap keymap = myEditor.getModel().getSelected(); + assert keymap != null; + for (Shortcut shortcut : keymap.getShortcuts(actionId)) { if (shortcut instanceof MouseShortcut) { firstMouse = shortcut; break; @@ -926,10 +917,9 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi group.add(new DumbAwareAction("Add Abbreviation") { @Override public void actionPerformed(@NotNull AnActionEvent e) { - final String abbr = Messages.showInputDialog("Enter new abbreviation:", "Abbreviation", null); + String abbr = Messages.showInputDialog("Enter new abbreviation:", "Abbreviation", null); if (abbr != null) { - String actionId = myActionsTree.getSelectedActionId(); - AbbreviationManager.getInstance().register(abbr, actionId); + AbbreviationManager.getInstance().register(abbr, myActionsTree.getSelectedActionId()); repaintLists(); } } @@ -943,29 +933,32 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi group.addSeparator(); - assert shortcuts != null; - for (final Shortcut shortcut : shortcuts) { + Keymap keymap = myEditor.getModel().getSelected(); + assert keymap != null; + for (final Shortcut shortcut : keymap.getShortcuts(actionId)) { group.add(new DumbAwareAction("Remove " + KeymapUtil.getShortcutText(shortcut)) { @Override public void actionPerformed(@NotNull AnActionEvent e) { - removeShortcut(shortcut); + Keymap keymap = createKeymapCopyIfNeeded(); + keymap.removeShortcut(actionId, shortcut); + if (StringUtil.startsWithChar(actionId, '$')) { + keymap.removeShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), shortcut); + } + + repaintLists(); + currentKeymapChanged(); } }); } if (Registry.is("actionSystem.enableAbbreviations")) { - for (final String abbreviation : abbreviations) { + for (final String abbreviation : AbbreviationManager.getInstance().getAbbreviations(actionId)) { group.addAction(new DumbAwareAction("Remove Abbreviation '" + abbreviation + "'") { @Override public void actionPerformed(@NotNull AnActionEvent e) { AbbreviationManager.getInstance().remove(abbreviation, actionId); repaintLists(); } - - @Override - public void update(@NotNull AnActionEvent e) { - super.update(e); - } }); } } @@ -973,37 +966,17 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi group.add(new DumbAwareAction("Reset Shortcuts") { @Override public void actionPerformed(@NotNull AnActionEvent e) { - ((KeymapImpl)mySelectedKeymap).clearOwnActionsId(actionId); - processCurrentKeymapChanged(myQuickLists); + ((KeymapImpl)createKeymapCopyIfNeeded()).clearOwnActionsId(actionId); + currentKeymapChanged(); repaintLists(); } @Override public void update(@NotNull AnActionEvent e) { - e.getPresentation().setVisible(mySelectedKeymap.canModify() && ((KeymapImpl)mySelectedKeymap).hasOwnActionId(actionId)); - super.update(e); + e.getPresentation().setVisible(((KeymapImpl)myEditor.getModel().getSelected()).hasOwnActionId(actionId)); } }); - if (e instanceof MouseEvent && ((MouseEvent)e).isPopupTrigger()) { - final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group); - popupMenu.getComponent().show(e.getComponent(), ((MouseEvent)e).getX(), ((MouseEvent)e).getY()); - } - else { - final DataContext dataContext = DataManager.getInstance().getDataContext(this); - final ListPopup popup = JBPopupFactory.getInstance() - .createActionGroupPopup("Edit Shortcuts", - group, - dataContext, - JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, - true); - - if (e instanceof MouseEvent) { - popup.show(new RelativePoint((MouseEvent)e)); - } - else { - popup.showInBestPositionFor(dataContext); - } - } + return group; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.form b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.form index bb2584ca890d..b19432465dff 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.form +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListPanel.form @@ -32,7 +32,7 @@ - + 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 666f9e74b657..27d2e6732741 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 @@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.ex.QuickList; +import com.intellij.openapi.actionSystem.ex.QuickListsManager; import com.intellij.openapi.keymap.KeyMapBundle; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.ui.*; @@ -36,7 +37,7 @@ class QuickListPanel { private final CollectionListModel actionsModel; private JPanel myPanel; private final JBList myActionsList; - JTextField myDisplayName; + JTextField myName; private JTextField myDescription; private JPanel myListPanel; QuickList item; @@ -97,10 +98,10 @@ class QuickListPanel { return; } - item.setName(myDisplayName.getText().trim()); + item.setName(myName.getText().trim()); item.setDescription(myDescription.getText().trim()); - ListModel model = getActionsList().getModel(); + ListModel model = myActionsList.getModel(); int size = model.getSize(); String[] ids; if (size == 0) { @@ -124,7 +125,8 @@ class QuickListPanel { return; } - myDisplayName.setText(this.item.getName()); + myName.setText(item.getName()); + myName.setEnabled(QuickListsManager.getInstance().getSchemeManager().isMetadataEditable(item)); myDescription.setText(item.getDescription()); actionsModel.removeAll(); @@ -140,18 +142,6 @@ class QuickListPanel { } } - public JList getActionsList() { - return myActionsList; - } - - public String getDescription() { - return myDescription.getText(); - } - - public String getDisplayName() { - return myDisplayName.getText(); - } - private void includeActionId(@NotNull String id) { if (QuickList.SEPARATOR_ID.equals(id) || actionsModel.getElementIndex(id) == -1) { actionsModel.add(id); diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsPanel.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsPanel.java index 28bf992eac38..18f2e8464277 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsPanel.java @@ -30,7 +30,7 @@ public class QuickListsPanel extends ConfigurableBase getSettings() { - return QuickListsManager.getInstance().getQuickLists(); + return QuickListsManager.getInstance().getSchemeManager().getAllSchemes(); } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsUi.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsUi.java index fedda315b451..62cb2d072fb3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsUi.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/QuickListsUi.java @@ -62,7 +62,13 @@ class QuickListsUi implements ConfigurableUi> { public String getName(@NotNull QuickList item) { return item.getName(); } + + @Override + public boolean isRemovable(@NotNull QuickList item) { + return QuickListsManager.getInstance().getSchemeManager().isMetadataEditable(item); + } }; + private final ListModelEditor editor = new ListModelEditor(itemEditor); private JComponent component; @@ -74,6 +80,8 @@ class QuickListsUi implements ConfigurableUi> { final CardLayout cardLayout = new CardLayout(); + // doesn't make any sense (and in any case scheme manager cannot preserve order) + editor.disableUpDownActions(); editor.getList().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { @@ -90,12 +98,12 @@ class QuickListsUi implements ConfigurableUi> { }); itemPanel = new QuickListPanel(editor.getModel()); - itemPanel.myDisplayName.getDocument().addDocumentListener(new DocumentAdapter() { + itemPanel.myName.getDocument().addDocumentListener(new DocumentAdapter() { @Override protected void textChanged(DocumentEvent e) { QuickList item = itemPanel.item; if (item != null) { - String name = itemPanel.myDisplayName.getText(); + String name = itemPanel.myName.getText(); boolean changed = !item.getName().equals(name); item.setName(name); if (changed) { diff --git a/platform/platform-impl/src/com/intellij/openapi/options/SchemeManagerImpl.kt b/platform/platform-impl/src/com/intellij/openapi/options/SchemeManagerImpl.kt new file mode 100644 index 000000000000..f2e4ac8eac7a --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/options/SchemeManagerImpl.kt @@ -0,0 +1,831 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.options + +import com.intellij.openapi.application.AccessToken +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.application.ex.DecodeDefaultsUtil +import com.intellij.openapi.application.writeAction +import com.intellij.openapi.components.RoamingType +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.components.impl.stores.DirectoryBasedStorage +import com.intellij.openapi.components.impl.stores.DirectoryStorageData +import com.intellij.openapi.components.impl.stores.StorageUtil +import com.intellij.openapi.components.impl.stores.StreamProvider +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.extensions.AbstractExtensionPointBean +import com.intellij.openapi.util.Comparing +import com.intellij.openapi.util.Condition +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtilRt +import com.intellij.openapi.vfs.* +import com.intellij.openapi.vfs.tracker.VirtualFileTracker +import com.intellij.util.PathUtil +import com.intellij.util.PathUtilRt +import com.intellij.util.SmartList +import com.intellij.util.ThrowableConvertor +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.io.URLUtil +import com.intellij.util.text.UniqueNameGenerator +import gnu.trove.THashMap +import gnu.trove.THashSet +import gnu.trove.TObjectObjectProcedure +import gnu.trove.TObjectProcedure +import org.jdom.Document +import org.jdom.Element +import java.io.File +import java.io.InputStream +import java.util.ArrayList +import java.util.Collections + +val LOG = Logger.getInstance(javaClass()) + +public class SchemeManagerImpl(private val fileSpec: String, + private val processor: SchemeProcessor, + private val roamingType: RoamingType, + private val provider: StreamProvider?, + private val ioDirectory: File) : SchemesManager(), SafeWriteRequestor { + private val schemes = ArrayList() + private val readOnlyExternalizableSchemes = THashMap() + + private var currentScheme: T? = null + private var currentSchemeName: String? = null + + private var directory: VirtualFile? = null + + private val schemeExtension: String + private val updateExtension: Boolean + + private val filesToDelete = THashSet() + + // scheme could be changed - so, hashcode will be changed - we must use identity hashing strategy + private val schemeToInfo = THashMap(ContainerUtil.identityStrategy()) + + init { + if (processor is SchemeExtensionProvider) { + schemeExtension = processor.getSchemeExtension() + updateExtension = processor.isUpgradeNeeded() + } + else { + schemeExtension = DirectoryStorageData.DEFAULT_EXT + updateExtension = false + } + + val application = ApplicationManager.getApplication() + val virtualFileTracker = ServiceManager.getService(javaClass()) + if (virtualFileTracker != null) { + val baseDirPath = ioDirectory.getAbsolutePath().replace(File.separatorChar, '/') + virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + baseDirPath, object : VirtualFileAdapter() { + override fun contentsChanged(event: VirtualFileEvent) { + if (event.getRequestor() != null || !isMy(event)) { + return + } + + val scheme = findExternalizableSchemeByFileName(event.getFile().getName()) + var oldCurrentScheme: T? = null + if (scheme != null) { + oldCurrentScheme = getCurrentScheme() + @suppress("UNCHECKED_CAST") + removeScheme(scheme as T) + processor.onSchemeDeleted(scheme) + } + + val readScheme = readSchemeFromFile(event.getFile(), false) + if (readScheme != null) { + processor.initScheme(readScheme) + processor.onSchemeAdded(readScheme) + + var newCurrentScheme = getCurrentScheme() + if (oldCurrentScheme != null && newCurrentScheme == null) { + setCurrentSchemeName(readScheme.getName()) + newCurrentScheme = getCurrentScheme() + } + + if (oldCurrentScheme !== newCurrentScheme) { + processor.onCurrentSchemeChanged(oldCurrentScheme) + } + } + } + + override fun fileCreated(event: VirtualFileEvent) { + if (event.getRequestor() == null) { + if (event.getFile().isDirectory()) { + val dir = getDirectory() + if (event.getFile() == dir) { + for (file in dir!!.getChildren()) { + if (isMy(file)) { + schemeCreatedExternally(file) + } + } + } + } + else if (isMy(event)) { + schemeCreatedExternally(event.getFile()) + } + } + } + + private fun schemeCreatedExternally(file: VirtualFile) { + val readScheme = readSchemeFromFile(file, false) + if (readScheme != null) { + processor.initScheme(readScheme) + processor.onSchemeAdded(readScheme) + } + } + + override fun fileDeleted(event: VirtualFileEvent) { + if (event.getRequestor() == null) { + if (event.getFile().isDirectory()) { + val dir = directory + if (event.getFile() == dir) { + directory = null + for (file in dir!!.getChildren()) { + if (isMy(file)) { + schemeDeletedExternally(file) + } + } + } + } + else if (isMy(event)) { + schemeDeletedExternally(event.getFile()) + } + } + } + + private fun schemeDeletedExternally(file: VirtualFile) { + val scheme = findExternalizableSchemeByFileName(file.getName()) + var oldCurrentScheme: T? = null + if (scheme != null) { + oldCurrentScheme = getCurrentScheme() + @suppress("UNCHECKED_CAST") + removeScheme(scheme as T) + processor.onSchemeDeleted(scheme) + } + + var newCurrentScheme = getCurrentScheme() + if (oldCurrentScheme != null && newCurrentScheme == null) { + if (!schemes.isEmpty()) { + setCurrentSchemeName(schemes.get(0).getName()) + newCurrentScheme = getCurrentScheme() + } + } + + if (oldCurrentScheme !== newCurrentScheme) { + processor.onCurrentSchemeChanged(oldCurrentScheme) + } + } + }, false, application) + } + } + + override fun loadBundledScheme(resourceName: String, requestor: Any, convertor: ThrowableConvertor) { + try { + val url = if (requestor is AbstractExtensionPointBean) + requestor.getLoaderForClass().getResource(resourceName) + else + DecodeDefaultsUtil.getDefaults(requestor, resourceName) + if (url == null) { + LOG.error("Cannot read scheme from $resourceName") + return + } + + val element = JDOMUtil.load(URLUtil.openStream(url)) + val scheme = convertor.convert(element) + if (scheme is ExternalizableScheme) { + val fileName = PathUtilRt.getFileName(url.getPath()) + val extension = getFileExtension(fileName, true) + val info = ExternalInfo(fileName.substring(0, fileName.length() - extension.length()), extension) + info.hash = JDOMUtil.getTreeHash(element, true) + info.schemeName = scheme.getName() + @suppress("UNCHECKED_CAST") + val oldInfo = schemeToInfo.put(scheme as E, info) + LOG.assertTrue(oldInfo == null) + val oldScheme = readOnlyExternalizableSchemes.put(scheme.getName(), scheme) + if (oldScheme != null) { + LOG.warn("Duplicated scheme ${scheme.getName()} - old: $oldScheme, new $scheme") + } + } + + schemes.add(scheme) + } + catch (e: Throwable) { + LOG.error("Cannot read scheme from $resourceName", e) + } + } + + private fun getFileExtension(fileName: CharSequence, allowAny: Boolean): String { + return if (StringUtilRt.endsWithIgnoreCase(fileName, schemeExtension)) { + schemeExtension + } + else if (StringUtilRt.endsWithIgnoreCase(fileName, DirectoryStorageData.DEFAULT_EXT)) { + DirectoryStorageData.DEFAULT_EXT + } + else if (allowAny) { + PathUtil.getFileExtension(fileName.toString())!! + } + else { + throw IllegalStateException("Scheme file extension $fileName is unknown, must be filtered out") + } + } + + private fun isMy(event: VirtualFileEvent) = isMy(event.getFile()) + + private fun isMy(file: VirtualFile) = StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), schemeExtension) + + override fun loadSchemes(): Collection { + val newSchemesOffset = schemes.size() + if (provider != null && provider.isEnabled()) { + provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input, readOnly -> + val scheme = loadScheme(name, input, true) + if (readOnly && scheme != null) { + readOnlyExternalizableSchemes.put(scheme.name, scheme) + } + true + } + } + else { + val dir = getDirectory() + val files = dir?.getChildren() + if (files != null) { + for (file in files) { + readSchemeFromFile(file, true) + } + } + } + + val list = SmartList() + for (i in newSchemesOffset..schemes.size() - 1) { + @suppress("UNCHECKED_CAST") + val scheme = schemes[i] as E + processor.initScheme(scheme) + list.add(scheme) + } + return list + } + + public fun reload() { + // we must not remove non-persistent (e.g. predefined) schemes, because we cannot load it (obviously) + for (i in schemes.indices.reversed()) { + val scheme = schemes.get(i) + @suppress("UNCHECKED_CAST") + if (scheme is ExternalizableScheme && getState(scheme as E) != BaseSchemeProcessor.State.NON_PERSISTENT) { + schemes.remove(i) + if (scheme === currentScheme) { + currentScheme = null + } + } + } + + retainExternalInfo(schemes) + + loadSchemes() + } + + private fun findExternalizableSchemeByFileName(fileName: String): E? { + for (scheme in schemes) { + @suppress("UNCHECKED_CAST") + if (scheme is ExternalizableScheme && fileName == "${getFileName(scheme)}$schemeExtension") { + return scheme as E + } + } + return null + } + + private fun isOverwriteOnLoad(existingScheme: E): Boolean { + if (readOnlyExternalizableSchemes.get(existingScheme.getName()) === existingScheme) { + // so, bundled scheme is shadowed + return true + } + + val info = schemeToInfo.get(existingScheme) + // scheme from file with old extension, so, we must ignore it + return info != null && schemeExtension != info.fileExtension + } + + private fun loadScheme(fileName: CharSequence, input: InputStream, duringLoad: Boolean): E? { + try { + val element = JDOMUtil.load(input) + @suppress("DEPRECATED_SYMBOL_WITH_MESSAGE", "UNCHECKED_CAST") + val scheme = (if (processor is BaseSchemeProcessor<*>) { + processor.readScheme(element, duringLoad) as E? + } + else { + processor.readScheme(Document(element.detach() as Element)) + }) ?: return null + + val extension = getFileExtension(fileName, false) + val fileNameWithoutExtension = fileName.subSequence(0, fileName.length() - extension.length()).toString() + if (duringLoad) { + if (filesToDelete.isNotEmpty() && filesToDelete.contains(fileName.toString())) { + LOG.warn("Scheme file $fileName is not loaded because marked to delete") + return null + } + + val existingScheme = findSchemeByName(scheme.getName()) + if (existingScheme != null) { + @suppress("UNCHECKED_CAST") + if (existingScheme is ExternalizableScheme && isOverwriteOnLoad(existingScheme as E)) { + removeScheme(existingScheme) + } + else { + // We don't load scheme with duplicated name - if we generate unique name for it, it will be saved then with new name. + // It is not what all can expect. Such situation in most cases indicates error on previous level, so, we just warn about it. + LOG.warn("Scheme file $fileName is not loaded because defines duplicated name ${scheme.getName()}") + return null + } + } + } + + var info: ExternalInfo? = schemeToInfo.get(scheme) + if (info == null) { + info = ExternalInfo(fileNameWithoutExtension, extension) + schemeToInfo.put(scheme, info) + } + else { + info.setFileNameWithoutExtension(fileNameWithoutExtension, extension) + } + info.hash = JDOMUtil.getTreeHash(element, true) + info.schemeName = scheme.getName() + + @suppress("UNCHECKED_CAST") + if (duringLoad) { + schemes.add(scheme as T) + } + else { + addScheme(scheme as T) + } + return scheme + } + catch (e: Exception) { + LOG.error("Cannot read scheme $fileName", e) + return null + } + } + + private fun getFileName(scheme: ExternalizableScheme) = schemeToInfo.get(scheme)?.fileNameWithoutExtension + + private fun canRead(name: CharSequence): Boolean { + return updateExtension && StringUtilRt.endsWithIgnoreCase(name, DirectoryStorageData.DEFAULT_EXT) || StringUtilRt.endsWithIgnoreCase(name, schemeExtension) + } + + private fun readSchemeFromFile(file: VirtualFile, duringLoad: Boolean): E? { + val fileName = file.getNameSequence() + if (file.isDirectory() || !canRead(fileName)) { + return null + } + + try { + return loadScheme(fileName, file.getInputStream(), duringLoad) + } + catch (e: Throwable) { + LOG.error("Cannot read scheme $fileName", e) + return null + } + } + + fun save(errors: MutableList) { + var hasSchemes = false + val nameGenerator = UniqueNameGenerator() + val schemesToSave = SmartList() + for (scheme in schemes) { + @suppress("UNCHECKED_CAST") + if (scheme is ExternalizableScheme) { + val state = getState(scheme as E) + if (state === BaseSchemeProcessor.State.NON_PERSISTENT) { + continue + } + + hasSchemes = true + + if (state !== BaseSchemeProcessor.State.UNCHANGED) { + schemesToSave.add(scheme) + } + + val fileName = getFileName(scheme) + if (fileName != null && !isRenamed(scheme)) { + nameGenerator.addExistingName(fileName) + } + } + } + + for (scheme in schemesToSave) { + errors.catch { + saveScheme(scheme, nameGenerator) + } + } + + val dir = getDirectory() + deleteFiles(dir, errors) + + if (!hasSchemes && dir != null) { + removeDirectoryIfEmpty(dir, errors) + } + } + + private fun removeDirectoryIfEmpty(dir: VirtualFile, errors: MutableList) { + for (file in dir.getChildren()) { + if (!file.`is`(VFileProperty.HIDDEN)) { + LOG.info("Directory " + dir.getNameSequence() + " is not deleted: at least one file " + file.getNameSequence() + " exists") + return + } + } + + LOG.info("Remove schemes directory " + dir.getNameSequence()) + directory = null + + val token = WriteAction.start() + try { + dir.delete(this) + } + catch (e: Throwable) { + errors.add(e) + } + finally { + token.finish() + } + } + + private fun getState(scheme: E): BaseSchemeProcessor.State { + return if (processor is BaseSchemeProcessor<*>) { + (processor as BaseSchemeProcessor).getState(scheme) + } + else { + @suppress("DEPRECATED_SYMBOL_WITH_MESSAGE") + if (processor.shouldBeSaved(scheme)) BaseSchemeProcessor.State.POSSIBLY_CHANGED else BaseSchemeProcessor.State.NON_PERSISTENT + } + } + + private fun saveScheme(scheme: E, nameGenerator: UniqueNameGenerator) { + var externalInfo: ExternalInfo? = schemeToInfo.get(scheme) + val currentFileNameWithoutExtension = if (externalInfo == null) null else externalInfo.fileNameWithoutExtension + val parent = processor.writeScheme(scheme) + val element = if (parent == null || parent is Element) parent as Element? else (parent as Document).detachRootElement() + if (JDOMUtil.isEmpty(element)) { + externalInfo?.scheduleDelete() + return + } + + var fileNameWithoutExtension = currentFileNameWithoutExtension + if (fileNameWithoutExtension == null || isRenamed(scheme)) { + fileNameWithoutExtension = nameGenerator.generateUniqueName(FileUtil.sanitizeName(scheme.getName())) + } + + val newHash = JDOMUtil.getTreeHash(element!!, true) + if (externalInfo != null && currentFileNameWithoutExtension === fileNameWithoutExtension && newHash == externalInfo.hash) { + return + } + + // save only if scheme differs from bundled + val bundledScheme = readOnlyExternalizableSchemes.get(scheme.getName()) + if (bundledScheme != null && schemeToInfo.get(bundledScheme)!!.hash == newHash) { + externalInfo?.scheduleDelete() + return + } + + val fileName = fileNameWithoutExtension!! + schemeExtension + // file will be overwritten, so, we don't need to delete it + filesToDelete.remove(fileName) + + // stream provider always use LF separator + val byteOut = StorageUtil.writeToBytes(element, "\n") + + var providerPath: String? + if (provider != null && provider.isEnabled()) { + providerPath = fileSpec + '/' + fileName + if (!provider.isApplicable(providerPath, roamingType)) { + providerPath = null + } + } + else { + providerPath = null + } + + // if another new scheme uses old name of this scheme, so, we must not delete it (as part of rename operation) + val renamed = externalInfo != null && fileNameWithoutExtension !== currentFileNameWithoutExtension && nameGenerator.value(currentFileNameWithoutExtension) + if (providerPath == null) { + var file: VirtualFile? = null + var dir = getDirectory() + if (dir == null || !dir.isValid()) { + dir = DirectoryBasedStorage.createDir(ioDirectory, this) + directory = dir!! + } + + if (renamed) { + file = dir.findChild(externalInfo!!.fileName) + if (file != null) { + writeAction { + file!!.rename(this, fileName) + } + } + } + + if (file == null) { + file = DirectoryBasedStorage.getFile(fileName, dir, this) + } + + writeAction { + file!!.getOutputStream(this).use { + byteOut.writeTo(it) + } + } + } + else { + if (renamed) { + externalInfo!!.scheduleDelete() + } + provider!!.saveContent(providerPath, byteOut.getInternalBuffer(), byteOut.size(), roamingType) + } + + if (externalInfo == null) { + externalInfo = ExternalInfo(fileNameWithoutExtension, schemeExtension) + schemeToInfo.put(scheme, externalInfo) + } + else { + externalInfo.setFileNameWithoutExtension(fileNameWithoutExtension, schemeExtension) + } + externalInfo.hash = newHash + externalInfo.schemeName = scheme.getName() + } + + + private fun ExternalInfo.scheduleDelete() { + filesToDelete.add(fileName) + } + + private fun isRenamed(scheme: ExternalizableScheme): Boolean { + val info = schemeToInfo.get(scheme) + return info != null && scheme.getName() != info.schemeName + } + + private fun deleteFiles(dir: VirtualFile?, errors: MutableList) { + if (filesToDelete.isEmpty()) { + return + } + + if (provider != null && provider.isEnabled()) { + for (name in filesToDelete) { + errors.catch { + StorageUtil.delete(provider, fileSpec + '/' + name, roamingType) + } + } + } + else if (dir != null) { + var token: AccessToken? = null + try { + for (file in dir.getChildren()) { + if (filesToDelete.contains(file.getName())) { + if (token == null) { + token = WriteAction.start() + } + + errors.catch { + file.delete(this) + } + } + } + } + finally { + if (token != null) { + token.finish() + } + } + } + + filesToDelete.clear() + } + + private fun getDirectory(): VirtualFile? { + var result = directory + if (result == null) { + result = LocalFileSystem.getInstance().findFileByIoFile(ioDirectory) + directory = result + } + return result + } + + override fun getRootDirectory() = ioDirectory + + override fun setSchemes(newSchemes: List, removeCondition: Condition?) { + if (removeCondition == null) { + schemes.clear() + } + else { + for (i in schemes.indices.reversed()) { + if (removeCondition.value(schemes.get(i))) { + schemes.remove(i) + } + } + } + + retainExternalInfo(newSchemes) + + schemes.addAll(newSchemes) + + if (currentSchemeName != null) { + currentScheme = findSchemeByName(currentSchemeName!!) + if (currentScheme != null) { + return + } + } + + currentScheme = schemes.firstOrNull() + currentSchemeName = currentScheme?.getName() + } + + private fun retainExternalInfo(newSchemes: List) { + if (schemeToInfo.isEmpty()) { + return + } + + schemeToInfo.retainEntries(object : TObjectObjectProcedure { + override fun execute(scheme: E, info: ExternalInfo): Boolean { + if (readOnlyExternalizableSchemes.get(scheme.getName()) == scheme) { + return true + } + + for (t in newSchemes) { + // by identity + if (t === scheme) { + if (filesToDelete.isNotEmpty()) { + filesToDelete.remove("${info.fileName}") + } + return true + } + } + + info.scheduleDelete() + return false + } + }) + } + + override fun addNewScheme(scheme: T, replaceExisting: Boolean) { + var toReplace = -1 + for (i in schemes.indices) { + val existing = schemes.get(i) + if (existing.getName() == scheme.getName()) { + if (!Comparing.equal>(existing.javaClass, scheme.javaClass)) { + LOG.warn("'${scheme.getName()}' ${existing.javaClass.getSimpleName()} replaced with ${scheme.javaClass.getSimpleName()}") + } + + toReplace = i + if (replaceExisting && existing is ExternalizableScheme) { + val oldInfo = schemeToInfo.remove(existing) + if (oldInfo != null && scheme is ExternalizableScheme && !schemeToInfo.containsKey(scheme)) { + @suppress("UNCHECKED_CAST") + schemeToInfo.put(scheme as E, oldInfo) + } + } + break + } + } + if (toReplace == -1) { + schemes.add(scheme) + } + else if (replaceExisting || scheme !is ExternalizableScheme) { + schemes.set(toReplace, scheme) + } + else { + scheme.renameScheme(UniqueNameGenerator.generateUniqueName(scheme.getName(), collectExistingNames(schemes))) + schemes.add(scheme) + } + + if (scheme is ExternalizableScheme && filesToDelete.isNotEmpty()) { + val info = schemeToInfo.get(scheme) + if (info != null) { + filesToDelete.remove("${info.fileName}") + } + } + } + + private fun collectExistingNames(schemes: Collection): Collection { + val result = THashSet(schemes.size()) + for (scheme in schemes) { + result.add(scheme.getName()) + } + return result + } + + override fun clearAllSchemes() { + schemeToInfo.forEachValue(object : TObjectProcedure { + override fun execute(info: ExternalInfo): Boolean { + info.scheduleDelete() + return true + } + }) + + currentScheme = null + schemes.clear() + schemeToInfo.clear() + } + + override fun getAllSchemes() = Collections.unmodifiableList(schemes) + + override fun findSchemeByName(schemeName: String): T? { + for (scheme in schemes) { + if (scheme.getName() == schemeName) { + return scheme + } + } + return null + } + + override fun setCurrentSchemeName(schemeName: String?) { + currentSchemeName = schemeName + currentScheme = null + } + + override fun getCurrentScheme(): T? { + var scheme = currentScheme + if (scheme == null && currentSchemeName != null) { + scheme = findSchemeByName(currentSchemeName!!) + if (scheme != null) { + currentScheme = scheme + } + } + return scheme + } + + override fun removeScheme(scheme: T) { + for (i in schemes.size() - 1 downTo 0) { + val s = schemes.get(i) + if (scheme.getName() == s.getName()) { + if (currentScheme == s) { + currentScheme = null + } + + if (s is ExternalizableScheme) { + schemeToInfo.remove(s)?.scheduleDelete() + } + schemes.remove(i) + break + } + } + } + + override fun getAllSchemeNames(): Collection { + if (schemes.isEmpty()) { + return emptyList() + } + + val names = ArrayList(schemes.size()) + for (scheme in schemes) { + names.add(scheme.getName()) + } + return names + } + + override fun isMetadataEditable(scheme: E) = !readOnlyExternalizableSchemes.containsKey(scheme.name) + + private class ExternalInfo(var fileNameWithoutExtension: String, var fileExtension: String?) { + // we keep it to detect rename + var schemeName: String? = null + var hash: Int = 0 + + val fileName: String + get() = "$fileNameWithoutExtension$fileExtension" + + fun setFileNameWithoutExtension(nameWithoutExtension: String, extension: String) { + fileNameWithoutExtension = nameWithoutExtension + fileExtension = extension + } + + override fun toString() = fileName + } + + override fun toString() = fileSpec +} + +private fun ExternalizableScheme.renameScheme(newName: String) { + if (newName != getName()) { + setName(newName) + LOG.assertTrue(newName == getName()) + } +} + +private inline fun MutableList.catch(runnable: () -> Unit) { + try { + runnable() + } + catch (e: Throwable) { + add(e) + } +} + +inline val Scheme.name: String + get() = getName() \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerFactoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerFactoryImpl.java index 2487b4773da3..575948fb3077 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerFactoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerFactoryImpl.java @@ -34,7 +34,7 @@ import java.util.List; public final class SchemesManagerFactoryImpl extends SchemesManagerFactory implements SettingsSavingComponent { private static final Logger LOG = Logger.getInstance(SchemesManagerFactoryImpl.class); - private final List myRegisteredManagers = ContainerUtil.createLockFreeCopyOnWriteList(); + private final List myRegisteredManagers = ContainerUtil.createLockFreeCopyOnWriteList(); @NotNull @Override @@ -44,13 +44,13 @@ public final class SchemesManagerFactoryImpl extends SchemesManagerFactory imple StateStorageManager storageManager = ((ApplicationImpl)ApplicationManager.getApplication()).getStateStore().getStateStorageManager(); String baseDirPath = storageManager.expandMacros(fileSpec); StreamProvider provider = storageManager.getStreamProvider(); - SchemesManagerImpl manager = new SchemesManagerImpl(fileSpec, processor, roamingType, provider, new File(baseDirPath)); + SchemeManagerImpl manager = new SchemeManagerImpl(fileSpec, processor, roamingType, provider, new File(baseDirPath)); myRegisteredManagers.add(manager); return manager; } - public void process(@NotNull Consumer processor) { - for (SchemesManagerImpl manager : myRegisteredManagers) { + public void process(@NotNull Consumer processor) { + for (SchemeManagerImpl manager : myRegisteredManagers) { try { processor.consume(manager); } @@ -62,15 +62,12 @@ public final class SchemesManagerFactoryImpl extends SchemesManagerFactory imple @Override public void save() { - List errors = null; - for (SchemesManager registeredManager : myRegisteredManagers) { + List errors = new SmartList(); + for (SchemeManagerImpl registeredManager : myRegisteredManagers) { try { - registeredManager.save(); + registeredManager.save(errors); } catch (Throwable e) { - if (errors == null) { - errors = new SmartList(); - } errors.add(e); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerImpl.java deleted file mode 100644 index 953530d998fb..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/options/SchemesManagerImpl.java +++ /dev/null @@ -1,949 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.options; - -import com.intellij.openapi.application.AccessToken; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.WriteAction; -import com.intellij.openapi.application.ex.DecodeDefaultsUtil; -import com.intellij.openapi.components.RoamingType; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.components.impl.stores.DirectoryBasedStorage; -import com.intellij.openapi.components.impl.stores.DirectoryStorageData; -import com.intellij.openapi.components.impl.stores.StorageUtil; -import com.intellij.openapi.components.impl.stores.StreamProvider; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.AbstractExtensionPointBean; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.JDOMUtil; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtilRt; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.tracker.VirtualFileTracker; -import com.intellij.util.PathUtil; -import com.intellij.util.PathUtilRt; -import com.intellij.util.SmartList; -import com.intellij.util.ThrowableConvertor; -import com.intellij.util.io.URLUtil; -import com.intellij.util.lang.CompoundRuntimeException; -import com.intellij.util.text.UniqueNameGenerator; -import gnu.trove.*; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.Parent; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.util.*; - -public final class SchemesManagerImpl extends SchemesManager implements SafeWriteRequestor { - private static final Logger LOG = Logger.getInstance(SchemesManagerFactoryImpl.class); - - private final ArrayList mySchemes = new ArrayList(); - private volatile T myCurrentScheme; - @Nullable - private String myCurrentSchemeName; - - private final String myFileSpec; - private final SchemeProcessor myProcessor; - private final RoamingType myRoamingType; - - private final StreamProvider provider; - private final File myIoDir; - private VirtualFile myDir; - - private final String mySchemeExtension; - private final boolean myUpdateExtension; - - private final Set filesToDelete = new THashSet(); - - // scheme could be changed - so, hashcode will be changed - we must use identity hashing strategy - @SuppressWarnings("unchecked") - private final THashMap schemeToInfo = new THashMap(TObjectHashingStrategy.IDENTITY); - - public SchemesManagerImpl(@NotNull String fileSpec, - @NotNull SchemeProcessor processor, - @NotNull RoamingType roamingType, - @Nullable StreamProvider provider, - @NotNull File baseDir) { - myFileSpec = fileSpec; - myProcessor = processor; - myRoamingType = roamingType; - this.provider = provider; - myIoDir = baseDir; - if (processor instanceof SchemeExtensionProvider) { - mySchemeExtension = ((SchemeExtensionProvider)processor).getSchemeExtension(); - myUpdateExtension = ((SchemeExtensionProvider)processor).isUpgradeNeeded(); - } - else { - mySchemeExtension = DirectoryStorageData.DEFAULT_EXT; - myUpdateExtension = false; - } - - Application application = ApplicationManager.getApplication(); - VirtualFileTracker virtualFileTracker = application == null ? null : ServiceManager.getService(VirtualFileTracker.class); - if (virtualFileTracker != null) { - final String baseDirPath = myIoDir.getAbsolutePath().replace(File.separatorChar, '/'); - virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + baseDirPath, new VirtualFileAdapter() { - @Override - public void contentsChanged(@NotNull VirtualFileEvent event) { - if (event.getRequestor() != null || !isMy(event)) { - return; - } - - E scheme = findExternalizableSchemeByFileName(event.getFile().getName()); - T oldCurrentScheme = null; - if (scheme != null) { - oldCurrentScheme = getCurrentScheme(); - //noinspection unchecked - removeScheme((T)scheme); - myProcessor.onSchemeDeleted(scheme); - } - - E readScheme = readSchemeFromFile(event.getFile(), null); - if (readScheme != null) { - myProcessor.initScheme(readScheme); - myProcessor.onSchemeAdded(readScheme); - - T newCurrentScheme = getCurrentScheme(); - if (oldCurrentScheme != null && newCurrentScheme == null) { - setCurrentSchemeName(readScheme.getName()); - newCurrentScheme = getCurrentScheme(); - } - - if (oldCurrentScheme != newCurrentScheme) { - myProcessor.onCurrentSchemeChanged(oldCurrentScheme); - } - } - } - - @Override - public void fileCreated(@NotNull VirtualFileEvent event) { - if (event.getRequestor() == null) { - if (event.getFile().isDirectory()) { - VirtualFile dir = getDirectory(); - if (event.getFile().equals(dir)) { - for (VirtualFile file : dir.getChildren()) { - if (isMy(file)) { - schemeCreatedExternally(file); - } - } - } - } - else if (isMy(event)) { - schemeCreatedExternally(event.getFile()); - } - } - } - - private void schemeCreatedExternally(@NotNull VirtualFile file) { - E readScheme = readSchemeFromFile(file, null); - if (readScheme != null) { - myProcessor.initScheme(readScheme); - myProcessor.onSchemeAdded(readScheme); - } - } - - @Override - public void fileDeleted(@NotNull VirtualFileEvent event) { - if (event.getRequestor() == null) { - if (event.getFile().isDirectory()) { - VirtualFile dir = myDir; - if (event.getFile().equals(dir)) { - myDir = null; - for (VirtualFile file : dir.getChildren()) { - if (isMy(file)) { - schemeDeletedExternally(file); - } - } - } - } - else if (isMy(event)) { - schemeDeletedExternally(event.getFile()); - } - } - } - - private void schemeDeletedExternally(@NotNull VirtualFile file) { - E scheme = findExternalizableSchemeByFileName(file.getName()); - T oldCurrentScheme = null; - if (scheme != null) { - oldCurrentScheme = getCurrentScheme(); - //noinspection unchecked - removeScheme((T)scheme); - myProcessor.onSchemeDeleted(scheme); - } - - T newCurrentScheme = getCurrentScheme(); - if (oldCurrentScheme != null && newCurrentScheme == null) { - if (!mySchemes.isEmpty()) { - setCurrentSchemeName(mySchemes.get(0).getName()); - newCurrentScheme = getCurrentScheme(); - } - } - - if (oldCurrentScheme != newCurrentScheme) { - myProcessor.onCurrentSchemeChanged(oldCurrentScheme); - } - } - }, false, application); - } - } - - @Override - public void loadBundledScheme(@NotNull String resourceName, @NotNull Object requestor, @NotNull ThrowableConvertor convertor) { - try { - URL url = requestor instanceof AbstractExtensionPointBean - ? (((AbstractExtensionPointBean)requestor).getLoaderForClass().getResource(resourceName)) - : DecodeDefaultsUtil.getDefaults(requestor, resourceName); - if (url == null) { - // Error shouldn't occur during this operation thus we report error instead of info - LOG.error("Cannot read scheme from " + resourceName); - return; - } - Element element = JDOMUtil.load(URLUtil.openStream(url)); - T scheme = convertor.convert(element); - - if (scheme instanceof ExternalizableScheme) { - String fileName = PathUtilRt.getFileName(url.getPath()); - String extension = getFileExtension(fileName, true); - ExternalInfo info = new ExternalInfo(fileName.substring(0, fileName.length() - extension.length()), extension); - info.hash = JDOMUtil.getTreeHash(element, true); - info.schemeName = scheme.getName(); - ExternalInfo oldInfo = schemeToInfo.put((ExternalizableScheme)scheme, info); - LOG.assertTrue(oldInfo == null); - } - - addNewScheme(scheme, false); - } - catch (Throwable e) { - LOG.error("Cannot read scheme from " + resourceName, e); - } - } - - @NotNull - private String getFileExtension(@NotNull CharSequence fileName, boolean allowAny) { - String extension; - if (StringUtilRt.endsWithIgnoreCase(fileName, mySchemeExtension)) { - extension = mySchemeExtension; - } - else if (StringUtilRt.endsWithIgnoreCase(fileName, DirectoryStorageData.DEFAULT_EXT)) { - extension = DirectoryStorageData.DEFAULT_EXT; - } - else if (allowAny) { - extension = PathUtil.getFileExtension(fileName.toString()); - LOG.assertTrue(extension != null); - } - else { - throw new IllegalStateException("Scheme file extension " + fileName + " is unknown, must be filtered out"); - } - return extension; - } - - private boolean isMy(@NotNull VirtualFileEvent event) { - return isMy(event.getFile()); - } - - private boolean isMy(@NotNull VirtualFile file) { - return StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), mySchemeExtension); - } - - @Override - @NotNull - public Collection loadSchemes() { - final Map result = new LinkedHashMap(); - if (provider != null && provider.isEnabled()) { - provider.processChildren(myFileSpec, myRoamingType, new Condition() { - @Override - public boolean value(@NotNull String name) { - return canRead(name); - } - }, new StreamProvider.ChildrenProcessor() { - @Override - public boolean process(@NotNull String name, @NotNull InputStream input) { - loadScheme(name, input, result); - return true; - } - }); - } - else { - VirtualFile dir = getDirectory(); - VirtualFile[] files = dir == null ? null : dir.getChildren(); - if (files != null) { - for (VirtualFile file : files) { - readSchemeFromFile(file, result); - } - } - } - - Collection list = result.values(); - for (E scheme : list) { - myProcessor.initScheme(scheme); - } - return list; - } - - public void reload() { - // we must not remove non-persistent (e.g. predefined) schemes, because we cannot load it (obviously) - for (int i = mySchemes.size() - 1; i >= 0; i--) { - T scheme = mySchemes.get(i); - //noinspection unchecked - if (scheme instanceof ExternalizableScheme && getState(((E)scheme)) != BaseSchemeProcessor.State.NON_PERSISTENT) { - mySchemes.remove(i); - if (scheme == myCurrentScheme) { - myCurrentScheme = null; - } - } - } - - retainExternalInfo(mySchemes); - - loadSchemes(); - } - - @Nullable - private E findExternalizableSchemeByFileName(@NotNull String fileName) { - for (T scheme : mySchemes) { - if (scheme instanceof ExternalizableScheme) { - if (fileName.equals((getFileName((ExternalizableScheme)scheme)) + mySchemeExtension)) { - //noinspection CastConflictsWithInstanceof,unchecked - return (E)scheme; - } - } - } - return null; - } - - private boolean isOverwriteOnLoad(@NotNull T existingScheme) { - if (existingScheme instanceof ExternalizableScheme) { - ExternalInfo info = schemeToInfo.get(existingScheme); - if (info != null && !mySchemeExtension.equals(info.fileExtension)) { - // scheme from file with old extension, so, we must ignore it - return true; - } - } - return false; - } - - @Nullable - private E loadScheme(@NotNull CharSequence fileName, @NotNull InputStream input, @Nullable Map result) { - boolean duringLoad = result != null; - try { - Element element = JDOMUtil.load(input); - E scheme; - if (myProcessor instanceof BaseSchemeProcessor) { - scheme = ((BaseSchemeProcessor)myProcessor).readScheme(element, duringLoad); - } - else { - //noinspection deprecation - scheme = myProcessor.readScheme(new Document((Element)element.detach())); - } - - if (scheme == null) { - return null; - } - - String extension = getFileExtension(fileName, false); - String fileNameWithoutExtension = fileName.subSequence(0, fileName.length() - extension.length()).toString(); - if (duringLoad) { - if (filesToDelete.contains(fileName.toString())) { - LOG.warn("Scheme file " + fileName + " is not loaded because marked to delete"); - return null; - } - - T existingScheme = findSchemeByName(scheme.getName()); - if (existingScheme != null) { - if (isOverwriteOnLoad(existingScheme)) { - removeScheme(existingScheme); - } - else { - // We don't load scheme with duplicated name - if we generate unique name for it, it will be saved then with new name. - // It is not what all can expect. Such situation in most cases indicates error on previous level, so, we just warn about it. - LOG.warn("Scheme file " + fileName + " is not loaded because defines duplicated name " + scheme.getName()); - return null; - } - } - } - - ExternalInfo info = schemeToInfo.get(scheme); - if (info == null) { - info = new ExternalInfo(fileNameWithoutExtension, extension); - schemeToInfo.put(scheme, info); - } - else { - info.setFileNameWithoutExtension(fileNameWithoutExtension, extension); - } - info.hash = JDOMUtil.getTreeHash(element, true); - info.schemeName = scheme.getName(); - - //noinspection unchecked - T s = (T)scheme; - if (duringLoad) { - mySchemes.add(s); - } - else { - addScheme(s); - } - - if (result != null) { - result.put(scheme.getName(), scheme); - } - return scheme; - } - catch (Exception e) { - LOG.error("Cannot read scheme " + fileName, e); - } - return null; - } - - @Nullable - private String getFileName(@NotNull ExternalizableScheme scheme) { - ExternalInfo info = schemeToInfo.get(scheme); - return info == null ? null : info.fileNameWithoutExtension; - } - - private boolean canRead(@NotNull CharSequence name) { - return myUpdateExtension && StringUtilRt.endsWithIgnoreCase(name, DirectoryStorageData.DEFAULT_EXT) || StringUtilRt.endsWithIgnoreCase(name, mySchemeExtension); - } - - @Nullable - private E readSchemeFromFile(@NotNull VirtualFile file, @Nullable Map result) { - CharSequence fileName = file.getNameSequence(); - if (file.isDirectory() || !canRead(fileName)) { - return null; - } - - try { - return loadScheme(fileName, file.getInputStream(), result); - } - catch (IOException e) { - LOG.error("Cannot read scheme " + fileName, e); - } - return null; - } - - @Override - public void save() { - boolean hasSchemes = false; - UniqueNameGenerator nameGenerator = new UniqueNameGenerator(); - List schemesToSave = new SmartList(); - for (T scheme : mySchemes) { - if (scheme instanceof ExternalizableScheme) { - //noinspection CastConflictsWithInstanceof,unchecked - E eScheme = (E)scheme; - BaseSchemeProcessor.State state = getState(eScheme); - if (state == BaseSchemeProcessor.State.NON_PERSISTENT) { - continue; - } - - hasSchemes = true; - - if (state != BaseSchemeProcessor.State.UNCHANGED) { - schemesToSave.add(eScheme); - } - - String fileName = getFileName(eScheme); - if (fileName != null && !isRenamed(eScheme)) { - nameGenerator.addExistingName(fileName); - } - } - } - - List errors = null; - - for (E scheme : schemesToSave) { - try { - saveScheme(scheme, nameGenerator); - } - catch (Throwable e) { - if (errors == null) { - errors = new SmartList(); - } - errors.add(e); - } - } - - VirtualFile dir = getDirectory(); - errors = deleteFiles(dir, errors); - - if (!hasSchemes && dir != null) { - errors = removeDirectoryIfEmpty(dir, errors); - } - - CompoundRuntimeException.doThrow(errors); - } - - @Nullable - private List removeDirectoryIfEmpty(@NotNull VirtualFile dir, @Nullable List errors) { - for (VirtualFile file : dir.getChildren()) { - if (!file.is(VFileProperty.HIDDEN)) { - LOG.info("Directory " + dir.getNameSequence() + " is not deleted: at least one file " + file.getNameSequence() + " exists"); - return errors; - } - } - - LOG.info("Remove schemes directory " + dir.getNameSequence()); - myDir = null; - - AccessToken token = WriteAction.start(); - try { - dir.delete(this); - } - catch (Throwable e) { - if (errors == null) { - errors = new SmartList(); - } - errors.add(e); - } - finally { - token.finish(); - } - return errors; - } - - @NotNull - private BaseSchemeProcessor.State getState(@NotNull E scheme) { - if (myProcessor instanceof BaseSchemeProcessor) { - return ((BaseSchemeProcessor)myProcessor).getState(scheme); - } - else { - //noinspection deprecation - return myProcessor.shouldBeSaved(scheme) ? BaseSchemeProcessor.State.POSSIBLY_CHANGED : BaseSchemeProcessor.State.NON_PERSISTENT; - } - } - - private void saveScheme(@NotNull E scheme, @NotNull UniqueNameGenerator nameGenerator) throws WriteExternalException, IOException { - @Nullable - ExternalInfo externalInfo = schemeToInfo.get(scheme); - String currentFileNameWithoutExtension = externalInfo == null ? null : externalInfo.fileNameWithoutExtension; - Parent parent = myProcessor.writeScheme(scheme); - Element element = parent == null || parent instanceof Element ? (Element)parent : ((Document)parent).detachRootElement(); - if (JDOMUtil.isEmpty(element)) { - if (externalInfo != null) { - scheduleDelete(externalInfo); - } - return; - } - - String fileNameWithoutExtension = currentFileNameWithoutExtension; - if (fileNameWithoutExtension == null || isRenamed(scheme)) { - fileNameWithoutExtension = nameGenerator.generateUniqueName(FileUtil.sanitizeName(scheme.getName())); - } - - int newHash = JDOMUtil.getTreeHash(element, true); - if (externalInfo != null && currentFileNameWithoutExtension == fileNameWithoutExtension && newHash == externalInfo.hash) { - return; - } - - String fileName = fileNameWithoutExtension + mySchemeExtension; - // file will be overwritten, so, we don't need to delete it - filesToDelete.remove(fileName); - - // stream provider always use LF separator - BufferExposingByteArrayOutputStream byteOut = StorageUtil.writeToBytes(element, "\n"); - - String providerPath; - if (provider != null && provider.isEnabled()) { - providerPath = myFileSpec + '/' + fileName; - if (!provider.isApplicable(providerPath, myRoamingType)) { - providerPath = null; - } - } - else { - providerPath = null; - } - - // if another new scheme uses old name of this scheme, so, we must not delete it (as part of rename operation) - boolean renamed = externalInfo != null && fileNameWithoutExtension != currentFileNameWithoutExtension && nameGenerator.value(currentFileNameWithoutExtension); - if (providerPath == null) { - VirtualFile file = null; - VirtualFile dir = getDirectory(); - if (dir == null || !dir.isValid()) { - dir = DirectoryBasedStorage.createDir(myIoDir, this); - myDir = dir; - } - - if (renamed) { - file = dir.findChild(externalInfo.fileNameWithoutExtension + externalInfo.fileExtension); - if (file != null) { - AccessToken token = WriteAction.start(); - try { - file.rename(this, fileName); - } - finally { - token.finish(); - } - } - } - - if (file == null) { - file = DirectoryBasedStorage.getFile(fileName, dir, this); - } - - AccessToken token = WriteAction.start(); - try { - OutputStream out = file.getOutputStream(this); - try { - byteOut.writeTo(out); - } - finally { - out.close(); - } - } - finally { - token.finish(); - } - } - else { - if (renamed) { - scheduleDelete(externalInfo); - } - provider.saveContent(providerPath, byteOut.getInternalBuffer(), byteOut.size(), myRoamingType); - } - - if (externalInfo == null) { - externalInfo = new ExternalInfo(fileNameWithoutExtension, mySchemeExtension); - schemeToInfo.put(scheme, externalInfo); - } - else { - externalInfo.setFileNameWithoutExtension(fileNameWithoutExtension, mySchemeExtension); - } - externalInfo.hash = newHash; - externalInfo.schemeName = scheme.getName(); - } - - - private void scheduleDelete(@NotNull ExternalInfo externalInfo) { - filesToDelete.add(externalInfo.fileNameWithoutExtension + externalInfo.fileExtension); - } - - private boolean isRenamed(@NotNull ExternalizableScheme scheme) { - ExternalInfo info = schemeToInfo.get(scheme); - return info != null && !scheme.getName().equals(info.schemeName); - } - - @Nullable - private List deleteFiles(@Nullable VirtualFile dir, List errors) { - if (filesToDelete.isEmpty()) { - return errors; - } - - if (provider != null && provider.isEnabled()) { - for (String name : filesToDelete) { - try { - StorageUtil.delete(provider, myFileSpec + '/' + name, myRoamingType); - } - catch (Throwable e) { - if (errors == null) { - errors = new SmartList(); - } - errors.add(e); - } - } - } - else if (dir != null) { - AccessToken token = null; - try { - for (VirtualFile file : dir.getChildren()) { - if (filesToDelete.contains(file.getName())) { - if (token == null) { - token = WriteAction.start(); - } - - try { - file.delete(this); - } - catch (IOException e) { - if (errors == null) { - errors = new SmartList(); - } - errors.add(e); - } - } - } - } - finally { - if (token != null) { - token.finish(); - } - } - } - - filesToDelete.clear(); - return errors; - } - - @Nullable - private VirtualFile getDirectory() { - VirtualFile result = myDir; - if (result == null) { - result = LocalFileSystem.getInstance().findFileByIoFile(myIoDir); - myDir = result; - } - return result; - } - - @Override - public File getRootDirectory() { - return myIoDir; - } - - private void schemeAdded(@NotNull T scheme) { - if (!(scheme instanceof ExternalizableScheme)) { - return; - } - - ExternalInfo info = schemeToInfo.get(scheme); - if (info != null) { - filesToDelete.remove(info.fileNameWithoutExtension + info.fileExtension); - } - } - - @Override - public void setSchemes(@NotNull final List newSchemes, @Nullable Condition removeCondition) { - if (removeCondition == null) { - mySchemes.clear(); - } - else { - for (int i = mySchemes.size() - 1; i >= 0; i--) { - T scheme = mySchemes.get(i); - if (removeCondition.value(scheme)) { - mySchemes.remove(i); - } - } - } - - retainExternalInfo(newSchemes); - - mySchemes.ensureCapacity(newSchemes.size()); - for (T scheme : newSchemes) { - mySchemes.add(scheme); - schemeAdded(scheme); - } - - if (myCurrentSchemeName != null) { - myCurrentScheme = findSchemeByName(myCurrentSchemeName); - if (myCurrentScheme != null) { - return; - } - } - - myCurrentScheme = mySchemes.isEmpty() ? null : mySchemes.get(0); - myCurrentSchemeName = myCurrentScheme == null ? null : myCurrentScheme.getName(); - } - - private void retainExternalInfo(@NotNull final List schemes) { - if (schemeToInfo.isEmpty()) { - return; - } - - schemeToInfo.retainEntries(new TObjectObjectProcedure() { - @Override - public boolean execute(ExternalizableScheme scheme, ExternalInfo info) { - for (T t : schemes) { - if (t == scheme) { - return true; - } - } - - scheduleDelete(info); - return false; - } - }); - } - - @Override - public void addNewScheme(@NotNull T scheme, boolean replaceExisting) { - int toReplace = -1; - for (int i = 0; i < mySchemes.size(); i++) { - T existing = mySchemes.get(i); - if (existing.getName().equals(scheme.getName())) { - if (!Comparing.equal(existing.getClass(), scheme.getClass())) { - LOG.warn("'" + scheme.getName() + "' " + existing.getClass().getSimpleName() + " replaced with " + scheme.getClass().getSimpleName()); - } - - toReplace = i; - if (replaceExisting && existing instanceof ExternalizableScheme) { - ExternalInfo oldInfo = schemeToInfo.remove((ExternalizableScheme)existing); - if (oldInfo != null && scheme instanceof ExternalizableScheme) { - ExternalInfo newInfo = schemeToInfo.get((ExternalizableScheme)scheme); - if (newInfo == null) { - schemeToInfo.put((ExternalizableScheme)scheme, oldInfo); - } - } - } - break; - } - } - if (toReplace == -1) { - mySchemes.add(scheme); - } - else if (replaceExisting || !(scheme instanceof ExternalizableScheme)) { - mySchemes.set(toReplace, scheme); - } - else { - //noinspection unchecked - renameScheme((ExternalizableScheme)scheme, UniqueNameGenerator.generateUniqueName(scheme.getName(), collectExistingNames(mySchemes))); - mySchemes.add(scheme); - } - - schemeAdded(scheme); - } - - @NotNull - private Collection collectExistingNames(@NotNull Collection schemes) { - Set result = new THashSet(schemes.size()); - for (T scheme : schemes) { - result.add(scheme.getName()); - } - return result; - } - - @Override - public void clearAllSchemes() { - schemeToInfo.forEachValue(new TObjectProcedure() { - @Override - public boolean execute(ExternalInfo info) { - scheduleDelete(info); - return true; - } - }); - - myCurrentScheme = null; - mySchemes.clear(); - schemeToInfo.clear(); - } - - @Override - @NotNull - public List getAllSchemes() { - return Collections.unmodifiableList(mySchemes); - } - - @Override - @Nullable - public T findSchemeByName(@NotNull String schemeName) { - for (T scheme : mySchemes) { - if (scheme.getName().equals(schemeName)) { - return scheme; - } - } - return null; - } - - @Override - public void setCurrentSchemeName(@Nullable String schemeName) { - myCurrentSchemeName = schemeName; - myCurrentScheme = null; - } - - @Override - @Nullable - public T getCurrentScheme() { - T scheme = myCurrentScheme; - if (scheme == null && myCurrentSchemeName != null) { - scheme = findSchemeByName(myCurrentSchemeName); - if (scheme != null) { - myCurrentScheme = scheme; - } - } - return scheme; - } - - @Override - public void removeScheme(@NotNull T scheme) { - for (int i = 0, n = mySchemes.size(); i < n; i++) { - T s = mySchemes.get(i); - if (scheme.getName().equals(s.getName())) { - if (myCurrentScheme == s) { - myCurrentScheme = null; - } - - if (s instanceof ExternalizableScheme) { - ExternalInfo info = schemeToInfo.remove((ExternalizableScheme)s); - if (info != null) { - scheduleDelete(info); - } - } - mySchemes.remove(i); - break; - } - } - } - - @Override - @NotNull - public Collection getAllSchemeNames() { - if (mySchemes.isEmpty()) { - return Collections.emptyList(); - } - - List names = new ArrayList(mySchemes.size()); - for (T scheme : mySchemes) { - names.add(scheme.getName()); - } - return names; - } - - private static void renameScheme(@NotNull ExternalizableScheme scheme, @NotNull String newName) { - if (!newName.equals(scheme.getName())) { - scheme.setName(newName); - LOG.assertTrue(newName.equals(scheme.getName())); - } - } - - private static final class ExternalInfo { - public ExternalInfo(@NotNull String fileNameWithoutExtension, @NotNull String fileExtension) { - this.fileNameWithoutExtension = fileNameWithoutExtension; - this.fileExtension = fileExtension; - } - - // we keep it to detect rename - private String schemeName; - - - @NotNull - private String fileNameWithoutExtension; - - private int hash; - - private String fileExtension; - - public void setFileNameWithoutExtension(@NotNull String nameWithoutExtension, @NotNull String extension) { - fileNameWithoutExtension = nameWithoutExtension; - fileExtension = extension; - } - - @Override - public String toString() { - return fileNameWithoutExtension; - } - } - - @Override - public String toString() { - return myFileSpec; - } -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/util/ui/CollectionModelEditor.java b/platform/platform-impl/src/com/intellij/util/ui/CollectionModelEditor.java index b3e0cbd6c396..f1dd95078ec2 100644 --- a/platform/platform-impl/src/com/intellij/util/ui/CollectionModelEditor.java +++ b/platform/platform-impl/src/com/intellij/util/ui/CollectionModelEditor.java @@ -25,6 +25,7 @@ import gnu.trove.TObjectObjectProcedure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.util.List; public abstract class CollectionModelEditor> implements ElementProducer { @@ -129,10 +130,7 @@ public abstract class CollectionModelEditor mutable = itemEditor.clone(item, false); modifiedToOriginal.put(mutable, item); originalToModified.put(item, mutable); - - List items = getItems(); - // silently replace item - items.set(index == -1 ? ContainerUtil.indexOfIdentity(items, item) : index, mutable); + silentlyReplaceItem(item, mutable, index); } return mutable; } @@ -146,4 +144,26 @@ public abstract class CollectionModelEditor modifiedToOriginal.forEachEntry(procedure); } } + + protected void silentlyReplaceItem(@NotNull T oldItem, @NotNull T newItem, int index) { + // silently replace item + List items = getItems(); + items.set(index == -1 ? ContainerUtil.indexOfIdentity(items, oldItem) : index, newItem); + } + + protected final boolean areSelectedItemsRemovable(@NotNull ListSelectionModel selectionMode) { + int minSelectionIndex = selectionMode.getMinSelectionIndex(); + int maxSelectionIndex = selectionMode.getMaxSelectionIndex(); + if (minSelectionIndex < 0 || maxSelectionIndex < 0) { + return false; + } + + List items = getItems(); + for (int i = minSelectionIndex; i <= maxSelectionIndex; i++) { + if (itemEditor.isRemovable(items.get(i))) { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/util/ui/ListModelEditor.java b/platform/platform-impl/src/com/intellij/util/ui/ListModelEditor.java index 42150a78dcc0..a93d61051905 100644 --- a/platform/platform-impl/src/com/intellij/util/ui/ListModelEditor.java +++ b/platform/platform-impl/src/com/intellij/util/ui/ListModelEditor.java @@ -15,6 +15,7 @@ */ package com.intellij.util.ui; +import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.util.containers.ContainerUtil; @@ -29,19 +30,19 @@ public class ListModelEditor extends ListModelEditorBase { private JBList list = new JBList(model); - public ListModelEditor(@NotNull final ListItemEditor itemEditor) { + public ListModelEditor(@NotNull ListItemEditor itemEditor) { super(itemEditor); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setCellRenderer(new MyListCellRenderer()); - toolbarDecorator = ToolbarDecorator.createDecorator(list) + toolbarDecorator = ToolbarDecorator.createDecorator(list, model) .setAddAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton button) { if (!model.isEmpty()) { T lastItem = model.getElementAt(model.getSize() - 1); - if (itemEditor.isEmpty(lastItem)) { + if (ListModelEditor.this.itemEditor.isEmpty(lastItem)) { ListScrollingUtil.selectItem(list, ContainerUtil.indexOfIdentity(model.getItems(), lastItem)); return; } @@ -51,7 +52,19 @@ public class ListModelEditor extends ListModelEditorBase { model.add(item); ListScrollingUtil.selectItem(list, ContainerUtil.indexOfIdentity(model.getItems(), item)); } - }); + }) + .setRemoveActionUpdater(new AnActionButtonUpdater() { + @Override + public boolean isEnabled(AnActionEvent e) { + return areSelectedItemsRemovable(list.getSelectionModel()); + } + }); + } + + @NotNull + public ListModelEditor disableUpDownActions() { + toolbarDecorator.disableUpDownActions(); + return this; } @NotNull diff --git a/platform/platform-impl/src/com/intellij/util/ui/ListModelEditorBase.java b/platform/platform-impl/src/com/intellij/util/ui/ListModelEditorBase.java index d46b94b73445..6ba98bf6648b 100644 --- a/platform/platform-impl/src/com/intellij/util/ui/ListModelEditorBase.java +++ b/platform/platform-impl/src/com/intellij/util/ui/ListModelEditorBase.java @@ -19,7 +19,6 @@ import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.CollectionListModel; import com.intellij.ui.MutableCollectionComboBoxModel; -import com.intellij.util.containers.ContainerUtil; import gnu.trove.TObjectObjectProcedure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -85,11 +84,7 @@ public abstract class ListModelEditorBase extends CollectionModelEditor extends CollectionModelEditor { @NotNull final List items() { return super.getInternalList(); } + void checkSelectionOnSilentReplace(@NotNull T oldItem, @NotNull T newItem) { + if (mySelection == oldItem) { + mySelection = newItem; + } + } + @Override protected void itemReplaced(@NotNull T existingItem, @Nullable T newItem) { helper.remove(existingItem); diff --git a/platform/platform-impl/src/com/intellij/util/ui/table/TableModelEditor.java b/platform/platform-impl/src/com/intellij/util/ui/table/TableModelEditor.java index 5d881b9e1777..486f68838b60 100644 --- a/platform/platform-impl/src/com/intellij/util/ui/table/TableModelEditor.java +++ b/platform/platform-impl/src/com/intellij/util/ui/table/TableModelEditor.java @@ -98,6 +98,7 @@ public class TableModelEditor extends CollectionModelEditor preferredScrollableViewportHeightInRows(int rows) { table.setPreferredScrollableViewportSize(new Dimension(200, table.getRowHeight() * rows)); return this; @@ -151,11 +152,13 @@ public class TableModelEditor extends CollectionModelEditor disableUpDownActions() { toolbarDecorator.disableUpDownActions(); return this; } + @NotNull public TableModelEditor enabled(boolean value) { table.setEnabled(value); return this; @@ -365,12 +368,7 @@ public class TableModelEditor extends CollectionModelEditor + \ No newline at end of file diff --git a/platform/platform-tests/testData/options/different-extensions/1.icls b/platform/platform-tests/testData/options/different-extensions/1.icls new file mode 100644 index 000000000000..2b2964d3870c --- /dev/null +++ b/platform/platform-tests/testData/options/different-extensions/1.icls @@ -0,0 +1 @@ + diff --git a/platform/platform-tests/testData/options/different-extensions/1.xml b/platform/platform-tests/testData/options/different-extensions/1.xml new file mode 100644 index 000000000000..22a3a0bd0441 --- /dev/null +++ b/platform/platform-tests/testData/options/different-extensions/1.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/platform/platform-tests/testData/options/options/1.xml b/platform/platform-tests/testData/options/options/1.xml new file mode 100644 index 000000000000..e4c594a71fec --- /dev/null +++ b/platform/platform-tests/testData/options/options/1.xml @@ -0,0 +1 @@ + diff --git a/platform/platform-tests/testData/options/options1/1.xml b/platform/platform-tests/testData/options/options1/1.xml new file mode 100644 index 000000000000..c023fb61fd3d --- /dev/null +++ b/platform/platform-tests/testData/options/options1/1.xml @@ -0,0 +1 @@ + diff --git a/platform/platform-tests/testData/options/options1/2.xml b/platform/platform-tests/testData/options/options1/2.xml new file mode 100644 index 000000000000..e5b1786c1ad0 --- /dev/null +++ b/platform/platform-tests/testData/options/options1/2.xml @@ -0,0 +1 @@ + diff --git a/platform/platform-tests/testSrc/bundledSchemes/default.xml b/platform/platform-tests/testSrc/bundledSchemes/default.xml new file mode 100644 index 000000000000..0cb9dad410e4 --- /dev/null +++ b/platform/platform-tests/testSrc/bundledSchemes/default.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/ApplicationStoreTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/ApplicationStoreTest.java deleted file mode 100644 index 5a47efde5c13..000000000000 --- a/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/ApplicationStoreTest.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.components.impl; - -import com.intellij.application.options.PathMacrosImpl; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.*; -import com.intellij.openapi.components.impl.stores.*; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.testFramework.LightPlatformTestCase; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.xmlb.XmlSerializerUtil; -import gnu.trove.THashMap; -import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; - -public class ApplicationStoreTest extends LightPlatformTestCase { - private File testAppConfig; - private MyComponentStore componentStore; - - @Override - public void setUp() throws Exception { - super.setUp(); - - String testAppConfigPath = System.getProperty("test.app.config.path"); - if (testAppConfigPath == null) { - testAppConfig = FileUtil.createTempDirectory("testAppSettings", null); - } - else { - testAppConfig = new File(FileUtil.expandUserHome(testAppConfigPath)); - } - FileUtil.delete(testAppConfig); - - componentStore = new MyComponentStore(testAppConfig.getAbsolutePath()); - } - - @Override - public void tearDown() throws Exception { - try { - Disposer.dispose(componentStore); - componentStore = null; - } - finally { - try { - super.tearDown(); - } - finally { - FileUtil.delete(testAppConfig); - } - } - } - - public void testStreamProviderSaveIfSeveralStoragesConfigured() throws Throwable { - SeveralStoragesConfigured component = new SeveralStoragesConfigured(); - - MyStreamProvider streamProvider = new MyStreamProvider(); - componentStore.getStateStorageManager().setStreamProvider(streamProvider); - - componentStore.initComponent(component, false); - component.foo = "newValue"; - StoreUtil.save(componentStore, null); - - assertThat( - streamProvider.data.get(RoamingType.PER_USER).get(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), - equalTo("\n" + - " \n" + - " \n" + - "")); - } - - public void testLoadFromStreamProvider() throws Exception { - SeveralStoragesConfigured component = new SeveralStoragesConfigured(); - - MyStreamProvider streamProvider = new MyStreamProvider(); - THashMap map = new THashMap(); - map.put(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml", "\n" + - " \n" + - " \n" + - ""); - streamProvider.data.put(RoamingType.PER_USER, map); - - componentStore.getStateStorageManager().setStreamProvider(streamProvider); - componentStore.initComponent(component, false); - assertThat(component.foo, equalTo("newValue")); - } - - public void testRemoveDeprecatedStorageOnWrite() throws Exception { - doRemoveDeprecatedStorageOnWrite(new SeveralStoragesConfigured()); - } - - public void testRemoveDeprecatedStorageOnWrite2() throws Exception { - doRemoveDeprecatedStorageOnWrite(new ActualStorageLast()); - } - - private void doRemoveDeprecatedStorageOnWrite(@NotNull Foo component) throws IOException { - File oldFile = saveConfig("other.xml", "" + - " \n" + - " \n" + - ""); - - saveConfig("proxy.settings.xml", "\n" + - " \n" + - " \n" + - ""); - - componentStore.initComponent(component, false); - assertThat(component.foo, equalTo("new")); - - component.foo = "new2"; - StoreUtil.save(componentStore, null); - - assertThat(oldFile.exists(), equalTo(false)); - } - - @NotNull - private File saveConfig(@NotNull String fileName, @Language("XML") String data) throws IOException { - File file = new File(testAppConfig, fileName); - FileUtil.writeToFile(file, data); - return file; - } - - private static class MyStreamProvider extends StreamProvider { - public final Map> data = new THashMap>(); - - @Override - public void saveContent(@NotNull String fileSpec, - @NotNull byte[] content, - int size, - @NotNull RoamingType roamingType) { - getMap(roamingType).put(fileSpec, new String(content, 0, size, CharsetToolkit.UTF8_CHARSET)); - } - - private Map getMap(@NotNull RoamingType roamingType) { - Map map = data.get(roamingType); - if (map == null) { - map = new THashMap(); - data.put(roamingType, map); - } - return map; - } - - @Nullable - @Override - public InputStream loadContent(@NotNull String fileSpec, @NotNull RoamingType roamingType) throws IOException { - String data = getMap(roamingType).get(fileSpec); - return data == null ? null : new ByteArrayInputStream(data.getBytes(CharsetToolkit.UTF8_CHARSET)); - } - - @Override - public void delete(@NotNull String fileSpec, @NotNull RoamingType roamingType) { - Map map = data.get(roamingType); - if (map != null) { - map.remove(fileSpec); - } - } - } - - static class MyComponentStore extends ComponentStoreImpl implements Disposable { - private final StateStorageManager stateStorageManager; - - MyComponentStore(@NotNull final String testAppConfigPath) { - TrackingPathMacroSubstitutor macroSubstitutor = new ApplicationPathMacroManager().createTrackingSubstitutor(); - stateStorageManager = new StateStorageManagerImpl(macroSubstitutor, "application", this, ApplicationManager.getApplication().getPicoContainer()) { - @NotNull - @Override - protected StorageData createStorageData(@NotNull String fileSpec, @NotNull String filePath) { - return new StorageData("application"); - } - - @Nullable - @Override - protected String getOldStorageSpec(@NotNull Object component, @NotNull String componentName, @NotNull StateStorageOperation operation) { - return null; - } - - @Override - protected TrackingPathMacroSubstitutor getMacroSubstitutor(@NotNull final String fileSpec) { - if (fileSpec.equals(StoragePathMacros.APP_CONFIG + "/" + PathMacrosImpl.EXT_FILE_NAME + ".xml")) { - return null; - } - return super.getMacroSubstitutor(fileSpec); - } - }; - - stateStorageManager.addMacro(StoragePathMacros.APP_CONFIG, testAppConfigPath); - } - - @Override - public void load() { - } - - @NotNull - @Override - public StateStorageManager getStateStorageManager() { - return stateStorageManager; - } - - @Override - public void dispose() { - } - - @Nullable - @Override - protected PathMacroManager getPathMacroManagerForDefaults() { - return null; - } - - @NotNull - @Override - protected MessageBus getMessageBus() { - return ApplicationManager.getApplication().getMessageBus(); - } - } - - abstract static class Foo { - public String foo = "defaultValue"; - } - - @State( - name = "HttpConfigurable", - storages = { - @Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), - @Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true) - } - ) - static class SeveralStoragesConfigured extends Foo implements PersistentStateComponent { - @Nullable - @Override - public SeveralStoragesConfigured getState() { - return this; - } - - @Override - public void loadState(SeveralStoragesConfigured state) { - XmlSerializerUtil.copyBean(state, this); - } - } - - @State( - name = "HttpConfigurable", - storages = { - @Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true), - @Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml") - } - ) - static class ActualStorageLast extends Foo implements PersistentStateComponent { - @Nullable - @Override - public ActualStorageLast getState() { - return this; - } - - @Override - public void loadState(ActualStorageLast state) { - XmlSerializerUtil.copyBean(state, this); - } - } -} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/ApplicationStoreTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/ApplicationStoreTest.kt new file mode 100644 index 000000000000..2178f1fb4698 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/ApplicationStoreTest.kt @@ -0,0 +1,225 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.components.impl + +import com.intellij.application.options.PathMacrosImpl +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.* +import com.intellij.openapi.components.impl.stores.* +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.CharsetToolkit +import com.intellij.testFramework.LightPlatformTestCase +import com.intellij.util.messages.MessageBus +import com.intellij.util.xmlb.XmlSerializerUtil +import gnu.trove.THashMap +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.intellij.lang.annotations.Language +import java.io.ByteArrayInputStream +import java.io.File +import java.io.IOException +import java.io.InputStream + +public class ApplicationStoreTest : LightPlatformTestCase() { + private var testAppConfig: File? = null + private var componentStore: MyComponentStore? = null + + throws(Exception::class) + override fun setUp() { + super.setUp() + + val testAppConfigPath = System.getProperty("test.app.config.path") + if (testAppConfigPath == null) { + testAppConfig = FileUtil.createTempDirectory("testAppSettings", null) + } + else { + testAppConfig = File(FileUtil.expandUserHome(testAppConfigPath)) + } + FileUtil.delete(testAppConfig!!) + + componentStore = MyComponentStore(testAppConfig!!.getAbsolutePath()) + } + + throws(Exception::class) + override fun tearDown() { + try { + Disposer.dispose(componentStore!!) + componentStore = null + } + finally { + try { + super.tearDown() + } + finally { + FileUtil.delete(testAppConfig!!) + } + } + } + + public fun testStreamProviderSaveIfSeveralStoragesConfigured() { + val component = SeveralStoragesConfigured() + + val streamProvider = MyStreamProvider() + componentStore!!.getStateStorageManager().setStreamProvider(streamProvider) + + componentStore!!.initComponent(component, false) + component.foo = "newValue" + StoreUtil.save(componentStore!!, null) + + assertThat(streamProvider.data.get(RoamingType.PER_USER)!!.get(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), equalTo("\n" + " \n" + " \n" + "")) + } + + public fun testLoadFromStreamProvider() { + val component = SeveralStoragesConfigured() + + val streamProvider = MyStreamProvider() + val map = THashMap() + map.put(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml", "\n" + " \n" + " \n" + "") + streamProvider.data.put(RoamingType.PER_USER, map) + + componentStore!!.getStateStorageManager().setStreamProvider(streamProvider) + componentStore!!.initComponent(component, false) + assertThat(component.foo, equalTo("newValue")) + } + + public fun testRemoveDeprecatedStorageOnWrite() { + } + + public fun testRemoveDeprecatedStorageOnWrite2() { + doRemoveDeprecatedStorageOnWrite(ActualStorageLast()) + } + + private fun doRemoveDeprecatedStorageOnWrite(component: Foo) { + val oldFile = saveConfig("other.xml", "" + " \n" + " \n" + "") + + saveConfig("proxy.settings.xml", "\n" + " \n" + " \n" + "") + + componentStore!!.initComponent(component, false) + assertThat(component.foo, equalTo("new")) + + component.foo = "new2" + StoreUtil.save(componentStore!!, null) + + assertThat(oldFile.exists(), equalTo(false)) + } + + throws(IOException::class) + private fun saveConfig(fileName: String, Language("XML") data: String): File { + val file = File(testAppConfig, fileName) + FileUtil.writeToFile(file, data) + return file + } + + private class MyStreamProvider : StreamProvider { + public val data: MutableMap> = THashMap() + + override fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) { + getMap(roamingType).put(fileSpec, String(content, 0, size, CharsetToolkit.UTF8_CHARSET)) + } + + private fun getMap(roamingType: RoamingType): MutableMap { + var map = data.get(roamingType) + if (map == null) { + map = THashMap() + data.put(roamingType, map) + } + return map + } + + throws(IOException::class) + override fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream? { + val data = getMap(roamingType).get(fileSpec) + return if (data == null) null else ByteArrayInputStream(data.toByteArray(CharsetToolkit.UTF8_CHARSET)) + } + + override fun delete(fileSpec: String, roamingType: RoamingType) { + val map = data.get(roamingType) + map?.remove(fileSpec) + } + } + + class MyComponentStore(testAppConfigPath: String) : ComponentStoreImpl(), Disposable { + private val stateStorageManager: StateStorageManager + + init { + val macroSubstitutor = ApplicationPathMacroManager().createTrackingSubstitutor() + stateStorageManager = object : StateStorageManagerImpl(macroSubstitutor, "application", this, ApplicationManager.getApplication().getPicoContainer()) { + override fun createStorageData(fileSpec: String, filePath: String): StorageData { + return StorageData("application") + } + + override fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation): String? { + return null + } + + override fun getMacroSubstitutor(fileSpec: String): TrackingPathMacroSubstitutor? { + if (fileSpec == StoragePathMacros.APP_CONFIG + "/" + PathMacrosImpl.EXT_FILE_NAME + ".xml") { + return null + } + return super.getMacroSubstitutor(fileSpec) + } + } + + stateStorageManager.addMacro(StoragePathMacros.APP_CONFIG, testAppConfigPath) + } + + override fun load() { + } + + override fun getStateStorageManager(): StateStorageManager { + return stateStorageManager + } + + override fun dispose() { + } + + override fun getPathMacroManagerForDefaults(): PathMacroManager? { + return null + } + + override fun getMessageBus(): MessageBus { + return ApplicationManager.getApplication().getMessageBus() + } + } + + abstract class Foo { + public var foo: String = "defaultValue" + } + + State(name = "HttpConfigurable", storages = arrayOf(Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true))) + class SeveralStoragesConfigured : Foo(), PersistentStateComponent { + override fun getState(): SeveralStoragesConfigured? { + return this + } + + override fun loadState(state: SeveralStoragesConfigured) { + XmlSerializerUtil.copyBean(state, this) + } + } + + State(name = "HttpConfigurable", storages = arrayOf(Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true), Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"))) + class ActualStorageLast : Foo(), PersistentStateComponent { + override fun getState(): ActualStorageLast? { + return this + } + + override fun loadState(state: ActualStorageLast) { + XmlSerializerUtil.copyBean(state, this) + } + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/options/MockStreamProvider.kt b/platform/platform-tests/testSrc/com/intellij/options/MockStreamProvider.kt new file mode 100644 index 000000000000..e7ed0ff3b495 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/options/MockStreamProvider.kt @@ -0,0 +1,40 @@ +package com.intellij.options + +import com.intellij.openapi.components.RoamingType +import com.intellij.openapi.components.impl.stores.StreamProvider +import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.SmartList +import java.io.File +import java.io.FileInputStream +import java.io.InputStream + +class MockStreamProvider(private val myBaseDir: File) : StreamProvider { + override fun isApplicable(fileSpec: String, roamingType: RoamingType) = roamingType === RoamingType.PER_USER + + override fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) { + FileUtil.writeToFile(File(myBaseDir, fileSpec), content, 0, size) + } + + override fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream? { + val file = File(myBaseDir, fileSpec) + //noinspection IOResourceOpenedButNotSafelyClosed + return if (file.exists()) FileInputStream(file) else null + } + + override fun listSubFiles(fileSpec: String, roamingType: RoamingType): Collection { + if (roamingType !== RoamingType.PER_USER) { + return emptyList() + } + + val files = File(myBaseDir, fileSpec).listFiles() ?: return emptyList() + val names = SmartList() + for (file in files) { + names.add(file.getName()) + } + return names + } + + override fun delete(fileSpec: String, roamingType: RoamingType) { + FileUtil.delete(File(myBaseDir, fileSpec)) + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/options/SchemeManagerTest.kt b/platform/platform-tests/testSrc/com/intellij/options/SchemeManagerTest.kt new file mode 100644 index 000000000000..2cb9d57572d9 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/options/SchemeManagerTest.kt @@ -0,0 +1,368 @@ +package com.intellij.options + +import com.intellij.openapi.application.invokeAndWaitIfNeed +import com.intellij.openapi.components.RoamingType +import com.intellij.openapi.options.BaseSchemeProcessor +import com.intellij.openapi.options.ExternalizableScheme +import com.intellij.openapi.options.SchemeManagerImpl +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.util.SmartList +import com.intellij.util.lang.CompoundRuntimeException +import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters +import com.intellij.util.xmlb.XmlSerializer +import com.intellij.util.xmlb.annotations.Attribute +import com.intellij.util.xmlb.annotations.Tag +import com.intellij.util.xmlb.annotations.Transient +import gnu.trove.THashMap +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.not +import org.hamcrest.CoreMatchers.notNullValue +import org.hamcrest.CoreMatchers.sameInstance +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.collection.IsMapContaining.hasKey +import org.jdom.Element +import org.jetbrains.testFramework.FixtureRule +import org.jetbrains.testFramework.TemporaryDirectory +import org.jetbrains.testFramework.exists +import org.junit.Rule +import org.junit.Test +import java.io.File + +val FILE_SPEC = "REMOTE" + +class SchemeManagerTest { + private val fixtureManager = FixtureRule() + public Rule fun getFixtureManager(): FixtureRule = fixtureManager + + private val tempDirManager = TemporaryDirectory() + public Rule fun getTemporaryFolder(): TemporaryDirectory = tempDirManager + + private var localBaseDir: File? = null + private var remoteBaseDir: File? = null + + private fun getTestDataPath() = PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/platform/platform-tests/testData/options" + + public Test fun testLoadSchemes() { + doLoadSaveTest("options1", "1->first;2->second") + } + + public Test fun testLoadSimpleSchemes() { + doLoadSaveTest("options", "1->1") + } + + public Test fun testDeleteScheme() { + val manager = createAndLoad("options1") + manager.removeScheme(TestScheme("first")) + manager.save() + + checkSchemes("2->second") + } + + public Test fun testRenameScheme() { + val manager = createAndLoad("options1") + + val scheme = manager.findSchemeByName("first") + assertThat(scheme, notNullValue()) + assert(scheme != null) + scheme!!.setName("renamed") + manager.save() + + checkSchemes("2->second;renamed->renamed") + } + + public Test fun testRenameScheme2() { + val manager = createAndLoad("options1") + + val first = manager.findSchemeByName("first") + assertThat(first, notNullValue()) + assert(first != null) + first!!.setName("2") + val second = manager.findSchemeByName("second") + assertThat(second, notNullValue()) + assert(second != null) + second!!.setName("1") + manager.save() + + checkSchemes("1->1;2->2") + } + + public Test fun testDeleteRenamedScheme() { + val manager = createAndLoad("options1") + + val firstScheme = manager.findSchemeByName("first") + assertThat(firstScheme, notNullValue()) + assert(firstScheme != null) + firstScheme!!.setName("first_renamed") + manager.save() + + checkSchemes(File(remoteBaseDir, "REMOTE"), "first_renamed->first_renamed;2->second", true) + checkSchemes(localBaseDir!!, "", false) + + firstScheme.setName("first_renamed2") + manager.removeScheme(firstScheme) + manager.save() + + checkSchemes(File(remoteBaseDir, "REMOTE"), "2->second", true) + checkSchemes(localBaseDir!!, "", false) + } + + public Test fun testDeleteAndCreateSchemeWithTheSameName() { + val manager = createAndLoad("options1") + val firstScheme = manager.findSchemeByName("first") + assertThat(firstScheme, notNullValue()) + + manager.removeScheme(firstScheme!!) + manager.addScheme(TestScheme("first")) + manager.save() + checkSchemes("2->second;first->first") + } + + public Test fun testGenerateUniqueSchemeName() { + val manager = createAndLoad("options1") + val scheme = TestScheme("first") + manager.addNewScheme(scheme, false) + + assertThat("first2", equalTo(scheme.getName())) + } + + public Test fun `different extensions`() { + val dir = tempDirManager.newDirectory() + FileUtil.copyDir(File("${getTestDataPath()}/different-extensions"), dir, false) + val schemesManager = SchemeManagerImpl(FILE_SPEC, object: TestSchemesProcessor() { + override fun isUpgradeNeeded() = true + + override fun getSchemeExtension() = ".icls" + }, RoamingType.PER_USER, null, dir) + schemesManager.loadSchemes() + val schemes = schemesManager.getAllSchemes() + assertThat(schemes.size(), equalTo(1)) + val scheme = schemes.get(0) + assertThat(scheme.data, equalTo("true")) + + assertThat(File(dir, "1.icls"), exists()) + assertThat(File(dir, "1.xml"), exists()) + + scheme.data = "newTrue" + save(schemesManager) + + assertThat(File(dir, "1.icls"), exists()) + assertThat(File(dir, "1.xml"), not(exists())) + } + + public Test fun setSchemes() { + val dir = tempDirManager.newDirectory() + val schemesManager = createSchemeManager(dir) + schemesManager.loadSchemes() + assertThat(schemesManager.getAllSchemes().isEmpty(), equalTo(true)) + + val scheme = TestScheme("s1") + schemesManager.setSchemes(listOf(scheme)) + + val schemes = schemesManager.getAllSchemes() + assertThat(schemes.size(), equalTo(1)) + assertThat(schemes.get(0), sameInstance(scheme)) + + assertThat(File(dir, "s1.xml"), not(exists())) + + scheme.data = "newTrue" + save(schemesManager) + + assertThat(File(dir, "s1.xml"), exists()) + + schemesManager.setSchemes(emptyList()) + + save(schemesManager) + + assertThat(dir, not(exists())) + } + + public Test fun `save only if scheme differs from bundled`() { + val dir = tempDirManager.newDirectory() + var schemeManager = createSchemeManager(dir) + val converter: (Element) -> TestScheme = { XmlSerializer.deserialize(it, javaClass())!! } + val bundledPath = "/bundledSchemes/default" + schemeManager.loadBundledScheme(bundledPath, this, converter) + var schemes = schemeManager.getAllSchemes() + assertThat(schemes.size(), equalTo(1)) + val customScheme = TestScheme("default") + assertThat(schemes.get(0), equalTo(customScheme)) + + save(schemeManager) + assertThat(dir, not(exists())) + + save(schemeManager) + schemeManager.setSchemes(listOf(customScheme)) + assertThat(dir, not(exists())) + + schemes = schemeManager.getAllSchemes() + assertThat(schemes.size(), equalTo(1)) + assertThat(schemes.get(0), sameInstance(customScheme)) + + customScheme.data = "foo" + save(schemeManager) + val schemeFile = File(dir, "default.xml") + assertThat(schemeFile, exists()) + + schemeManager = createSchemeManager(dir) + schemeManager.loadBundledScheme(bundledPath, this, converter) + schemeManager.loadSchemes() + + schemes = schemeManager.getAllSchemes() + assertThat(schemes.get(0), equalTo(customScheme)) + } + + private fun createSchemeManager(dir: File) = SchemeManagerImpl(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir) + + public Test fun `don't remove dir if no schemes but at least one non-hidden file exists`() { + val dir = tempDirManager.newDirectory() + val schemesManager = SchemeManagerImpl(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir) + + val scheme = TestScheme("s1") + schemesManager.setSchemes(listOf(scheme)) + + save(schemesManager) + + val schemeFile = File(dir, "s1.xml") + assertThat(schemeFile.exists(), equalTo(true)) + + schemesManager.setSchemes(emptyList()) + + FileUtil.writeToFile(File(dir, "empty"), byteArrayOf()) + + save(schemesManager) + + assertThat(schemeFile.exists(), equalTo(false)) + assertThat(dir.exists(), equalTo(true)) + } + + public Test fun rename() { + val dir = tempDirManager.newDirectory() + val schemesManager = SchemeManagerImpl(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir) + schemesManager.loadSchemes() + assertThat(schemesManager.getAllSchemes().isEmpty(), equalTo(true)) + + val scheme = TestScheme("s1") + schemesManager.setSchemes(listOf(scheme)) + + val schemes = schemesManager.getAllSchemes() + assertThat(schemes.size(), equalTo(1)) + assertThat(schemes.get(0), sameInstance(scheme)) + + assertThat(File(dir, "s1.xml").exists(), equalTo(false)) + + scheme.data = "newTrue" + save(schemesManager) + + assertThat(File(dir, "s1.xml").exists(), equalTo(true)) + + scheme.setName("s2") + + save(schemesManager) + + assertThat(File(dir, "s1.xml").exists(), equalTo(false)) + assertThat(File(dir, "s2.xml").exists(), equalTo(true)) + } + + private fun save(schemeManager: SchemeManagerImpl) { + invokeAndWaitIfNeed { + schemeManager.save() + } + } + + private fun createAndLoad(testData: String): SchemeManagerImpl { + createTempFiles(testData) + return createAndLoad() + } + + private fun doLoadSaveTest(testData: String, expected: String, localExpected: String = "") { + val schemesManager = createAndLoad(testData) + schemesManager.save() + checkSchemes(File(remoteBaseDir, "REMOTE"), expected, true) + checkSchemes(localBaseDir!!, localExpected, false) + } + + private fun checkSchemes(expected: String) { + checkSchemes(File(remoteBaseDir, "REMOTE"), expected, true) + checkSchemes(localBaseDir!!, "", false) + } + + private fun createAndLoad(): SchemeManagerImpl { + val schemesManager = SchemeManagerImpl(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, MockStreamProvider(remoteBaseDir!!), localBaseDir!!) + schemesManager.loadSchemes() + return schemesManager + } + + private fun createTempFiles(testData: String) { + val temp = tempDirManager.newDirectory() + localBaseDir = File(temp, "__local") + remoteBaseDir = temp + FileUtil.copyDir(File("${getTestDataPath()}/$testData"), File(temp, "REMOTE")) + } +} + +private fun checkSchemes(baseDir: File, expected: String, ignoreDeleted: Boolean) { + val filesToScheme = StringUtil.split(expected, ";") + val fileToSchemeMap = THashMap() + for (fileToScheme in filesToScheme) { + val index = fileToScheme.indexOf("->") + fileToSchemeMap.put(fileToScheme.substring(0, index), fileToScheme.substring(index + 2)) + } + + val files = baseDir.listFiles() + if (files != null) { + for (file in files) { + val fileName = FileUtil.getNameWithoutExtension(file) + if ("--deleted" == fileName && ignoreDeleted) { + assertThat>(fileToSchemeMap, hasKey(fileName)) + } + } + } + + for (file in fileToSchemeMap.keySet()) { + assertThat(File(baseDir, "$file.xml").isFile(), equalTo(true)) + } + + if (files != null) { + val schemesProcessor = TestSchemesProcessor() + for (file in files) { + val fileName = FileUtil.getNameWithoutExtension(file) + val scheme = schemesProcessor.readScheme(JDOMUtil.load(file), true) + assertThat(fileToSchemeMap.get(fileName), equalTo(scheme!!.getName())) + } + } +} + +public data Tag("scheme") class TestScheme(Attribute private var name: String = "", Attribute var data: String? = null) : ExternalizableScheme { + override fun getName() = name + + override Transient fun setName(newName: String) { + name = newName + } + + @suppress("DEPRECATED_SYMBOL_WITH_MESSAGE") + override fun getExternalInfo() = null +} + +public open class TestSchemesProcessor : BaseSchemeProcessor() { + override fun readScheme(element: Element) = XmlSerializer.deserialize(element, javaClass()) + + override fun writeScheme(scheme: TestScheme) = scheme.serialize() +} + +fun SchemeManagerImpl<*, *>.save() { + val errors = SmartList() + save(errors) + CompoundRuntimeException.doThrow(errors) +} + +public fun T.serialize(): Element = XmlSerializer.serialize(this, SkipDefaultValuesSerializationFilters()) + +public fun Element.toByteArray(): ByteArray { + val out = BufferExposingByteArrayOutputStream(512) + JDOMUtil.writeParent(this, out, "\n") + return out.toByteArray() +} \ No newline at end of file diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt b/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt index a120d1267904..121d0428151d 100644 --- a/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt +++ b/platform/script-debugger/protocol/protocol-model-generator/src/Generator.kt @@ -88,7 +88,7 @@ class Generator(outputDir: String, rootPackage: String, requestClassName: String } fun resolveType(typedObject: ItemDescriptor, scope: ResolveAndGenerateScope): TypeDescriptor { - val optional = typedObject is ItemDescriptor.Named && (typedObject : ItemDescriptor.Named).optional() + val optional = typedObject is ItemDescriptor.Named && typedObject.optional() return switchByType(typedObject, object : TypeVisitor { override fun visitRef(refName: String): TypeDescriptor { return TypeDescriptor(resolveRefType(scope.getDomainName(), refName, scope.getTypeDirection()), optional) @@ -120,7 +120,7 @@ class Generator(outputDir: String, rootPackage: String, requestClassName: String } override fun visitArray(items: ProtocolMetaModel.ArrayItemType): TypeDescriptor { - val type = scope.resolveType(items).type + val type = scope.resolveType(items).type return TypeDescriptor(ListType(type), optional, false, type == BoxableType.ANY_STRING) } @@ -244,7 +244,7 @@ fun capitalizeFirstChar(s: String): String { } fun switchByType(typedObject: ItemDescriptor, visitor: TypeVisitor): R { - val refName = if (typedObject is ItemDescriptor.Referenceable) (typedObject : ItemDescriptor.Referenceable).ref() else null + val refName = if (typedObject is ItemDescriptor.Referenceable) typedObject.ref() else null if (refName != null) { return visitor.visitRef(refName) } @@ -265,7 +265,7 @@ fun switchByType(typedObject: ItemDescriptor, visitor: TypeVisitor): R { return visitor.visitObject(null) } - val properties = (typedObject : ItemDescriptor.Type).properties() + val properties = typedObject.properties() if (properties == null || properties.isEmpty()) { return visitor.visitMap() } diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt b/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt index 92760b3cf22f..aab52a4fa349 100644 --- a/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt +++ b/platform/script-debugger/protocol/protocol-model-generator/src/NamePath.kt @@ -7,7 +7,7 @@ data class NamePath(val lastComponent: String, val parent: NamePath? = null) { var current: NamePath? = this while (current != null) { res++ - current = current!!.parent + current = current.parent } } return res diff --git a/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt b/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt index 26bd05474c9d..7fb40a76594d 100644 --- a/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt +++ b/platform/script-debugger/protocol/protocol-model-generator/src/TypeMap.kt @@ -15,10 +15,7 @@ class TypeMap { private val typesToGenerate = ArrayList() fun resolve(domainName: String, typeName: String, direction: TypeData.Direction): BoxableType? { - val domainGenerator = domainGeneratorMap!!.get(domainName) - if (domainGenerator == null) { - throw RuntimeException("Failed to find domain generator: " + domainName) - } + val domainGenerator = domainGeneratorMap!!.get(domainName) ?: throw RuntimeException("Failed to find domain generator: " + domainName) return direction.get(getTypeData(domainName, typeName)).resolve(this, domainGenerator) } @@ -28,7 +25,7 @@ class TypeMap { fun generateRequestedTypes() { // size may grow during iteration - var list = typesToGenerate.copyToArray() + var list = typesToGenerate.toTypedArray() typesToGenerate.clear() while (true) { for (binding in list) { @@ -39,7 +36,7 @@ class TypeMap { break } else { - list = typesToGenerate.copyToArray() + list = typesToGenerate.toTypedArray() typesToGenerate.clear() } } @@ -52,6 +49,6 @@ class TypeMap { result = TypeData(typeName) map.put(key, result) } - return result!! + return result } } diff --git a/platform/testFramework/testSrc/FixtureRule.kt b/platform/testFramework/testSrc/FixtureRule.kt index 9edd6647a696..06506fa0c744 100644 --- a/platform/testFramework/testSrc/FixtureRule.kt +++ b/platform/testFramework/testSrc/FixtureRule.kt @@ -13,27 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.ide +package org.jetbrains.testFramework -import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.application.invokeAndWaitIfNeed import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory import org.junit.rules.ExternalResource -import javax.swing.SwingUtilities - -public fun invokeAndWaitIfNeed(runnable: () -> Unit) { - if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable) -} - -public inline fun runWriteAction(runnable: () -> Unit) { - val token = WriteAction.start() - try { - runnable() - } - finally { - token.finish() - } -} public class FixtureRule() : ExternalResource() { val projectFixture = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder().getFixture() diff --git a/platform/testFramework/testSrc/TemporaryDirectory.kt b/platform/testFramework/testSrc/TemporaryDirectory.kt new file mode 100644 index 000000000000..d49ca2587174 --- /dev/null +++ b/platform/testFramework/testSrc/TemporaryDirectory.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.testFramework + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.util.SmartList +import org.junit.rules.ExternalResource +import java.io.File + +public class TemporaryDirectory : ExternalResource() { + private val files = SmartList() + + override fun after() { + for (file in files) { + FileUtil.delete(file) + } + files.clear() + } + + public fun newDirectory(): File { + val file = FileUtilRt.generateRandomTemporaryPath() + files.add(file) + return file; + } +} diff --git a/platform/testFramework/testSrc/matchers.kt b/platform/testFramework/testSrc/matchers.kt new file mode 100644 index 000000000000..7f2fb364bba4 --- /dev/null +++ b/platform/testFramework/testSrc/matchers.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.testFramework + +import org.hamcrest.Description +import org.hamcrest.Factory +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeDiagnosingMatcher +import java.io.File + +public class FileExistenceMatcher(private val exists: Boolean) : TypeSafeDiagnosingMatcher(javaClass()) { + override fun matchesSafely(file: File, mismatchDescription: Description): Boolean { + if (exists == file.exists()) { + return true + } + + mismatchDescription.appendText("is not a file") + return false + } + + override fun describeTo(description: Description) { + description.appendText("an existing file") + } +} + +public Factory fun exists(): Matcher = FileExistenceMatcher(true) \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/containers/OrderedSet.java b/platform/util/src/com/intellij/util/containers/OrderedSet.java index 0ff04acbe345..e5ac58899dd9 100644 --- a/platform/util/src/com/intellij/util/containers/OrderedSet.java +++ b/platform/util/src/com/intellij/util/containers/OrderedSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,13 @@ public class OrderedSet extends ArrayList implements Set, RandomAccess this(ContainerUtil.canonicalStrategy()); } + public OrderedSet(@NotNull Collection set) { + super(set.size()); + + myHashSet = new OpenTHashSet(set.size()); + addAll(set); + } + public OrderedSet(@NotNull TObjectHashingStrategy hashingStrategy) { this(hashingStrategy, 4); } @@ -38,6 +45,12 @@ public class OrderedSet extends ArrayList implements Set, RandomAccess myHashSet = new OpenTHashSet(capacity, hashingStrategy); } + public OrderedSet(int capacity) { + super(capacity); + + myHashSet = new OpenTHashSet(capacity); + } + @Override public boolean removeAll(@NotNull Collection c) { boolean removed = false; diff --git a/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java b/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java index 6bc5f00ab044..39e4cd83abe5 100644 --- a/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java +++ b/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java @@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Map; /** - * Please use {@link SkipDefaultsSerializationFilter}, + * Please use {@link SkipDefaultsSerializationFilter} if state class doesn't implement "equals" (in Kotlin use data classes {@see http://kotlinlang.org/docs/reference/data-classes.html}) */ public class SkipDefaultValuesSerializationFilters extends SerializationFilterBase { private final Map, Object> myDefaultBeans = new THashMap, Object>();