diff --git a/gradle/versions.properties.193 b/gradle/versions.properties.193 index 785428b1b4d..c40861de446 100644 --- a/gradle/versions.properties.193 +++ b/gradle/versions.properties.193 @@ -1,4 +1,4 @@ -versions.intellijSdk=193.3793.14-EAP-SNAPSHOT +versions.intellijSdk=193.4099-EAP-CANDIDATE-SNAPSHOT versions.androidBuildTools=r23.0.1 versions.idea.NodeJS=181.3494.12 versions.jar.asm-all=7.0.1 diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/benchmark/AbstractCompletionBenchmarkAction.kt.193 b/idea/src/org/jetbrains/kotlin/idea/actions/internal/benchmark/AbstractCompletionBenchmarkAction.kt.193 new file mode 100644 index 00000000000..7016ba1f00a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/benchmark/AbstractCompletionBenchmarkAction.kt.193 @@ -0,0 +1,218 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.actions.internal.benchmark + +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.completion.CompletionType +import com.intellij.codeInsight.navigation.NavigationUtil +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.ScrollType +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.MessageType +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.wm.WindowManager +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.search.DelegatingGlobalSearchScope +import com.intellij.psi.search.FileTypeIndex +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBTextField +import com.intellij.uiDesigner.core.GridConstraints +import kotlinx.coroutines.* +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.caches.project.ModuleOrigin +import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo +import org.jetbrains.kotlin.idea.completion.CompletionBenchmarkSink +import org.jetbrains.kotlin.idea.core.moveCaret +import org.jetbrains.kotlin.idea.core.util.EDT +import org.jetbrains.kotlin.idea.core.util.getLineCount +import org.jetbrains.kotlin.idea.core.util.toPsiFile +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtFile +import java.util.* +import javax.swing.JFileChooser +import javax.swing.JPanel + +abstract class AbstractCompletionBenchmarkAction : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + + val benchmarkSink = CompletionBenchmarkSink.enableAndGet() + val scenario = createBenchmarkScenario(project, benchmarkSink) ?: return + + GlobalScope.launch(EDT) { + scenario.doBenchmark() + CompletionBenchmarkSink.disable() + } + } + + internal abstract fun createBenchmarkScenario( + project: Project, + benchmarkSink: CompletionBenchmarkSink.Impl + ): AbstractCompletionBenchmarkScenario? + + companion object { + fun showPopup(project: Project, text: String) { + val statusBar = WindowManager.getInstance().getStatusBar(project) + JBPopupFactory.getInstance() + .createHtmlTextBalloonBuilder(text, MessageType.ERROR, null) + .setFadeoutTime(5000) + .createBalloon().showInCenterOf(statusBar.component) + } + + internal fun List.randomElement(random: Random): T? = if (this.isNotEmpty()) this[random.nextInt(this.size)] else null + internal fun List.shuffledSequence(random: Random): Sequence = + generateSequence { this.randomElement(random) }.distinct() + + internal fun collectSuitableKotlinFiles(project: Project, filePredicate: (KtFile) -> Boolean): MutableList { + val scope = GlobalSearchScope.allScope(project) + + fun KtFile.isUsableForBenchmark(): Boolean { + val moduleInfo = this.getNullableModuleInfo() ?: return false + if (this.isCompiled || !this.isWritable || this.isScript()) return false + return moduleInfo.moduleOrigin == ModuleOrigin.MODULE + } + + val kotlinVFiles = FileTypeIndex.getFiles(KotlinFileType.INSTANCE, scope) + + return kotlinVFiles + .asSequence() + .mapNotNull { vfile -> (vfile.toPsiFile(project) as? KtFile) } + .filterTo(mutableListOf()) { it.isUsableForBenchmark() && filePredicate(it) } + } + + internal fun JPanel.addBoxWithLabel(tooltip: String, label: String = "$tooltip:", default: String, i: Int): JBTextField { + this.add(JBLabel(label), GridConstraints().apply { row = i; column = 0 }) + val textField = JBTextField().apply { + text = default + toolTipText = tooltip + } + this.add(textField, GridConstraints().apply { row = i; column = 1; fill = GridConstraints.FILL_HORIZONTAL }) + return textField + } + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = ApplicationManager.getApplication().isInternal + } +} + +internal abstract class AbstractCompletionBenchmarkScenario( + val project: Project, private val benchmarkSink: CompletionBenchmarkSink.Impl, + val random: Random = Random(), private val timeout: Long = 15000 +) { + + + sealed class Result { + abstract fun toCSV(stringBuilder: StringBuilder) + + open class SuccessResult(val lines: Int, val filePath: String, val first: Long, val full: Long) : Result() { + override fun toCSV(stringBuilder: StringBuilder): Unit = with(stringBuilder) { + append(filePath) + append(", ") + append(lines) + append(", ") + append(first) + append(", ") + append(full) + } + } + + class ErrorResult(val filePath: String) : Result() { + override fun toCSV(stringBuilder: StringBuilder): Unit = with(stringBuilder) { + append(filePath) + append(", ") + append(", ") + append(", ") + } + } + } + + + protected suspend fun typeAtOffsetAndGetResult(text: String, offset: Int, file: KtFile): Result { + NavigationUtil.openFileWithPsiElement(file.navigationElement, false, true) + + val document = + PsiDocumentManager.getInstance(project).getDocument(file) ?: return Result.ErrorResult("${file.virtualFile.path}:O$offset") + + val location = "${file.virtualFile.path}:${document.getLineNumber(offset)}" + + val editor = EditorFactory.getInstance().getEditors(document, project).firstOrNull() ?: return Result.ErrorResult(location) + + + delay(500) + + editor.moveCaret(offset, scrollType = ScrollType.CENTER) + + delay(500) + + CommandProcessor.getInstance().executeCommand(project, { + runWriteAction { + document.insertString(editor.caretModel.offset, "\n$text\n") + PsiDocumentManager.getInstance(project).commitDocument(document) + } + editor.moveCaret(editor.caretModel.offset + text.length + 1) + AutoPopupController.getInstance(project).scheduleAutoPopup(editor, CompletionType.BASIC, null) + }, "insertTextAndInvokeCompletion", "completionBenchmark") + + val result = try { + withTimeout(timeout) { collectResult(file, location) } + } catch (_: CancellationException) { + Result.ErrorResult(location) + } + + CommandProcessor.getInstance().executeCommand(project, { + runWriteAction { + document.deleteString(offset, offset + text.length + 2) + PsiDocumentManager.getInstance(project).commitDocument(document) + } + }, "revertToOriginal", "completionBenchmark") + + delay(100) + return result + } + + private suspend fun collectResult(file: KtFile, location: String): Result { + val results = benchmarkSink.channel.receive() + return Result.SuccessResult(file.getLineCount(), location, results.firstFlush, results.full) + } + + protected fun saveResults(allResults: List) { + val jfc = JFileChooser() + val result = jfc.showSaveDialog(null) + if (result == JFileChooser.APPROVE_OPTION) { + val file = jfc.selectedFile + file.writeText(buildString { + appendln("n, file, lines, ff, full") + var i = 0 + allResults.forEach { + append(i++) + append(", ") + it.toCSV(this) + appendln() + } + }) + } + AbstractCompletionBenchmarkAction.showPopup(project, "Done") + } + + abstract suspend fun doBenchmark() +} \ No newline at end of file