Use ReplState to implement completion in IDE REPL

Consider sucessfully evaluated lines as previous lines when analyzing console file
This commit is contained in:
Pavel V. Talanov
2016-04-07 20:06:55 +03:00
parent 08ef1749b6
commit 9cff590366
3 changed files with 77 additions and 25 deletions
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2016 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -50,11 +49,14 @@ public class KotlinScriptDefinitionProvider {
return findScriptDefinition(psiFile) != null; return findScriptDefinition(psiFile) != null;
} }
@TestOnly
public void addScriptDefinition(@NotNull KotlinScriptDefinition scriptDefinition) { public void addScriptDefinition(@NotNull KotlinScriptDefinition scriptDefinition) {
definitions.add(0, scriptDefinition); definitions.add(0, scriptDefinition);
} }
public void removeScriptDefinition(@NotNull KotlinScriptDefinition scriptDefinition) {
definitions.remove(scriptDefinition);
}
public void setScriptDefinitions(@NotNull List<KotlinScriptDefinition> definitions) { public void setScriptDefinitions(@NotNull List<KotlinScriptDefinition> definitions) {
this.definitions.clear(); this.definitions.clear();
this.definitions.addAll(definitions); this.definitions.addAll(definitions);
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2016 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@ import com.intellij.execution.console.LanguageConsoleBuilder
import com.intellij.execution.console.LanguageConsoleView import com.intellij.execution.console.LanguageConsoleView
import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler
import com.intellij.execution.process.OSProcessHandler import com.intellij.execution.process.OSProcessHandler
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory
import com.intellij.execution.ui.RunContentDescriptor import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
@@ -32,6 +30,7 @@ import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.CommonShortcuts import com.intellij.openapi.actionSystem.CommonShortcuts
import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
@@ -43,7 +42,12 @@ import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.console.actions.BuildAndRestartConsoleAction import org.jetbrains.kotlin.console.actions.BuildAndRestartConsoleAction
import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction
@@ -51,9 +55,21 @@ import org.jetbrains.kotlin.console.gutter.ConsoleGutterContentProvider
import org.jetbrains.kotlin.console.gutter.ConsoleIndicatorRenderer import org.jetbrains.kotlin.console.gutter.ConsoleIndicatorRenderer
import org.jetbrains.kotlin.console.gutter.IconWithTooltip import org.jetbrains.kotlin.console.gutter.IconWithTooltip
import org.jetbrains.kotlin.console.gutter.ReplIcons import org.jetbrains.kotlin.console.gutter.ReplIcons
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.completion.doNotComplete import org.jetbrains.kotlin.idea.caches.resolve.ModuleTestSourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.psi.moduleInfo
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyScriptDescriptor
import org.jetbrains.kotlin.resolve.repl.ReplState
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptParameter
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import kotlin.properties.Delegates import kotlin.properties.Delegates
@@ -68,7 +84,13 @@ class KotlinConsoleRunner(
title: String, title: String,
path: String? path: String?
) : AbstractConsoleRunnerWithHistory<LanguageConsoleView>(myProject, title, path) { ) : AbstractConsoleRunnerWithHistory<LanguageConsoleView>(myProject, title, path) {
private val replState = ReplState()
override fun finishConsole() { override fun finishConsole() {
KotlinConsoleKeeper.getInstance(project).removeConsole(consoleView.virtualFile)
KotlinScriptDefinitionProvider.getInstance(project).removeScriptDefinition(consoleScriptDefinition)
if (ApplicationManager.getApplication().isUnitTestMode) { if (ApplicationManager.getApplication().isUnitTestMode) {
// Ignore super with myConsoleView.setEditable(false) // Ignore super with myConsoleView.setEditable(false)
return return
@@ -99,17 +121,22 @@ class KotlinConsoleRunner(
val executor = CommandExecutor(this) val executor = CommandExecutor(this)
var compilerHelper: ConsoleCompilerHelper by Delegates.notNull() var compilerHelper: ConsoleCompilerHelper by Delegates.notNull()
private val consoleScriptDefinition = object : KotlinScriptDefinition {
override fun isScript(file: PsiFile) = file.originalFile.virtualFile == consoleView.virtualFile
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) = emptyList<ScriptParameter>()
override fun getScriptName(script: KtScript) = Name.identifier("REPL")
}
override fun createProcess() = cmdLine.createProcess() override fun createProcess() = cmdLine.createProcess()
override fun createConsoleView(): LanguageConsoleView? { override fun createConsoleView(): LanguageConsoleView? {
val consoleView = LanguageConsoleBuilder() val builder = LanguageConsoleBuilder()
.gutterContentProvider(ConsoleGutterContentProvider())
.build(project, KotlinLanguage.INSTANCE) val consoleView = builder.gutterContentProvider(ConsoleGutterContentProvider()).build(project, KotlinLanguage.INSTANCE)
consoleView.prompt = null consoleView.prompt = null
disableCompletion(consoleView)
val consoleEditor = consoleView.consoleEditor val consoleEditor = consoleView.consoleEditor
setupPlaceholder(consoleEditor) setupPlaceholder(consoleEditor)
@@ -120,9 +147,17 @@ class KotlinConsoleRunner(
val executeAction = KtExecuteCommandAction(consoleView.virtualFile) val executeAction = KtExecuteCommandAction(consoleView.virtualFile)
executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component) executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component)
KotlinScriptDefinitionProvider.getInstance(project).addScriptDefinition(consoleScriptDefinition)
enableCompletion(consoleView)
return consoleView return consoleView
} }
private fun enableCompletion(consoleView: LanguageConsoleView) {
val consoleKtFile = PsiManager.getInstance(project).findFile(consoleView.virtualFile) as? KtFile ?: return
consoleKtFile.moduleInfo = ModuleTestSourceInfo(module)
}
override fun createProcessHandler(process: Process): OSProcessHandler { override fun createProcessHandler(process: Process): OSProcessHandler {
val processHandler = ReplOutputHandler( val processHandler = ReplOutputHandler(
this, this,
@@ -133,11 +168,6 @@ class KotlinConsoleRunner(
val keeper = KotlinConsoleKeeper.getInstance(project) val keeper = KotlinConsoleKeeper.getInstance(project)
keeper.putVirtualFileToConsole(consoleFile, this) keeper.putVirtualFileToConsole(consoleFile, this)
processHandler.addProcessListener(object : ProcessAdapter() {
override fun processTerminated(event: ProcessEvent) {
keeper.removeConsole(consoleFile)
}
})
return processHandler return processHandler
} }
@@ -180,12 +210,6 @@ class KotlinConsoleRunner(
editor.setPlaceholderAttributes(placeholderAttrs) editor.setPlaceholderAttributes(placeholderAttrs)
} }
private fun disableCompletion(consoleView: LanguageConsoleView) {
val consoleFile = consoleView.virtualFile
val jetFile = PsiManager.getInstance(project).findFile(consoleFile) as? KtFile ?: return
jetFile.doNotComplete = true
}
fun setupGutters() { fun setupGutters() {
fun configureEditorGutter(editor: EditorEx, color: Color, iconWithTooltip: IconWithTooltip): RangeHighlighter { fun configureEditorGutter(editor: EditorEx, color: Color, iconWithTooltip: IconWithTooltip): RangeHighlighter {
editor.settings.isLineMarkerAreaShown = true // hack to show gutter editor.settings.isLineMarkerAreaShown = true // hack to show gutter
@@ -225,4 +249,30 @@ class KotlinConsoleRunner(
processHandler.destroyProcess() processHandler.destroyProcess()
Disposer.dispose(disposableDescriptor) Disposer.dispose(disposableDescriptor)
} }
fun successfulLine(text: String) {
runReadAction {
val lineNumber = replState.successfulLinesCount + 1
val virtualFile =
LightVirtualFile("line$lineNumber${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, text).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile = (PsiFileFactory.getInstance(project) as PsiFileFactoryImpl).trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Failed to setup PSI for file:\n$text")
replState.submitLine(psiFile)
psiFile.moduleInfo = ModuleTestSourceInfo(module)
val scriptDescriptor = psiFile.script!!.resolveToDescriptor() as? LazyScriptDescriptor ?: error("Failed to analyze line:\n$text")
ForceResolveUtil.forceResolveAllContents(scriptDescriptor)
replState.lineSuccess(psiFile, scriptDescriptor)
replState.submitLine(consoleFile) // reset file scope customizer
}
}
val consoleFile: KtFile
get() {
val consoleFile = consoleView.virtualFile
return PsiManager.getInstance(project).findFile(consoleFile) as KtFile
}
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2016 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -89,7 +89,7 @@ class ReplOutputHandler(
"COMPILE_ERROR" -> outputProcessor.highlightCompilerErrors(createCompilerMessages(content)) "COMPILE_ERROR" -> outputProcessor.highlightCompilerErrors(createCompilerMessages(content))
"RUNTIME_ERROR" -> outputProcessor.printRuntimeError("${content.trim()}\n") "RUNTIME_ERROR" -> outputProcessor.printRuntimeError("${content.trim()}\n")
"INTERNAL_ERROR" -> outputProcessor.printInternalErrorMessage(content) "INTERNAL_ERROR" -> outputProcessor.printInternalErrorMessage(content)
"SUCCESS" -> Unit "SUCCESS" -> runner.commandHistory.lastUnprocessedEntry()?.entryText?.let { runner.successfulLine(it) }
else -> logError(ReplOutputHandler::class.java, "Unexpected output type:\n$outputType") else -> logError(ReplOutputHandler::class.java, "Unexpected output type:\n$outputType")
} }