Support alternative source popup for Kotlin files (KT-21958)
#KT-21958 Fixed
This commit is contained in:
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ide.util.ModuleRendererFactory
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil.findFilesWithExactPackage
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class KotlinAlternativeSourceNotificationProvider(private val myProject: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
|
||||
override fun getKey(): Key<EditorNotificationPanel> {
|
||||
return KEY
|
||||
}
|
||||
|
||||
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
|
||||
if (!DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE) {
|
||||
return null
|
||||
}
|
||||
|
||||
val session = XDebuggerManager.getInstance(myProject).currentSession
|
||||
if (session == null) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
val position = session.currentPosition
|
||||
if (file != position?.file) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(myProject).isDumb) return null
|
||||
|
||||
val ktFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return null
|
||||
|
||||
val packageFqName = ktFile.packageFqName
|
||||
val fileName = ktFile.name
|
||||
|
||||
val alternativeKtFiles = findFilesWithExactPackage(packageFqName, GlobalSearchScope.allScope(myProject), myProject).filterTo(HashSet()) {
|
||||
it.name == fileName
|
||||
}
|
||||
|
||||
FILE_PROCESSED_KEY.set(file, true)
|
||||
|
||||
if (alternativeKtFiles.size <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentFirstAlternatives: Collection<KtFile> = listOf(ktFile) + alternativeKtFiles.filter { it != ktFile }
|
||||
|
||||
val frame = session.currentStackFrame
|
||||
val locationDeclName: String? = when (frame) {
|
||||
is JavaStackFrame -> {
|
||||
val location = frame.descriptor.location
|
||||
location?.declaringType()?.name()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
return AlternativeSourceNotificationPanel(currentFirstAlternatives, myProject, file, locationDeclName)
|
||||
}
|
||||
|
||||
private class AlternativeSourceNotificationPanel(
|
||||
alternatives: Collection<KtFile>,
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
locationDeclName: String?
|
||||
) : EditorNotificationPanel() {
|
||||
private class ComboBoxFileElement(val ktFile: KtFile) {
|
||||
private val label: String by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val factory = ModuleRendererFactory.findInstance(ktFile)
|
||||
val moduleRenderer = factory.moduleRenderer
|
||||
moduleRenderer.getListCellRendererComponent(ourDummyList, ktFile, 1, false, false)
|
||||
moduleRenderer.text ?: ""
|
||||
}
|
||||
|
||||
override fun toString(): String = label
|
||||
|
||||
companion object {
|
||||
private val ourDummyList = JBList<KtFile>()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setText("Alternative source available for file ${file.name}")
|
||||
|
||||
val items = alternatives.map { ComboBoxFileElement(it) }
|
||||
myLinksPanel.add(
|
||||
ComboBox<ComboBoxFileElement>(items.toTypedArray()).apply {
|
||||
addActionListener {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val session = context.debuggerSession
|
||||
val ktFile = (selectedItem as ComboBoxFileElement).ktFile
|
||||
val vFile = ktFile.containingFile.virtualFile
|
||||
|
||||
when {
|
||||
session != null && vFile != null ->
|
||||
session.process.managerThread.schedule(object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
if (!StringUtil.isEmpty(locationDeclName)) {
|
||||
DebuggerUtilsEx.setAlternativeSourceUrl(locationDeclName, vFile.url, project)
|
||||
}
|
||||
|
||||
DebuggerUIUtil.invokeLater {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
session.refresh(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
else -> {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
ktFile.navigate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createActionLabel("Disable") {
|
||||
DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE = false
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
val fileEditorManager = FileEditorManager.getInstance(project)
|
||||
val editor = fileEditorManager.getSelectedEditor(file)
|
||||
if (editor != null) {
|
||||
fileEditorManager.removeTopComponent(editor, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY = Key.create<EditorNotificationPanel>("KotlinAlternativeSource")
|
||||
|
||||
// FIXME: Share AlternativeSourceNotificationProvider.FILE_PROCESSED_KEY
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION")
|
||||
private val FILE_PROCESSED_KEY = Key.findKeyByName("AlternativeSourceCheckDone") as Key<Boolean>
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
@@ -87,7 +88,10 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
|
||||
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
|
||||
|
||||
val psiFile = getPsiFileByLocation(location)
|
||||
val psiFile = getPsiFileByLocation(location)?.let {
|
||||
replaceWithAlternativeSource(it, location)
|
||||
}
|
||||
|
||||
if (psiFile == null) {
|
||||
val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata()
|
||||
if (isKotlinStrataAvailable) {
|
||||
@@ -148,6 +152,17 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
|
||||
class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate)
|
||||
|
||||
private fun replaceWithAlternativeSource(psiFile: PsiFile, location: Location): PsiFile {
|
||||
fun findAlternativeSource(): PsiFile? {
|
||||
val qName = location.declaringType().name()
|
||||
val alternativeFileUrl = DebuggerUtilsEx.getAlternativeSourceUrl(qName, myDebugProcess.project) ?: return null
|
||||
val alternativePsiFile = VirtualFileManager.getInstance().findFileByUrl(alternativeFileUrl) ?: return null
|
||||
return psiFile.manager.findFile(alternativePsiFile)
|
||||
}
|
||||
|
||||
return findAlternativeSource() ?: psiFile
|
||||
}
|
||||
|
||||
// Returns a property or a constructor if debugger stops at class declaration
|
||||
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
<library.javaSourceRootDetector implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceRootDetector"/>
|
||||
|
||||
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
|
||||
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinAlternativeSourceNotificationProvider"/>
|
||||
|
||||
<consoleFilterProvider implementation="org.jetbrains.kotlin.idea.run.KotlinConsoleFilterProvider"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user