[groovy] console: show module popup under the action button

- extract GroovySelectModuleStep
- add ability to create a popup but not show it
- refactor ModuleChooserUtil and GroovyConsoleUtil
- move strings to bundle
This commit is contained in:
Daniil Ovchinnikov
2017-05-10 18:54:19 +03:00
parent 1f46d1e297
commit 0205cd51d0
10 changed files with 243 additions and 194 deletions
@@ -385,3 +385,6 @@ trait.method.cannot.be.protected=Trait methods are not allowed to be protected
traits.are.not.supported.in.groovy.0=Traits are not supported in Groovy {0}
non.static.classes.not.allowed=Non-static inner classes are not allowed in traits
selfType.class.does.not.inherit=@SelfType: Class ''{0}'' does not inherit ''{1}''
select.module.description=Which module to use classpath of?
select.module.title=Select module...
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,30 +15,24 @@
*/
package org.jetbrains.plugins.groovy.console;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.config.AbstractConfigUtils;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.config.GroovyFacetUtil;
import org.jetbrains.plugins.groovy.util.LibrariesUtil;
import org.jetbrains.plugins.groovy.util.ModuleChooserUtil;
import java.util.Arrays;
import static org.jetbrains.plugins.groovy.console.GroovyConsoleUtilKt.getApplicableModules;
import static org.jetbrains.plugins.groovy.util.ModuleChooserUtil.formatModuleVersion;
public class GroovyConsoleUtil {
public static final Condition<Module> APPLICABLE_MODULE = module -> GroovyFacetUtil.isSuitableModule(module);
private static final Function<Module, String> MODULE_VERSION = module -> {
final String moduleGroovyHomePath = LibrariesUtil.getGroovyHomePath(module);
boolean bundled = moduleGroovyHomePath == null || !hasGroovyAll(module);
@@ -61,19 +55,12 @@ public class GroovyConsoleUtil {
public static void selectModuleAndRun(Project project, Consumer<Module> consumer) {
ModuleChooserUtil.selectModule(project,
ModuleChooserUtil.filterGroovyCompatibleModules(
Arrays.asList(ModuleManager.getInstance(project).getModules()), APPLICABLE_MODULE),
MODULE_VERSION, consumer);
getApplicableModules(project),
GroovyConsoleUtil::getTitle, consumer);
}
public static void selectModuleAndRun(Project project, Consumer<Module> consumer, DataContext context) {
ModuleChooserUtil.selectModule(project, ModuleChooserUtil.filterGroovyCompatibleModules(
Arrays.asList(ModuleManager.getInstance(project).getModules()), APPLICABLE_MODULE),
MODULE_VERSION, consumer, context);
}
@Contract("null -> null; !null -> !null")
public static String getTitle(@Nullable Module module) {
return module == null ? null : String.format("%s (%s)", module.getName(), MODULE_VERSION.fun(module));
@NotNull
public static String getTitle(@NotNull Module module) {
return formatModuleVersion(module, MODULE_VERSION.fun(module));
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.console
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import org.jetbrains.plugins.groovy.config.GroovyFacetUtil.isSuitableModule
import org.jetbrains.plugins.groovy.util.filterGroovyCompatibleModules
fun getApplicableModules(project: Project): List<Module> {
val modules = ModuleManager.getInstance(project).modules.toList()
return filterGroovyCompatibleModules(modules, ::isSuitableModule)
}
fun getAnyApplicableModule(project: Project): Module? {
return getApplicableModules(project).firstOrNull()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,22 +19,15 @@ import com.intellij.execution.console.ConsoleHistoryController;
import com.intellij.ide.scratch.ScratchFileService;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.console.GroovyConsole;
import org.jetbrains.plugins.groovy.console.GroovyConsoleRootType;
import org.jetbrains.plugins.groovy.util.ModuleChooserUtil;
import java.util.Arrays;
import java.util.List;
import static org.jetbrains.plugins.groovy.console.GroovyConsoleUtil.APPLICABLE_MODULE;
import static org.jetbrains.plugins.groovy.console.GroovyConsoleUtilKt.getAnyApplicableModule;
public class GrNewConsoleAction extends AnAction {
@@ -63,15 +56,6 @@ public class GrNewConsoleAction extends AnAction {
protected Module getModule(AnActionEvent e) {
final Project project = e.getProject();
if (project == null) return null;
final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext());
if (file != null) {
final Module moduleForFile = ModuleUtilCore.findModuleForFile(file, project);
if (moduleForFile != null) return moduleForFile;
}
final List<Module> modules = ModuleChooserUtil.filterGroovyCompatibleModules(
Arrays.asList(ModuleManager.getInstance(project).getModules()), APPLICABLE_MODULE);
return modules.isEmpty() ? null : modules.get(0);
return getAnyApplicableModule(project);
}
}
@@ -22,16 +22,18 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.Consumer
import org.jetbrains.plugins.groovy.GroovyBundle.message
import org.jetbrains.plugins.groovy.console.GroovyConsole
import org.jetbrains.plugins.groovy.console.GroovyConsoleStateService
import org.jetbrains.plugins.groovy.console.GroovyConsoleUtil
import org.jetbrains.plugins.groovy.console.GroovyConsoleUtil.getTitle
import org.jetbrains.plugins.groovy.console.getApplicableModules
import org.jetbrains.plugins.groovy.util.createSelectModulePopup
import org.jetbrains.plugins.groovy.util.removeUserData
class GrSelectModuleAction(
private val project: Project,
private val file: VirtualFile
) : AnAction(null, "Which module to use classpath of?", AllIcons.Nodes.Module) {
) : AnAction(message("select.module.title"), message("select.module.description"), AllIcons.Nodes.Module) {
private val consoleService by lazy {
GroovyConsoleStateService.getInstance(project)
@@ -39,19 +41,22 @@ class GrSelectModuleAction(
override fun update(e: AnActionEvent) {
val module = consoleService.getSelectedModule(file)
e.presentation.text = if (module == null || module.isDisposed) "Select module..." else GroovyConsoleUtil.getTitle(module)
if (module == null || module.isDisposed) return
e.presentation.text = getTitle(module)
}
override fun displayTextInToolbar(): Boolean {
return true
}
override fun displayTextInToolbar(): Boolean = true
override fun actionPerformed(e: AnActionEvent) {
GroovyConsoleUtil.selectModuleAndRun(project, Consumer { module: Module ->
if (consoleService.getSelectedModule(file) == module) return@Consumer
file.removeUserData(GroovyConsole.GROOVY_CONSOLE)?.stop()
consoleService.setFileModule(file, module)
ProjectView.getInstance(project).refresh()
}, e.dataContext)
val component = e.inputEvent?.component ?: return
val popup = createSelectModulePopup(project, getApplicableModules(project), ::getTitle, { moduleSelected(it) })
popup.showUnderneathOf(component)
}
private fun moduleSelected(module: Module) {
if (consoleService.getSelectedModule(file) == module) return
file.removeUserData(GroovyConsole.GROOVY_CONSOLE)?.stop()
consoleService.setFileModule(file, module)
ProjectView.getInstance(project).refresh()
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,10 +28,12 @@ import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.util.ModuleChooserUtil;
import java.util.Collection;
import java.util.List;
import static org.jetbrains.plugins.groovy.util.ModuleChooserUtil.formatModuleVersion;
public abstract class GroovyShellActionBase extends AnAction {
@@ -47,10 +49,10 @@ public abstract class GroovyShellActionBase extends AnAction {
// non-static to distinguish different module acceptability conditions
private final Key<CachedValue<Boolean>> APPLICABLE_MODULE_CACHE = Key.create("APPLICABLE_MODULE_CACHE");
private final Function<Module, String> VERSION_PROVIDER = new Function<Module, String>() {
private final Function<Module, String> TITLE_PROVIDER = new Function<Module, String>() {
@Override
public String fun(Module module) {
return myConfig.getVersion(module);
return formatModuleVersion(module, myConfig.getVersion(module));
}
};
@@ -86,9 +88,8 @@ public abstract class GroovyShellActionBase extends AnAction {
public void actionPerformed(AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
assert project != null;
Collection<Module> suitableModules = ModuleChooserUtil.filterGroovyCompatibleModules(myConfig.getPossiblySuitableModules(project),
APPLICABLE_MODULE);
ModuleChooserUtil.selectModule(project, suitableModules, VERSION_PROVIDER, RUNNER);
List<Module> suitableModules = ModuleChooserUtil.filterGroovyCompatibleModules(myConfig.getPossiblySuitableModules(project),
APPLICABLE_MODULE);
ModuleChooserUtil.selectModule(project, suitableModules, TITLE_PROVIDER, RUNNER);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.util
import com.intellij.openapi.module.Module
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.util.PlatformIcons
import org.jetbrains.plugins.groovy.GroovyBundle.message
class GroovySelectModuleStep(
modules: List<Module>,
titleProvider: (Module) -> String,
private val consumer: (Module) -> Unit
) : BaseListPopupStep<Module>(message("select.module.description"), modules, PlatformIcons.CONTENT_ROOT_ICON_CLOSED) {
private val myTitles = modules.associate { it to titleProvider(it) }
override fun getTextFor(value: Module): String = myTitles[value] ?: value.name
override fun getIndexedString(value: Module): String = value.name
override fun isSpeedSearchEnabled(): Boolean = true
override fun onChosen(selectedValue: Module, finalChoice: Boolean): PopupStep<*>? {
consumer(selectedValue)
return null
}
}
@@ -1,133 +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 org.jetbrains.plugins.groovy.util;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdkType;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ui.configuration.ModulesAlphaComparator;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.Condition;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class ModuleChooserUtil {
private static final String GROOVY_LAST_MODULE = "Groovy.Last.Module.Chosen";
public static void selectModule(@NotNull Project project,
final Collection<Module> suitableModules,
final Function<Module, String> versionProvider,
final Consumer<Module> callback) {
selectModule(project, suitableModules, versionProvider, callback, null);
}
public static void selectModule(@NotNull Project project,
final Collection<Module> suitableModules,
final Function<Module, String> versionProvider,
final Consumer<Module> callback,
@Nullable DataContext context) {
final List<Module> modules = new ArrayList<>();
final Map<Module, String> versions = new HashMap<>();
for (Module module : suitableModules) {
modules.add(module);
versions.put(module, versionProvider.fun(module));
}
if (modules.size() == 1) {
callback.consume(modules.get(0));
return;
}
Collections.sort(modules, ModulesAlphaComparator.INSTANCE);
BaseListPopupStep<Module> step =
new BaseListPopupStep<Module>("Which module to use classpath of?", modules, PlatformIcons.CONTENT_ROOT_ICON_CLOSED) {
@NotNull
@Override
public String getTextFor(Module value) {
return String.format("%s (%s)", value.getName(), versions.get(value));
}
@Override
public String getIndexedString(Module value) {
return value.getName();
}
@Override
public boolean isSpeedSearchEnabled() {
return true;
}
@Override
public PopupStep onChosen(Module selectedValue, boolean finalChoice) {
PropertiesComponent.getInstance(selectedValue.getProject()).setValue(GROOVY_LAST_MODULE, selectedValue.getName());
callback.consume(selectedValue);
return null;
}
};
final String lastModuleName = PropertiesComponent.getInstance(project).getValue(GROOVY_LAST_MODULE);
if (lastModuleName != null) {
int defaultOption = ContainerUtil.indexOf(modules, (Condition<Module>)module -> module.getName().equals(lastModuleName));
if (defaultOption >= 0) {
step.setDefaultOptionIndex(defaultOption);
}
}
final ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(step);
if (context == null) {
listPopup.showCenteredInCurrentWindow(project);
}
else {
listPopup.showInBestPositionFor(context);
}
}
@NotNull
private static Condition<Module> isGroovyCompatibleModule(final Condition<Module> condition) {
return module -> {
if (condition.value(module)) {
final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) {
return true;
}
}
return false;
};
}
public static List<Module> filterGroovyCompatibleModules(Collection<Module> modules, final Condition<Module> condition) {
return ContainerUtil.filter(modules, isGroovyCompatibleModule(condition));
}
public static boolean hasGroovyCompatibleModules(Collection<Module> modules, final Condition<Module> condition) {
return ContainerUtil.or(modules, isGroovyCompatibleModule(condition));
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.util
import com.intellij.openapi.util.Condition
inline infix fun <T> ((T) -> Boolean).and(crossinline another: (T) -> Boolean): (T) -> Boolean {
return {
this(it) && another(it)
}
}
fun <T> Condition<T>.toPredicate(): (T) -> Boolean {
return {
value(it)
}
}
@@ -0,0 +1,99 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("ModuleChooserUtil")
package org.jetbrains.plugins.groovy.util
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ui.configuration.ModulesAlphaComparator
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.ListPopup
import com.intellij.openapi.ui.popup.ListPopupStep
import com.intellij.openapi.util.Condition
import com.intellij.util.Consumer
import com.intellij.util.Function
import java.util.*
private val GROOVY_LAST_MODULE = "Groovy.Last.Module.Chosen"
fun selectModule(project: Project,
modules: List<Module>,
titleProvider: Function<Module, String>,
consumer: Consumer<Module>) {
modules.singleOrNull()?.let {
consumer.consume(it)
return
}
Collections.sort(modules, ModulesAlphaComparator.INSTANCE)
createSelectModulePopup(project, modules, { titleProvider.`fun`(it) }, { consumer.consume(it) }).showCenteredInCurrentWindow(project)
}
fun createSelectModulePopup(project: Project,
modules: List<Module>,
titleProvider: (Module) -> String,
consumer: (Module) -> Unit): ListPopup {
val step = createSelectModulePopupStep(project, modules, titleProvider, consumer)
return JBPopupFactory.getInstance().createListPopup(step)
}
fun createSelectModulePopupStep(project: Project,
modules: List<Module>,
titleProvider: (Module) -> String,
consumer: (Module) -> Unit): ListPopupStep<Module> {
val propertiesComponent = PropertiesComponent.getInstance(project)
val step = GroovySelectModuleStep(modules, titleProvider) {
propertiesComponent.setValue(GROOVY_LAST_MODULE, it.name)
consumer(it)
}
propertiesComponent.getValue(GROOVY_LAST_MODULE)?.let { lastModuleName ->
step.defaultOptionIndex = modules.indexOfFirst { it.name == lastModuleName }
}
return step
}
fun formatModuleVersion(module: Module, version: String): String = "${module.name} (${version})"
fun filterGroovyCompatibleModules(modules: Collection<Module>, condition: Condition<Module>): List<Module> {
return filterGroovyCompatibleModules(modules, condition.toPredicate())
}
fun filterGroovyCompatibleModules(modules: Collection<Module>, condition: (Module) -> Boolean): List<Module> {
return modules.filter(isGroovyCompatibleModule(condition))
}
fun hasGroovyCompatibleModules(modules: Collection<Module>, condition: Condition<Module>): Boolean {
return hasGroovyCompatibleModules(modules, condition.toPredicate())
}
fun hasGroovyCompatibleModules(modules: Collection<Module>, condition: (Module) -> Boolean): Boolean {
return modules.any(isGroovyCompatibleModule(condition))
}
private inline fun isGroovyCompatibleModule(crossinline condition: (Module) -> Boolean): (Module) -> Boolean {
val sdkTypeCheck = { it: Module ->
val sdk = ModuleRootManager.getInstance(it).sdk
sdk != null && sdk.sdkType is JavaSdkType
}
return sdkTypeCheck and condition
}