mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
[debugger-rd] IJPL-160146: Handle a quick evaluate remote dev scenario through XQuickEvaluateHandler
The only thing that is (and will) be kept in split is bridge between thin client and backend plugins GitOrigin-RevId: 92e5e6889830d1e6681e9d87f44e9ab7e039f3f8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1a5deb0cca
commit
3c4285226d
@@ -238,7 +238,6 @@ c:com.intellij.xdebugger.impl.XDebuggerSupport
|
||||
- getMarkObjectHandler():com.intellij.xdebugger.impl.actions.MarkObjectActionHandler
|
||||
- getMuteBreakpointsHandler():com.intellij.xdebugger.impl.actions.DebuggerToggleActionHandler
|
||||
- getPauseHandler():com.intellij.xdebugger.impl.actions.DebuggerActionHandler
|
||||
- getQuickEvaluateHandler():com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler
|
||||
- getResumeActionHandler():com.intellij.xdebugger.impl.actions.DebuggerActionHandler
|
||||
- getRunToCursorHandler():com.intellij.xdebugger.impl.actions.DebuggerActionHandler
|
||||
- getShowExecutionPointHandler():com.intellij.xdebugger.impl.actions.DebuggerActionHandler
|
||||
@@ -1022,16 +1021,6 @@ c:com.intellij.xdebugger.impl.evaluate.quick.XDebuggerTreeCreator
|
||||
- createDescriptorByNode(java.lang.Object,com.intellij.concurrency.ResultConsumer):V
|
||||
- createTree(com.intellij.openapi.util.Pair):com.intellij.ui.treeStructure.Tree
|
||||
- getTitle(com.intellij.openapi.util.Pair):java.lang.String
|
||||
f:com.intellij.xdebugger.impl.evaluate.quick.XQuickEvaluateHandler
|
||||
- com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler
|
||||
- sf:Companion:com.intellij.xdebugger.impl.evaluate.quick.XQuickEvaluateHandler$Companion
|
||||
- <init>():V
|
||||
- canShowHint(com.intellij.openapi.project.Project):Z
|
||||
- createValueHint(com.intellij.openapi.project.Project,com.intellij.openapi.editor.Editor,java.awt.Point,com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType):com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
- createValueHintAsync(com.intellij.openapi.project.Project,com.intellij.openapi.editor.Editor,java.awt.Point,com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType):com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler$CancellableHint
|
||||
- getValueLookupDelay(com.intellij.openapi.project.Project):I
|
||||
- isEnabled(com.intellij.openapi.project.Project):Z
|
||||
f:com.intellij.xdebugger.impl.evaluate.quick.XQuickEvaluateHandler$Companion
|
||||
c:com.intellij.xdebugger.impl.evaluate.quick.XValueHint
|
||||
- com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
- <init>(com.intellij.openapi.project.Project,com.intellij.openapi.editor.Editor,java.awt.Point,com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType,com.intellij.xdebugger.evaluation.ExpressionInfo,com.intellij.xdebugger.evaluation.XDebuggerEvaluator,com.intellij.xdebugger.XDebugSession,Z):V
|
||||
|
||||
@@ -49,5 +49,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.debugger" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -4,6 +4,6 @@
|
||||
</dependencies>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<platform.rpc.backend.remoteApiProvider
|
||||
implementation="com.intellij.platform.debugger.impl.backend.BackendDebuggerValueLookupHintsRemoteApiProvider"/>
|
||||
implementation="com.intellij.platform.debugger.impl.backend.BackendXDebuggerRemoteApiProviders"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.backend
|
||||
|
||||
import com.intellij.platform.rpc.backend.RemoteApiProvider
|
||||
import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi
|
||||
import fleet.rpc.remoteApiDescriptor
|
||||
|
||||
private class BackendXDebuggerRemoteApiProviders : RemoteApiProvider {
|
||||
override fun RemoteApiProvider.Sink.remoteApis() {
|
||||
remoteApi(remoteApiDescriptor<XDebuggerValueLookupHintsRemoteApi>()) {
|
||||
BackendXDebuggerValueLookupHintsRemoteApi()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,28 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.backend
|
||||
|
||||
import com.intellij.codeInsight.TargetElementUtil
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.EditorId
|
||||
import com.intellij.openapi.editor.impl.findEditor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.platform.project.ProjectId
|
||||
import com.intellij.platform.project.findProject
|
||||
import com.intellij.platform.rpc.backend.RemoteApiProvider
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.evaluation.ExpressionInfo
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator
|
||||
import com.intellij.xdebugger.impl.DebuggerSupport
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.XQuickEvaluateHandler
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.XValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType
|
||||
import com.intellij.xdebugger.impl.rpc.RemoteValueHint
|
||||
import com.intellij.xdebugger.impl.rpc.RemoteValueHintId
|
||||
import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi
|
||||
import fleet.rpc.remoteApiDescriptor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -32,26 +35,49 @@ import java.awt.Point
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
private class BackendDebuggerValueLookupHintsRemoteApiProvider : RemoteApiProvider {
|
||||
override fun RemoteApiProvider.Sink.remoteApis() {
|
||||
remoteApi(remoteApiDescriptor<XDebuggerValueLookupHintsRemoteApi>()) {
|
||||
BackendDebuggerValueLookupHintsRemoteApi()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BackendDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupHintsRemoteApi {
|
||||
internal class BackendXDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupHintsRemoteApi {
|
||||
override suspend fun getExpressionInfo(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType): ExpressionInfo? {
|
||||
return withContext(Dispatchers.EDT) {
|
||||
val project = projectId.findProject()
|
||||
val editor = editorId.findEditor()
|
||||
val evaluator = XDebuggerManager.getInstance(project).getCurrentSession()?.debugProcess?.evaluator ?: return@withContext null
|
||||
val expressionInfo = XQuickEvaluateHandler.getExpressionInfo(evaluator, project, hintType, editor, offset)
|
||||
val expressionInfo = getExpressionInfo(evaluator, project, hintType, editor, offset)
|
||||
|
||||
return@withContext expressionInfo
|
||||
}
|
||||
}
|
||||
|
||||
private data class EditorEvaluateExpressionData(
|
||||
val adjustedOffset: Int,
|
||||
val hasSelection: Boolean,
|
||||
val selectionStart: Int,
|
||||
val selectionEnd: Int,
|
||||
)
|
||||
|
||||
private suspend fun getExpressionInfo(
|
||||
evaluator: XDebuggerEvaluator, project: Project,
|
||||
type: ValueHintType?, editor: Editor, offset: Int,
|
||||
): ExpressionInfo? {
|
||||
val document = editor.getDocument()
|
||||
val evaluateExpressionData = readAction {
|
||||
// adjust offset to match with other actions, like go to declaration
|
||||
val adjustedOffset = TargetElementUtil.adjustOffset(PsiDocumentManager.getInstance(project).getPsiFile(document), document, offset)
|
||||
val selectionModel = editor.getSelectionModel()
|
||||
val selectionStart = selectionModel.selectionStart
|
||||
val selectionEnd = selectionModel.selectionEnd
|
||||
val hasSelection = selectionModel.hasSelection()
|
||||
EditorEvaluateExpressionData(adjustedOffset, hasSelection, selectionStart, selectionEnd)
|
||||
}
|
||||
if ((type == ValueHintType.MOUSE_CLICK_HINT || type == ValueHintType.MOUSE_ALT_OVER_HINT) && evaluateExpressionData.hasSelection
|
||||
&& evaluateExpressionData.adjustedOffset in evaluateExpressionData.selectionStart..evaluateExpressionData.selectionEnd
|
||||
) {
|
||||
return ExpressionInfo(TextRange(evaluateExpressionData.selectionStart, evaluateExpressionData.selectionEnd))
|
||||
}
|
||||
val expressionInfo = evaluator.getExpressionInfoAtOffsetAsync(project, document, evaluateExpressionData.adjustedOffset,
|
||||
type == ValueHintType.MOUSE_CLICK_HINT || type == ValueHintType.MOUSE_ALT_OVER_HINT).await()
|
||||
return expressionInfo
|
||||
}
|
||||
|
||||
|
||||
override suspend fun canShowHint(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType): Boolean {
|
||||
return withContext(Dispatchers.EDT) {
|
||||
@@ -59,24 +85,44 @@ private class BackendDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupHin
|
||||
val editor = editorId.findEditor()
|
||||
val point = editor.offsetToXY(offset)
|
||||
|
||||
val canShowHint = getValueHintFromDebuggers(project, editor, point, hintType) != null
|
||||
val canShowHint = getValueHintFromDebuggerPlugins(project, editor, point, hintType) != null
|
||||
return@withContext canShowHint
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createHint(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType): RemoteValueHint? {
|
||||
override suspend fun createHint(
|
||||
projectId: ProjectId,
|
||||
editorId: EditorId,
|
||||
offset: Int,
|
||||
hintType: ValueHintType,
|
||||
fromPlugins: Boolean,
|
||||
): RemoteValueHintId? {
|
||||
return withContext(Dispatchers.EDT) {
|
||||
val project = projectId.findProject()
|
||||
val editor = editorId.findEditor()
|
||||
val point = editor.offsetToXY(offset)
|
||||
|
||||
val hint = getValueHintFromDebuggers(project, editor, point, hintType) ?: return@withContext null
|
||||
val hint = if (fromPlugins) {
|
||||
getValueHintFromDebuggerPlugins(project, editor, point, hintType) ?: return@withContext null
|
||||
}
|
||||
else {
|
||||
val session = XDebuggerManager.getInstance(project).getCurrentSession()
|
||||
if (session == null) {
|
||||
return@withContext null
|
||||
}
|
||||
val evaluator = session.getDebugProcess().getEvaluator()
|
||||
if (evaluator == null) {
|
||||
return@withContext null
|
||||
}
|
||||
val expressionInfo = getExpressionInfo(evaluator, project, hintType, editor, offset) ?: return@withContext null
|
||||
XValueHint(project, editor, point, hintType, expressionInfo, evaluator, session, false)
|
||||
}
|
||||
val hintId = BackendDebuggerValueLookupHintsHolder.getInstance(project).registerNewHint(hint)
|
||||
RemoteValueHint(hintId)
|
||||
RemoteValueHintId(hintId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getValueHintFromDebuggers(
|
||||
private suspend fun getValueHintFromDebuggerPlugins(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
point: Point,
|
||||
@@ -94,9 +140,9 @@ private class BackendDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupHin
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun showHint(projectId: ProjectId, hintId: Int): Flow<Unit> {
|
||||
override suspend fun showHint(projectId: ProjectId, hintId: RemoteValueHintId): Flow<Unit> {
|
||||
val project = projectId.findProject()
|
||||
val hint = BackendDebuggerValueLookupHintsHolder.getInstance(project).getHintById(hintId) ?: return emptyFlow()
|
||||
val hint = BackendDebuggerValueLookupHintsHolder.getInstance(project).getHintById(hintId.id) ?: return emptyFlow()
|
||||
return callbackFlow {
|
||||
withContext(Dispatchers.EDT) {
|
||||
hint.invokeHint {
|
||||
@@ -108,10 +154,10 @@ private class BackendDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupHin
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeHint(projectId: ProjectId, hintId: Int) {
|
||||
override suspend fun removeHint(projectId: ProjectId, hintId: RemoteValueHintId) {
|
||||
val project = projectId.findProject()
|
||||
val hint = BackendDebuggerValueLookupHintsHolder.getInstance(project).getHintById(hintId) ?: return
|
||||
BackendDebuggerValueLookupHintsHolder.getInstance(project).removeHint(hintId)
|
||||
val hint = BackendDebuggerValueLookupHintsHolder.getInstance(project).getHintById(hintId.id) ?: return
|
||||
BackendDebuggerValueLookupHintsHolder.getInstance(project).removeHint(hintId.id)
|
||||
withContext(Dispatchers.EDT) {
|
||||
hint.hideHint()
|
||||
}
|
||||
@@ -46,5 +46,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.frontend" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,6 +1,7 @@
|
||||
<idea-plugin package="com.intellij.platform.debugger.impl.frontend">
|
||||
<dependencies>
|
||||
<plugin id="com.intellij.platform.experimental.frontend"/>
|
||||
<module name="intellij.platform.frontend"/>
|
||||
</dependencies>
|
||||
<actions>
|
||||
<action id="QuickEvaluateExpression" class="com.intellij.platform.debugger.impl.frontend.actions.QuickEvaluateAction"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.EditorGutter;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.XQuickEvaluateHandler;
|
||||
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.common.ValueLookupManager;
|
||||
import com.intellij.xdebugger.impl.DebuggerSupport;
|
||||
import com.intellij.xdebugger.impl.actions.DebuggerActionHandler;
|
||||
@@ -28,6 +29,7 @@ public class QuickEvaluateAction extends XDebuggerActionBase {
|
||||
|
||||
private static class QuickEvaluateHandlerWrapper extends DebuggerActionHandler {
|
||||
private final QuickEvaluateHandler myHandler;
|
||||
private final QuickEvaluateHandler myXQuickEvaluateHandler = new XQuickEvaluateHandler();
|
||||
|
||||
QuickEvaluateHandlerWrapper(final QuickEvaluateHandler handler) {
|
||||
myHandler = handler;
|
||||
@@ -38,14 +40,22 @@ public class QuickEvaluateAction extends XDebuggerActionBase {
|
||||
Editor editor = event.getData(CommonDataKeys.EDITOR);
|
||||
if (editor != null) {
|
||||
LogicalPosition logicalPosition = editor.getCaretModel().getLogicalPosition();
|
||||
QuickEvaluateHandler handler;
|
||||
// first try to use platform's evaluate handler
|
||||
if (myXQuickEvaluateHandler.isEnabled(project, event)) {
|
||||
handler = myXQuickEvaluateHandler;
|
||||
}
|
||||
else {
|
||||
handler = myHandler;
|
||||
}
|
||||
ValueLookupManager.getInstance(project).
|
||||
showHint(myHandler, editor, editor.logicalPositionToXY(logicalPosition), null, ValueHintType.MOUSE_CLICK_HINT);
|
||||
showHint(handler, editor, editor.logicalPositionToXY(logicalPosition), null, ValueHintType.MOUSE_CLICK_HINT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(@NotNull Project project, @NotNull AnActionEvent event) {
|
||||
if (!myHandler.isEnabled(project, event)) {
|
||||
if (!myHandler.isEnabled(project, event) && !myXQuickEvaluateHandler.isEnabled(project, event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.frontend
|
||||
|
||||
import com.intellij.platform.kernel.KernelService
|
||||
import com.jetbrains.rhizomedb.asOf
|
||||
import fleet.kernel.rete.Rete
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
||||
/**
|
||||
* Allows reading from the current DB state.
|
||||
* It may be useful when you don't know whether function will be called in EDT context (where DB state is propagated)
|
||||
* or in some background thread, where DB state is not propagated by default.
|
||||
*
|
||||
* Avoid using this API if your code is called in EDT.
|
||||
*
|
||||
* NB: This is a delicate API! Use with caution, since using [f] may be called on the "old" DB state where some latest changes are not applied
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal fun <T> withCurrentDb(f: () -> T): T {
|
||||
return asOf(KernelService.instance.kernelCoroutineScope.getCompleted().coroutineContext[Rete]!!.lastKnownDb.value) {
|
||||
f()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.frontend.evaluate.quick
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.editorId
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.common.RemoteValueHint
|
||||
import com.intellij.platform.debugger.impl.frontend.withCurrentDb
|
||||
import com.intellij.platform.frontend.FrontendApplicationInfo
|
||||
import com.intellij.platform.frontend.FrontendType
|
||||
import com.intellij.platform.kernel.withKernel
|
||||
import com.intellij.platform.project.asEntity
|
||||
import com.intellij.platform.project.projectId
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.impl.XDebuggerActiveSessionEntity
|
||||
import com.intellij.xdebugger.impl.evaluate.childCoroutineScope
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.XValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType
|
||||
import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import org.jetbrains.concurrency.asPromise
|
||||
import java.awt.Point
|
||||
|
||||
private val LOG = Logger.getInstance(XQuickEvaluateHandler::class.java)
|
||||
|
||||
internal class XQuickEvaluateHandler : QuickEvaluateHandler() {
|
||||
override fun isEnabled(project: Project): Boolean {
|
||||
val frontendType = FrontendApplicationInfo.getFrontendType()
|
||||
if (frontendType is FrontendType.RemoteDev && !frontendType.isLuxSupported) {
|
||||
return false
|
||||
}
|
||||
val hasActiveSession = withCurrentDb {
|
||||
val projectEntity = project.asEntity()
|
||||
XDebuggerActiveSessionEntity.all().any { it.projectEntity == projectEntity }
|
||||
}
|
||||
return hasActiveSession
|
||||
}
|
||||
|
||||
override fun createValueHint(project: Project, editor: Editor, point: Point, type: ValueHintType?): AbstractValueHint? {
|
||||
return null
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun createValueHintAsync(project: Project, editor: Editor, point: Point, type: ValueHintType): CancellableHint {
|
||||
val offset = AbstractValueHint.calculateOffset(editor, point)
|
||||
val document = editor.getDocument()
|
||||
val documentCoroutineScope = editor.childCoroutineScope("XQuickEvaluateHandler#valueHint")
|
||||
val projectId = project.projectId()
|
||||
val editorId = editor.editorId()
|
||||
val expressionInfoDeferred = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
withKernel {
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
remoteApi.getExpressionInfo(projectId, editorId, offset, type)
|
||||
}
|
||||
}
|
||||
val hintDeferred: Deferred<AbstractValueHint?> = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
val expressionInfo = expressionInfoDeferred.await()
|
||||
val textLength = document.textLength
|
||||
if (expressionInfo == null) {
|
||||
return@async null
|
||||
}
|
||||
val range = expressionInfo.textRange
|
||||
if (range.startOffset > range.endOffset || range.startOffset < 0 || range.endOffset > textLength) {
|
||||
LOG.error("invalid range: $range, text length = $textLength")
|
||||
return@async null
|
||||
}
|
||||
if (Registry.`is`("debugger.valueLookupFrontendBackend") || FrontendApplicationInfo.getFrontendType() is FrontendType.RemoteDev) {
|
||||
RemoteValueHint(project, projectId, editor, point, type, offset, expressionInfo, fromPlugins = false)
|
||||
}
|
||||
else {
|
||||
val session = XDebuggerManager.getInstance(project).getCurrentSession()
|
||||
if (session == null) {
|
||||
return@async null
|
||||
}
|
||||
val evaluator = session.getDebugProcess().getEvaluator()
|
||||
if (evaluator == null) {
|
||||
return@async null
|
||||
}
|
||||
XValueHint(project, editor, point, type, expressionInfo, evaluator, session, false)
|
||||
}
|
||||
}
|
||||
hintDeferred.invokeOnCompletion {
|
||||
documentCoroutineScope.cancel()
|
||||
}
|
||||
return CancellableHint(hintDeferred.asCompletableFuture().asPromise(), expressionInfoDeferred)
|
||||
}
|
||||
|
||||
|
||||
override fun canShowHint(project: Project): Boolean {
|
||||
return isEnabled(project)
|
||||
}
|
||||
|
||||
override fun getValueLookupDelay(project: Project?): Int {
|
||||
return XDebuggerSettingsManager.getInstance().getDataViewSettings().getValueLookupDelay()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.frontend.evaluate.quick.common
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.editorId
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.kernel.withKernel
|
||||
import com.intellij.platform.project.ProjectId
|
||||
import com.intellij.platform.project.projectId
|
||||
import com.intellij.xdebugger.evaluation.ExpressionInfo
|
||||
import com.intellij.xdebugger.impl.evaluate.childCoroutineScope
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType
|
||||
import com.intellij.xdebugger.impl.rpc.RemoteValueHintId
|
||||
import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import fleet.util.logging.logger
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.concurrency.asPromise
|
||||
import org.jetbrains.concurrency.resolvedPromise
|
||||
import java.awt.Point
|
||||
|
||||
@ApiStatus.Internal
|
||||
class RemoteValueHint(
|
||||
project: Project, private val projectId: ProjectId,
|
||||
private val editor: Editor,
|
||||
point: Point,
|
||||
private val type: ValueHintType,
|
||||
private val offset: Int,
|
||||
expressionInfo: ExpressionInfo,
|
||||
private val fromPlugins: Boolean,
|
||||
) : AbstractValueHint(project, editor, point, type, expressionInfo.textRange) {
|
||||
private var remoteHint: Deferred<RemoteValueHintId?>? = null
|
||||
private var hintCoroutineScope: CoroutineScope? = null
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun evaluateAndShowHint() {
|
||||
hintCoroutineScope = editor.childCoroutineScope("RemoteValueHintScope")
|
||||
val remoteHint = hintCoroutineScope!!.async(Dispatchers.IO) {
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
remoteApi.createHint(projectId, editor.editorId(), offset, type, fromPlugins)
|
||||
}
|
||||
this@RemoteValueHint.remoteHint = remoteHint
|
||||
hintCoroutineScope!!.launch(Dispatchers.IO) {
|
||||
val hint = remoteHint.await() ?: return@launch
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
val closedEvent = remoteApi.showHint(projectId, hint)
|
||||
withContext(Dispatchers.EDT) {
|
||||
closedEvent.collect {
|
||||
hideHint()
|
||||
processHintHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hideHint() {
|
||||
hintCoroutineScope?.launch(Dispatchers.IO) {
|
||||
val hint = remoteHint?.await()
|
||||
if (hint == null) {
|
||||
hintCoroutineScope?.cancel()
|
||||
return@launch
|
||||
}
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
remoteApi.removeHint(projectId, hint)
|
||||
hintCoroutineScope?.cancel()
|
||||
}
|
||||
super.hideHint()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import com.intellij.openapi.editor.event.EditorMouseMotionListener;
|
||||
import com.intellij.openapi.project.IndexNotReadyException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.XQuickEvaluateHandler;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
@@ -39,6 +40,7 @@ public class ValueLookupManager implements EditorMouseMotionListener, EditorMous
|
||||
private final Alarm myAlarm;
|
||||
private HintRequest myHintRequest = null;
|
||||
private AbstractValueHint myCurrentHint = null;
|
||||
private final @NotNull QuickEvaluateHandler myXQuickEvaluateHandler = new XQuickEvaluateHandler();
|
||||
private boolean myListening;
|
||||
|
||||
public ValueLookupManager(@NotNull Project project) {
|
||||
@@ -100,22 +102,23 @@ public class ValueLookupManager implements EditorMouseMotionListener, EditorMous
|
||||
|
||||
Point point = e.getMouseEvent().getPoint();
|
||||
|
||||
if (!Registry.is("debugger.valueLookupFrontendBackend")) {
|
||||
for (DebuggerSupport support : DebuggerSupport.getDebuggerSupports()) {
|
||||
QuickEvaluateHandler handler = support.getQuickEvaluateHandler();
|
||||
if (handler.isEnabled(myProject)) {
|
||||
requestHint(handler, editor, point, e, type);
|
||||
return;
|
||||
}
|
||||
// handle platform handler first
|
||||
if (myXQuickEvaluateHandler.isEnabled(myProject)) {
|
||||
requestHint(myXQuickEvaluateHandler, editor, point, e, type);
|
||||
return;
|
||||
}
|
||||
// otherwise, handle plugin handlers
|
||||
// for remote dev: specific DebuggerSupport with remote bridge will be used
|
||||
for (DebuggerSupport support : DebuggerSupport.getDebuggerSupports()) {
|
||||
QuickEvaluateHandler handler = support.getQuickEvaluateHandler();
|
||||
if (handler.isEnabled(myProject)) {
|
||||
requestHint(handler, editor, point, e, type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if no providers were triggered - hide
|
||||
hideHint();
|
||||
}
|
||||
else {
|
||||
QuickEvaluateHandler handler = new ValueLookupManagerQuickEvaluateHandler();
|
||||
requestHint(handler, editor, point, e, type);
|
||||
}
|
||||
// if no providers were triggered - hide
|
||||
hideHint();
|
||||
}
|
||||
|
||||
private void requestHint(final QuickEvaluateHandler handler,
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.frontend.evaluate.quick.common
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.editorId
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.kernel.withKernel
|
||||
import com.intellij.platform.project.ProjectId
|
||||
import com.intellij.platform.project.projectId
|
||||
import com.intellij.xdebugger.evaluation.ExpressionInfo
|
||||
import com.intellij.xdebugger.impl.evaluate.childCoroutineScope
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType
|
||||
import com.intellij.xdebugger.impl.rpc.RemoteValueHint
|
||||
import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import fleet.util.logging.logger
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.concurrency.asPromise
|
||||
import org.jetbrains.concurrency.resolvedPromise
|
||||
import java.awt.Point
|
||||
|
||||
private val LOG = logger<ValueLookupManagerQuickEvaluateHandler>()
|
||||
|
||||
/**
|
||||
* Bridge between [QuickEvaluateHandler]s provided by [DebuggerSupport]s and [ValueLookupManager]
|
||||
*/
|
||||
// TODO: QuickEvaluateHandler should be converted to frontend EP and this one should be just XQuickEvaluateHandler
|
||||
@ApiStatus.Internal
|
||||
open class ValueLookupManagerQuickEvaluateHandler : QuickEvaluateHandler() {
|
||||
override fun isEnabled(project: Project): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun createValueHint(project: Project, editor: Editor, point: Point, type: ValueHintType?): AbstractValueHint? {
|
||||
return null
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun createValueHintAsync(project: Project, editor: Editor, point: Point, type: ValueHintType?): CancellableHint {
|
||||
if (type == null) {
|
||||
return CancellableHint(resolvedPromise(), null)
|
||||
}
|
||||
val offset = AbstractValueHint.calculateOffset(editor, point)
|
||||
val document = editor.getDocument()
|
||||
val documentCoroutineScope = editor.childCoroutineScope("ValueLookupManagerQuickEvaluateHandler#valueHint")
|
||||
val projectId = project.projectId()
|
||||
val editorId = editor.editorId()
|
||||
val canShowHint = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
withKernel {
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
remoteApi.canShowHint(projectId, editorId, offset, type)
|
||||
}
|
||||
}
|
||||
val expressionInfoDeferred = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
if (!canShowHint.await()) {
|
||||
return@async null
|
||||
}
|
||||
withKernel {
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
val projectId = project.projectId()
|
||||
val editorId = editor.editorId()
|
||||
remoteApi.getExpressionInfo(projectId, editorId, offset, type)
|
||||
}
|
||||
}
|
||||
val hintDeferred: Deferred<AbstractValueHint?> = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
if (!canShowHint.await()) {
|
||||
return@async null
|
||||
}
|
||||
val expressionInfo = expressionInfoDeferred.await()
|
||||
val textLength = document.textLength
|
||||
if (expressionInfo == null) {
|
||||
return@async null
|
||||
}
|
||||
val range = expressionInfo.textRange
|
||||
if (range.startOffset > range.endOffset || range.startOffset < 0 || range.endOffset > textLength) {
|
||||
LOG.error("invalid range: $range, text length = $textLength")
|
||||
return@async null
|
||||
}
|
||||
val hint = ValueLookupManagerValueHint(project, projectId, editor, point, type, offset, expressionInfo)
|
||||
return@async hint
|
||||
}
|
||||
hintDeferred.invokeOnCompletion {
|
||||
documentCoroutineScope.cancel()
|
||||
}
|
||||
|
||||
return CancellableHint(hintDeferred.asCompletableFuture().asPromise(), expressionInfoDeferred)
|
||||
}
|
||||
|
||||
override fun canShowHint(project: Project): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getValueLookupDelay(project: Project?): Int {
|
||||
return XDebuggerSettingsManager.getInstance().getDataViewSettings().getValueLookupDelay()
|
||||
}
|
||||
}
|
||||
|
||||
private class ValueLookupManagerValueHint(
|
||||
project: Project, private val projectId: ProjectId,
|
||||
private val editor: Editor,
|
||||
point: Point,
|
||||
private val type: ValueHintType,
|
||||
private val offset: Int,
|
||||
expressionInfo: ExpressionInfo,
|
||||
) : AbstractValueHint(project, editor, point, type, expressionInfo.textRange) {
|
||||
private var remoteHint: Deferred<RemoteValueHint?>? = null
|
||||
private var hintCoroutineScope: CoroutineScope? = null
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun evaluateAndShowHint() {
|
||||
hintCoroutineScope = editor.childCoroutineScope("ValueLookupManagerValueHintScope")
|
||||
val remoteHint = hintCoroutineScope!!.async(Dispatchers.IO) {
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
remoteApi.createHint(projectId, editor.editorId(), offset, type)
|
||||
}
|
||||
this@ValueLookupManagerValueHint.remoteHint = remoteHint
|
||||
hintCoroutineScope!!.launch(Dispatchers.IO) {
|
||||
val hint = remoteHint.await() ?: return@launch
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
val closedEvent = remoteApi.showHint(projectId, hint.id)
|
||||
withContext(Dispatchers.EDT) {
|
||||
closedEvent.collect {
|
||||
hideHint()
|
||||
processHintHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hideHint() {
|
||||
hintCoroutineScope?.launch(Dispatchers.IO) {
|
||||
val hint = remoteHint?.await()
|
||||
if (hint == null) {
|
||||
hintCoroutineScope?.cancel()
|
||||
return@launch
|
||||
}
|
||||
val remoteApi = XDebuggerValueLookupHintsRemoteApi.getInstance()
|
||||
remoteApi.removeHint(projectId, hint.id)
|
||||
hintCoroutineScope?.cancel()
|
||||
}
|
||||
super.hideHint()
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,7 @@
|
||||
<registryKey defaultValue="false" key="debugger.valueLookupFrontendBackend"
|
||||
description="Provides a way to use frontend-backend implementation of debugger's evaluation popup"/>
|
||||
<platform.entityTypes implementation="com.intellij.xdebugger.impl.evaluate.XDebuggerValueLookupEntityTypesProvider"/>
|
||||
<platform.entityTypes implementation="com.intellij.xdebugger.impl.XDebuggerActiveSessionEntityTypesProvider"/>
|
||||
|
||||
<intentionAction>
|
||||
<language>UAST</language>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.xdebugger.impl;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
@@ -145,6 +145,7 @@ public abstract class DebuggerSupport {
|
||||
}
|
||||
|
||||
public @NotNull QuickEvaluateHandler getQuickEvaluateHandler() {
|
||||
// See [XQuickEvaluateHandler] which is provided in frontend
|
||||
return DISABLED_QUICK_EVALUATE;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.xdebugger.impl
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.kernel.EntityTypeProvider
|
||||
import com.intellij.platform.kernel.withKernel
|
||||
import com.intellij.platform.project.ProjectEntity
|
||||
import com.intellij.platform.project.asEntity
|
||||
import com.jetbrains.rhizomedb.EID
|
||||
import com.jetbrains.rhizomedb.Entity
|
||||
import com.jetbrains.rhizomedb.EntityType
|
||||
import com.jetbrains.rhizomedb.RefFlags
|
||||
import com.jetbrains.rhizomedb.entity
|
||||
import fleet.kernel.DurableEntityType
|
||||
import fleet.kernel.change
|
||||
import fleet.kernel.shared
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
private class XDebuggerActiveSessionEntityTypesProvider : EntityTypeProvider {
|
||||
override fun entityTypes(): List<EntityType<*>> {
|
||||
return listOf(
|
||||
XDebuggerActiveSessionEntity,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
data class XDebuggerActiveSessionEntity(override val eid: EID) : Entity {
|
||||
val projectEntity by Project
|
||||
|
||||
@ApiStatus.Internal
|
||||
companion object : DurableEntityType<XDebuggerActiveSessionEntity>(
|
||||
XDebuggerActiveSessionEntity::class.java.name,
|
||||
"com.intellij",
|
||||
::XDebuggerActiveSessionEntity
|
||||
) {
|
||||
val Project = requiredRef<ProjectEntity>("project", RefFlags.UNIQUE)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun synchronizeActiveSessionWithDb(coroutineScope: CoroutineScope, project: Project, activeSessionState: Flow<XDebugSessionImpl?>) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
activeSessionState.collect { newActiveSession ->
|
||||
withKernel {
|
||||
change {
|
||||
shared {
|
||||
val projectEntity = project.asEntity()
|
||||
// if there is no active session -> remove entity from DB
|
||||
if (newActiveSession == null) {
|
||||
entity(XDebuggerActiveSessionEntity.Project, projectEntity)?.delete()
|
||||
}
|
||||
else {
|
||||
// insert entity if active session is added
|
||||
XDebuggerActiveSessionEntity.upsert(XDebuggerActiveSessionEntity.Project, projectEntity) {
|
||||
it[XDebuggerActiveSessionEntity.Project] = projectEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,9 @@ import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
|
||||
import com.intellij.xdebugger.ui.DebuggerColors;
|
||||
import kotlin.Unit;
|
||||
import kotlinx.coroutines.CoroutineScope;
|
||||
import kotlinx.coroutines.flow.MutableStateFlow;
|
||||
import kotlinx.coroutines.flow.StateFlow;
|
||||
import kotlinx.coroutines.flow.StateFlowKt;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -76,7 +79,9 @@ import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.intellij.xdebugger.impl.CoroutineUtilsKt.createMutableStateFlow;
|
||||
import static com.intellij.xdebugger.impl.XDebuggerActiveSessionControllerKt.synchronizeActiveSessionWithDb;
|
||||
|
||||
@ApiStatus.Internal
|
||||
@State(name = "XDebuggerManager", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
|
||||
@@ -92,7 +97,7 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers
|
||||
private final XDebuggerPinToTopManager myPinToTopManager;
|
||||
private final XDebuggerExecutionPointManager myExecutionPointManager;
|
||||
private final Map<ProcessHandler, XDebugSessionImpl> mySessions = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||
private final AtomicReference<XDebugSessionImpl> myActiveSession = new AtomicReference<>();
|
||||
private final MutableStateFlow<@Nullable XDebugSessionImpl> myActiveSession = createMutableStateFlow(null);
|
||||
|
||||
private XDebuggerState myState = new XDebuggerState();
|
||||
|
||||
@@ -109,6 +114,8 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers
|
||||
myPinToTopManager = new XDebuggerPinToTopManager(coroutineScope);
|
||||
myExecutionPointManager = new XDebuggerExecutionPointManager(project, coroutineScope);
|
||||
|
||||
synchronizeActiveSessionWithDb(coroutineScope, project, myActiveSession);
|
||||
|
||||
messageBusConnection.subscribe(FileDocumentManagerListener.TOPIC, new FileDocumentManagerListener() {
|
||||
@Override
|
||||
public void fileContentLoaded(@NotNull VirtualFile file, @NotNull Document document) {
|
||||
@@ -383,11 +390,18 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers
|
||||
|
||||
@Override
|
||||
public @Nullable XDebugSessionImpl getCurrentSession() {
|
||||
return myActiveSession.get();
|
||||
return myActiveSession.getValue();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public StateFlow<@Nullable XDebugSessionImpl> getCurrentSessionFlow() {
|
||||
return myActiveSession;
|
||||
}
|
||||
|
||||
boolean setCurrentSession(@Nullable XDebugSessionImpl session) {
|
||||
XDebugSessionImpl previousSession = myActiveSession.getAndSet(session);
|
||||
XDebugSessionImpl previousSession = StateFlowKt.getAndUpdate(myActiveSession, (currentValue) -> {
|
||||
return session;
|
||||
});
|
||||
boolean sessionChanged = previousSession != session;
|
||||
if (sessionChanged) {
|
||||
if (session != null) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.xdebugger.impl;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
@@ -7,8 +7,6 @@ import com.intellij.xdebugger.impl.actions.*;
|
||||
import com.intellij.xdebugger.impl.actions.handlers.*;
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointPanelProvider;
|
||||
import com.intellij.xdebugger.impl.breakpoints.ui.BreakpointPanelProvider;
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.XQuickEvaluateHandler;
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class XDebuggerSupport extends DebuggerSupport {
|
||||
@@ -26,7 +24,6 @@ public class XDebuggerSupport extends DebuggerSupport {
|
||||
private final XDebuggerPauseActionHandler myPauseHandler;
|
||||
private final XDebuggerSuspendedActionHandler myShowExecutionPointHandler;
|
||||
private final XDebuggerEvaluateActionHandler myEvaluateHandler;
|
||||
private final XQuickEvaluateHandler myQuickEvaluateHandler;
|
||||
|
||||
private final XAddToWatchesFromEditorActionHandler myAddToWatchesActionHandler;
|
||||
private final XAddToInlineWatchesFromEditorActionHandler myAddToInlineWatchesActionHandler;
|
||||
@@ -91,7 +88,6 @@ public class XDebuggerSupport extends DebuggerSupport {
|
||||
};
|
||||
myMuteBreakpointsHandler = new XDebuggerMuteBreakpointsHandler();
|
||||
myEvaluateHandler = new XDebuggerEvaluateActionHandler();
|
||||
myQuickEvaluateHandler = new XQuickEvaluateHandler();
|
||||
myMarkObjectActionHandler = new XMarkObjectActionHandler();
|
||||
myEditBreakpointActionHandler = new XDebuggerEditBreakpointActionHandler();
|
||||
}
|
||||
@@ -186,12 +182,6 @@ public class XDebuggerSupport extends DebuggerSupport {
|
||||
return myEvaluateHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public QuickEvaluateHandler getQuickEvaluateHandler() {
|
||||
return myQuickEvaluateHandler;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DebuggerActionHandler getAddToWatchesActionHandler() {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.xdebugger.impl
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
// Used only for Java code, since MutableStateFlow function cannot be called there.
|
||||
internal fun <T> createMutableStateFlow(initialValue: T): MutableStateFlow<T> {
|
||||
return MutableStateFlow(initialValue)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.xdebugger.impl.evaluate.quick
|
||||
|
||||
import com.intellij.codeInsight.TargetElementUtil
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.evaluation.ExpressionInfo
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator
|
||||
import com.intellij.xdebugger.impl.evaluate.childCoroutineScope
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.QuickEvaluateHandler
|
||||
import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.concurrency.asPromise
|
||||
import org.jetbrains.concurrency.await
|
||||
import java.awt.Point
|
||||
|
||||
private val LOG = Logger.getInstance(XQuickEvaluateHandler::class.java)
|
||||
|
||||
class XQuickEvaluateHandler : QuickEvaluateHandler() {
|
||||
override fun isEnabled(project: Project): Boolean {
|
||||
val session = XDebuggerManager.getInstance(project).getCurrentSession()
|
||||
return session != null && session.getDebugProcess().getEvaluator() != null
|
||||
}
|
||||
|
||||
override fun createValueHint(project: Project, editor: Editor, point: Point, type: ValueHintType?): AbstractValueHint? {
|
||||
return null
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun createValueHintAsync(project: Project, editor: Editor, point: Point, type: ValueHintType): CancellableHint {
|
||||
val session = XDebuggerManager.getInstance(project).getCurrentSession()
|
||||
if (session == null) {
|
||||
return CancellableHint.resolved(null)
|
||||
}
|
||||
val evaluator = session.getDebugProcess().getEvaluator()
|
||||
if (evaluator == null) {
|
||||
return CancellableHint.resolved(null)
|
||||
}
|
||||
val offset = AbstractValueHint.calculateOffset(editor, point)
|
||||
val document = editor.getDocument()
|
||||
val documentCoroutineScope = editor.childCoroutineScope("XQuickEvaluateHandler#valueHint")
|
||||
val expressionInfoDeferred = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
getExpressionInfo(evaluator, project, type, editor, offset)
|
||||
}
|
||||
val hintDeferred: Deferred<AbstractValueHint?> = documentCoroutineScope.async(Dispatchers.IO) {
|
||||
val expressionInfo = expressionInfoDeferred.await()
|
||||
val textLength = document.textLength
|
||||
if (expressionInfo == null) {
|
||||
return@async null
|
||||
}
|
||||
val range = expressionInfo.textRange
|
||||
if (range.startOffset > range.endOffset || range.startOffset < 0 || range.endOffset > textLength) {
|
||||
LOG.error("invalid range: $range, text length = $textLength, evaluator: $evaluator")
|
||||
return@async null
|
||||
}
|
||||
XValueHint(project, editor, point, type, expressionInfo, evaluator, session, false)
|
||||
}
|
||||
hintDeferred.invokeOnCompletion {
|
||||
documentCoroutineScope.cancel()
|
||||
}
|
||||
return CancellableHint(hintDeferred.asCompletableFuture().asPromise(), expressionInfoDeferred)
|
||||
}
|
||||
|
||||
|
||||
override fun canShowHint(project: Project): Boolean {
|
||||
return isEnabled(project)
|
||||
}
|
||||
|
||||
override fun getValueLookupDelay(project: Project?): Int {
|
||||
return XDebuggerSettingsManager.getInstance().getDataViewSettings().getValueLookupDelay()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private data class EditorEvaluateExpressionData(val adjustedOffset: Int, val hasSelection: Boolean, val selectionStart: Int, val selectionEnd: Int)
|
||||
|
||||
@ApiStatus.Internal
|
||||
suspend fun getExpressionInfo(
|
||||
evaluator: XDebuggerEvaluator, project: Project,
|
||||
type: ValueHintType?, editor: Editor, offset: Int,
|
||||
): ExpressionInfo? {
|
||||
val document = editor.getDocument()
|
||||
val evaluateExpressionData = readAction {
|
||||
// adjust offset to match with other actions, like go to declaration
|
||||
val adjustedOffset = TargetElementUtil.adjustOffset(PsiDocumentManager.getInstance(project).getPsiFile(document), document, offset)
|
||||
val selectionModel = editor.getSelectionModel()
|
||||
val selectionStart = selectionModel.selectionStart
|
||||
val selectionEnd = selectionModel.selectionEnd
|
||||
val hasSelection = selectionModel.hasSelection()
|
||||
EditorEvaluateExpressionData(adjustedOffset, hasSelection, selectionStart, selectionEnd)
|
||||
}
|
||||
if ((type == ValueHintType.MOUSE_CLICK_HINT || type == ValueHintType.MOUSE_ALT_OVER_HINT) && evaluateExpressionData.hasSelection
|
||||
&& evaluateExpressionData.adjustedOffset in evaluateExpressionData.selectionStart..evaluateExpressionData.selectionEnd
|
||||
) {
|
||||
return ExpressionInfo(TextRange(evaluateExpressionData.selectionStart, evaluateExpressionData.selectionEnd))
|
||||
}
|
||||
val expressionInfo = evaluator.getExpressionInfoAtOffsetAsync(project, document, evaluateExpressionData.adjustedOffset,
|
||||
type == ValueHintType.MOUSE_CLICK_HINT || type == ValueHintType.MOUSE_ALT_OVER_HINT).await()
|
||||
return expressionInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType
|
||||
import fleet.rpc.RemoteApi
|
||||
import fleet.rpc.Rpc
|
||||
import fleet.rpc.remoteApiDescriptor
|
||||
import fleet.util.UID
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -22,11 +21,11 @@ interface XDebuggerValueLookupHintsRemoteApi : RemoteApi<Unit> {
|
||||
|
||||
suspend fun canShowHint(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType): Boolean
|
||||
|
||||
suspend fun createHint(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType): RemoteValueHint?
|
||||
suspend fun createHint(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType, fromPlugins: Boolean): RemoteValueHintId?
|
||||
|
||||
suspend fun showHint(projectId: ProjectId, hintId: Int): Flow<Unit>
|
||||
suspend fun showHint(projectId: ProjectId, hintId: RemoteValueHintId): Flow<Unit>
|
||||
|
||||
suspend fun removeHint(projectId: ProjectId, hintId: Int)
|
||||
suspend fun removeHint(projectId: ProjectId, hintId: RemoteValueHintId)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@@ -40,4 +39,4 @@ interface XDebuggerValueLookupHintsRemoteApi : RemoteApi<Unit> {
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Serializable
|
||||
data class RemoteValueHint(val id: Int)
|
||||
data class RemoteValueHintId(val id: Int)
|
||||
Reference in New Issue
Block a user