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
|
package org.jetbrains.kotlin.idea.actions
|
||||||
|
|
||||||
import com.intellij.codeInsight.intention.IntentionAction
|
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.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
@@ -30,18 +30,18 @@ class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware {
|
|||||||
openLogsDirIfPresent(project)
|
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) {
|
override fun update(e: AnActionEvent) {
|
||||||
val presentation = e.presentation
|
val presentation = e.presentation
|
||||||
presentation.isEnabledAndVisible = e.project != null && ShowFilePathAction.isSupported()
|
presentation.isEnabledAndVisible = e.project != null && RevealFileAction.isSupported()
|
||||||
presentation.text = NAME
|
presentation.text = NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openLogsDirIfPresent(project: Project) {
|
private fun openLogsDirIfPresent(project: Project) {
|
||||||
val logsDir = findLogsDir()
|
val logsDir = findLogsDir()
|
||||||
if (logsDir != null) {
|
if (logsDir != null) {
|
||||||
ShowFilePathAction.openDirectory(logsDir)
|
RevealFileAction.openDirectory(logsDir)
|
||||||
} else {
|
} else {
|
||||||
val parent = WindowManager.getInstance().getStatusBar(project)?.component
|
val parent = WindowManager.getInstance().getStatusBar(project)?.component
|
||||||
?: WindowManager.getInstance().findVisibleFrame().rootPane
|
?: WindowManager.getInstance().findVisibleFrame().rootPane
|
||||||
@@ -79,6 +79,6 @@ class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val gradleTroubleshootingLink = "https://docs.gradle.org/current/userguide/kotlin_dsl.html#troubleshooting"
|
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.intellij.lang.annotations.Language;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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.config.KotlinFacetSettings;
|
||||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
|
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
|
||||||
import org.jetbrains.kotlin.platform.TargetPlatformKt;
|
import org.jetbrains.kotlin.platform.TargetPlatformKt;
|
||||||
@@ -76,15 +78,15 @@ class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
|||||||
"}\n";
|
"}\n";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(@NotNull Project project,
|
public Promise<Result> run(@NotNull Project project,
|
||||||
@NotNull ProjectTaskContext context,
|
@NotNull ProjectTaskContext context,
|
||||||
@Nullable ProjectTaskNotification callback,
|
@NotNull ProjectTask ... tasks) {
|
||||||
@NotNull Collection<? extends ProjectTask> tasks) {
|
AsyncPromise<Result> resultPromise = new AsyncPromise<>();
|
||||||
MultiMap<String, String> buildTasksMap = MultiMap.createLinkedSet();
|
MultiMap<String, String> buildTasksMap = MultiMap.createLinkedSet();
|
||||||
MultiMap<String, String> cleanTasksMap = MultiMap.createLinkedSet();
|
MultiMap<String, String> cleanTasksMap = MultiMap.createLinkedSet();
|
||||||
MultiMap<String, String> initScripts = 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);
|
List<Module> modules = addModulesBuildTasks(taskMap.get(ModuleBuildTask.class), buildTasksMap, initScripts);
|
||||||
// TODO there should be 'gradle' way to build files instead of related modules entirely
|
// 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 successCounter = new AtomicInteger();
|
||||||
AtomicInteger errorCounter = new AtomicInteger();
|
AtomicInteger errorCounter = new AtomicInteger();
|
||||||
|
|
||||||
TaskCallback taskCallback = callback == null ? null : new TaskCallback() {
|
TaskCallback taskCallback = new TaskCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
handle(true);
|
handle(true);
|
||||||
@@ -120,7 +122,7 @@ class KotlinMPPGradleProjectTaskRunner extends ProjectTaskRunner
|
|||||||
CompilerUtil.refreshOutputRoots(affectedRoots);
|
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,
|
ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID,
|
||||||
taskCallback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
|
taskCallback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
|
||||||
}
|
}
|
||||||
|
return resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.core.RevisionsCollector
|
||||||
import com.intellij.history.integration.LocalHistoryImpl
|
import com.intellij.history.integration.LocalHistoryImpl
|
||||||
import com.intellij.history.integration.patches.PatchCreator
|
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.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.application.PathManager
|
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
|
const val ZIP_FRACTION = 1.0 - PATCHES_FRACTION - LOGS_FRACTION - PROJECT_SYSTEM_FRACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
val project = e.project!!
|
val project = e.project!!
|
||||||
val projectBaseDir = File(project.baseDir!!.path)
|
val projectBaseDir = File(project.baseDir!!.path)
|
||||||
@@ -183,7 +184,7 @@ class CreateIncrementalCompilationBackup : AnAction("Create backup for debugging
|
|||||||
|
|
||||||
WaitForProgressToShow.runOrInvokeLaterAboveProgress(
|
WaitForProgressToShow.runOrInvokeLaterAboveProgress(
|
||||||
{
|
{
|
||||||
ShowFilePathAction.showDialog(
|
RevealFileAction.showDialog(
|
||||||
project,
|
project,
|
||||||
"Successfully created backup " + backupFile.absolutePath,
|
"Successfully created backup " + backupFile.absolutePath,
|
||||||
"Created backup",
|
"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) {
|
if (!isAutoRun && module != null && isMakeBeforeRun) {
|
||||||
val project = scratchFile.project
|
val project = scratchFile.project
|
||||||
ProjectTaskManager.getInstance(project).build(arrayOf(module), object : ProjectTaskNotification {
|
ProjectTaskManager.getInstance(project).build(module).onSuccess { executionResult ->
|
||||||
override fun finished(context: ProjectTaskContext, executionResult: ProjectTaskResult) {
|
if (executionResult.isAborted || executionResult.hasErrors()) {
|
||||||
if (executionResult.isAborted || executionResult.errors > 0) {
|
executor.errorOccurs("There were compilation errors in module ${module.name}")
|
||||||
executor.errorOccurs("There were compilation errors in module ${module.name}")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (DumbService.isDumb(project)) {
|
if (DumbService.isDumb(project)) {
|
||||||
DumbService.getInstance(project).smartInvokeLater {
|
DumbService.getInstance(project).smartInvokeLater {
|
||||||
executeScratch()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
executeScratch()
|
executeScratch()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
executeScratch()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
executeScratch()
|
executeScratch()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,13 +42,11 @@ class ConsoleCompilerHelper(
|
|||||||
|
|
||||||
fun compileModule() {
|
fun compileModule() {
|
||||||
if (ExecutionManager.getInstance(project).contentManager.removeRunContent(executor, contentDescriptor)) {
|
if (ExecutionManager.getInstance(project).contentManager.removeRunContent(executor, contentDescriptor)) {
|
||||||
ProjectTaskManager.getInstance(project).build(arrayOf(module), object : ProjectTaskNotification {
|
ProjectTaskManager.getInstance(project).build(module).onSuccess { executionResult ->
|
||||||
override fun finished(context: ProjectTaskContext, executionResult: ProjectTaskResult) {
|
if (!module.isDisposed) {
|
||||||
if (!module.isDisposed) {
|
KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = executionResult.hasErrors())
|
||||||
KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = executionResult.errors > 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.GsonBuilder
|
||||||
import com.google.gson.JsonIOException
|
import com.google.gson.JsonIOException
|
||||||
import com.google.gson.JsonSyntaxException
|
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.plugins.*
|
||||||
import com.intellij.ide.util.PropertiesComponent
|
import com.intellij.ide.util.PropertiesComponent
|
||||||
import com.intellij.notification.NotificationDisplayType
|
import com.intellij.notification.NotificationDisplayType
|
||||||
@@ -306,7 +306,7 @@ class KotlinPluginUpdater : Disposable {
|
|||||||
NotificationType.INFORMATION
|
NotificationType.INFORMATION
|
||||||
) { notification, _ ->
|
) { notification, _ ->
|
||||||
val logFile = File(PathManager.getLogPath(), "idea.log")
|
val logFile = File(PathManager.getLogPath(), "idea.log")
|
||||||
ShowFilePathAction.openFile(logFile)
|
RevealFileAction.openFile(logFile)
|
||||||
|
|
||||||
notification.expire()
|
notification.expire()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,4 +77,8 @@ class KotlinSdkType : SdkType("KotlinSDK") {
|
|||||||
override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {
|
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)
|
components.add(component)
|
||||||
panel.add(component)
|
panel.add(component)
|
||||||
add(panel)
|
add(panel)
|
||||||
IJSwingUtilities.adjustComponentsOnMac(label, component)
|
|
||||||
column++
|
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> {
|
private fun findCallers(method: PsiMethod): LinkedHashSet<PsiMethod> {
|
||||||
val root = KotlinMethodNode(method, HashSet(), project, Runnable { })
|
val root = KotlinMethodNode(method, HashSet(), project, Runnable { })
|
||||||
return (0..root.childCount - 1).flatMapTo(LinkedHashSet<PsiMethod>()) {
|
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