KT-32366: Use TextEditorWithPreview as editor for scratch files

- get rid of `ScratchFileHook` completely
- use `KtsScratchFileEditorProvider` to encapsulate details about scratch file presentation
- remove obsolete functions from `scratchUtils`
This commit is contained in:
Roman Golyshev
2019-08-19 12:05:17 +03:00
committed by Roman Golyshev
parent 37c6102a03
commit 1d6de45db6
12 changed files with 152 additions and 140 deletions
@@ -21,8 +21,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.scratch.attachedOutputHandler
import org.jetbrains.kotlin.idea.scratch.getScratchFile
import org.jetbrains.kotlin.idea.scratch.ui.findScratchFileEditorWithPreview
class ClearScratchAction : ScratchAction(
KotlinBundle.message("scratch.clear.button"),
@@ -33,8 +32,7 @@ class ClearScratchAction : ScratchAction(
val selectedEditor = FileEditorManager.getInstance(project).selectedTextEditor ?: return
val textEditor = TextEditorProvider.getInstance().getTextEditor(selectedEditor)
val scratchFile = textEditor.getScratchFile() ?: return
textEditor.attachedOutputHandler?.clear(scratchFile)
textEditor.findScratchFileEditorWithPreview()?.clearOutputHandlers()
}
}
@@ -12,14 +12,9 @@ import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.actions.KOTLIN_WORKSHEET_EXTENSION
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandler
import org.jetbrains.kotlin.idea.scratch.ui.ScratchTopPanel
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.idea.scratch.ui.findScratchFileEditorWithPreview
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.scratch")
internal fun Logger.printDebugMessage(str: String) {
@@ -32,12 +27,6 @@ val VirtualFile.isKotlinWorksheet: Boolean
val VirtualFile.isKotlinScratch: Boolean
get() = ScratchFileService.getInstance().getRootType(this) is ScratchRootType
fun getEditorWithoutScratchFile(fileManager: FileEditorManager, virtualFile: VirtualFile): TextEditor? {
val editor = fileManager.getSelectedEditor(virtualFile) as? TextEditor
if (editor?.getScratchFile() != null) return null
return editor
}
fun getScratchFileFromEditorSelectedForFile(fileManager: FileEditorManager, virtualFile: VirtualFile): ScratchFile? {
val editor = fileManager.getSelectedEditor(virtualFile) as? TextEditor ?: return null
return editor.getScratchFile()
@@ -49,22 +38,5 @@ fun getScratchFileFromSelectedEditor(project: Project): ScratchFile? {
}
fun TextEditor.getScratchFile(): ScratchFile? {
return scratchTopPanel?.scratchFile
return findScratchFileEditorWithPreview()?.scratchFile
}
fun TextEditor.addScratchPanel(panel: ScratchTopPanel) {
scratchTopPanel = panel
FileEditorManager.getInstance(panel.scratchFile.project).addTopComponent(this, panel.component)
Disposer.register(this, panel)
}
fun TextEditor.attachOutputHandler(handler: ScratchOutputHandler) {
outputHandler = handler
}
val TextEditor.attachedOutputHandler: ScratchOutputHandler? get() = outputHandler
private var TextEditor.scratchTopPanel: ScratchTopPanel? by UserDataProperty<TextEditor, ScratchTopPanel>(Key.create("scratch.panel"))
private var TextEditor.outputHandler: ScratchOutputHandler? by UserDataProperty<TextEditor, ScratchOutputHandler>(Key.create("scratch.output.handler"))
@@ -0,0 +1,140 @@
/*
* 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.scratch.ui
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionToolbar
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorPolicy
import com.intellij.openapi.fileEditor.FileEditorProvider
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.pom.Navigatable
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.scratch.*
import org.jetbrains.kotlin.idea.scratch.output.InlayScratchOutputHandler
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter
import org.jetbrains.kotlin.psi.UserDataProperty
private const val KTS_SCRATCH_EDITOR_PROVIDER: String = "KtsScratchFileEditorProvider"
class KtScratchFileEditorProvider : FileEditorProvider, DumbAware {
override fun getEditorTypeId(): String = KTS_SCRATCH_EDITOR_PROVIDER
override fun accept(project: Project, file: VirtualFile): Boolean {
if (!file.isValid) return false
if (!(file.isKotlinScratch || file.isKotlinWorksheet)) return false
val psiFile = PsiManager.getInstance(project).findFile(file) ?: return false
return ScratchFileLanguageProvider.get(psiFile.fileType) != null
}
override fun createEditor(project: Project, file: VirtualFile): FileEditor {
val scratchFile = createScratchFile(project, file) ?: return TextEditorProvider.getInstance().createEditor(project, file)
return KtScratchFileEditorWithPreview.create(scratchFile)
}
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.HIDE_DEFAULT_EDITOR
}
class KtScratchFileEditorWithPreview private constructor(
val scratchFile: ScratchFile,
editor: TextEditor,
preview: TextEditor
) : TextEditorWithPreview(editor, preview), TextEditor {
private val inlayOutputHandler = InlayScratchOutputHandler(editor)
private val scratchTopPanel = ScratchTopPanel(scratchFile)
init {
editor.parentScratchEditorWithPreview = this
scratchFile.compilingScratchExecutor?.addOutputHandler(inlayOutputHandler)
scratchFile.replScratchExecutor?.addOutputHandler(inlayOutputHandler)
ScratchFileAutoRunner.addListener(scratchFile.project, editor)
}
override fun dispose() {
scratchFile.replScratchExecutor?.stop()
scratchFile.compilingScratchExecutor?.stop()
super.dispose()
}
override fun navigateTo(navigatable: Navigatable) {
myEditor.navigateTo(navigatable)
}
override fun canNavigateTo(navigatable: Navigatable): Boolean {
return myEditor.canNavigateTo(navigatable)
}
override fun getEditor(): Editor {
return myEditor.editor
}
override fun createToolbar(): ActionToolbar? {
return scratchTopPanel.actionsToolbar
}
fun clearOutputHandlers() {
inlayOutputHandler.clear(scratchFile)
}
companion object {
fun create(scratchFile: ScratchFile): KtScratchFileEditorWithPreview {
val textEditorProvider = TextEditorProvider.getInstance()
val mainEditor = textEditorProvider.createEditor(scratchFile.project, scratchFile.file) as TextEditor
val editorFactory = EditorFactory.getInstance()
val viewer = editorFactory.createViewer(editorFactory.createDocument(""))
Disposer.register(mainEditor, Disposable { editorFactory.releaseEditor(viewer) })
val previewEditor = textEditorProvider.getTextEditor(viewer)
return KtScratchFileEditorWithPreview(scratchFile, mainEditor, previewEditor)
}
}
}
fun TextEditor.findScratchFileEditorWithPreview(): KtScratchFileEditorWithPreview? =
if (this is KtScratchFileEditorWithPreview) this else parentScratchEditorWithPreview
private var TextEditor.parentScratchEditorWithPreview: KtScratchFileEditorWithPreview?
by UserDataProperty(Key.create("parent.preview.editor"))
fun createScratchFile(project: Project, file: VirtualFile): ScratchFile? {
val psiFile = PsiManager.getInstance(project).findFile(file) ?: return null
val scratchFile = ScratchFileLanguageProvider.get(psiFile.language)?.newScratchFile(project, file) ?: return null
setupCodeAnalyzerRestarterOutputHandler(project, scratchFile)
return scratchFile
}
private fun setupCodeAnalyzerRestarterOutputHandler(project: Project, scratchFile: ScratchFile) {
scratchFile.replScratchExecutor?.addOutputHandler(object : ScratchOutputHandlerAdapter() {
override fun onFinish(file: ScratchFile) {
ApplicationManager.getApplication().invokeLater {
if (!file.project.isDisposed) {
val scratch = file.getPsiFile()
if (scratch?.isValid == true) {
DaemonCodeAnalyzer.getInstance(project).restart(scratch)
}
}
}
}
})
}
@@ -1,87 +0,0 @@
/*
* 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.scratch.ui
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ProjectComponent
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.scratch.*
import org.jetbrains.kotlin.idea.scratch.output.InlayScratchOutputHandler
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter
class ScratchFileHook(val project: Project) : ProjectComponent {
override fun projectOpened() {
project.messageBus.connect(project).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, ScratchEditorListener())
}
private inner class ScratchEditorListener : FileEditorManagerListener {
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
if (!isPluggable(file)) return
val editor = getEditorWithoutScratchFile(source, file) ?: return
val scratchFile = createScratchFile(project, file) ?: return
val inlayOutputHandler = InlayScratchOutputHandler(editor)
scratchFile.compilingScratchExecutor?.addOutputHandler(inlayOutputHandler)
scratchFile.replScratchExecutor?.addOutputHandler(inlayOutputHandler)
editor.attachOutputHandler(inlayOutputHandler)
editor.addScratchPanel(ScratchTopPanel(scratchFile))
ScratchFileAutoRunner.addListener(project, editor)
}
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {}
}
private fun isPluggable(file: VirtualFile): Boolean {
if (!file.isValid) return false
if (!(file.isKotlinScratch || file.isKotlinWorksheet)) return false
val psiFile = PsiManager.getInstance(project).findFile(file) ?: return false
return ScratchFileLanguageProvider.get(psiFile.fileType) != null
}
}
private fun createScratchFile(project: Project, file: VirtualFile): ScratchFile? {
val psiFile = PsiManager.getInstance(project).findFile(file) ?: return null
val scratchFile = ScratchFileLanguageProvider.get(psiFile.language)?.newScratchFile(project, file) ?: return null
setupReplRestartingOutputHandler(project, scratchFile)
return scratchFile
}
private fun setupReplRestartingOutputHandler(project: Project, scratchFile: ScratchFile) {
scratchFile.replScratchExecutor?.addOutputHandler(object : ScratchOutputHandlerAdapter() {
override fun onFinish(file: ScratchFile) {
ApplicationManager.getApplication().invokeLater {
if (!file.project.isDisposed) {
val scratch = file.getPsiFile()
if (scratch?.isValid == true) {
DaemonCodeAnalyzer.getInstance(project).restart(scratch)
}
}
}
}
})
}
@@ -29,17 +29,11 @@ import org.jetbrains.kotlin.idea.scratch.actions.StopScratchAction
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter
import javax.swing.JComponent
class ScratchTopPanel(val scratchFile: ScratchFile) : Disposable {
override fun dispose() {
scratchFile.replScratchExecutor?.stop()
scratchFile.compilingScratchExecutor?.stop()
}
class ScratchTopPanel(val scratchFile: ScratchFile) {
private val moduleChooserAction: ModulesComboBoxAction = ModulesComboBoxAction(scratchFile)
private val actionsToolbar: ActionToolbar
val actionsToolbar: ActionToolbar
init {
scratchFile.addModuleListener { _, _ -> updateToolbar() }
setupTopPanelUpdateHandlers()
val toolbarGroup = DefaultActionGroup().apply {
@@ -59,9 +53,9 @@ class ScratchTopPanel(val scratchFile: ScratchFile) : Disposable {
actionsToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, toolbarGroup, true)
}
val component: JComponent = actionsToolbar.component
private fun setupTopPanelUpdateHandlers() {
scratchFile.addModuleListener { _, _ -> updateToolbar() }
val toolbarHandler = createUpdateToolbarHandler()
scratchFile.replScratchExecutor?.addOutputHandler(toolbarHandler)
scratchFile.compilingScratchExecutor?.addOutputHandler(toolbarHandler)
@@ -100,6 +100,7 @@
description="Add all transitive dependencies on MPP modules when Android project is imported"
defaultValue="true"
restartRequired="false"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
</extensions>
</idea-plugin>
@@ -104,6 +104,7 @@
description="Add all transitive dependencies on MPP modules when Android project is imported"
defaultValue="true"
restartRequired="false"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
</extensions>
</idea-plugin>
@@ -96,6 +96,7 @@
description="Add all transitive dependencies on MPP modules when Android project is imported"
defaultValue="true"
restartRequired="false"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
</extensions>
</idea-plugin>
@@ -95,6 +95,7 @@
description="Add all transitive dependencies on MPP modules when Android project is imported"
defaultValue="true"
restartRequired="false"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
</extensions>
</idea-plugin>
-3
View File
@@ -17,9 +17,6 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ui.ScratchFileHook</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ScratchFileModuleInfoProvider</implementation-class>
</component>
-3
View File
@@ -17,9 +17,6 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ui.ScratchFileHook</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ScratchFileModuleInfoProvider</implementation-class>
</component>
-3
View File
@@ -17,9 +17,6 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ui.ScratchFileHook</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ScratchFileModuleInfoProvider</implementation-class>
</component>