Obsolete API usages removed from 193 branch

#KT-35918 Comment
This commit is contained in:
Alexander Podkhalyuzin
2020-01-20 10:10:24 +01:00
committed by Vladimir Dolzhenko
parent 312c7bc9bf
commit dd73629db1
15 changed files with 1583 additions and 34 deletions
@@ -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()}"
}
}
@@ -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
@@ -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. //
// ---------------------------------------- //
}
@@ -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",
@@ -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) {
}
}
@@ -236,7 +236,6 @@ class KotlinChangeSignatureDialog(
components.add(component)
panel.add(component)
add(panel)
IJSwingUtilities.adjustComponentsOnMac(label, component)
column++
}
}
@@ -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
)
}
}
}
@@ -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()
}
}