From 2ccf4b7d7f899c1e0eb6399047a958a7b48a6069 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Wed, 27 Dec 2017 11:52:53 +0300 Subject: [PATCH] Initial support for scratch files --- .../kotlin/idea/KotlinBundle.properties | 6 +- .../kotlin/idea/scratch/KtScratchFile.kt | 62 +++++++++++++ .../scratch/KtScratchFileLanguageProvider.kt | 27 ++++++ .../kotlin/idea/scratch/ScratchFile.kt | 26 ++++++ .../scratch/ScratchFileLanguageProvider.kt | 44 ++++++++++ .../scratch/ScratchFileModuleInfoProvider.kt | 64 ++++++++++++++ .../scratch/actions/ClearScratchAction.kt | 38 ++++++++ .../idea/scratch/actions/RunScratchAction.kt | 59 +++++++++++++ .../kotlin/idea/scratch/scratchUtils.kt | 59 +++++++++++++ .../kotlin/idea/scratch/ui/ScratchFileHook.kt | 63 ++++++++++++++ .../kotlin/idea/scratch/ui/ScratchTopPanel.kt | 87 +++++++++++++++++++ idea/src/META-INF/extensions/ide.xml | 7 ++ idea/src/META-INF/plugin.xml | 6 ++ .../idea/codeInsight/CodeInsightUtils.java | 11 ++- .../idea/refactoring/kotlinRefactoringUtil.kt | 5 +- 15 files changed, 559 insertions(+), 5 deletions(-) create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFileLanguageProvider.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileModuleInfoProvider.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ClearScratchAction.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/scratchUtils.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchFileHook.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties index 21b87599368..5451ec5c723 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties @@ -245,4 +245,8 @@ debugger.field.watchpoints.properties.panel.field.modification.label=Field &modi debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization # Android Lint -android.klint.inspections.group.name=Android Lint for Kotlin \ No newline at end of file +android.klint.inspections.group.name=Android Lint for Kotlin + +# Scratch Files +scratch.run.button=Run Scratch File +scratch.clear.button=Clear results diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt new file mode 100644 index 00000000000..d82ef5d4561 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt @@ -0,0 +1,62 @@ +/* + * 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 + +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.util.PsiUtil +import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.refactoring.getLineNumber +import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.psiUtil.endOffset + +class KtScratchFile(psiFile: KtFile) : ScratchFile(psiFile) { + + override fun getExpressions(): List { + // todo multiple expressions at one line + val doc = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile) ?: return emptyList() + var line = 0 + val result = arrayListOf() + while (line < doc.lineCount) { + val start = psiFile.getLineStartOffset(line) ?: continue + val elementAtOffset = CodeInsightUtils.getTopmostElementAtOffset( + PsiUtil.getElementAtOffset(psiFile, start), + start, + KtImportDirective::class.java, + KtDeclaration::class.java + ) + + if (elementAtOffset is PsiWhiteSpace) { + line = doc.getLineNumber(elementAtOffset.endOffset) + 1 + continue + } + + if (elementAtOffset == null) { + line += 1 + continue + } + + result.add(ScratchExpression(elementAtOffset, elementAtOffset.getLineNumber(true), elementAtOffset.getLineNumber(false))) + line = elementAtOffset.getLineNumber(false) + 1 + } + + return result + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFileLanguageProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFileLanguageProvider.kt new file mode 100644 index 00000000000..fe3dac673cd --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFileLanguageProvider.kt @@ -0,0 +1,27 @@ +/* + * 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 + +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.scratch.repl.KtScratchReplExecutor +import org.jetbrains.kotlin.psi.KtFile + +class KtScratchFileLanguageProvider: ScratchFileLanguageProvider() { + override fun createFile(psiFile: PsiFile): ScratchFile? { + return (psiFile as? KtFile)?.let { KtScratchFile(psiFile)} + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt new file mode 100644 index 00000000000..c18a9837d28 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile + +abstract class ScratchFile(val psiFile: PsiFile) { + abstract fun getExpressions(): List +} + +data class ScratchExpression(val element: PsiElement, val lineStart: Int, val lineEnd: Int = lineStart) \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt new file mode 100644 index 00000000000..bc2a0a40f12 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt @@ -0,0 +1,44 @@ +/* + * 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 + +import com.intellij.lang.Language +import com.intellij.lang.LanguageExtension +import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.fileTypes.LanguageFileType +import com.intellij.psi.PsiFile + +abstract class ScratchFileLanguageProvider { + abstract fun createFile(psiFile: PsiFile): ScratchFile? + + companion object { + private val EXTENSION = LanguageExtension("org.jetbrains.kotlin.scratchFileLanguageProvider") + + fun get(language: Language): ScratchFileLanguageProvider? { + return ScratchFileLanguageProvider.EXTENSION.forLanguage(language) + } + + fun get(fileType: FileType): ScratchFileLanguageProvider? { + return (fileType as? LanguageFileType)?.language?.let { get(it) } + } + + fun createFile(psiFile: PsiFile): ScratchFile? { + return get(psiFile.language)?.createFile(psiFile) + } + + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileModuleInfoProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileModuleInfoProvider.kt new file mode 100644 index 00000000000..1e0ea4cb64e --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileModuleInfoProvider.kt @@ -0,0 +1,64 @@ +/* + * 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 + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.ide.scratch.ScratchFileService +import com.intellij.openapi.components.AbstractProjectComponent +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import org.jetbrains.kotlin.idea.caches.resolve.NotUnderContentRootModuleInfo +import org.jetbrains.kotlin.idea.caches.resolve.productionSourceInfo +import org.jetbrains.kotlin.idea.caches.resolve.testSourceInfo +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.moduleInfo + +class ScratchFileModuleInfoProvider(project: Project) : AbstractProjectComponent(project) { + + override fun projectOpened() { + myProject.messageBus.connect(myProject).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, ScratchFileModuleListener()) + } + + private inner class ScratchFileModuleListener : FileEditorManagerListener { + override fun fileOpened(source: FileEditorManager, file: VirtualFile) { + if (!file.isValid) return + if (!ScratchFileService.isInScratchRoot(file)) return + + val ktFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return + + getScratchPanel(ktFile)?.let { panel -> + ktFile.moduleInfo = getModuleInfo(panel.getModule()) + + panel.addModuleListener { + ktFile.moduleInfo = getModuleInfo(it) + // Drop caches for old module + ProjectRootManager.getInstance(myProject).incModificationCount() + // Force re-highlighting + DaemonCodeAnalyzer.getInstance(myProject).restart(ktFile) + } + } + } + } + + private fun getModuleInfo(it: Module?) = + it?.testSourceInfo() ?: it?.productionSourceInfo() ?: NotUnderContentRootModuleInfo +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ClearScratchAction.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ClearScratchAction.kt new file mode 100644 index 00000000000..569276976ec --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ClearScratchAction.kt @@ -0,0 +1,38 @@ +/* + * 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.actions + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider +import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor + +class ClearScratchAction : AnAction( + KotlinBundle.message("scratch.clear.button"), + KotlinBundle.message("scratch.clear.button"), + AllIcons.Actions.GC +) { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val panel = getScratchPanelFromSelectedEditor(project) ?: return + val scratchFile = panel.scratchFile + + ScratchFileLanguageProvider.get(scratchFile.psiFile.language)?.getOutputHandler()?.clear(scratchFile) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..6dc9e29c21d --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt @@ -0,0 +1,59 @@ +/* + * 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.actions + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.compiler.CompilerManager +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider +import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor + +class RunScratchAction : AnAction( + KotlinBundle.message("scratch.run.button"), + KotlinBundle.message("scratch.run.button"), + AllIcons.Actions.Execute +) { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + + val scratchTopPanel = getScratchPanelFromSelectedEditor(project) ?: return + val scratchFile = scratchTopPanel.scratchFile + + val isMakeBeforeRun = false // todo use property from panel + val isRepl = true //todo use property from panel + + val provider = ScratchFileLanguageProvider.get(scratchFile.psiFile.language) ?: return + + val module = scratchTopPanel.getModule() + if (module == null) { + return + } + + val runnable = r@ { + // todo + } + + if (isMakeBeforeRun) { + CompilerManager.getInstance(project) + .make(module) { aborted, errors, _, _ -> if (!aborted && errors == 0) runnable() } + } else { + runnable() + } + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/scratchUtils.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/scratchUtils.kt new file mode 100644 index 00000000000..e6e87295241 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/scratchUtils.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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 + +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.TextEditor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.scratch.ui.ScratchTopPanel +import org.jetbrains.kotlin.psi.UserDataProperty + +fun getEditorWithoutScratchPanel(project: Project, virtualFile: VirtualFile): TextEditor? { + val allTextEditors = getAllTextEditors(project, virtualFile) + for (editor in allTextEditors) { + if (editor.scratchTopPanel != null) return null + } + return allTextEditors.firstOrNull() +} + +fun getEditorWithScratchPanel(project: Project, virtualFile: VirtualFile): Pair? { + val firstWithPanel = getAllTextEditors(project, virtualFile).firstOrNull { it.scratchTopPanel != null } + return firstWithPanel?.let { firstWithPanel to firstWithPanel.scratchTopPanel!! } +} + +fun getScratchPanel(psiFile: PsiFile): ScratchTopPanel? { + return getAllTextEditors(psiFile.project, psiFile.virtualFile).mapNotNull { it.scratchTopPanel }.firstOrNull() +} + +fun getAllEditorsWithScratchPanel(project: Project): List> = + FileEditorManager.getInstance(project).allEditors.filterIsInstance().mapNotNull { + val panel = it.scratchTopPanel + if (panel != null) it to panel else null + } + +fun getScratchPanelFromSelectedEditor(project: Project): ScratchTopPanel? = + FileEditorManager.getInstance(project).selectedEditors.asSequence() + .filterIsInstance() + .mapNotNull { it.scratchTopPanel } + .firstOrNull() + +private fun getAllTextEditors(project: Project, virtualFile: VirtualFile) = + FileEditorManager.getInstance(project).getAllEditors(virtualFile).filterIsInstance().asSequence() + +fun TextEditor.addScratchPanel(panel: ScratchTopPanel) { + scratchTopPanel = panel + FileEditorManager.getInstance(panel.scratchFile.psiFile.project).addTopComponent(this, panel) +} + +fun TextEditor.removeScratchPanel(panel: ScratchTopPanel) { + scratchTopPanel = null + FileEditorManager.getInstance(panel.scratchFile.psiFile.project).removeTopComponent(this, panel) +} + +private var TextEditor.scratchTopPanel: ScratchTopPanel? by UserDataProperty(Key.create("scratch.panel")) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchFileHook.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchFileHook.kt new file mode 100644 index 00000000000..0cf257f582c --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchFileHook.kt @@ -0,0 +1,63 @@ +/* + * 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.ide.scratch.ScratchFileService +import com.intellij.openapi.components.AbstractProjectComponent +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.* + +class ScratchFileHook(project: Project) : AbstractProjectComponent(project) { + + override fun projectOpened() { + myProject.messageBus.connect(myProject).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, ScratchEditorListener()) + } + + override fun projectClosed() { + getAllEditorsWithScratchPanel(myProject).forEach { (editor, panel) -> editor.removeScratchPanel(panel) } + } + + private inner class ScratchEditorListener : FileEditorManagerListener { + private fun isPluggable(file: VirtualFile): Boolean { + if (!file.isValid) return false + if (!ScratchFileService.isInScratchRoot(file)) return false + val psiFile = PsiManager.getInstance(myProject).findFile(file) ?: return false + return ScratchFileLanguageProvider.get(psiFile.fileType) != null + } + + override fun fileOpened(source: FileEditorManager, file: VirtualFile) { + if (!isPluggable(file)) return + + val panel = ScratchTopPanel.createPanel(myProject, file) ?: return + getEditorWithoutScratchPanel(myProject, file)?.let { editor -> + editor.addScratchPanel(panel) + } + } + + override fun fileClosed(source: FileEditorManager, file: VirtualFile) { + if (!isPluggable(file)) return + + getEditorWithScratchPanel(myProject, file)?.let { (editor, panel) -> + editor.removeScratchPanel(panel) + } + } + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt new file mode 100644 index 00000000000..4f276f66120 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt @@ -0,0 +1,87 @@ +/* + * 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.application.options.ModulesComboBox +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import com.intellij.ui.components.panels.HorizontalLayout +import com.intellij.uiDesigner.core.Spacer +import org.jetbrains.kotlin.idea.scratch.ScratchFile +import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider +import org.jetbrains.kotlin.idea.scratch.actions.ClearScratchAction +import org.jetbrains.kotlin.idea.scratch.actions.RunScratchAction +import org.jetbrains.kotlin.idea.scratch.getScratchPanel +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JSeparator + +val ScratchFile.scratchTopPanel: ScratchTopPanel? + get() = getScratchPanel(psiFile).firstOrNull { it.scratchFile == this@scratchTopPanel } + +class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel(HorizontalLayout(5)) { + companion object { + fun createPanel(project: Project, virtualFile: VirtualFile): ScratchTopPanel? { + val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return null + val scratchFile = ScratchFileLanguageProvider.createFile(psiFile) ?: return null + return ScratchTopPanel(scratchFile) + } + } + + private val moduleChooser: ModulesComboBox + + init { + add(createActionsToolbar()) + add(JSeparator()) + add(JLabel("Use classpath of module: ")) + moduleChooser = createModuleChooser(scratchFile.psiFile.project) + add(moduleChooser) + add(Spacer()) + } + + fun getModule(): Module? = moduleChooser.selectedModule + + fun addModuleListener(f: (Module) -> Unit) { + moduleChooser.addActionListener { + moduleChooser.selectedModule?.let { f(it) } + } + } + + private fun createActionsToolbar(): JComponent { + val toolbarGroup = DefaultActionGroup().apply { + add(RunScratchAction()) + addSeparator() + add(ClearScratchAction()) + } + + return ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, toolbarGroup, true).component + } + + private fun createModuleChooser(project: Project): ModulesComboBox { + return ModulesComboBox().apply { + fillModules(project) + selectedIndex = 0 + } + } +} diff --git a/idea/src/META-INF/extensions/ide.xml b/idea/src/META-INF/extensions/ide.xml index 115be5ad864..51d03d52c76 100644 --- a/idea/src/META-INF/extensions/ide.xml +++ b/idea/src/META-INF/extensions/ide.xml @@ -16,6 +16,11 @@ + + + + + + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 8dc5eb66406..ad9e822f967 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -52,6 +52,12 @@ org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.scratch.ui.ScratchFileHook + + + org.jetbrains.kotlin.idea.scratch.ScratchFileModuleInfoProvider + diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java b/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java index 24f47c1c8ba..b35ed9d0b87 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java @@ -25,6 +25,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.util.text.CharArrayUtil; +import kotlin.collections.ArraysKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.ClassKind; @@ -361,9 +362,9 @@ public class CodeInsightUtils { @Nullable - public static T getTopmostElementAtOffset(@NotNull PsiElement element, int offset, @NotNull Class klass) { + public static T getTopmostElementAtOffset(@NotNull PsiElement element, int offset, @NotNull Class... classes) { T lastElementOfType = null; - if (klass.isInstance(element)) { + if (anyIsInstance(element, classes)) { lastElementOfType = (T) element; } do { @@ -371,7 +372,7 @@ public class CodeInsightUtils { if (parent == null || (parent.getTextOffset() < offset) || parent instanceof KtBlockExpression) { break; } - if (klass.isInstance(parent)) { + if (anyIsInstance(parent, classes)) { lastElementOfType = (T) parent; } element = parent; @@ -380,4 +381,8 @@ public class CodeInsightUtils { return lastElementOfType; } + + private static boolean anyIsInstance(PsiElement finalElement, @NotNull Class[] klass) { + return ArraysKt.any(klass, aClass -> aClass.isInstance(finalElement)); + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt index 623f26b25cd..ff4ffb2d088 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt @@ -321,7 +321,10 @@ fun PsiFile.getLineStartOffset(line: Int): Int? { val startOffset = doc.getLineStartOffset(line) val element = findElementAt(startOffset) ?: return startOffset - return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset + if (element is PsiWhiteSpace || element is PsiComment) { + return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset + } + return startOffset } return null