Obsolete API usages removed from 193 branch
#KT-35918 Comment
This commit is contained in:
committed by
Vladimir Dolzhenko
parent
312c7bc9bf
commit
dd73629db1
@@ -6,7 +6,7 @@
|
||||
package org.jetbrains.kotlin.idea.actions
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.ide.actions.ShowFilePathAction
|
||||
import com.intellij.ide.actions.RevealFileAction
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.editor.Editor
|
||||
@@ -30,18 +30,18 @@ class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware {
|
||||
openLogsDirIfPresent(project)
|
||||
}
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = ShowFilePathAction.isSupported()
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = RevealFileAction.isSupported()
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val presentation = e.presentation
|
||||
presentation.isEnabledAndVisible = e.project != null && ShowFilePathAction.isSupported()
|
||||
presentation.isEnabledAndVisible = e.project != null && RevealFileAction.isSupported()
|
||||
presentation.text = NAME
|
||||
}
|
||||
|
||||
private fun openLogsDirIfPresent(project: Project) {
|
||||
val logsDir = findLogsDir()
|
||||
if (logsDir != null) {
|
||||
ShowFilePathAction.openDirectory(logsDir)
|
||||
RevealFileAction.openDirectory(logsDir)
|
||||
} else {
|
||||
val parent = WindowManager.getInstance().getStatusBar(project)?.component
|
||||
?: WindowManager.getInstance().findVisibleFrame().rootPane
|
||||
@@ -79,6 +79,6 @@ class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware {
|
||||
companion object {
|
||||
private const val gradleTroubleshootingLink = "https://docs.gradle.org/current/userguide/kotlin_dsl.html#troubleshooting"
|
||||
|
||||
val NAME = "Show Kotlin Gradle DSL Logs in ${ShowFilePathAction.getFileManagerName()}"
|
||||
val NAME = "Show Kotlin Gradle DSL Logs in ${RevealFileAction.getFileManagerName()}"
|
||||
}
|
||||
}
|
||||
+9
-6
@@ -34,6 +34,8 @@ import com.intellij.util.containers.MultiMap;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.concurrency.AsyncPromise;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettings;
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
|
||||
import org.jetbrains.kotlin.platform.TargetPlatformKt;
|
||||
@@ -76,15 +78,15 @@ class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
||||
"}\n";
|
||||
|
||||
@Override
|
||||
public void run(@NotNull Project project,
|
||||
public Promise<Result> run(@NotNull Project project,
|
||||
@NotNull ProjectTaskContext context,
|
||||
@Nullable ProjectTaskNotification callback,
|
||||
@NotNull Collection<? extends ProjectTask> tasks) {
|
||||
@NotNull ProjectTask ... tasks) {
|
||||
AsyncPromise<Result> resultPromise = new AsyncPromise<>();
|
||||
MultiMap<String, String> buildTasksMap = MultiMap.createLinkedSet();
|
||||
MultiMap<String, String> cleanTasksMap = MultiMap.createLinkedSet();
|
||||
MultiMap<String, String> initScripts = MultiMap.createLinkedSet();
|
||||
|
||||
Map<Class<? extends ProjectTask>, List<ProjectTask>> taskMap = JpsProjectTaskRunner.groupBy(tasks);
|
||||
Map<Class<? extends ProjectTask>, List<ProjectTask>> taskMap = JpsProjectTaskRunner.groupBy(Arrays.asList(tasks));
|
||||
|
||||
List<Module> modules = addModulesBuildTasks(taskMap.get(ModuleBuildTask.class), buildTasksMap, initScripts);
|
||||
// TODO there should be 'gradle' way to build files instead of related modules entirely
|
||||
@@ -95,7 +97,7 @@ class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
||||
AtomicInteger successCounter = new AtomicInteger();
|
||||
AtomicInteger errorCounter = new AtomicInteger();
|
||||
|
||||
TaskCallback taskCallback = callback == null ? null : new TaskCallback() {
|
||||
TaskCallback taskCallback = new TaskCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
handle(true);
|
||||
@@ -120,7 +122,7 @@ class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
||||
CompilerUtil.refreshOutputRoots(affectedRoots);
|
||||
}
|
||||
}
|
||||
callback.finished(new ProjectTaskResult(false, errors, 0));
|
||||
resultPromise.setResult(errors > 0 ? TaskRunnerResults.FAILURE : TaskRunnerResults.SUCCESS);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -168,6 +170,7 @@ class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
||||
ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID,
|
||||
taskCallback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
|
||||
}
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.gradle.execution;
|
||||
|
||||
import com.intellij.build.BuildViewManager;
|
||||
import com.intellij.compiler.impl.CompilerUtil;
|
||||
import com.intellij.execution.executors.DefaultRunExecutor;
|
||||
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
|
||||
import com.intellij.openapi.externalSystem.model.DataNode;
|
||||
import com.intellij.openapi.externalSystem.model.ProjectKeys;
|
||||
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
|
||||
import com.intellij.openapi.externalSystem.model.project.ModuleData;
|
||||
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
|
||||
import com.intellij.openapi.externalSystem.task.TaskCallback;
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootModificationTracker;
|
||||
import com.intellij.openapi.util.UserDataHolderBase;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.task.*;
|
||||
import com.intellij.task.impl.JpsProjectTaskRunner;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.SystemProperties;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettings;
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
|
||||
import org.jetbrains.kotlin.platform.TargetPlatformKt;
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform;
|
||||
import org.jetbrains.kotlin.platform.konan.KonanPlatformKt;
|
||||
import org.jetbrains.plugins.gradle.execution.build.CachedModuleDataFinder;
|
||||
import org.jetbrains.plugins.gradle.execution.build.GradleProjectTaskRunner;
|
||||
import org.jetbrains.plugins.gradle.service.project.GradleBuildSrcProjectsResolver;
|
||||
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil;
|
||||
import org.jetbrains.plugins.gradle.service.task.GradleTaskManager;
|
||||
import org.jetbrains.plugins.gradle.settings.GradleSettings;
|
||||
import org.jetbrains.plugins.gradle.util.GradleConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration.PROGRESS_LISTENER_KEY;
|
||||
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.*;
|
||||
import static com.intellij.openapi.util.text.StringUtil.*;
|
||||
import static org.jetbrains.kotlin.idea.gradle.execution.KotlinMPPGradleProjectTaskRunnerUtilKt.isDelegatedBuild;
|
||||
import static org.jetbrains.plugins.gradle.execution.GradleRunnerUtil.resolveProjectPath;
|
||||
|
||||
/**
|
||||
* This is a modified copy of {@link GradleProjectTaskRunner} that allows building Kotlin Common and Kotlin Native modules
|
||||
* in IDEA by delegating to Gradle builder ("Delegate IDE build/run actions to gradle"). See #KT-27295, #KT-27296.
|
||||
*
|
||||
* TODO: Refactor this class to remove duplicated logic when {@link GradleProjectTaskRunner} will allow extending it to
|
||||
* collect custom Gradle tasks. See #IDEA-204372, #KT-28880.
|
||||
*/
|
||||
class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
||||
{
|
||||
|
||||
@Language("Groovy")
|
||||
private static final String FORCE_COMPILE_TASKS_INIT_SCRIPT_TEMPLATE = "projectsEvaluated { \n" +
|
||||
" rootProject.findProject('%s')?.tasks?.withType(AbstractCompile) { \n" +
|
||||
" outputs.upToDateWhen { false } \n" +
|
||||
" } \n" +
|
||||
"}\n";
|
||||
|
||||
@Override
|
||||
public void run(@NotNull Project project,
|
||||
@NotNull ProjectTaskContext context,
|
||||
@Nullable ProjectTaskNotification callback,
|
||||
@NotNull Collection<? extends ProjectTask> tasks) {
|
||||
MultiMap<String, String> buildTasksMap = MultiMap.createLinkedSet();
|
||||
MultiMap<String, String> cleanTasksMap = MultiMap.createLinkedSet();
|
||||
MultiMap<String, String> initScripts = MultiMap.createLinkedSet();
|
||||
|
||||
Map<Class<? extends ProjectTask>, List<ProjectTask>> taskMap = JpsProjectTaskRunner.groupBy(tasks);
|
||||
|
||||
List<Module> modules = addModulesBuildTasks(taskMap.get(ModuleBuildTask.class), buildTasksMap, initScripts);
|
||||
// TODO there should be 'gradle' way to build files instead of related modules entirely
|
||||
List<Module> modulesOfFiles = addModulesBuildTasks(taskMap.get(ModuleFilesBuildTask.class), buildTasksMap, initScripts);
|
||||
|
||||
// TODO send a message if nothing to build
|
||||
Set<String> rootPaths = buildTasksMap.keySet();
|
||||
AtomicInteger successCounter = new AtomicInteger();
|
||||
AtomicInteger errorCounter = new AtomicInteger();
|
||||
|
||||
TaskCallback taskCallback = callback == null ? null : new TaskCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
handle(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
handle(false);
|
||||
}
|
||||
|
||||
private void handle(boolean success) {
|
||||
int successes = success ? successCounter.incrementAndGet() : successCounter.get();
|
||||
int errors = success ? errorCounter.get() : errorCounter.incrementAndGet();
|
||||
if (successes + errors == rootPaths.size()) {
|
||||
if (!project.isDisposed()) {
|
||||
// refresh on output roots is required in order for the order enumerator to see all roots via VFS
|
||||
final List<Module> affectedModules = ContainerUtil.concat(modules, modulesOfFiles);
|
||||
// have to refresh in case of errors too, because run configuration may be set to ignore errors
|
||||
Collection<String> affectedRoots = ContainerUtil.newHashSet(
|
||||
CompilerPathsEx.getOutputPaths(affectedModules.toArray(Module.EMPTY_ARRAY)));
|
||||
if (!affectedRoots.isEmpty()) {
|
||||
CompilerUtil.refreshOutputRoots(affectedRoots);
|
||||
}
|
||||
}
|
||||
callback.finished(new ProjectTaskResult(false, errors, 0));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO compiler options should be configurable
|
||||
@Language("Groovy")
|
||||
String compilerOptionsInitScript = "allprojects {\n" +
|
||||
" tasks.withType(JavaCompile) {\n" +
|
||||
" options.compilerArgs += [\"-Xlint:deprecation\"]\n" +
|
||||
" }" +
|
||||
"}\n";
|
||||
|
||||
String gradleVmOptions = GradleSettings.getInstance(project).getGradleVmOptions();
|
||||
for (String rootProjectPath : rootPaths) {
|
||||
Collection<String> buildTasks = buildTasksMap.get(rootProjectPath);
|
||||
if (buildTasks.isEmpty()) continue;
|
||||
Collection<String> cleanTasks = cleanTasksMap.get(rootProjectPath);
|
||||
|
||||
ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings();
|
||||
|
||||
File projectFile = new File(rootProjectPath);
|
||||
final String projectName;
|
||||
if (projectFile.isFile()) {
|
||||
projectName = projectFile.getParentFile().getName();
|
||||
}
|
||||
else {
|
||||
projectName = projectFile.getName();
|
||||
}
|
||||
String executionName = "Build " + projectName;
|
||||
settings.setExecutionName(executionName);
|
||||
settings.setExternalProjectPath(rootProjectPath);
|
||||
settings.setTaskNames(ContainerUtil.collect(ContainerUtil.concat(cleanTasks, buildTasks).iterator()));
|
||||
//settings.setScriptParameters(scriptParameters);
|
||||
settings.setVmOptions(gradleVmOptions);
|
||||
settings.setExternalSystemIdString(GradleConstants.SYSTEM_ID.getId());
|
||||
|
||||
UserDataHolderBase userData = new UserDataHolderBase();
|
||||
userData.putUserData(PROGRESS_LISTENER_KEY, BuildViewManager.class);
|
||||
|
||||
Collection<String> scripts = initScripts.getModifiable(rootProjectPath);
|
||||
scripts.add(compilerOptionsInitScript);
|
||||
userData.putUserData(GradleTaskManager.INIT_SCRIPT_KEY, join(scripts, SystemProperties.getLineSeparator()));
|
||||
userData.putUserData(GradleTaskManager.INIT_SCRIPT_PREFIX_KEY, executionName);
|
||||
|
||||
ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID,
|
||||
taskCallback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRun(@NotNull ProjectTask projectTask) {
|
||||
if (projectTask instanceof ModuleBuildTask) {
|
||||
final ModuleBuildTask moduleBuildTask = (ModuleBuildTask) projectTask;
|
||||
final Module module = moduleBuildTask.getModule();
|
||||
|
||||
if (! isDelegatedBuild(module)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) return false;
|
||||
|
||||
// ---------------------------------------- //
|
||||
// TODO BEGIN: Extract custom Kotlin logic. //
|
||||
// ---------------------------------------- //
|
||||
if (isProjectWithNativeSourceOrCommonProductionSourceModules(module.getProject())) return true;
|
||||
// ---------------------------------------- //
|
||||
// TODO END: Extract custom Kotlin logic. //
|
||||
// ---------------------------------------- //
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<Module> addModulesBuildTasks(@Nullable Collection<? extends ProjectTask> projectTasks,
|
||||
@NotNull MultiMap<String, String> buildTasksMap,
|
||||
@NotNull MultiMap<String, String> initScripts) {
|
||||
if (ContainerUtil.isEmpty(projectTasks)) return Collections.emptyList();
|
||||
|
||||
List<Module> affectedModules = new SmartList<>();
|
||||
Map<Module, String> rootPathsMap = FactoryMap.create(module -> notNullize(resolveProjectPath(module)));
|
||||
final CachedModuleDataFinder moduleDataFinder = new CachedModuleDataFinder();
|
||||
for (ProjectTask projectTask : projectTasks) {
|
||||
if (!(projectTask instanceof ModuleBuildTask)) continue;
|
||||
|
||||
ModuleBuildTask moduleBuildTask = (ModuleBuildTask)projectTask;
|
||||
Module module = moduleBuildTask.getModule();
|
||||
affectedModules.add(module);
|
||||
|
||||
final String rootProjectPath = rootPathsMap.get(module);
|
||||
if (isEmpty(rootProjectPath)) continue;
|
||||
|
||||
final String projectId = getExternalProjectId(module);
|
||||
if (projectId == null) continue;
|
||||
final String externalProjectPath = getExternalProjectPath(module);
|
||||
if (externalProjectPath == null || endsWith(externalProjectPath, "buildSrc")) continue;
|
||||
|
||||
final DataNode<? extends ModuleData> moduleDataNode = moduleDataFinder.findMainModuleData(module);
|
||||
if (moduleDataNode == null) continue;
|
||||
|
||||
// all buildSrc runtime projects will be built by gradle implicitly
|
||||
if (Boolean.parseBoolean(moduleDataNode.getData().getProperty(GradleBuildSrcProjectsResolver.BUILD_SRC_MODULE_PROPERTY))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String gradlePath = GradleProjectResolverUtil.getGradlePath(module);
|
||||
if (gradlePath == null) continue;
|
||||
String taskPrefix = endsWithChar(gradlePath, ':') ? gradlePath : (gradlePath + ':');
|
||||
|
||||
List<String> gradleTasks = ContainerUtil.mapNotNull(
|
||||
findAll(moduleDataNode, ProjectKeys.TASK), node ->
|
||||
node.getData().isInherited() ? null : trimStart(node.getData().getName(), taskPrefix));
|
||||
|
||||
Collection<String> projectInitScripts = initScripts.getModifiable(rootProjectPath);
|
||||
Collection<String> buildRootTasks = buildTasksMap.getModifiable(rootProjectPath);
|
||||
final String moduleType = getExternalModuleType(module);
|
||||
|
||||
if (!moduleBuildTask.isIncrementalBuild()) {
|
||||
projectInitScripts.add(String.format(FORCE_COMPILE_TASKS_INIT_SCRIPT_TEMPLATE, gradlePath));
|
||||
}
|
||||
String assembleTask = "assemble";
|
||||
if (GradleConstants.GRADLE_SOURCE_SET_MODULE_TYPE_KEY.equals(moduleType)) {
|
||||
String sourceSetName = GradleProjectResolverUtil.getSourceSetName(module);
|
||||
String gradleTask = isEmpty(sourceSetName) || "main".equals(sourceSetName) ? "classes" : sourceSetName + "Classes";
|
||||
if (gradleTasks.contains(gradleTask)) {
|
||||
buildRootTasks.add(taskPrefix + gradleTask);
|
||||
}
|
||||
else if ("main".equals(sourceSetName) || "test".equals(sourceSetName)) {
|
||||
buildRootTasks.add(taskPrefix + assembleTask);
|
||||
}
|
||||
// ---------------------------------------- //
|
||||
// TODO BEGIN: Extract custom Kotlin logic. //
|
||||
// ---------------------------------------- //
|
||||
else if (isNativeSourceModule(module)) {
|
||||
// Add tasks for Kotlin/Native.
|
||||
buildRootTasks.addAll(addPrefix(findNativeGradleBuildTasks(gradleTasks, sourceSetName), taskPrefix));
|
||||
}
|
||||
else if (isCommonProductionSourceModule(module)) {
|
||||
// Add tasks for compiling metadata.
|
||||
buildRootTasks.addAll(addPrefix(findMetadataBuildTasks(gradleTasks, sourceSetName), taskPrefix));
|
||||
}
|
||||
// ---------------------------------------- //
|
||||
// TODO END: Extract custom Kotlin logic. //
|
||||
// ---------------------------------------- //
|
||||
}
|
||||
else {
|
||||
if (gradleTasks.contains("classes")) {
|
||||
buildRootTasks.add(taskPrefix + "classes");
|
||||
buildRootTasks.add(taskPrefix + "testClasses");
|
||||
}
|
||||
else if (gradleTasks.contains(assembleTask)) {
|
||||
buildRootTasks.add(taskPrefix + assembleTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
return affectedModules;
|
||||
}
|
||||
|
||||
// ---------------------------------------- //
|
||||
// TODO BEGIN: Extract custom Kotlin logic. //
|
||||
// ---------------------------------------- //
|
||||
private static boolean isProjectWithNativeSourceOrCommonProductionSourceModules(Project project) {
|
||||
return CachedValuesManager.getManager(project).getCachedValue(
|
||||
project,
|
||||
() -> new CachedValueProvider.Result<>(
|
||||
Arrays.stream(ModuleManager.getInstance(project).getModules()).anyMatch(
|
||||
module -> isNativeSourceModule(module) || isCommonProductionSourceModule(module)
|
||||
),
|
||||
ProjectRootModificationTracker.getInstance(project)
|
||||
));
|
||||
}
|
||||
|
||||
private static boolean isNativeSourceModule(Module module) {
|
||||
final KotlinFacet kotlinFacet = KotlinFacet.Companion.get(module);
|
||||
if (kotlinFacet == null) return false;
|
||||
|
||||
final TargetPlatform platform = kotlinFacet.getConfiguration().getSettings().getTargetPlatform();
|
||||
if (platform == null) return false;
|
||||
|
||||
return KonanPlatformKt.isNative(platform);
|
||||
}
|
||||
|
||||
private static boolean isCommonProductionSourceModule(Module module) {
|
||||
final KotlinFacet kotlinFacet = KotlinFacet.Companion.get(module);
|
||||
if (kotlinFacet == null) return false;
|
||||
|
||||
final KotlinFacetSettings facetSettings = kotlinFacet.getConfiguration().getSettings();
|
||||
if (facetSettings.isTestModule()) return false;
|
||||
|
||||
final TargetPlatform platform = facetSettings.getTargetPlatform();
|
||||
if (platform == null) return false;
|
||||
|
||||
return TargetPlatformKt.isCommon(platform);
|
||||
}
|
||||
|
||||
private static Collection<String> findNativeGradleBuildTasks(Collection<String> gradleTasks, String sourceSetName) {
|
||||
// First, attempt to find Kotlin/Native convention Gradle task that unites all outputType-specific build tasks.
|
||||
final String conventionGradleTask = sourceSetName + "Binaries";
|
||||
if (gradleTasks.contains(conventionGradleTask)) {
|
||||
return Collections.singletonList(conventionGradleTask);
|
||||
}
|
||||
|
||||
// If convention task not found, then attempt to find all appropriate build tasks for the given source set.
|
||||
final Collection<String> linkPrefixes;
|
||||
final String targetName;
|
||||
if (sourceSetName.endsWith("Main")) {
|
||||
targetName = StringUtil.substringBeforeLast(sourceSetName,"Main");
|
||||
linkPrefixes = ContainerUtil.newArrayList("link", "linkMain");
|
||||
}
|
||||
else if (sourceSetName.endsWith("Test")) {
|
||||
targetName = StringUtil.substringBeforeLast(sourceSetName,"Test");
|
||||
linkPrefixes = Collections.singletonList("linkTest");
|
||||
}
|
||||
else {
|
||||
targetName = sourceSetName;
|
||||
linkPrefixes = Collections.singletonList("link");
|
||||
}
|
||||
|
||||
return linkPrefixes.stream()
|
||||
// get base task name (without disambiguation classifier)
|
||||
.map(linkPrefix -> linkPrefix + capitalize(targetName))
|
||||
// find all Gradle tasks that start with base task name
|
||||
.flatMap(nativeTaskName -> gradleTasks.stream().filter(taskName -> taskName.startsWith(nativeTaskName)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Collection<String> findMetadataBuildTasks(Collection<String> gradleTasks, String sourceSetName) {
|
||||
if ("commonMain".equals(sourceSetName)) {
|
||||
final String metadataTaskName = "metadataMainClasses";
|
||||
if (gradleTasks.contains(metadataTaskName)) {
|
||||
return Collections.singletonList(metadataTaskName);
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static Collection<String> addPrefix(Collection<String> tasks, String taskPrefix) {
|
||||
return tasks.stream().map(task -> taskPrefix + task).collect(Collectors.toList());
|
||||
}
|
||||
// ---------------------------------------- //
|
||||
// TODO END: Extract custom Kotlin logic. //
|
||||
// ---------------------------------------- //
|
||||
}
|
||||
+3
-2
@@ -20,7 +20,7 @@ import com.intellij.compiler.server.BuildManager
|
||||
import com.intellij.history.core.RevisionsCollector
|
||||
import com.intellij.history.integration.LocalHistoryImpl
|
||||
import com.intellij.history.integration.patches.PatchCreator
|
||||
import com.intellij.ide.actions.ShowFilePathAction
|
||||
import com.intellij.ide.actions.RevealFileAction
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.PathManager
|
||||
@@ -50,6 +50,7 @@ class CreateIncrementalCompilationBackup : AnAction("Create backup for debugging
|
||||
const val ZIP_FRACTION = 1.0 - PATCHES_FRACTION - LOGS_FRACTION - PROJECT_SYSTEM_FRACTION
|
||||
}
|
||||
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project!!
|
||||
val projectBaseDir = File(project.baseDir!!.path)
|
||||
@@ -183,7 +184,7 @@ class CreateIncrementalCompilationBackup : AnAction("Create backup for debugging
|
||||
|
||||
WaitForProgressToShow.runOrInvokeLaterAboveProgress(
|
||||
{
|
||||
ShowFilePathAction.showDialog(
|
||||
RevealFileAction.showDialog(
|
||||
project,
|
||||
"Successfully created backup " + backupFile.absolutePath,
|
||||
"Created backup",
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2010-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.kotlin.idea.internal.makeBackup
|
||||
|
||||
import com.intellij.compiler.server.BuildManager
|
||||
import com.intellij.history.core.RevisionsCollector
|
||||
import com.intellij.history.integration.LocalHistoryImpl
|
||||
import com.intellij.history.integration.patches.PatchCreator
|
||||
import com.intellij.ide.actions.ShowFilePathAction
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vcs.changes.Change
|
||||
import com.intellij.util.WaitForProgressToShow
|
||||
import com.intellij.util.io.ZipUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class CreateIncrementalCompilationBackup : AnAction("Create backup for debugging Kotlin incremental compilation") {
|
||||
companion object {
|
||||
const val BACKUP_DIR_NAME = ".backup"
|
||||
const val PATCHES_TO_CREATE = 5
|
||||
|
||||
const val PATCHES_FRACTION = .25
|
||||
const val LOGS_FRACTION = .05
|
||||
const val PROJECT_SYSTEM_FRACTION = .05
|
||||
const val ZIP_FRACTION = 1.0 - PATCHES_FRACTION - LOGS_FRACTION - PROJECT_SYSTEM_FRACTION
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project!!
|
||||
val projectBaseDir = File(project.baseDir!!.path)
|
||||
val backupDir = File(FileUtil.createTempDirectory("makeBackup", null), BACKUP_DIR_NAME)
|
||||
|
||||
ProgressManager.getInstance().run(
|
||||
object : Task.Backgroundable(project, "Creating backup for debugging Kotlin incremental compilation", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
createPatches(backupDir, project, indicator)
|
||||
copyLogs(backupDir, indicator)
|
||||
copyProjectSystemDir(backupDir, project, indicator)
|
||||
|
||||
zipProjectDir(backupDir, project, projectBaseDir, indicator)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPatches(backupDir: File, project: Project, indicator: ProgressIndicator) {
|
||||
runReadAction {
|
||||
val localHistoryImpl = LocalHistoryImpl.getInstanceImpl()!!
|
||||
val gateway = localHistoryImpl.gateway!!
|
||||
val localHistoryFacade = localHistoryImpl.facade
|
||||
|
||||
val revisionsCollector = RevisionsCollector(
|
||||
localHistoryFacade,
|
||||
gateway.createTransientRootEntry(),
|
||||
project.baseDir!!.path,
|
||||
project.locationHash,
|
||||
null
|
||||
)
|
||||
|
||||
var patchesCreated = 0
|
||||
|
||||
val patchesDir = File(backupDir, "patches")
|
||||
patchesDir.mkdirs()
|
||||
|
||||
val revisions = revisionsCollector.result!!
|
||||
for (rev in revisions) {
|
||||
val label = rev.label
|
||||
if (label != null && label.startsWith(HISTORY_LABEL_PREFIX)) {
|
||||
val patchFile = File(patchesDir, label.removePrefix(HISTORY_LABEL_PREFIX) + ".patch")
|
||||
|
||||
indicator.text = "Creating patch $patchFile"
|
||||
indicator.fraction = PATCHES_FRACTION * patchesCreated / PATCHES_TO_CREATE
|
||||
|
||||
val differences = revisions[0].getDifferencesWith(rev)!!
|
||||
val changes = differences.map { d ->
|
||||
Change(d.getLeftContentRevision(gateway), d.getRightContentRevision(gateway))
|
||||
}
|
||||
|
||||
PatchCreator.create(project, changes, patchFile.path, false, null)
|
||||
|
||||
if (++patchesCreated >= PATCHES_TO_CREATE) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyLogs(backupDir: File, indicator: ProgressIndicator) {
|
||||
indicator.text = "Copying logs"
|
||||
indicator.fraction = PATCHES_FRACTION
|
||||
|
||||
val logsDir = File(backupDir, "logs")
|
||||
FileUtil.copyDir(File(PathManager.getLogPath()), logsDir)
|
||||
|
||||
indicator.fraction = PATCHES_FRACTION + LOGS_FRACTION
|
||||
}
|
||||
|
||||
private fun copyProjectSystemDir(backupDir: File, project: Project, indicator: ProgressIndicator) {
|
||||
indicator.text = "Copying project's system dir "
|
||||
indicator.fraction = PATCHES_FRACTION
|
||||
|
||||
val projectSystemDir = File(backupDir, "project-system")
|
||||
FileUtil.copyDir(BuildManager.getInstance().getProjectSystemDirectory(project)!!, projectSystemDir)
|
||||
|
||||
indicator.fraction = PATCHES_FRACTION + LOGS_FRACTION + PROJECT_SYSTEM_FRACTION
|
||||
}
|
||||
|
||||
private fun zipProjectDir(backupDir: File, project: Project, projectDir: File, indicator: ProgressIndicator) {
|
||||
// files and relative paths
|
||||
|
||||
val files = ArrayList<Pair<File, String>>() // files and relative paths
|
||||
var totalBytes = 0L
|
||||
|
||||
for (dir in listOf(projectDir, backupDir.parentFile!!)) {
|
||||
FileUtil.processFilesRecursively(
|
||||
dir,
|
||||
/*processor*/ {
|
||||
if (it!!.isFile
|
||||
&& !it.name.endsWith(".hprof")
|
||||
&& !(it.name.startsWith("make_backup_") && it.name.endsWith(".zip"))
|
||||
) {
|
||||
|
||||
indicator.text = "Scanning project dir: $it"
|
||||
|
||||
files.add(Pair(it, FileUtil.getRelativePath(dir, it)!!))
|
||||
totalBytes += it.length()
|
||||
}
|
||||
true
|
||||
},
|
||||
/*directoryFilter*/ {
|
||||
val name = it!!.name
|
||||
name != ".git" && name != "out"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
val backupFile = File(projectDir, "make_backup_" + SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(Date()) + ".zip")
|
||||
|
||||
|
||||
val zos = ZipOutputStream(FileOutputStream(backupFile))
|
||||
|
||||
var processedBytes = 0L
|
||||
|
||||
zos.use {
|
||||
for ((file, relativePath) in files) {
|
||||
indicator.text = "Adding file to backup: $relativePath"
|
||||
indicator.fraction = PATCHES_FRACTION + LOGS_FRACTION + processedBytes.toDouble() / totalBytes * ZIP_FRACTION
|
||||
|
||||
ZipUtil.addFileToZip(zos, file, relativePath, null, null)
|
||||
|
||||
processedBytes += file.length()
|
||||
}
|
||||
}
|
||||
|
||||
FileUtil.delete(backupDir)
|
||||
|
||||
WaitForProgressToShow.runOrInvokeLaterAboveProgress(
|
||||
{
|
||||
ShowFilePathAction.showDialog(
|
||||
project,
|
||||
"Successfully created backup " + backupFile.absolutePath,
|
||||
"Created backup",
|
||||
backupFile,
|
||||
null
|
||||
)
|
||||
}, null, project
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -75,21 +75,19 @@ class RunScratchAction : ScratchAction(
|
||||
|
||||
if (!isAutoRun && module != null && isMakeBeforeRun) {
|
||||
val project = scratchFile.project
|
||||
ProjectTaskManager.getInstance(project).build(arrayOf(module), object : ProjectTaskNotification {
|
||||
override fun finished(context: ProjectTaskContext, executionResult: ProjectTaskResult) {
|
||||
if (executionResult.isAborted || executionResult.errors > 0) {
|
||||
executor.errorOccurs("There were compilation errors in module ${module.name}")
|
||||
}
|
||||
ProjectTaskManager.getInstance(project).build(module).onSuccess { executionResult ->
|
||||
if (executionResult.isAborted || executionResult.hasErrors()) {
|
||||
executor.errorOccurs("There were compilation errors in module ${module.name}")
|
||||
}
|
||||
|
||||
if (DumbService.isDumb(project)) {
|
||||
DumbService.getInstance(project).smartInvokeLater {
|
||||
executeScratch()
|
||||
}
|
||||
} else {
|
||||
if (DumbService.isDumb(project)) {
|
||||
DumbService.getInstance(project).smartInvokeLater {
|
||||
executeScratch()
|
||||
}
|
||||
} else {
|
||||
executeScratch()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
executeScratch()
|
||||
}
|
||||
|
||||
@@ -42,13 +42,11 @@ class ConsoleCompilerHelper(
|
||||
|
||||
fun compileModule() {
|
||||
if (ExecutionManager.getInstance(project).contentManager.removeRunContent(executor, contentDescriptor)) {
|
||||
ProjectTaskManager.getInstance(project).build(arrayOf(module), object : ProjectTaskNotification {
|
||||
override fun finished(context: ProjectTaskContext, executionResult: ProjectTaskResult) {
|
||||
if (!module.isDisposed) {
|
||||
KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = executionResult.errors > 0)
|
||||
}
|
||||
ProjectTaskManager.getInstance(project).build(module).onSuccess { executionResult ->
|
||||
if (!module.isDisposed) {
|
||||
KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = executionResult.hasErrors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea
|
||||
|
||||
import com.intellij.openapi.util.IconLoader
|
||||
import com.intellij.psi.PsiModifierListOwner
|
||||
import com.intellij.psi.impl.ElementPresentationUtil
|
||||
import com.intellij.util.PlatformIcons
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinIdeFileIconProviderService : KotlinIconProviderService() {
|
||||
override fun getFileIcon(): Icon = KOTLIN_FILE
|
||||
|
||||
override fun getLightVariableIcon(element: PsiModifierListOwner, flags: Int): Icon {
|
||||
val baseIcon = ElementPresentationUtil.createLayeredIcon(PlatformIcons.VARIABLE_ICON, element, false)
|
||||
return ElementPresentationUtil.addVisibilityIcon(element, flags, baseIcon)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KOTLIN_FILE = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_file.svg")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonIOException
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.intellij.ide.actions.ShowFilePathAction
|
||||
import com.intellij.ide.plugins.*
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.notification.NotificationDisplayType
|
||||
import com.intellij.notification.NotificationGroup
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.*
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.updateSettings.impl.PluginDownloader
|
||||
import com.intellij.openapi.updateSettings.impl.UpdateSettings
|
||||
import com.intellij.openapi.util.JDOMUtil
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.io.HttpRequests
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import org.jetbrains.kotlin.idea.update.verify
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.net.URLEncoder
|
||||
import java.time.DateTimeException
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
sealed class PluginUpdateStatus {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
object LatestVersionInstalled : PluginUpdateStatus()
|
||||
|
||||
class Update(
|
||||
val pluginDescriptor: IdeaPluginDescriptor,
|
||||
val hostToInstallFrom: String?
|
||||
) : PluginUpdateStatus()
|
||||
|
||||
class CheckFailed(val message: String, val detail: String? = null) : PluginUpdateStatus()
|
||||
|
||||
class Unverified(val verifierName: String, val reason: String?, val updateStatus: Update) : PluginUpdateStatus()
|
||||
|
||||
fun mergeWith(other: PluginUpdateStatus): PluginUpdateStatus {
|
||||
if (other is Update) {
|
||||
when (this) {
|
||||
is LatestVersionInstalled -> {
|
||||
return other
|
||||
}
|
||||
is Update -> {
|
||||
if (VersionComparatorUtil.compare(other.pluginDescriptor.version, pluginDescriptor.version) > 0) {
|
||||
return other
|
||||
}
|
||||
}
|
||||
is CheckFailed, is Unverified -> {
|
||||
// proceed to return this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromException(message: String, e: Exception): PluginUpdateStatus {
|
||||
val writer = StringWriter()
|
||||
e.printStackTrace(PrintWriter(writer))
|
||||
return CheckFailed(message, writer.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinPluginUpdater : Disposable {
|
||||
private var updateDelay = INITIAL_UPDATE_DELAY
|
||||
private val alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this)
|
||||
private val notificationGroup = NotificationGroup("Kotlin plugin updates", NotificationDisplayType.STICKY_BALLOON, true)
|
||||
|
||||
@Volatile
|
||||
private var checkQueued = false
|
||||
|
||||
@Volatile
|
||||
private var lastUpdateStatus: PluginUpdateStatus? = null
|
||||
|
||||
fun kotlinFileEdited(file: VirtualFile) {
|
||||
if (!file.isInLocalFileSystem) return
|
||||
if (ApplicationManager.getApplication().isUnitTestMode || ApplicationManager.getApplication().isHeadlessEnvironment) return
|
||||
if (!UpdateSettings.getInstance().isCheckNeeded) return
|
||||
|
||||
val lastUpdateTime = java.lang.Long.parseLong(PropertiesComponent.getInstance().getValue(PROPERTY_NAME, "0"))
|
||||
if (lastUpdateTime == 0L || System.currentTimeMillis() - lastUpdateTime > CACHED_REQUEST_DELAY) {
|
||||
queueUpdateCheck { updateStatus ->
|
||||
when (updateStatus) {
|
||||
is PluginUpdateStatus.Update -> notifyPluginUpdateAvailable(updateStatus)
|
||||
is PluginUpdateStatus.CheckFailed -> LOG.info("Plugin update check failed: ${updateStatus.message}, details: ${updateStatus.detail}")
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun queueUpdateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread()
|
||||
if (!checkQueued) {
|
||||
checkQueued = true
|
||||
alarm.addRequest({ updateCheck(callback) }, updateDelay)
|
||||
updateDelay *= 2 // exponential backoff
|
||||
}
|
||||
}
|
||||
|
||||
fun runUpdateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
updateCheck(callback)
|
||||
}
|
||||
}
|
||||
|
||||
fun runCachedUpdate(callback: (PluginUpdateStatus) -> Boolean) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread()
|
||||
val cachedStatus = lastUpdateStatus
|
||||
if (cachedStatus != null && System.currentTimeMillis() - cachedStatus.timestamp < CACHED_REQUEST_DELAY) {
|
||||
if (cachedStatus !is PluginUpdateStatus.CheckFailed) {
|
||||
callback(cachedStatus)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
queueUpdateCheck(callback)
|
||||
}
|
||||
|
||||
private fun updateCheck(callback: (PluginUpdateStatus) -> Boolean) {
|
||||
var updateStatus: PluginUpdateStatus
|
||||
if (KotlinPluginUtil.isSnapshotVersion() || KotlinPluginUtil.isPatched()) {
|
||||
updateStatus = PluginUpdateStatus.LatestVersionInstalled
|
||||
} else {
|
||||
try {
|
||||
updateStatus = checkUpdatesInMainRepository()
|
||||
|
||||
for (host in RepositoryHelper.getPluginHosts().filterNotNull()) {
|
||||
val customUpdateStatus = checkUpdatesInCustomRepository(host)
|
||||
updateStatus = updateStatus.mergeWith(customUpdateStatus)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
updateStatus = PluginUpdateStatus.fromException("Kotlin plugin update check failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdateStatus = updateStatus
|
||||
checkQueued = false
|
||||
|
||||
if (updateStatus is PluginUpdateStatus.Update) {
|
||||
updateStatus = verify(updateStatus)
|
||||
}
|
||||
|
||||
if (updateStatus !is PluginUpdateStatus.CheckFailed) {
|
||||
recordSuccessfulUpdateCheck()
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater({
|
||||
callback(updateStatus)
|
||||
}, ModalityState.any())
|
||||
}
|
||||
|
||||
private fun initPluginDescriptor(newVersion: String): IdeaPluginDescriptor {
|
||||
val originalPlugin = PluginManager.getPlugin(KotlinPluginUtil.KOTLIN_PLUGIN_ID)!!
|
||||
return PluginNode(KotlinPluginUtil.KOTLIN_PLUGIN_ID).apply {
|
||||
version = newVersion
|
||||
name = originalPlugin.name
|
||||
description = originalPlugin.description
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUpdatesInMainRepository(): PluginUpdateStatus {
|
||||
val buildNumber = ApplicationInfo.getInstance().apiVersion
|
||||
val currentVersion = KotlinPluginUtil.getPluginVersion()
|
||||
val os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8)
|
||||
val uid = PermanentInstallationID.get()
|
||||
val pluginId = KotlinPluginUtil.KOTLIN_PLUGIN_ID.idString
|
||||
val url =
|
||||
"https://plugins.jetbrains.com/plugins/list?pluginId=$pluginId&build=$buildNumber&pluginVersion=$currentVersion&os=$os&uuid=$uid"
|
||||
val responseDoc = HttpRequests.request(url).connect {
|
||||
JDOMUtil.load(it.inputStream)
|
||||
}
|
||||
if (responseDoc.name != "plugin-repository") {
|
||||
return PluginUpdateStatus.CheckFailed("Unexpected plugin repository response", JDOMUtil.writeElement(responseDoc, "\n"))
|
||||
}
|
||||
if (responseDoc.children.isEmpty()) {
|
||||
// No plugin version compatible with current IDEA build; don't retry updates
|
||||
return PluginUpdateStatus.LatestVersionInstalled
|
||||
}
|
||||
val newVersion = responseDoc.getChild("category")?.getChild("idea-plugin")?.getChild("version")?.text
|
||||
?: return PluginUpdateStatus.CheckFailed(
|
||||
"Couldn't find plugin version in repository response",
|
||||
JDOMUtil.writeElement(responseDoc, "\n")
|
||||
)
|
||||
val pluginDescriptor = initPluginDescriptor(newVersion)
|
||||
return updateIfNotLatest(pluginDescriptor, null)
|
||||
}
|
||||
|
||||
private fun checkUpdatesInCustomRepository(host: String): PluginUpdateStatus {
|
||||
val plugins = try {
|
||||
RepositoryHelper.loadPlugins(host, null)
|
||||
} catch (e: Exception) {
|
||||
return PluginUpdateStatus.fromException("Checking custom plugin repository $host failed", e)
|
||||
}
|
||||
|
||||
val kotlinPlugin = plugins.find { pluginDescriptor ->
|
||||
pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID && PluginManagerCore.isCompatible(pluginDescriptor)
|
||||
} ?: return PluginUpdateStatus.LatestVersionInstalled
|
||||
|
||||
return updateIfNotLatest(kotlinPlugin, host)
|
||||
}
|
||||
|
||||
private fun updateIfNotLatest(kotlinPlugin: IdeaPluginDescriptor, host: String?): PluginUpdateStatus {
|
||||
if (VersionComparatorUtil.compare(kotlinPlugin.version, KotlinPluginUtil.getPluginVersion()) <= 0) {
|
||||
return PluginUpdateStatus.LatestVersionInstalled
|
||||
}
|
||||
|
||||
return PluginUpdateStatus.Update(kotlinPlugin, host)
|
||||
}
|
||||
|
||||
private fun recordSuccessfulUpdateCheck() {
|
||||
PropertiesComponent.getInstance().setValue(PROPERTY_NAME, System.currentTimeMillis().toString())
|
||||
updateDelay = INITIAL_UPDATE_DELAY
|
||||
}
|
||||
|
||||
private fun notifyPluginUpdateAvailable(update: PluginUpdateStatus.Update) {
|
||||
val notification = notificationGroup.createNotification(
|
||||
"Kotlin",
|
||||
"A new version ${update.pluginDescriptor.version} of the Kotlin plugin is available. <b><a href=\"#\">Install</a></b>",
|
||||
NotificationType.INFORMATION
|
||||
) { notification, _ ->
|
||||
notification.expire()
|
||||
installPluginUpdate(update) {
|
||||
notifyPluginUpdateAvailable(update)
|
||||
}
|
||||
}
|
||||
|
||||
notification.notify(null)
|
||||
}
|
||||
|
||||
fun installPluginUpdate(
|
||||
update: PluginUpdateStatus.Update,
|
||||
successCallback: () -> Unit = {}, cancelCallback: () -> Unit = {}, errorCallback: () -> Unit = {}
|
||||
) {
|
||||
val descriptor = update.pluginDescriptor
|
||||
val pluginDownloader = PluginDownloader.createDownloader(descriptor, update.hostToInstallFrom, null)
|
||||
ProgressManager.getInstance().run(object : Task.Backgroundable(
|
||||
null, "Downloading plugins", true, PluginManagerUISettings.getInstance()
|
||||
) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
var installed = false
|
||||
var message: String? = null
|
||||
val prepareResult = try {
|
||||
pluginDownloader.prepareToInstall(indicator)
|
||||
} catch (e: IOException) {
|
||||
LOG.info(e)
|
||||
message = e.message
|
||||
false
|
||||
}
|
||||
|
||||
if (prepareResult) {
|
||||
installed = true
|
||||
pluginDownloader.install()
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
PluginManagerMain.notifyPluginsUpdated(null)
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
if (!installed) {
|
||||
errorCallback()
|
||||
notifyNotInstalled(message)
|
||||
} else {
|
||||
successCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
cancelCallback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun notifyNotInstalled(message: String?) {
|
||||
val fullMessage = message?.let { ": $it" } ?: ""
|
||||
val notification = notificationGroup.createNotification(
|
||||
"Kotlin",
|
||||
"Plugin update was not installed$fullMessage. <a href=\"#\">See the log for more information</a>",
|
||||
NotificationType.INFORMATION
|
||||
) { notification, _ ->
|
||||
val logFile = File(PathManager.getLogPath(), "idea.log")
|
||||
ShowFilePathAction.openFile(logFile)
|
||||
|
||||
notification.expire()
|
||||
}
|
||||
|
||||
notification.notify(null)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INITIAL_UPDATE_DELAY = 2000L
|
||||
private val CACHED_REQUEST_DELAY = TimeUnit.DAYS.toMillis(1)
|
||||
|
||||
private const val PROPERTY_NAME = "kotlin.lastUpdateCheck"
|
||||
private val LOG = Logger.getInstance(KotlinPluginUpdater::class.java)
|
||||
|
||||
fun getInstance(): KotlinPluginUpdater = ServiceManager.getService(KotlinPluginUpdater::class.java)
|
||||
|
||||
class ResponseParseException(message: String, cause: Exception? = null) : IllegalStateException(message, cause)
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private class PluginDTO {
|
||||
var cdate: String? = null
|
||||
var channel: String? = null
|
||||
|
||||
// `true` if the version is seen in plugin site and available for download.
|
||||
// Maybe be `false` if author requested version deletion.
|
||||
var listed: Boolean = true
|
||||
|
||||
// `true` if version is approved and verified
|
||||
var approve: Boolean = true
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ResponseParseException::class)
|
||||
fun fetchPluginReleaseDate(pluginId: PluginId, version: String, channel: String?): LocalDate? {
|
||||
val url = "https://plugins.jetbrains.com/api/plugins/${pluginId.idString}/updates?version=$version"
|
||||
|
||||
val pluginDTOs: Array<PluginDTO> = try {
|
||||
HttpRequests.request(url).connect {
|
||||
GsonBuilder().create().fromJson(it.inputStream.reader(), Array<PluginDTO>::class.java)
|
||||
}
|
||||
} catch (ioException: JsonIOException) {
|
||||
throw IOException(ioException)
|
||||
} catch (syntaxException: JsonSyntaxException) {
|
||||
throw ResponseParseException("Can't parse json response", syntaxException)
|
||||
}
|
||||
|
||||
val selectedPluginDTO = pluginDTOs
|
||||
.firstOrNull {
|
||||
it.listed && it.approve && (it.channel == channel || (it.channel == "" && channel == null))
|
||||
}
|
||||
?: return null
|
||||
|
||||
val dateString = selectedPluginDTO.cdate ?: throw ResponseParseException("Empty cdate")
|
||||
|
||||
return try {
|
||||
val dateLong = dateString.toLong()
|
||||
Instant.ofEpochMilli(dateLong).atZone(ZoneOffset.UTC).toLocalDate()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw ResponseParseException("Can't parse long date", e)
|
||||
} catch (e: DateTimeException) {
|
||||
throw ResponseParseException("Can't convert to date", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.idea
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonIOException
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.intellij.ide.actions.ShowFilePathAction
|
||||
import com.intellij.ide.actions.RevealFileAction
|
||||
import com.intellij.ide.plugins.*
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.notification.NotificationDisplayType
|
||||
@@ -306,7 +306,7 @@ class KotlinPluginUpdater : Disposable {
|
||||
NotificationType.INFORMATION
|
||||
) { notification, _ ->
|
||||
val logFile = File(PathManager.getLogPath(), "idea.log")
|
||||
ShowFilePathAction.openFile(logFile)
|
||||
RevealFileAction.openFile(logFile)
|
||||
|
||||
notification.expire()
|
||||
}
|
||||
|
||||
@@ -77,4 +77,8 @@ class KotlinSdkType : SdkType("KotlinSDK") {
|
||||
override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {
|
||||
|
||||
}
|
||||
|
||||
override fun allowCreationByUser(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.framework
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.projectRoots.*
|
||||
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.util.Consumer
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import javax.swing.JComponent
|
||||
|
||||
class KotlinSdkType : SdkType("KotlinSDK") {
|
||||
companion object {
|
||||
@JvmField
|
||||
val INSTANCE = KotlinSdkType()
|
||||
|
||||
val defaultHomePath: String
|
||||
get() = PathUtil.kotlinPathsForIdeaPlugin.homePath.absolutePath
|
||||
|
||||
@JvmOverloads
|
||||
fun setUpIfNeeded(checkIfNeeded: () -> Boolean = { true }) {
|
||||
val projectSdks: Array<Sdk> = ProjectJdkTable.getInstance().allJdks
|
||||
if (projectSdks.any { it.sdkType is KotlinSdkType }) return
|
||||
if (!checkIfNeeded()) return // do not create Kotlin SDK
|
||||
|
||||
val newSdkName = SdkConfigurationUtil.createUniqueSdkName(INSTANCE, defaultHomePath, projectSdks.toList())
|
||||
val newJdk = ProjectJdkImpl(newSdkName, INSTANCE)
|
||||
newJdk.homePath = defaultHomePath
|
||||
INSTANCE.setupSdkPaths(newJdk)
|
||||
|
||||
ApplicationManager.getApplication().invokeAndWait {
|
||||
runWriteAction {
|
||||
if (ProjectJdkTable.getInstance().allJdks.any { it.sdkType is KotlinSdkType }) return@runWriteAction
|
||||
ProjectJdkTable.getInstance().addJdk(newJdk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPresentableName() = "Kotlin SDK"
|
||||
|
||||
override fun getIcon() = KotlinIcons.SMALL_LOGO
|
||||
|
||||
override fun isValidSdkHome(path: String?) = true
|
||||
|
||||
override fun suggestSdkName(currentSdkName: String?, sdkHome: String?) = "Kotlin SDK"
|
||||
|
||||
override fun suggestHomePath() = null
|
||||
|
||||
override fun sdkHasValidPath(sdk: Sdk) = true
|
||||
|
||||
override fun getVersionString(sdk: Sdk) = bundledRuntimeVersion()
|
||||
|
||||
override fun supportsCustomCreateUI() = true
|
||||
|
||||
override fun showCustomCreateUI(sdkModel: SdkModel, parentComponent: JComponent, selectedSdk: Sdk?, sdkCreatedCallback: Consumer<Sdk>) {
|
||||
sdkCreatedCallback.consume(createSdkWithUniqueName(sdkModel.sdks.toList()))
|
||||
}
|
||||
|
||||
fun createSdkWithUniqueName(existingSdks: Collection<Sdk>): ProjectJdkImpl {
|
||||
val sdkName = suggestSdkName(SdkConfigurationUtil.createUniqueSdkName(this, "", existingSdks), "")
|
||||
return ProjectJdkImpl(sdkName, this).apply {
|
||||
homePath = defaultHomePath
|
||||
}
|
||||
}
|
||||
|
||||
override fun createAdditionalDataConfigurable(sdkModel: SdkModel, sdkModificator: SdkModificator) = null
|
||||
|
||||
override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {
|
||||
|
||||
}
|
||||
}
|
||||
-1
@@ -236,7 +236,6 @@ class KotlinChangeSignatureDialog(
|
||||
components.add(component)
|
||||
panel.add(component)
|
||||
add(panel)
|
||||
IJSwingUtilities.adjustComponentsOnMac(label, component)
|
||||
column++
|
||||
}
|
||||
}
|
||||
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
* Copyright 2010-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.kotlin.idea.refactoring.changeSignature.ui
|
||||
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.colors.EditorFontType
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiCodeFragment
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.changeSignature.ChangeSignatureDialogBase
|
||||
import com.intellij.refactoring.changeSignature.MethodDescriptor
|
||||
import com.intellij.refactoring.changeSignature.ParameterTableModelItemBase
|
||||
import com.intellij.refactoring.ui.ComboBoxVisibilityPanel
|
||||
import com.intellij.ui.DottedBorder
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.IJSwingUtilities
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.table.JBTableRow
|
||||
import com.intellij.util.ui.table.JBTableRowEditor
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinMethodDescriptor.Kind
|
||||
import org.jetbrains.kotlin.idea.refactoring.validateElement
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Font
|
||||
import java.awt.Toolkit
|
||||
import java.awt.event.ItemEvent
|
||||
import java.util.*
|
||||
import javax.swing.*
|
||||
|
||||
class KotlinChangeSignatureDialog(
|
||||
project: Project,
|
||||
methodDescriptor: KotlinMethodDescriptor,
|
||||
context: PsiElement,
|
||||
private val commandName: String?
|
||||
) : ChangeSignatureDialogBase<
|
||||
KotlinParameterInfo,
|
||||
PsiElement,
|
||||
Visibility,
|
||||
KotlinMethodDescriptor,
|
||||
ParameterTableModelItemBase<KotlinParameterInfo>,
|
||||
KotlinCallableParameterTableModel>(project, methodDescriptor, false, context) {
|
||||
override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE
|
||||
|
||||
override fun createParametersInfoModel(descriptor: KotlinMethodDescriptor) =
|
||||
createParametersInfoModel(descriptor, myDefaultValueContext)
|
||||
|
||||
override fun createReturnTypeCodeFragment() = createReturnTypeCodeFragment(myProject, myMethod)
|
||||
|
||||
private val parametersTableModel: KotlinCallableParameterTableModel get() = super.myParametersTableModel
|
||||
|
||||
override fun getRowPresentation(
|
||||
item: ParameterTableModelItemBase<KotlinParameterInfo>,
|
||||
selected: Boolean,
|
||||
focused: Boolean
|
||||
): JComponent? {
|
||||
val panel = JPanel(BorderLayout())
|
||||
|
||||
val valOrVar = if (myMethod.kind === Kind.PRIMARY_CONSTRUCTOR) {
|
||||
when (item.parameter.valOrVar) {
|
||||
KotlinValVar.None -> " "
|
||||
KotlinValVar.Val -> "val "
|
||||
KotlinValVar.Var -> "var "
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val parameterName = getPresentationName(item)
|
||||
val typeText = item.typeCodeFragment.text
|
||||
val defaultValue = item.defaultValueCodeFragment.text
|
||||
val separator = StringUtil.repeatSymbol(' ', getParamNamesMaxLength() - parameterName.length + 1)
|
||||
var text = "$valOrVar$parameterName:$separator$typeText"
|
||||
|
||||
if (StringUtil.isNotEmpty(defaultValue)) {
|
||||
text += " // default value = $defaultValue"
|
||||
}
|
||||
|
||||
val field = object : EditorTextField(" $text", project, fileType) {
|
||||
override fun shouldHaveBorder() = false
|
||||
}
|
||||
|
||||
val plainFont = EditorColorsManager.getInstance().globalScheme.getFont(EditorFontType.PLAIN)
|
||||
field.font = Font(plainFont.fontName, plainFont.style, 12)
|
||||
|
||||
if (selected && focused) {
|
||||
panel.background = UIUtil.getTableSelectionBackground()
|
||||
field.setAsRendererWithSelection(UIUtil.getTableSelectionBackground(), UIUtil.getTableSelectionForeground())
|
||||
} else {
|
||||
panel.background = UIUtil.getTableBackground()
|
||||
if (selected && !focused) {
|
||||
panel.border = DottedBorder(UIUtil.getTableForeground())
|
||||
}
|
||||
}
|
||||
panel.add(field, BorderLayout.WEST)
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun getPresentationName(item: ParameterTableModelItemBase<KotlinParameterInfo>): String {
|
||||
val parameter = item.parameter
|
||||
return if (parameter == parametersTableModel.receiver) "<receiver>" else parameter.name
|
||||
}
|
||||
|
||||
private fun getColumnTextMaxLength(nameFunction: Function1<ParameterTableModelItemBase<KotlinParameterInfo>, String?>) =
|
||||
parametersTableModel.items.asSequence().map { nameFunction(it)?.length ?: 0 }.max() ?: 0
|
||||
|
||||
private fun getParamNamesMaxLength() = getColumnTextMaxLength { getPresentationName(it) }
|
||||
|
||||
private fun getTypesMaxLength() = getColumnTextMaxLength { it.typeCodeFragment?.text }
|
||||
|
||||
private fun getDefaultValuesMaxLength() = getColumnTextMaxLength { it.defaultValueCodeFragment?.text }
|
||||
|
||||
override fun isListTableViewSupported() = true
|
||||
|
||||
override fun isEmptyRow(row: ParameterTableModelItemBase<KotlinParameterInfo>): Boolean {
|
||||
if (row.parameter.name.isNotEmpty()) return false
|
||||
if (row.parameter.typeText.isNotEmpty()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun createCallerChooser(title: String, treeToReuse: Tree?, callback: Consumer<Set<PsiElement>>) =
|
||||
KotlinCallerChooser(myMethod.method, myProject, title, treeToReuse, callback)
|
||||
|
||||
// Forbid receiver propagation
|
||||
override fun mayPropagateParameters() =
|
||||
parameters.any { it.isNewParameter && it != parametersTableModel.receiver }
|
||||
|
||||
override fun getTableEditor(table: JTable, item: ParameterTableModelItemBase<KotlinParameterInfo>): JBTableRowEditor? {
|
||||
return object : JBTableRowEditor() {
|
||||
private val components = ArrayList<JComponent>()
|
||||
private val nameEditor = EditorTextField(item.parameter.name, project, fileType)
|
||||
|
||||
private fun updateNameEditor() {
|
||||
nameEditor.isEnabled = item.parameter != parametersTableModel.receiver
|
||||
}
|
||||
|
||||
private fun isDefaultColumnEnabled() =
|
||||
item.parameter.isNewParameter && item.parameter != myMethod.receiver
|
||||
|
||||
override fun prepareEditor(table: JTable, row: Int) {
|
||||
layout = BoxLayout(this, BoxLayout.X_AXIS)
|
||||
var column = 0
|
||||
|
||||
for (columnInfo in parametersTableModel.columnInfos) {
|
||||
val panel = JPanel(VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false))
|
||||
val editor: EditorTextField?
|
||||
val component: JComponent
|
||||
val columnFinal = column
|
||||
|
||||
if (KotlinCallableParameterTableModel.isTypeColumn(columnInfo)) {
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(item.typeCodeFragment)
|
||||
editor = EditorTextField(document, project, fileType)
|
||||
component = editor
|
||||
} else if (KotlinCallableParameterTableModel.isNameColumn(columnInfo)) {
|
||||
editor = nameEditor
|
||||
component = editor
|
||||
updateNameEditor()
|
||||
} else if (KotlinCallableParameterTableModel.isDefaultValueColumn(columnInfo) && isDefaultColumnEnabled()) {
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(item.defaultValueCodeFragment)
|
||||
editor = EditorTextField(document, project, fileType)
|
||||
component = editor
|
||||
} else if (KotlinPrimaryConstructorParameterTableModel.isValVarColumn(columnInfo)) {
|
||||
val comboBox = JComboBox(KotlinValVar.values())
|
||||
comboBox.selectedItem = item.parameter.valOrVar
|
||||
comboBox.addItemListener {
|
||||
parametersTableModel.setValueAtWithoutUpdate(it.item, row, columnFinal)
|
||||
updateSignature()
|
||||
}
|
||||
component = comboBox
|
||||
editor = null
|
||||
} else if (KotlinFunctionParameterTableModel.isReceiverColumn(columnInfo)) {
|
||||
val checkBox = JCheckBox()
|
||||
checkBox.isSelected = parametersTableModel.receiver == item.parameter
|
||||
checkBox.addItemListener {
|
||||
val newReceiver = if (it.stateChange == ItemEvent.SELECTED) item.parameter else null
|
||||
(parametersTableModel as KotlinFunctionParameterTableModel).receiver = newReceiver
|
||||
updateSignature()
|
||||
updateNameEditor()
|
||||
}
|
||||
component = checkBox
|
||||
editor = null
|
||||
} else
|
||||
continue
|
||||
|
||||
val label = JBLabel(columnInfo.name, UIUtil.ComponentStyle.SMALL)
|
||||
panel.add(label)
|
||||
|
||||
if (editor != null) {
|
||||
editor.addDocumentListener(
|
||||
object : DocumentListener {
|
||||
override fun documentChanged(e: DocumentEvent) {
|
||||
fireDocumentChanged(e, columnFinal)
|
||||
}
|
||||
}
|
||||
)
|
||||
editor.setPreferredWidth(table.width / parametersTableModel.columnCount)
|
||||
}
|
||||
|
||||
components.add(component)
|
||||
panel.add(component)
|
||||
add(panel)
|
||||
IJSwingUtilities.adjustComponentsOnMac(label, component)
|
||||
column++
|
||||
}
|
||||
}
|
||||
|
||||
override fun getValue(): JBTableRow {
|
||||
return JBTableRow { column ->
|
||||
val columnInfo = parametersTableModel.columnInfos[column]
|
||||
|
||||
when {
|
||||
KotlinPrimaryConstructorParameterTableModel.isValVarColumn(columnInfo) ->
|
||||
(components[column] as @Suppress("NO_TYPE_ARGUMENTS_ON_RHS") JComboBox).selectedItem
|
||||
KotlinCallableParameterTableModel.isTypeColumn(columnInfo) ->
|
||||
item.typeCodeFragment
|
||||
KotlinCallableParameterTableModel.isNameColumn(columnInfo) ->
|
||||
(components[column] as EditorTextField).text
|
||||
KotlinCallableParameterTableModel.isDefaultValueColumn(columnInfo) ->
|
||||
item.defaultValueCodeFragment
|
||||
else ->
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getColumnWidth(letters: Int): Int {
|
||||
var font = EditorColorsManager.getInstance().globalScheme.getFont(EditorFontType.PLAIN)
|
||||
font = Font(font.fontName, font.style, 12)
|
||||
return letters * Toolkit.getDefaultToolkit().getFontMetrics(font).stringWidth("W")
|
||||
}
|
||||
|
||||
private fun getEditorIndex(x: Int): Int {
|
||||
@Suppress("NAME_SHADOWING") var x = x
|
||||
|
||||
val columnLetters = if (isDefaultColumnEnabled())
|
||||
intArrayOf(4, getParamNamesMaxLength(), getTypesMaxLength(), getDefaultValuesMaxLength())
|
||||
else
|
||||
intArrayOf(4, getParamNamesMaxLength(), getTypesMaxLength())
|
||||
|
||||
var columnIndex = 0
|
||||
for (i in (if (myMethod.kind === Kind.PRIMARY_CONSTRUCTOR) 0 else 1) until columnLetters.size) {
|
||||
val width = getColumnWidth(columnLetters[i])
|
||||
|
||||
if (x <= width)
|
||||
return columnIndex
|
||||
|
||||
columnIndex++
|
||||
x -= width
|
||||
}
|
||||
|
||||
return columnIndex - 1
|
||||
}
|
||||
|
||||
override fun getPreferredFocusedComponent(): JComponent {
|
||||
val me = mouseEvent
|
||||
val index = when {
|
||||
me != null -> getEditorIndex(me.point.getX().toInt())
|
||||
myMethod.kind === Kind.PRIMARY_CONSTRUCTOR -> 1
|
||||
else -> 0
|
||||
}
|
||||
val component = components[index]
|
||||
return if (component is EditorTextField) component.focusTarget else component
|
||||
}
|
||||
|
||||
override fun getFocusableComponents(): Array<JComponent> {
|
||||
return Array(components.size) {
|
||||
val component = components[it]
|
||||
(component as? EditorTextField)?.focusTarget ?: component
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun calculateSignature(): String {
|
||||
val changeInfo = evaluateChangeInfo(
|
||||
parametersTableModel,
|
||||
myReturnTypeCodeFragment,
|
||||
getMethodDescriptor(),
|
||||
visibility,
|
||||
methodName,
|
||||
myDefaultValueContext,
|
||||
true
|
||||
)
|
||||
return changeInfo.getNewSignature(getMethodDescriptor().originalPrimaryCallable)
|
||||
}
|
||||
|
||||
override fun createVisibilityControl() = ComboBoxVisibilityPanel(
|
||||
arrayOf(Visibilities.INTERNAL, Visibilities.PRIVATE, Visibilities.PROTECTED, Visibilities.PUBLIC)
|
||||
)
|
||||
|
||||
override fun updateSignatureAlarmFired() {
|
||||
super.updateSignatureAlarmFired()
|
||||
validateButtons()
|
||||
}
|
||||
|
||||
override fun validateAndCommitData(): String? {
|
||||
if (myMethod.canChangeReturnType() == MethodDescriptor.ReadWriteOption.ReadWrite &&
|
||||
myReturnTypeCodeFragment.getTypeInfo(isCovariant = true, forPreview = false).type == null
|
||||
) {
|
||||
if (Messages.showOkCancelDialog(
|
||||
myProject,
|
||||
"Return type '${myReturnTypeCodeFragment!!.text}' cannot be resolved.\nContinue?",
|
||||
RefactoringBundle.message("changeSignature.refactoring.name"),
|
||||
Messages.getWarningIcon()
|
||||
) != Messages.OK
|
||||
) {
|
||||
return EXIT_SILENTLY
|
||||
}
|
||||
}
|
||||
|
||||
for (item in parametersTableModel.items) {
|
||||
if (item.typeCodeFragment.getTypeInfo(isCovariant = true, forPreview = false).type == null) {
|
||||
val paramText = if (item.parameter != parametersTableModel.receiver) "parameter '${item.parameter.name}'" else "receiver"
|
||||
if (Messages.showOkCancelDialog(
|
||||
myProject,
|
||||
"Type '${item.typeCodeFragment.text}' for $paramText cannot be resolved.\nContinue?",
|
||||
RefactoringBundle.message("changeSignature.refactoring.name"),
|
||||
Messages.getWarningIcon()
|
||||
) != Messages.OK
|
||||
) {
|
||||
return EXIT_SILENTLY
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun canRun() {
|
||||
if (myNamePanel.isVisible && myMethod.canChangeName() && !methodName.isIdentifier()) {
|
||||
throw ConfigurationException(KotlinRefactoringBundle.message("function.name.is.invalid"))
|
||||
}
|
||||
|
||||
if (myMethod.canChangeReturnType() === MethodDescriptor.ReadWriteOption.ReadWrite) {
|
||||
(myReturnTypeCodeFragment as? KtTypeCodeFragment)
|
||||
?.validateElement(KotlinRefactoringBundle.message("return.type.is.invalid"))
|
||||
}
|
||||
|
||||
for (item in parametersTableModel.items) {
|
||||
val parameterName = item.parameter.name
|
||||
|
||||
if (item.parameter != parametersTableModel.receiver && !parameterName.isIdentifier()) {
|
||||
throw ConfigurationException(KotlinRefactoringBundle.message("parameter.name.is.invalid", parameterName))
|
||||
}
|
||||
|
||||
(item.typeCodeFragment as? KtTypeCodeFragment)
|
||||
?.validateElement(KotlinRefactoringBundle.message("parameter.type.is.invalid", item.typeCodeFragment.text))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRefactoringProcessor(): BaseRefactoringProcessor {
|
||||
val changeInfo = evaluateChangeInfo(
|
||||
parametersTableModel,
|
||||
myReturnTypeCodeFragment,
|
||||
getMethodDescriptor(),
|
||||
visibility,
|
||||
methodName,
|
||||
myDefaultValueContext,
|
||||
false
|
||||
)
|
||||
changeInfo.primaryPropagationTargets = myMethodsToPropagateParameters ?: emptyList()
|
||||
return KotlinChangeSignatureProcessor(myProject, changeInfo, commandName ?: title)
|
||||
}
|
||||
|
||||
private fun getMethodDescriptor(): KotlinMethodDescriptor = myMethod
|
||||
|
||||
override fun getSelectedIdx(): Int {
|
||||
return myMethod.parameters.withIndex().firstOrNull { it.value.isNewParameter }?.index
|
||||
?: super.getSelectedIdx()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun createParametersInfoModel(
|
||||
descriptor: KotlinMethodDescriptor,
|
||||
defaultValueContext: PsiElement
|
||||
): KotlinCallableParameterTableModel {
|
||||
val typeContext = getTypeCodeFragmentContext(descriptor.baseDeclaration)
|
||||
return when (descriptor.kind) {
|
||||
Kind.FUNCTION -> KotlinFunctionParameterTableModel(descriptor, typeContext, defaultValueContext)
|
||||
Kind.PRIMARY_CONSTRUCTOR -> KotlinPrimaryConstructorParameterTableModel(descriptor, typeContext, defaultValueContext)
|
||||
Kind.SECONDARY_CONSTRUCTOR -> KotlinSecondaryConstructorParameterTableModel(descriptor, typeContext, defaultValueContext)
|
||||
}
|
||||
}
|
||||
|
||||
fun getTypeCodeFragmentContext(startFrom: PsiElement): KtElement {
|
||||
return startFrom.parentsWithSelf.mapNotNull {
|
||||
when {
|
||||
it is KtNamedFunction -> it.bodyExpression ?: it.valueParameterList
|
||||
it is KtPropertyAccessor -> it.bodyExpression
|
||||
it is KtDeclaration && KtPsiUtil.isLocal(it) -> null
|
||||
it is KtConstructor<*> -> it
|
||||
it is KtClassOrObject -> it
|
||||
it is KtFile -> it
|
||||
else -> null
|
||||
}
|
||||
}.first()
|
||||
}
|
||||
|
||||
private fun createReturnTypeCodeFragment(project: Project, method: KotlinMethodDescriptor) =
|
||||
KtPsiFactory(project).createTypeCodeFragment(method.returnTypeInfo.render(), getTypeCodeFragmentContext(method.baseDeclaration))
|
||||
|
||||
fun createRefactoringProcessorForSilentChangeSignature(
|
||||
project: Project,
|
||||
commandName: String,
|
||||
method: KotlinMethodDescriptor,
|
||||
defaultValueContext: PsiElement
|
||||
): BaseRefactoringProcessor {
|
||||
val parameterTableModel = createParametersInfoModel(method, defaultValueContext)
|
||||
parameterTableModel.setParameterInfos(method.parameters)
|
||||
val changeInfo = evaluateChangeInfo(
|
||||
parameterTableModel,
|
||||
createReturnTypeCodeFragment(project, method),
|
||||
method,
|
||||
method.visibility,
|
||||
method.name,
|
||||
defaultValueContext,
|
||||
false
|
||||
)
|
||||
return KotlinChangeSignatureProcessor(project, changeInfo, commandName)
|
||||
}
|
||||
|
||||
fun PsiCodeFragment?.getTypeInfo(isCovariant: Boolean, forPreview: Boolean): KotlinTypeInfo {
|
||||
if (this !is KtTypeCodeFragment) return KotlinTypeInfo(isCovariant)
|
||||
|
||||
val typeRef = getContentElement()
|
||||
val type = typeRef?.analyze(BodyResolveMode.PARTIAL)?.get(BindingContext.TYPE, typeRef)
|
||||
return when {
|
||||
type != null && !type.isError -> KotlinTypeInfo(isCovariant, type, if (forPreview) typeRef.text else null)
|
||||
typeRef != null -> KotlinTypeInfo(isCovariant, null, typeRef.text)
|
||||
else -> KotlinTypeInfo(isCovariant)
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateChangeInfo(
|
||||
parametersModel: KotlinCallableParameterTableModel,
|
||||
returnTypeCodeFragment: PsiCodeFragment?,
|
||||
methodDescriptor: KotlinMethodDescriptor,
|
||||
visibility: Visibility?,
|
||||
methodName: String,
|
||||
defaultValueContext: PsiElement,
|
||||
forPreview: Boolean
|
||||
): KotlinChangeInfo {
|
||||
val parameters = parametersModel.items.map { parameter ->
|
||||
val parameterInfo = parameter.parameter
|
||||
|
||||
parameterInfo.currentTypeInfo = parameter.typeCodeFragment.getTypeInfo(false, forPreview)
|
||||
|
||||
val codeFragment = parameter.defaultValueCodeFragment as KtExpressionCodeFragment
|
||||
val oldDefaultValue = parameterInfo.defaultValueForCall
|
||||
if (codeFragment.text != (if (oldDefaultValue != null) oldDefaultValue.text else "")) {
|
||||
parameterInfo.defaultValueForCall = codeFragment.getContentElement()
|
||||
}
|
||||
|
||||
parameterInfo
|
||||
}
|
||||
|
||||
return KotlinChangeInfo(
|
||||
methodDescriptor.original,
|
||||
methodName,
|
||||
returnTypeCodeFragment.getTypeInfo(true, forPreview),
|
||||
visibility ?: Visibilities.DEFAULT_VISIBILITY,
|
||||
parameters,
|
||||
parametersModel.receiver,
|
||||
defaultValueContext
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -74,7 +74,7 @@ class KotlinChangeSignatureTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
private fun findCallers(method: PsiMethod): LinkedHashSet<PsiMethod> {
|
||||
val root = KotlinMethodNode(method, HashSet(), project, Runnable { })
|
||||
return (0..root.childCount - 1).flatMapTo(LinkedHashSet<PsiMethod>()) {
|
||||
(root.getChildAt(it) as KotlinMethodNode).method.toLightMethods()
|
||||
(root.getChildAt(it) as KotlinMethodNode).member.toLightMethods()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user