diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt index 5a89cf81a6b..3c0e3d54bf0 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt @@ -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()}" } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.java b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.java index 17ddfa26ae6..bf2b1d64d69 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.java +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.java @@ -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 run(@NotNull Project project, @NotNull ProjectTaskContext context, - @Nullable ProjectTaskNotification callback, - @NotNull Collection tasks) { + @NotNull ProjectTask ... tasks) { + AsyncPromise resultPromise = new AsyncPromise<>(); MultiMap buildTasksMap = MultiMap.createLinkedSet(); MultiMap cleanTasksMap = MultiMap.createLinkedSet(); MultiMap initScripts = MultiMap.createLinkedSet(); - Map, List> taskMap = JpsProjectTaskRunner.groupBy(tasks); + Map, List> taskMap = JpsProjectTaskRunner.groupBy(Arrays.asList(tasks)); List 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 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.kt.192 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.kt.192 new file mode 100644 index 00000000000..17ddfa26ae6 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinMPPGradleProjectTaskRunner.kt.192 @@ -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 tasks) { + MultiMap buildTasksMap = MultiMap.createLinkedSet(); + MultiMap cleanTasksMap = MultiMap.createLinkedSet(); + MultiMap initScripts = MultiMap.createLinkedSet(); + + Map, List> taskMap = JpsProjectTaskRunner.groupBy(tasks); + + List modules = addModulesBuildTasks(taskMap.get(ModuleBuildTask.class), buildTasksMap, initScripts); + // TODO there should be 'gradle' way to build files instead of related modules entirely + List modulesOfFiles = addModulesBuildTasks(taskMap.get(ModuleFilesBuildTask.class), buildTasksMap, initScripts); + + // TODO send a message if nothing to build + Set 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 affectedModules = ContainerUtil.concat(modules, modulesOfFiles); + // have to refresh in case of errors too, because run configuration may be set to ignore errors + Collection 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 buildTasks = buildTasksMap.get(rootProjectPath); + if (buildTasks.isEmpty()) continue; + Collection 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 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 addModulesBuildTasks(@Nullable Collection projectTasks, + @NotNull MultiMap buildTasksMap, + @NotNull MultiMap initScripts) { + if (ContainerUtil.isEmpty(projectTasks)) return Collections.emptyList(); + + List affectedModules = new SmartList<>(); + Map 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 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 gradleTasks = ContainerUtil.mapNotNull( + findAll(moduleDataNode, ProjectKeys.TASK), node -> + node.getData().isInherited() ? null : trimStart(node.getData().getName(), taskPrefix)); + + Collection projectInitScripts = initScripts.getModifiable(rootProjectPath); + Collection 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 findNativeGradleBuildTasks(Collection 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 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 findMetadataBuildTasks(Collection 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 addPrefix(Collection tasks, String taskPrefix) { + return tasks.stream().map(task -> taskPrefix + task).collect(Collectors.toList()); + } + // ---------------------------------------- // + // TODO END: Extract custom Kotlin logic. // + // ---------------------------------------- // +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt index b0eac39d0c2..158c75028a8 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt @@ -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", diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt.192 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt.192 new file mode 100644 index 00000000000..b0eac39d0c2 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/internal/makeBackup/CreateIncrementalCompilationBackup.kt.192 @@ -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>() // 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 + ) + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt index bd7ec9cc219..e19ca15b836 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt @@ -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() } diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt index 02f938dbdd8..296417b6c85 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt @@ -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()) } - }) + } } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinIdeFileIconProviderService.kt.as35 b/idea/src/org/jetbrains/kotlin/idea/KotlinIdeFileIconProviderService.kt.as35 new file mode 100644 index 00000000000..4997d39f358 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinIdeFileIconProviderService.kt.as35 @@ -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") + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.192 b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.192 new file mode 100644 index 00000000000..6765dd38a72 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.192 @@ -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. Install", + 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. See the log for more information", + 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 = try { + HttpRequests.request(url).connect { + GsonBuilder().create().fromJson(it.inputStream.reader(), Array::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) + } + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt index 6765dd38a72..c0b28c85f7b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinPluginUpdater.kt @@ -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() } diff --git a/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt b/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt index 154e0206ee5..a37104b1fdb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt +++ b/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt @@ -77,4 +77,8 @@ class KotlinSdkType : SdkType("KotlinSDK") { override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) { } + + override fun allowCreationByUser(): Boolean { + return false + } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt.192 b/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt.192 new file mode 100644 index 00000000000..154e0206ee5 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/framework/KotlinSdkType.kt.192 @@ -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 = 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) { + sdkCreatedCallback.consume(createSdkWithUniqueName(sdkModel.sdks.toList())) + } + + fun createSdkWithUniqueName(existingSdks: Collection): 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) { + + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt index df401cceb79..ee40b2bd17e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt @@ -236,7 +236,6 @@ class KotlinChangeSignatureDialog( components.add(component) panel.add(component) add(panel) - IJSwingUtilities.adjustComponentsOnMac(label, component) column++ } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt.192 b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt.192 new file mode 100644 index 00000000000..772a0856469 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/ui/KotlinChangeSignatureDialog.kt.192 @@ -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, + 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, + 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): String { + val parameter = item.parameter + return if (parameter == parametersTableModel.receiver) "" else parameter.name + } + + private fun getColumnTextMaxLength(nameFunction: Function1, 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): 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>) = + 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): JBTableRowEditor? { + return object : JBTableRowEditor() { + private val components = ArrayList() + 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 { + 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 + ) + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt index 892710bb7c9..ab7a6e03382 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt @@ -74,7 +74,7 @@ class KotlinChangeSignatureTest : KotlinLightCodeInsightFixtureTestCase() { private fun findCallers(method: PsiMethod): LinkedHashSet { val root = KotlinMethodNode(method, HashSet(), project, Runnable { }) return (0..root.childCount - 1).flatMapTo(LinkedHashSet()) { - (root.getChildAt(it) as KotlinMethodNode).method.toLightMethods() + (root.getChildAt(it) as KotlinMethodNode).member.toLightMethods() } }