summaryrefslogtreecommitdiff
path: root/platform/script-debugger/debugger-ui
diff options
context:
space:
mode:
authorAndrej Shadura <andrew.shadura@collabora.co.uk>2019-08-28 14:13:29 +0200
committerAndrej Shadura <andrew.shadura@collabora.co.uk>2019-08-29 17:48:13 +0200
commite19ef5983707e6a5c8d127f1ac8f02754cef82fd (patch)
tree9e3852cb9abc81ed6aa444465928d45fd7763dea /platform/script-debugger/debugger-ui
New upstream version 0~183.5153.4+dfsg
Diffstat (limited to 'platform/script-debugger/debugger-ui')
-rw-r--r--platform/script-debugger/debugger-ui/intellij.platform.scriptDebugger.ui.iml19
-rw-r--r--platform/script-debugger/debugger-ui/src/BasicDebuggerViewSupport.kt41
-rw-r--r--platform/script-debugger/debugger-ui/src/CallFrameView.kt86
-rw-r--r--platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt261
-rw-r--r--platform/script-debugger/debugger-ui/src/DebuggerViewSupport.kt74
-rw-r--r--platform/script-debugger/debugger-ui/src/FunctionScopesValueGroup.kt32
-rw-r--r--platform/script-debugger/debugger-ui/src/MemberFilter.kt29
-rw-r--r--platform/script-debugger/debugger-ui/src/ProcessHandlerWrapper.kt76
-rw-r--r--platform/script-debugger/debugger-ui/src/RemoteVmConnection.kt171
-rw-r--r--platform/script-debugger/debugger-ui/src/ScopeVariablesGroup.kt91
-rw-r--r--platform/script-debugger/debugger-ui/src/SourceInfo.kt55
-rw-r--r--platform/script-debugger/debugger-ui/src/SuspendContextView.kt237
-rw-r--r--platform/script-debugger/debugger-ui/src/VariableContext.kt44
-rw-r--r--platform/script-debugger/debugger-ui/src/VariableContextWrapper.kt43
-rw-r--r--platform/script-debugger/debugger-ui/src/VariableView.kt491
-rw-r--r--platform/script-debugger/debugger-ui/src/VariablesGroup.kt34
-rw-r--r--platform/script-debugger/debugger-ui/src/VmConnection.kt110
-rw-r--r--platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.kt28
-rw-r--r--platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt114
-rw-r--r--platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/execution/DebuggableProgramRunner.kt66
-rw-r--r--platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/FileUrlMapper.kt34
-rw-r--r--platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.kt79
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/CustomPropertiesValuePresentation.kt66
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggableRunConfiguration.java54
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggerSupportUtils.java31
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/LazyVariablesGroup.kt107
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Location.java93
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/MemberFilterWithNameMappings.kt41
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/ObjectValuePresentation.java17
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/PsiVisitors.java83
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RejectErrorReporter.kt15
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RemoteDebugConfiguration.java154
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/VariableViewBase.java18
-rw-r--r--platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Variables.kt301
-rw-r--r--platform/script-debugger/debugger-ui/testSrc/Content.java26
-rw-r--r--platform/script-debugger/debugger-ui/testSrc/TestCompositeNode.java133
-rw-r--r--platform/script-debugger/debugger-ui/testSrc/TestValueNode.java44
-rw-r--r--platform/script-debugger/debugger-ui/testSrc/testUtil.kt22
38 files changed, 3420 insertions, 0 deletions
diff --git a/platform/script-debugger/debugger-ui/intellij.platform.scriptDebugger.ui.iml b/platform/script-debugger/debugger-ui/intellij.platform.scriptDebugger.ui.iml
new file mode 100644
index 00000000..663770e0
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/intellij.platform.scriptDebugger.ui.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="org.jetbrains.debugger" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="intellij.platform.debugger" />
+ <orderEntry type="module" module-name="intellij.platform.ide.impl" />
+ <orderEntry type="module" module-name="intellij.platform.scriptDebugger.backend" />
+ <orderEntry type="module" module-name="intellij.platform.debugger.impl" />
+ <orderEntry type="library" name="Guava" level="project" />
+ <orderEntry type="module" module-name="intellij.platform.lang.impl" />
+ <orderEntry type="library" name="netty-codec-http" level="project" />
+ </component>
+</module> \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/BasicDebuggerViewSupport.kt b/platform/script-debugger/debugger-ui/src/BasicDebuggerViewSupport.kt
new file mode 100644
index 00000000..d5a63deb
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/BasicDebuggerViewSupport.kt
@@ -0,0 +1,41 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XValueChildrenList
+import com.intellij.xdebugger.frame.XValueNode
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.onError
+import org.jetbrains.concurrency.onSuccess
+import org.jetbrains.concurrency.resolvedPromise
+import org.jetbrains.debugger.values.ObjectValue
+import org.jetbrains.debugger.values.Value
+import javax.swing.Icon
+
+open class BasicDebuggerViewSupport : MemberFilter, DebuggerViewSupport {
+ protected val defaultMemberFilterPromise: Promise<MemberFilter> = resolvedPromise<MemberFilter>(this)
+
+ override fun propertyNamesToString(list: List<String>, quotedAware: Boolean): String = ValueModifierUtil.propertyNamesToString(list, quotedAware)
+
+ override fun computeObjectPresentation(value: ObjectValue, variable: Variable, context: VariableContext, node: XValueNode, icon: Icon): Unit = VariableView.setObjectPresentation(value, icon, node)
+
+ override fun computeArrayPresentation(value: Value, variable: Variable, context: VariableContext, node: XValueNode, icon: Icon) {
+ VariableView.setArrayPresentation(value, context, icon, node)
+ }
+
+ override fun getMemberFilter(context: VariableContext): Promise<MemberFilter> = defaultMemberFilterPromise
+
+ override fun computeReceiverVariable(context: VariableContext, callFrame: CallFrame, node: XCompositeNode): Promise<*> {
+ return callFrame.receiverVariable
+ .onSuccess(node) {
+ node.addChildren(if (it == null) XValueChildrenList.EMPTY else XValueChildrenList.singleton(VariableView(it, context)), true)
+ }
+ .onError(node) {
+ node.addChildren(XValueChildrenList.EMPTY, true)
+ }
+ }
+}
+
+interface PresentationProvider {
+ fun computePresentation(node: XValueNode, icon: Icon): Boolean
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/CallFrameView.kt b/platform/script-debugger/debugger-ui/src/CallFrameView.kt
new file mode 100644
index 00000000..69566437
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/CallFrameView.kt
@@ -0,0 +1,86 @@
+// 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 file.
+package org.jetbrains.debugger.frame
+
+import com.intellij.icons.AllIcons
+import com.intellij.ui.ColoredTextContainer
+import com.intellij.ui.SimpleTextAttributes
+import com.intellij.xdebugger.evaluation.XDebuggerEvaluator
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XStackFrame
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.*
+
+// isInLibraryContent call could be costly, so we compute it only once (our customizePresentation called on each repaint)
+class CallFrameView @JvmOverloads constructor(val callFrame: CallFrame,
+ override val viewSupport: DebuggerViewSupport,
+ val script: Script? = null,
+ sourceInfo: SourceInfo? = null,
+ isInLibraryContent: Boolean? = null,
+ override val vm: Vm? = null) : XStackFrame(), VariableContext {
+ private val sourceInfo = sourceInfo ?: viewSupport.getSourceInfo(script, callFrame)
+ private val isInLibraryContent: Boolean = isInLibraryContent ?: (this.sourceInfo != null && viewSupport.isInLibraryContent(this.sourceInfo, script))
+
+ private var evaluator: XDebuggerEvaluator? = null
+
+ override fun getEqualityObject(): Any = callFrame.equalityObject
+
+ override fun computeChildren(node: XCompositeNode) {
+ node.setAlreadySorted(true)
+ createAndAddScopeList(node, callFrame.variableScopes, this, callFrame)
+ }
+
+ override val evaluateContext: EvaluateContext
+ get() = callFrame.evaluateContext
+
+ override fun watchableAsEvaluationExpression(): Boolean = true
+
+ override val memberFilter: Promise<MemberFilter>
+ get() = viewSupport.getMemberFilter(this)
+
+ fun getMemberFilter(scope: Scope): Promise<MemberFilter> = createVariableContext(scope, this, callFrame).memberFilter
+
+ override fun getEvaluator(): XDebuggerEvaluator? {
+ if (evaluator == null) {
+ evaluator = viewSupport.createFrameEvaluator(this)
+ }
+ return evaluator
+ }
+
+ override fun getSourcePosition(): SourceInfo? = sourceInfo
+
+ override fun customizePresentation(component: ColoredTextContainer) {
+ if (sourceInfo == null) {
+ val scriptName = if (script == null) "unknown" else script.url.trimParameters().toDecodedForm()
+ val line = callFrame.line
+ component.append(if (line == -1) scriptName else "$scriptName:$line", SimpleTextAttributes.ERROR_ATTRIBUTES)
+ return
+ }
+
+ val fileName = sourceInfo.file.name
+ val line = sourceInfo.line + 1
+
+ val textAttributes =
+ if (isInLibraryContent || callFrame.isFromAsyncStack) SimpleTextAttributes.GRAYED_ATTRIBUTES
+ else SimpleTextAttributes.REGULAR_ATTRIBUTES
+
+ val functionName = sourceInfo.functionName
+ if (functionName == null || (functionName.isEmpty() && callFrame.hasOnlyGlobalScope)) {
+ if (fileName.startsWith("index.")) {
+ sourceInfo.file.parent?.let {
+ component.append("${it.name}/", textAttributes)
+ }
+ }
+ component.append("$fileName:$line", textAttributes)
+ }
+ else {
+ if (functionName.isEmpty()) {
+ component.append("anonymous", if (isInLibraryContent) SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES else SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES)
+ }
+ else {
+ component.append(functionName, textAttributes)
+ }
+ component.append("(), $fileName:$line", textAttributes)
+ }
+ component.setIcon(AllIcons.Debugger.Frame)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt b/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt
new file mode 100644
index 00000000..3fb9e5da
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt
@@ -0,0 +1,261 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.execution.DefaultExecutionResult
+import com.intellij.execution.ExecutionResult
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.Url
+import com.intellij.util.containers.ContainerUtil
+import com.intellij.util.io.socketConnection.ConnectionStatus
+import com.intellij.xdebugger.DefaultDebugProcessHandler
+import com.intellij.xdebugger.XDebugProcess
+import com.intellij.xdebugger.XDebugSession
+import com.intellij.xdebugger.breakpoints.XBreakpointHandler
+import com.intellij.xdebugger.breakpoints.XLineBreakpoint
+import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider
+import com.intellij.xdebugger.frame.XSuspendContext
+import com.intellij.xdebugger.impl.XDebugSessionImpl
+import com.intellij.xdebugger.stepping.XSmartStepIntoHandler
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.connection.RemoteVmConnection
+import org.jetbrains.debugger.connection.VmConnection
+import java.util.concurrent.ConcurrentMap
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.swing.event.HyperlinkListener
+
+interface MultiVmDebugProcess {
+ val mainVm: Vm?
+ val activeOrMainVm: Vm?
+ val collectVMs: List<Vm>
+ get() {
+ val mainVm = mainVm ?: return emptyList()
+ val result = mutableListOf<Vm>()
+ fun addRecursively(vm: Vm) {
+ if (vm.attachStateManager.isAttached) {
+ result.add(vm)
+ vm.childVMs.forEach { addRecursively(it) }
+ }
+ }
+ addRecursively(mainVm)
+ return result
+ }
+}
+
+abstract class DebugProcessImpl<out C : VmConnection<*>>(session: XDebugSession,
+ val connection: C,
+ private val editorsProvider: XDebuggerEditorsProvider,
+ private val smartStepIntoHandler: XSmartStepIntoHandler<*>? = null,
+ protected val executionResult: ExecutionResult? = null) : XDebugProcess(session), MultiVmDebugProcess {
+ protected val repeatStepInto: AtomicBoolean = AtomicBoolean()
+ @Volatile var lastStep: StepAction? = null
+ @Volatile protected var lastCallFrame: CallFrame? = null
+ @Volatile protected var isForceStep: Boolean = false
+ @Volatile protected var disableDoNotStepIntoLibraries: Boolean = false
+
+ protected val urlToFileCache: ConcurrentMap<Url, VirtualFile> = ContainerUtil.newConcurrentMap<Url, VirtualFile>()
+
+ var processBreakpointConditionsAtIdeSide: Boolean = false
+
+ private val connectedListenerAdded = AtomicBoolean()
+ private val breakpointsInitiated = AtomicBoolean()
+
+ private val _breakpointHandlers: Array<XBreakpointHandler<*>> by lazy(LazyThreadSafetyMode.NONE) { createBreakpointHandlers() }
+
+ protected val realProcessHandler: ProcessHandler?
+ get() = executionResult?.processHandler
+
+ final override fun getSmartStepIntoHandler(): XSmartStepIntoHandler<*>? = smartStepIntoHandler
+
+ final override fun getBreakpointHandlers(): Array<out XBreakpointHandler<*>> = when (connection.state.status) {
+ ConnectionStatus.DISCONNECTED, ConnectionStatus.DETACHED, ConnectionStatus.CONNECTION_FAILED -> XBreakpointHandler.EMPTY_ARRAY
+ else -> _breakpointHandlers
+ }
+
+ final override fun getEditorsProvider(): XDebuggerEditorsProvider = editorsProvider
+
+ val vm: Vm?
+ get() = connection.vm
+
+ final override val mainVm: Vm?
+ get() = connection.vm
+
+ final override val activeOrMainVm: Vm?
+ get() = (session.suspendContext?.activeExecutionStack as? ExecutionStackView)?.suspendContext?.vm ?: mainVm
+
+ init {
+ if (session is XDebugSessionImpl && executionResult is DefaultExecutionResult) {
+ session.addRestartActions(*executionResult.restartActions)
+ }
+ connection.stateChanged {
+ when (it.status) {
+ ConnectionStatus.DISCONNECTED, ConnectionStatus.DETACHED -> {
+ if (it.status == ConnectionStatus.DETACHED) {
+ if (realProcessHandler != null) {
+ // here must we must use effective process handler
+ processHandler.detachProcess()
+ }
+ }
+ getSession().stop()
+ }
+
+ ConnectionStatus.CONNECTION_FAILED -> {
+ getSession().reportError(it.message)
+ getSession().stop()
+ }
+
+ else -> getSession().rebuildViews()
+ }
+ }
+ }
+
+ protected abstract fun createBreakpointHandlers(): Array<XBreakpointHandler<*>>
+
+ private fun updateLastCallFrame(vm: Vm) {
+ lastCallFrame = vm.suspendContextManager.context?.topFrame
+ }
+
+ final override fun checkCanPerformCommands(): Boolean = activeOrMainVm != null
+
+ final override fun isValuesCustomSorted(): Boolean = true
+
+ final override fun startStepOver(context: XSuspendContext?) {
+ val vm = context.vm
+ updateLastCallFrame(vm)
+ continueVm(vm, StepAction.OVER)
+ }
+
+ val XSuspendContext?.vm: Vm
+ get() = (this as? SuspendContextView)?.activeVm ?: mainVm!!
+
+ final override fun startForceStepInto(context: XSuspendContext?) {
+ isForceStep = true
+ startStepInto(context)
+ }
+
+ final override fun startStepInto(context: XSuspendContext?) {
+ val vm = context.vm
+ updateLastCallFrame(vm)
+ continueVm(vm, StepAction.IN)
+ }
+
+ final override fun startStepOut(context: XSuspendContext?) {
+ val vm = context.vm
+ if (isVmStepOutCorrect()) {
+ lastCallFrame = null
+ }
+ else {
+ updateLastCallFrame(vm)
+ }
+ continueVm(vm, StepAction.OUT)
+ }
+
+ // some VM (firefox for example) doesn't implement step out correctly, so, we need to fix it
+ protected open fun isVmStepOutCorrect(): Boolean = true
+
+ override fun resume(context: XSuspendContext?) {
+ continueVm(context.vm, StepAction.CONTINUE)
+ }
+
+ open fun resume(vm: Vm) {
+ continueVm(vm, StepAction.CONTINUE)
+ }
+
+ @Suppress("unused")
+ @Deprecated("Pass vm explicitly", ReplaceWith("continueVm(vm!!, stepAction)"))
+ protected open fun continueVm(stepAction: StepAction): Promise<*>? = continueVm(activeOrMainVm!!, stepAction)
+
+ /**
+ * You can override this method to avoid SuspendContextManager implementation, but it is not recommended.
+ */
+ protected open fun continueVm(vm: Vm, stepAction: StepAction): Promise<*>? {
+ val suspendContextManager = vm.suspendContextManager
+ if (stepAction === StepAction.CONTINUE) {
+ if (suspendContextManager.context == null) {
+ // on resumed we ask session to resume, and session then call our "resume", but we have already resumed, so, we don't need to send "continue" message
+ return null
+ }
+
+ lastStep = null
+ lastCallFrame = null
+ urlToFileCache.clear()
+ disableDoNotStepIntoLibraries = false
+ }
+ else {
+ lastStep = stepAction
+ }
+ return suspendContextManager.continueVm(stepAction)
+ }
+
+ protected fun setOverlay(context: SuspendContext<*>) {
+ val vm = mainVm
+ if (context.vm == vm) {
+ vm.suspendContextManager.setOverlayMessage("Paused in debugger")
+ }
+ }
+
+ final override fun startPausing() {
+ activeOrMainVm!!.suspendContextManager.suspend()
+ .onError(RejectErrorReporter(session, "Cannot pause"))
+ }
+
+ final override fun getCurrentStateMessage(): String = connection.state.message
+
+ final override fun getCurrentStateHyperlinkListener(): HyperlinkListener? = connection.state.messageLinkListener
+
+ override fun doGetProcessHandler(): ProcessHandler = executionResult?.processHandler ?: object : DefaultDebugProcessHandler() { override fun isSilentlyDestroyOnClose() = true }
+
+ fun saveResolvedFile(url: Url, file: VirtualFile) {
+ urlToFileCache.putIfAbsent(url, file)
+ }
+
+ // go plugin compatibility
+ @Suppress("unused")
+ open fun getLocationsForBreakpoint(breakpoint: XLineBreakpoint<*>): List<Location> = getLocationsForBreakpoint(activeOrMainVm!!, breakpoint)
+
+ open fun getLocationsForBreakpoint(vm: Vm, breakpoint: XLineBreakpoint<*>): List<Location> = throw UnsupportedOperationException()
+
+ override fun isLibraryFrameFilterSupported(): Boolean = true
+
+ // todo make final (go plugin compatibility)
+ override fun checkCanInitBreakpoints(): Boolean {
+ if (connection.state.status == ConnectionStatus.CONNECTED) {
+ return true
+ }
+
+ if (connectedListenerAdded.compareAndSet(false, true)) {
+ connection.stateChanged {
+ if (it.status == ConnectionStatus.CONNECTED) {
+ initBreakpoints()
+ }
+ }
+ }
+ return false
+ }
+
+ protected fun initBreakpoints() {
+ if (breakpointsInitiated.compareAndSet(false, true)) {
+ doInitBreakpoints()
+ }
+ }
+
+ protected open fun doInitBreakpoints() {
+ mainVm?.let(::beforeInitBreakpoints)
+ runReadAction { session.initBreakpoints() }
+ }
+
+ protected open fun beforeInitBreakpoints(vm: Vm) {
+ }
+
+ protected fun addChildVm(vm: Vm, childConnection: RemoteVmConnection<*>) {
+ mainVm?.childVMs?.add(vm)
+ childConnection.stateChanged {
+ if (it.status == ConnectionStatus.CONNECTION_FAILED || it.status == ConnectionStatus.DISCONNECTED || it.status == ConnectionStatus.DETACHED) {
+ mainVm?.childVMs?.remove(vm)
+ }
+ }
+
+ mainVm?.debugListener?.childVmAdded(vm)
+ }
+}
diff --git a/platform/script-debugger/debugger-ui/src/DebuggerViewSupport.kt b/platform/script-debugger/debugger-ui/src/DebuggerViewSupport.kt
new file mode 100644
index 00000000..93fbc2d8
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/DebuggerViewSupport.kt
@@ -0,0 +1,74 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.util.ThreeState
+import com.intellij.xdebugger.XSourcePosition
+import com.intellij.xdebugger.evaluation.XDebuggerEvaluator
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XInlineDebuggerDataCallback
+import com.intellij.xdebugger.frame.XNavigatable
+import com.intellij.xdebugger.frame.XValueNode
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.frame.CallFrameView
+import org.jetbrains.debugger.values.ObjectValue
+import org.jetbrains.debugger.values.Value
+import org.jetbrains.rpc.LOG
+import javax.swing.Icon
+
+interface DebuggerViewSupport {
+ val vm: Vm?
+ get() = null
+
+ fun getSourceInfo(script: Script?, frame: CallFrame): SourceInfo? = null
+
+ fun getSourceInfo(functionName: String?, scriptUrl: String, line: Int, column: Int): SourceInfo? = null
+
+ fun getSourceInfo(functionName: String?, script: Script, line: Int, column: Int): SourceInfo? = null
+
+ fun propertyNamesToString(list: List<String>, quotedAware: Boolean): String
+
+ // Please, don't hesitate to ask to share some generic implementations. Don't reinvent the wheel and keep in mind - user expects the same UI across all IDEA-based IDEs.
+ fun computeObjectPresentation(value: ObjectValue, variable: Variable, context: VariableContext, node: XValueNode, icon: Icon)
+
+ fun computeArrayPresentation(value: Value, variable: Variable, context: VariableContext, node: XValueNode, icon: Icon)
+
+ fun createFrameEvaluator(frame: CallFrameView): XDebuggerEvaluator = PromiseDebuggerEvaluator(frame)
+
+ /**
+ * [org.jetbrains.debugger.values.FunctionValue] is special case and handled by SDK
+ */
+ fun canNavigateToSource(variable: Variable, context: VariableContext): Boolean = false
+
+ fun computeSourcePosition(name: String, value: Value?, variable: Variable, context: VariableContext, navigatable: XNavigatable) {
+ }
+
+ fun computeInlineDebuggerData(name: String, variable: Variable, context: VariableContext, callback: XInlineDebuggerDataCallback): ThreeState = ThreeState.UNSURE
+
+ // return null if you don't need to add additional properties
+ fun computeAdditionalObjectProperties(value: ObjectValue, variable: Variable, context: VariableContext, node: XCompositeNode): Promise<Any?>? = null
+
+ fun getMemberFilter(context: VariableContext): Promise<MemberFilter>
+
+ fun transformErrorOnGetUsedReferenceValue(value: Value?, error: String?): Value? = value
+
+ fun isInLibraryContent(sourceInfo: SourceInfo, script: Script?): Boolean = false
+
+ fun computeReceiverVariable(context: VariableContext, callFrame: CallFrame, node: XCompositeNode): Promise<*>
+}
+
+open class PromiseDebuggerEvaluator(protected val context: VariableContext) : XDebuggerEvaluator() {
+ final override fun evaluate(expression: String, callback: XDebuggerEvaluator.XEvaluationCallback, expressionPosition: XSourcePosition?) {
+ try {
+ evaluate(expression, expressionPosition)
+ .onSuccess { callback.evaluated(VariableView(VariableImpl(expression, it.value), context)) }
+ .onError { callback.errorOccurred(it.message ?: it.toString()) }
+ }
+ catch (e: Throwable) {
+ LOG.error(e)
+ callback.errorOccurred(e.toString())
+ return
+ }
+ }
+
+ protected open fun evaluate(expression: String, expressionPosition: XSourcePosition?): Promise<EvaluateResult> = context.evaluateContext.evaluate(expression)
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/FunctionScopesValueGroup.kt b/platform/script-debugger/debugger-ui/src/FunctionScopesValueGroup.kt
new file mode 100644
index 00000000..28016637
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/FunctionScopesValueGroup.kt
@@ -0,0 +1,32 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XValueChildrenList
+import com.intellij.xdebugger.frame.XValueGroup
+import org.jetbrains.concurrency.errorIfNotMessage
+import org.jetbrains.concurrency.onSuccess
+import org.jetbrains.debugger.values.FunctionValue
+import org.jetbrains.rpc.LOG
+import java.util.*
+
+internal class FunctionScopesValueGroup(private val functionValue: FunctionValue, private val variableContext: VariableContext) : XValueGroup("Function scopes") {
+ override fun computeChildren(node: XCompositeNode) {
+ node.setAlreadySorted(true)
+
+ functionValue.resolve()
+ .onSuccess(node) {
+ val scopes = it.scopes
+ if (scopes == null || scopes.size == 0) {
+ node.addChildren(XValueChildrenList.EMPTY, true)
+ }
+ else {
+ createAndAddScopeList(node, Arrays.asList(*scopes), variableContext, null)
+ }
+ }
+ .onError {
+ LOG.errorIfNotMessage(it)
+ node.setErrorMessage(it.message!!)
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/MemberFilter.kt b/platform/script-debugger/debugger-ui/src/MemberFilter.kt
new file mode 100644
index 00000000..dda5f961
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/MemberFilter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2016 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.debugger
+
+interface MemberFilter {
+ fun isMemberVisible(variable: Variable): Boolean = variable.isReadable
+
+ val additionalVariables: Collection<Variable>
+ get() = emptyList()
+
+ fun rawNameToSource(variable: Variable): String = variable.name
+
+ fun sourceNameToRaw(name: String): String? = null
+
+ fun hasNameMappings(): Boolean = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/ProcessHandlerWrapper.kt b/platform/script-debugger/debugger-ui/src/ProcessHandlerWrapper.kt
new file mode 100644
index 00000000..9d2998ba
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/ProcessHandlerWrapper.kt
@@ -0,0 +1,76 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.execution.KillableProcess
+import com.intellij.execution.process.ProcessAdapter
+import com.intellij.execution.process.ProcessEvent
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.xdebugger.XDebugProcess
+import org.jetbrains.rpc.LOG
+import java.io.OutputStream
+
+class ProcessHandlerWrapper(private val debugProcess: XDebugProcess, private val handler: ProcessHandler) : ProcessHandler(), KillableProcess {
+ init {
+ if (handler.isStartNotified) {
+ super.startNotify()
+ }
+
+ handler.addProcessListener(object : ProcessAdapter() {
+ override fun startNotified(event: ProcessEvent) {
+ super@ProcessHandlerWrapper.startNotify()
+ }
+
+ override fun processTerminated(event: ProcessEvent) {
+ notifyProcessTerminated(event.exitCode)
+ }
+ })
+ }
+
+ override fun isSilentlyDestroyOnClose(): Boolean = handler.isSilentlyDestroyOnClose
+
+ override fun startNotify() {
+ handler.startNotify()
+ }
+
+ override fun destroyProcessImpl() {
+ stop(true)
+ }
+
+ override fun detachProcessImpl() {
+ stop(false)
+ }
+
+ private fun stop(destroy: Boolean) {
+ fun stopProcess(destroy: Boolean) {
+ if (destroy) {
+ handler.destroyProcess()
+ }
+ else {
+ handler.detachProcess()
+ }
+ }
+
+ debugProcess.stopAsync()
+ .onSuccess() { stopProcess(destroy) }
+ .onError {
+ try {
+ LOG.error(it)
+ }
+ finally {
+ stopProcess(destroy)
+ }
+ }
+ }
+
+ override fun detachIsDefault(): Boolean = handler.detachIsDefault()
+
+ override fun getProcessInput(): OutputStream? = handler.processInput
+
+ override fun canKillProcess(): Boolean = handler is KillableProcess && handler.canKillProcess()
+
+ override fun killProcess() {
+ if (handler is KillableProcess) {
+ handler.killProcess()
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/RemoteVmConnection.kt b/platform/script-debugger/debugger-ui/src/RemoteVmConnection.kt
new file mode 100644
index 00000000..2222566b
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/RemoteVmConnection.kt
@@ -0,0 +1,171 @@
+// 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 file.
+package org.jetbrains.debugger.connection
+
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.ui.popup.JBPopupFactory
+import com.intellij.openapi.util.Condition
+import com.intellij.openapi.util.Conditions
+import com.intellij.ui.ColoredListCellRenderer
+import com.intellij.util.containers.ContainerUtil
+import com.intellij.util.io.connectRetrying
+import com.intellij.util.io.socketConnection.ConnectionStatus
+import io.netty.bootstrap.Bootstrap
+import org.jetbrains.concurrency.*
+import org.jetbrains.debugger.Vm
+import org.jetbrains.io.NettyUtil
+import org.jetbrains.rpc.LOG
+import java.net.ConnectException
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Consumer
+import javax.swing.JList
+
+abstract class RemoteVmConnection<VmT : Vm> : VmConnection<VmT>() {
+
+ var address: InetSocketAddress? = null
+
+ private val connectCancelHandler = AtomicReference<() -> Unit>()
+
+ abstract fun createBootstrap(address: InetSocketAddress, vmResult: AsyncPromise<VmT>): Bootstrap
+
+ @JvmOverloads
+ fun open(address: InetSocketAddress, stopCondition: Condition<Void>? = null): Promise<VmT> {
+ if (address.isUnresolved) {
+ val error = "Host ${address.hostString} is unresolved"
+ setState(ConnectionStatus.CONNECTION_FAILED, error)
+ return rejectedPromise(error)
+ }
+
+ this.address = address
+ setState(ConnectionStatus.WAITING_FOR_CONNECTION, "Connecting to ${address.hostString}:${address.port}")
+
+ val result = AsyncPromise<VmT>()
+ result
+ .onSuccess {
+ connectionSucceeded(it, address)
+ }
+ .onError {
+ if (it !is ConnectException) {
+ LOG.errorIfNotMessage(it)
+ }
+ setState(ConnectionStatus.CONNECTION_FAILED, it.message)
+ }
+ .onProcessed {
+ connectCancelHandler.set(null)
+ }
+
+ val future = ApplicationManager.getApplication().executeOnPooledThread {
+ if (Thread.interrupted()) {
+ return@executeOnPooledThread
+ }
+ connectCancelHandler.set { result.setError("Closed explicitly") }
+
+ doOpen(result, address, stopCondition)
+ }
+
+ connectCancelHandler.set {
+ try {
+ future.cancel(true)
+ }
+ finally {
+ result.setError("Cancelled")
+ }
+ }
+ return result
+ }
+
+ protected fun connectionSucceeded(it: VmT, address: InetSocketAddress) {
+ vm = it
+ setState(ConnectionStatus.CONNECTED, "Connected to ${connectedAddressToPresentation(address, it)}")
+ startProcessing()
+ }
+
+ protected open fun doOpen(result: AsyncPromise<VmT>, address: InetSocketAddress, stopCondition: Condition<Void>?) {
+ val maxAttemptCount = if (stopCondition == null) NettyUtil.DEFAULT_CONNECT_ATTEMPT_COUNT else -1
+ val resultRejected = Condition<Void> { result.state == Promise.State.REJECTED }
+ val combinedCondition = Conditions.or(stopCondition ?: Conditions.alwaysFalse(), resultRejected)
+ val connectResult = createBootstrap(address, result).connectRetrying(address, maxAttemptCount, combinedCondition)
+ connectResult.handleError(Consumer { result.setError(it) })
+ connectResult.handleThrowable(Consumer { result.setError(it) })
+ val channel = connectResult.channel
+ channel?.closeFuture()?.addListener {
+ if (result.isSucceeded) {
+ close("Process disconnected unexpectedly", ConnectionStatus.DISCONNECTED)
+ }
+ }
+ if (channel != null) {
+ stateChanged {
+ if (it.status == ConnectionStatus.DISCONNECTED) {
+ channel.close()
+ }
+ }
+ }
+ }
+
+ protected open fun connectedAddressToPresentation(address: InetSocketAddress, vm: Vm): String = "${address.hostName}:${address.port}"
+
+ override fun detachAndClose(): Promise<*> {
+ try {
+ connectCancelHandler.getAndSet(null)?.invoke()
+ }
+ finally {
+ return super.detachAndClose()
+ }
+ }
+}
+
+fun RemoteVmConnection<*>.open(address: InetSocketAddress, processHandler: ProcessHandler): Promise<out Vm> = open(address, Condition<java.lang.Void> { processHandler.isProcessTerminating || processHandler.isProcessTerminated })
+
+fun <T> chooseDebuggee(targets: Collection<T>, selectedIndex: Int, renderer: (T, ColoredListCellRenderer<*>) -> Unit): Promise<T> {
+ if (targets.size == 1) {
+ return resolvedPromise(targets.first())
+ }
+ else if (targets.isEmpty()) {
+ return rejectedPromise("No tabs to inspect")
+ }
+
+ val result = org.jetbrains.concurrency.AsyncPromise<T>()
+ ApplicationManager.getApplication().invokeLater {
+ val model = ContainerUtil.newArrayList(targets)
+ val builder = JBPopupFactory.getInstance()
+ .createPopupChooserBuilder(model)
+ .setRenderer(
+ object : ColoredListCellRenderer<T>() {
+ override fun customizeCellRenderer(list: JList<out T>, value: T, index: Int, selected: Boolean, hasFocus: Boolean) {
+ renderer(value, this)
+ }
+ })
+ .setTitle("Choose Page to Debug")
+ .setCancelOnWindowDeactivation(false)
+ .setItemChosenCallback { value ->
+ result.setResult(value)
+ }
+ if (selectedIndex != -1) {
+ builder.setSelectedValue(model[selectedIndex], false)
+ }
+ builder
+ .createPopup()
+ .showInFocusCenter()
+ }
+ return result
+}
+
+@Deprecated("Use NodeCommandLineUtil.initRemoteVmConnectionSync instead")
+@Throws(ExecutionException::class)
+fun initRemoteVmConnectionSync(connection: RemoteVmConnection<*>, debugPort: Int): Vm {
+ val address = InetSocketAddress(InetAddress.getLoopbackAddress(), debugPort)
+ val vmPromise = connection.open(address)
+ val vm: Vm
+ try {
+ vm = vmPromise.blockingGet(30, TimeUnit.SECONDS)!!
+ }
+ catch (e: Exception) {
+ throw ExecutionException("Cannot connect to VM ($address)", e)
+ }
+
+ return vm
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/ScopeVariablesGroup.kt b/platform/script-debugger/debugger-ui/src/ScopeVariablesGroup.kt
new file mode 100644
index 00000000..9c1c0cdc
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/ScopeVariablesGroup.kt
@@ -0,0 +1,91 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.xdebugger.XDebuggerBundle
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XValueChildrenList
+import com.intellij.xdebugger.frame.XValueGroup
+import org.jetbrains.concurrency.onError
+import org.jetbrains.concurrency.onSuccess
+import org.jetbrains.concurrency.thenAsyncAccept
+
+class ScopeVariablesGroup(val scope: Scope, parentContext: VariableContext, callFrame: CallFrame?) : XValueGroup(scope.createScopeNodeName()) {
+ private val context = createVariableContext(scope, parentContext, callFrame)
+
+ private val callFrame = if (scope.type == ScopeType.LOCAL) callFrame else null
+
+ override fun isAutoExpand(): Boolean = scope.type == ScopeType.BLOCK || scope.type == ScopeType.LOCAL || scope.type == ScopeType.CATCH
+
+ override fun getComment(): String? {
+ val className = scope.description
+ return if ("Object" == className) null else className
+ }
+
+ override fun computeChildren(node: XCompositeNode) {
+ val promise = processScopeVariables(scope, node, context, callFrame == null)
+ if (callFrame == null) {
+ return
+ }
+
+ promise
+ .onSuccess(node) {
+ context.memberFilter
+ .thenAsyncAccept(node) {
+ if (it.hasNameMappings()) {
+ it.sourceNameToRaw(RECEIVER_NAME)?.let {
+ return@thenAsyncAccept callFrame.evaluateContext.evaluate(it)
+ .onSuccess(node) {
+ VariableImpl(RECEIVER_NAME, it.value, null)
+ node.addChildren(XValueChildrenList.singleton(VariableView(
+ VariableImpl(RECEIVER_NAME, it.value, null), context)), true)
+ }
+ }
+ }
+
+ context.viewSupport.computeReceiverVariable(context, callFrame, node)
+ }
+ .onError(node) {
+ context.viewSupport.computeReceiverVariable(context, callFrame, node)
+ }
+ }
+ }
+}
+
+fun createAndAddScopeList(node: XCompositeNode, scopes: List<Scope>, context: VariableContext, callFrame: CallFrame?) {
+ val list = XValueChildrenList(scopes.size)
+ for (scope in scopes) {
+ list.addTopGroup(ScopeVariablesGroup(scope, context, callFrame))
+ }
+ node.addChildren(list, true)
+}
+
+fun createVariableContext(scope: Scope, parentContext: VariableContext, callFrame: CallFrame?): VariableContext {
+ if (callFrame == null || scope.type == ScopeType.LIBRARY) {
+ // functions scopes - we can watch variables only from global scope
+ return ParentlessVariableContext(parentContext, scope, scope.type == ScopeType.GLOBAL)
+ }
+ else {
+ return VariableContextWrapper(parentContext, scope)
+ }
+}
+
+private class ParentlessVariableContext(parentContext: VariableContext, scope: Scope, private val watchableAsEvaluationExpression: Boolean) : VariableContextWrapper(parentContext, scope) {
+ override fun watchableAsEvaluationExpression() = watchableAsEvaluationExpression
+}
+
+private fun Scope.createScopeNodeName(): String {
+ when (type) {
+ ScopeType.GLOBAL -> return XDebuggerBundle.message("scope.global")
+ ScopeType.LOCAL -> return XDebuggerBundle.message("scope.local")
+ ScopeType.WITH -> return XDebuggerBundle.message("scope.with")
+ ScopeType.CLOSURE -> return XDebuggerBundle.message("scope.closure")
+ ScopeType.CATCH -> return XDebuggerBundle.message("scope.catch")
+ ScopeType.LIBRARY -> return XDebuggerBundle.message("scope.library")
+ ScopeType.INSTANCE -> return XDebuggerBundle.message("scope.instance")
+ ScopeType.CLASS -> return XDebuggerBundle.message("scope.class")
+ ScopeType.BLOCK -> return XDebuggerBundle.message("scope.block")
+ ScopeType.SCRIPT -> return XDebuggerBundle.message("scope.script")
+ ScopeType.UNKNOWN -> return XDebuggerBundle.message("scope.unknown")
+ else -> throw IllegalArgumentException(type.name)
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/SourceInfo.kt b/platform/script-debugger/debugger-ui/src/SourceInfo.kt
new file mode 100644
index 00000000..823ff20c
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/SourceInfo.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2016 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.debugger
+
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.fileEditor.FileDocumentManager
+import com.intellij.openapi.fileEditor.OpenFileDescriptor
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.Url
+import com.intellij.util.Urls
+import com.intellij.xdebugger.XSourcePosition
+
+class SourceInfo @JvmOverloads constructor(private val file: VirtualFile, private val line: Int, val column: Int = -1, private var offset: Int = -1, val functionName: String? = null, url: Url? = null) : XSourcePosition {
+ private var _url = url
+
+ override fun getFile(): VirtualFile = file
+
+ val url: Url
+ get() {
+ var result = _url
+ if (result == null) {
+ result = Urls.newFromVirtualFile(file)
+ _url = result
+ }
+ return result
+ }
+
+ override fun getLine(): Int = line
+
+ override fun getOffset(): Int {
+ if (offset == -1) {
+ val document = runReadAction { if (file.isValid) FileDocumentManager.getInstance().getDocument(file) else null } ?: return -1
+ offset = if (line < document.lineCount) document.getLineStartOffset(line) else -1
+ }
+ return offset
+ }
+
+ override fun createNavigatable(project: Project): OpenFileDescriptor = OpenFileDescriptor(project, file, line, column)
+
+ override fun toString(): String = file.path + ":" + line + if (column == -1) "" else ":" + column
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt
new file mode 100644
index 00000000..274320b4
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt
@@ -0,0 +1,237 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.icons.AllIcons
+import com.intellij.openapi.diagnostic.logger
+import com.intellij.ui.ColoredTextContainer
+import com.intellij.ui.SimpleTextAttributes
+import com.intellij.util.ui.UIUtil
+import com.intellij.xdebugger.frame.XExecutionStack
+import com.intellij.xdebugger.frame.XStackFrame
+import com.intellij.xdebugger.frame.XSuspendContext
+import com.intellij.xdebugger.impl.frame.XDebuggerFramesList
+import com.intellij.xdebugger.settings.XDebuggerSettingsManager
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.rejectedPromise
+import org.jetbrains.concurrency.resolvedPromise
+import org.jetbrains.debugger.frame.CallFrameView
+import org.jetbrains.debugger.values.StringValue
+import java.awt.Color
+import java.util.*
+
+/**
+ * Debugging several VMs simultaneously should be similar to debugging multi-threaded Java application when breakpoints suspend only one thread.
+ * 1. When thread is paused and another thread reaches breakpoint, show notification about it with possibility to switch thread.
+ * 2. Stepping and releasing affects only current thread.
+ * 3. When another thread is selected in Frames view, it changes its icon from (0) to (v) and becomes current, i.e. step/release commands
+ * are applied to it.
+ * 4. Stepping/releasing updates current thread icon and clears frame, but doesn't switch thread. To release other threads, user needs to
+ * select them firstly.
+ */
+abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess,
+ activeStack: ExecutionStackView,
+ @Volatile var activeVm: Vm)
+ : XSuspendContext() {
+
+ private val stacks: MutableMap<Vm, ScriptExecutionStack> = Collections.synchronizedMap(LinkedHashMap<Vm, ScriptExecutionStack>())
+
+ init {
+ val mainVm = debugProcess.mainVm
+ val vmList = debugProcess.collectVMs
+ if (mainVm != null && !vmList.isEmpty()) {
+ // main vm should go first
+ vmList.forEach {
+ val context = it.suspendContextManager.context
+
+ val stack: ScriptExecutionStack =
+ if (context == null) {
+ RunningThreadExecutionStackView(it)
+ }
+ else if (context == activeStack.suspendContext) {
+ activeStack
+ }
+ else {
+ logger<SuspendContextView>().error("Paused VM was lost.")
+ InactiveAtBreakpointExecutionStackView(it)
+ }
+ stacks[it] = stack
+ }
+ }
+ else {
+ stacks[activeVm] = activeStack
+ }
+ }
+
+ override fun getActiveExecutionStack(): ScriptExecutionStack? = stacks[activeVm]
+
+ override fun getExecutionStacks(): Array<out XExecutionStack> = stacks.values.toTypedArray()
+
+ fun evaluateExpression(expression: String): Promise<String> {
+ val activeStack = stacks[activeVm]!!
+ val frame = activeStack.topFrame ?: return rejectedPromise("Top frame is null")
+ if (frame !is CallFrameView) return rejectedPromise("Can't evaluate on non-paused thread")
+ return evaluateExpression(frame.callFrame.evaluateContext, expression)
+ }
+
+ private fun evaluateExpression(evaluateContext: EvaluateContext, expression: String) = evaluateContext.evaluate(expression)
+ .thenAsync {
+ val value = it.value
+ if (value is StringValue && value.isTruncated) {
+ value.fullString
+ }
+ else {
+ resolvedPromise(value.valueString!!)
+ }
+ }
+
+ fun pauseInactiveThread(inactiveThread: ExecutionStackView) {
+ stacks[inactiveThread.vm] = inactiveThread
+ }
+
+ fun hasPausedThreads(): Boolean {
+ return stacks.values.any { it is ExecutionStackView }
+ }
+
+ fun resume(vm: Vm) {
+ val prevStack = stacks[vm]
+ if (prevStack is ExecutionStackView) {
+ stacks[vm] = RunningThreadExecutionStackView(prevStack.vm)
+ }
+ }
+
+ fun resumeCurrentThread() {
+ resume(activeVm)
+ }
+
+ fun setActiveThread(selectedStackFrame: XStackFrame?): Boolean {
+ if (selectedStackFrame !is CallFrameView) return false
+
+ var selectedVm: Vm? = null
+ for ((key, value) in stacks) {
+ if (value is ExecutionStackView && value.topFrame?.vm == selectedStackFrame.vm) {
+ selectedVm = key
+ break
+ }
+ }
+
+ val selectedVmStack = stacks[selectedVm]
+ if (selectedVm != null && selectedVmStack is ExecutionStackView) {
+ activeVm = selectedVm
+ stacks[selectedVm] = selectedVmStack.copyWithIsCurrent(true)
+
+ stacks.keys.forEach {
+ val stack = stacks[it]
+ if (it != selectedVm && stack is ExecutionStackView) {
+ stacks[it] = stack.copyWithIsCurrent(false)
+ }
+ }
+
+ return stacks[selectedVm] !== selectedVmStack
+ }
+
+ return false
+ }
+}
+
+class RunningThreadExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadRunning) {
+ override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {
+ // add dependency to DebuggerBundle?
+ container?.errorOccurred("Frames not available for unsuspended thread")
+ }
+
+ override fun getTopFrame(): XStackFrame? = null
+}
+
+class InactiveAtBreakpointExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadAtBreakpoint) {
+ override fun getTopFrame(): XStackFrame? = null
+
+ override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {}
+}
+
+abstract class ScriptExecutionStack(val vm: Vm, displayName: String, icon: javax.swing.Icon): XExecutionStack(displayName, icon) {
+ override fun hashCode(): Int {
+ return vm.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is ScriptExecutionStack && other.vm == vm
+ }
+}
+
+// TODO should be AllIcons.Debugger.ThreadCurrent, but because of strange logic to add non-equal XExecutionStacks we can't update icon.
+private fun getThreadIcon(isCurrent: Boolean) = AllIcons.Debugger.ThreadAtBreakpoint
+
+class ExecutionStackView(val suspendContext: SuspendContext<*>,
+ internal val viewSupport: DebuggerViewSupport,
+ private val topFrameScript: Script?,
+ private val topFrameSourceInfo: SourceInfo? = null,
+ displayName: String = "",
+ isCurrent: Boolean = true)
+ : ScriptExecutionStack(suspendContext.vm, displayName, getThreadIcon(isCurrent)) {
+
+ private var topCallFrameView: CallFrameView? = null
+
+ override fun getTopFrame(): CallFrameView? {
+ val topCallFrame = suspendContext.topFrame
+ if (topCallFrameView == null || topCallFrameView!!.callFrame != topCallFrame) {
+ topCallFrameView = topCallFrame?.let { CallFrameView(it, viewSupport, topFrameScript, topFrameSourceInfo, vm = suspendContext.vm) }
+ }
+ return topCallFrameView
+ }
+
+ override fun computeStackFrames(firstFrameIndex: Int, container: XExecutionStack.XStackFrameContainer) {
+ // WipSuspendContextManager set context to null on resume _before_ vm.getDebugListener().resumed() call() (in any case, XFramesView can queue event to EDT), so, IDE state could be outdated compare to VM (our) state
+ suspendContext.frames
+ .onSuccess(suspendContext) { frames ->
+ val count = frames.size - firstFrameIndex
+ val result: List<XStackFrame>
+ if (count < 1) {
+ result = emptyList()
+ }
+ else {
+ result = ArrayList(count)
+ for (i in firstFrameIndex until frames.size) {
+ if (i == 0) {
+ result.add(topFrame!!)
+ continue
+ }
+
+ val frame = frames[i]
+ val asyncFunctionName = frame.asyncFunctionName
+ if (asyncFunctionName != null) {
+ result.add(AsyncFramesHeader(asyncFunctionName))
+ }
+ // if script is null, it is native function (Object.forEach for example), so, skip it
+ val script = suspendContext.vm.scriptManager.getScript(frame)
+ if (script != null) {
+ val sourceInfo = viewSupport.getSourceInfo(script, frame)
+ val isInLibraryContent = sourceInfo != null && viewSupport.isInLibraryContent(sourceInfo, script)
+ if (isInLibraryContent && !XDebuggerSettingsManager.getInstance().dataViewSettings.isShowLibraryStackFrames) {
+ continue
+ }
+
+ result.add(CallFrameView(frame, viewSupport, script, sourceInfo, isInLibraryContent, suspendContext.vm))
+ }
+ }
+ }
+ container.addStackFrames(result, true)
+ }
+ }
+
+ fun copyWithIsCurrent(isCurrent: Boolean): ExecutionStackView {
+ if (icon == getThreadIcon(isCurrent)) return this
+
+ return ExecutionStackView(suspendContext, viewSupport, topFrameScript, topFrameSourceInfo, displayName, isCurrent)
+ }
+}
+
+private val ASYNC_HEADER_ATTRIBUTES = SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE or SimpleTextAttributes.STYLE_BOLD,
+ UIUtil.getInactiveTextColor())
+
+private class AsyncFramesHeader(val asyncFunctionName: String) : XStackFrame(), XDebuggerFramesList.ItemWithCustomBackgroundColor {
+ override fun customizePresentation(component: ColoredTextContainer) {
+ component.append("Async call from $asyncFunctionName", ASYNC_HEADER_ATTRIBUTES)
+ }
+
+ override fun getBackgroundColor(): Color? = null
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/VariableContext.kt b/platform/script-debugger/debugger-ui/src/VariableContext.kt
new file mode 100644
index 00000000..a06dfb46
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/VariableContext.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2016 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.debugger
+
+import org.jetbrains.concurrency.Promise
+
+interface VariableContext {
+ val evaluateContext: EvaluateContext
+
+ /**
+ * Parent variable name if this context is [org.jetbrains.debugger.VariableView]
+ */
+ val variableName: String?
+ get() = null
+
+ val parent: VariableContext?
+ get() = null
+
+ fun watchableAsEvaluationExpression(): Boolean
+
+ val viewSupport: DebuggerViewSupport
+
+ val memberFilter: Promise<MemberFilter>
+ get() = viewSupport.getMemberFilter(this)
+
+ val scope: Scope?
+ get() = null
+
+ val vm: Vm?
+ get() = null
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/VariableContextWrapper.kt b/platform/script-debugger/debugger-ui/src/VariableContextWrapper.kt
new file mode 100644
index 00000000..6f0f434e
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/VariableContextWrapper.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2016 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.debugger
+
+import com.intellij.openapi.util.AtomicNotNullLazyValue
+import org.jetbrains.concurrency.Promise
+
+internal open class VariableContextWrapper(override val parent: VariableContext, override val scope: Scope?) : VariableContext {
+ // it's worth to cache it (JavaScriptDebuggerViewSupport, for example, performs expensive computation)
+ private val memberFilterPromise = object : AtomicNotNullLazyValue<Promise<MemberFilter>>() {
+ override fun compute() = parent.viewSupport.getMemberFilter(this@VariableContextWrapper)
+ }
+
+ override val variableName: String?
+ get() = parent.variableName
+
+ override val memberFilter: Promise<MemberFilter>
+ get() = memberFilterPromise.value
+
+ override val evaluateContext: EvaluateContext
+ get() = parent.evaluateContext
+
+ override val viewSupport: DebuggerViewSupport
+ get() = parent.viewSupport
+
+ override val vm: Vm?
+ get() = parent.vm
+
+ override fun watchableAsEvaluationExpression() = parent.watchableAsEvaluationExpression()
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/VariableView.kt b/platform/script-debugger/debugger-ui/src/VariableView.kt
new file mode 100644
index 00000000..2d60f1fe
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/VariableView.kt
@@ -0,0 +1,491 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.icons.AllIcons
+import com.intellij.openapi.diagnostic.logger
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.pom.Navigatable
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiReference
+import com.intellij.util.SmartList
+import com.intellij.util.ThreeState
+import com.intellij.xdebugger.XExpression
+import com.intellij.xdebugger.XSourcePositionWrapper
+import com.intellij.xdebugger.frame.*
+import com.intellij.xdebugger.frame.presentation.XKeywordValuePresentation
+import com.intellij.xdebugger.frame.presentation.XNumericValuePresentation
+import com.intellij.xdebugger.frame.presentation.XStringValuePresentation
+import com.intellij.xdebugger.frame.presentation.XValuePresentation
+import org.jetbrains.concurrency.*
+import org.jetbrains.debugger.values.*
+import java.util.*
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.regex.Pattern
+import javax.swing.Icon
+
+fun VariableView(variable: Variable, context: VariableContext): VariableView = VariableView(variable.name, variable, context)
+
+class VariableView(override val variableName: String, private val variable: Variable, private val context: VariableContext) : XNamedValue(variableName), VariableContext {
+ @Volatile private var value: Value? = null
+ // lazy computed
+ private var _memberFilter: MemberFilter? = null
+
+ @Volatile private var remainingChildren: List<Variable>? = null
+ @Volatile private var remainingChildrenOffset: Int = 0
+
+ override fun watchableAsEvaluationExpression(): Boolean = context.watchableAsEvaluationExpression()
+
+ override val viewSupport: DebuggerViewSupport
+ get() = context.viewSupport
+
+ override val parent: VariableContext = context
+
+ override val memberFilter: Promise<MemberFilter>
+ get() = context.viewSupport.getMemberFilter(this)
+
+ override val evaluateContext: EvaluateContext
+ get() = context.evaluateContext
+
+ override val scope: Scope?
+ get() = context.scope
+
+ override val vm: Vm?
+ get() = context.vm
+
+ override fun computePresentation(node: XValueNode, place: XValuePlace) {
+ value = variable.value
+ if (value != null) {
+ computePresentation(value!!, node)
+ return
+ }
+
+ if (variable !is ObjectProperty || variable.getter == null) {
+ // it is "used" expression (WEB-6779 Debugger/Variables: Automatically show used variables)
+ evaluateContext.evaluate(variable.name)
+ .onSuccess(node) {
+ if (it.wasThrown) {
+ setEvaluatedValue(viewSupport.transformErrorOnGetUsedReferenceValue(it.value, null), null, node)
+ }
+ else {
+ value = it.value
+ computePresentation(it.value, node)
+ }
+ }
+ .onError(node) { setEvaluatedValue(viewSupport.transformErrorOnGetUsedReferenceValue(null, it.message), it.message, node) }
+ return
+ }
+
+ node.setPresentation(null, object : XValuePresentation() {
+ override fun renderValue(renderer: XValuePresentation.XValueTextRenderer) {
+ renderer.renderValue("\u2026")
+ }
+ }, false)
+ node.setFullValueEvaluator(object : XFullValueEvaluator(" (invoke getter)") {
+ override fun startEvaluation(callback: XFullValueEvaluator.XFullValueEvaluationCallback) {
+ var valueModifier = variable.valueModifier
+ var nonProtoContext = context
+ while (nonProtoContext is VariableView && nonProtoContext.variableName == PROTOTYPE_PROP) {
+ valueModifier = nonProtoContext.variable.valueModifier
+ nonProtoContext = nonProtoContext.parent
+ }
+ valueModifier!!.evaluateGet(variable, evaluateContext)
+ .onSuccess(node) {
+ callback.evaluated("")
+ setEvaluatedValue(it, null, node)
+ }
+ }
+ }.setShowValuePopup(false))
+ }
+
+ private fun setEvaluatedValue(value: Value?, error: String?, node: XValueNode) {
+ if (value == null) {
+ node.setPresentation(AllIcons.Debugger.Db_primitive, null, error ?: "Internal Error", false)
+ }
+ else {
+ this.value = value
+ computePresentation(value, node)
+ }
+ }
+
+ private fun computePresentation(value: Value, node: XValueNode) {
+ when (value.type) {
+ ValueType.OBJECT, ValueType.NODE -> context.viewSupport.computeObjectPresentation((value as ObjectValue), variable, context, node, icon)
+
+ ValueType.FUNCTION -> node.setPresentation(icon, ObjectValuePresentation(trimFunctionDescription(value)), true)
+
+ ValueType.ARRAY -> context.viewSupport.computeArrayPresentation(value, variable, context, node, icon)
+
+ ValueType.BOOLEAN, ValueType.NULL, ValueType.UNDEFINED -> node.setPresentation(icon, XKeywordValuePresentation(value.valueString!!), false)
+
+ ValueType.NUMBER, ValueType.BIGINT -> node.setPresentation(icon, createNumberPresentation(value.valueString!!), false)
+
+ ValueType.STRING -> {
+ node.setPresentation(icon, XStringValuePresentation(value.valueString!!), false)
+ // isTruncated in terms of debugger backend, not in our terms (i.e. sometimes we cannot control truncation),
+ // so, even in case of StringValue, we check value string length
+ if ((value is StringValue && value.isTruncated) || value.valueString!!.length > XValueNode.MAX_VALUE_LENGTH) {
+ node.setFullValueEvaluator(MyFullValueEvaluator(value))
+ }
+ }
+
+ else -> node.setPresentation(icon, null, value.valueString!!, true)
+ }
+ }
+
+ override fun computeChildren(node: XCompositeNode) {
+ node.setAlreadySorted(true)
+
+ if (value !is ObjectValue) {
+ node.addChildren(XValueChildrenList.EMPTY, true)
+ return
+ }
+
+ val list = remainingChildren
+ if (list != null) {
+ val to = Math.min(remainingChildrenOffset + XCompositeNode.MAX_CHILDREN_TO_SHOW, list.size)
+ val isLast = to == list.size
+ node.addChildren(createVariablesList(list, remainingChildrenOffset, to, this, _memberFilter), isLast)
+ if (!isLast) {
+ node.tooManyChildren(list.size - to)
+ remainingChildrenOffset += XCompositeNode.MAX_CHILDREN_TO_SHOW
+ }
+ return
+ }
+
+ val objectValue = value as ObjectValue
+ val hasNamedProperties = objectValue.hasProperties() != ThreeState.NO
+ val hasIndexedProperties = objectValue.hasIndexedProperties() != ThreeState.NO
+ val promises = SmartList<Promise<*>>()
+ val additionalProperties = viewSupport.computeAdditionalObjectProperties(objectValue, variable, this, node)
+ if (additionalProperties != null) {
+ promises.add(additionalProperties)
+ }
+
+ // we don't support indexed properties if additional properties added - behavior is undefined if object has indexed properties and additional properties also specified
+ if (hasIndexedProperties) {
+ promises.add(computeIndexedProperties(objectValue as ArrayValue, node, !hasNamedProperties && additionalProperties == null))
+ }
+
+ if (hasNamedProperties) {
+ // named properties should be added after additional properties
+ if (additionalProperties == null || additionalProperties.state != Promise.State.PENDING) {
+ promises.add(computeNamedProperties(objectValue, node, !hasIndexedProperties && additionalProperties == null))
+ }
+ else {
+ promises.add(additionalProperties.thenAsync(node) { computeNamedProperties(objectValue, node, true) })
+ }
+ }
+
+ if (hasIndexedProperties == hasNamedProperties || additionalProperties != null) {
+ promises.all().processed(node) { node.addChildren(XValueChildrenList.EMPTY, true) }
+ }
+ }
+
+ abstract class ObsolescentIndexedVariablesConsumer(protected val node: XCompositeNode) : IndexedVariablesConsumer() {
+ override val isObsolete: Boolean
+ get() = node.isObsolete
+ }
+
+ private fun computeIndexedProperties(value: ArrayValue, node: XCompositeNode, isLastChildren: Boolean): Promise<*> {
+ return value.getIndexedProperties(0, value.length, XCompositeNode.MAX_CHILDREN_TO_SHOW, object : ObsolescentIndexedVariablesConsumer(node) {
+ override fun consumeRanges(ranges: IntArray?) {
+ if (ranges == null) {
+ val groupList = XValueChildrenList()
+ addGroups(value, ::lazyVariablesGroup, groupList, 0, value.length, XCompositeNode.MAX_CHILDREN_TO_SHOW, this@VariableView)
+ node.addChildren(groupList, isLastChildren)
+ }
+ else {
+ addRanges(value, ranges, node, this@VariableView, isLastChildren)
+ }
+ }
+
+ override fun consumeVariables(variables: List<Variable>) {
+ node.addChildren(createVariablesList(variables, this@VariableView, null), isLastChildren)
+ }
+ })
+ }
+
+ private fun computeNamedProperties(value: ObjectValue, node: XCompositeNode, isLastChildren: Boolean) = processVariables(this, value.properties, node) { memberFilter, variables ->
+ _memberFilter = memberFilter
+
+ if (value.type == ValueType.ARRAY && value !is ArrayValue) {
+ computeArrayRanges(variables, node)
+ return@processVariables
+ }
+
+ var functionValue = value as? FunctionValue
+ if (functionValue != null && (functionValue.hasScopes() == ThreeState.NO)) {
+ functionValue = null
+ }
+
+ remainingChildren = processNamedObjectProperties(variables, node, this@VariableView, memberFilter, XCompositeNode.MAX_CHILDREN_TO_SHOW, isLastChildren && functionValue == null)
+ if (remainingChildren != null) {
+ remainingChildrenOffset = XCompositeNode.MAX_CHILDREN_TO_SHOW
+ }
+
+ if (functionValue != null) {
+ // we pass context as variable context instead of this variable value - we cannot watch function scopes variables, so, this variable name doesn't matter
+ node.addChildren(XValueChildrenList.bottomGroup(FunctionScopesValueGroup(functionValue, context)), isLastChildren && remainingChildren == null)
+ }
+ }
+
+ private fun computeArrayRanges(properties: List<Variable>, node: XCompositeNode) {
+ val variables = filterAndSort(properties, _memberFilter!!)
+ var count = variables.size
+ val bucketSize = XCompositeNode.MAX_CHILDREN_TO_SHOW
+ if (count <= bucketSize) {
+ node.addChildren(createVariablesList(variables, this, null), true)
+ return
+ }
+
+ while (count > 0) {
+ if (Character.isDigit(variables.get(count - 1).name[0])) {
+ break
+ }
+ count--
+ }
+
+ val groupList = XValueChildrenList()
+ if (count > 0) {
+ addGroups(variables, ::createArrayRangeGroup, groupList, 0, count, bucketSize, this)
+ }
+
+ var notGroupedVariablesOffset: Int
+ if ((variables.size - count) > bucketSize) {
+ notGroupedVariablesOffset = variables.size
+ while (notGroupedVariablesOffset > 0) {
+ if (!variables.get(notGroupedVariablesOffset - 1).name.startsWith("__")) {
+ break
+ }
+ notGroupedVariablesOffset--
+ }
+
+ if (notGroupedVariablesOffset > 0) {
+ addGroups(variables, ::createArrayRangeGroup, groupList, count, notGroupedVariablesOffset, bucketSize, this)
+ }
+ }
+ else {
+ notGroupedVariablesOffset = count
+ }
+
+ for (i in notGroupedVariablesOffset..variables.size - 1) {
+ val variable = variables.get(i)
+ groupList.add(VariableView(_memberFilter!!.rawNameToSource(variable), variable, this))
+ }
+
+ node.addChildren(groupList, true)
+ }
+
+ private val icon: Icon
+ get() = getIcon(value!!)
+
+ override fun getModifier(): XValueModifier? {
+ if (!variable.isMutable) {
+ return null
+ }
+
+ return object : XValueModifier() {
+ override fun getInitialValueEditorText(): String? {
+ if (value!!.type == ValueType.STRING) {
+ val string = value!!.valueString!!
+ val builder = StringBuilder(string.length)
+ builder.append('"')
+ StringUtil.escapeStringCharacters(string.length, string, builder)
+ builder.append('"')
+ return builder.toString()
+ }
+ else {
+ return if (value!!.type.isObjectType) null else value!!.valueString
+ }
+ }
+
+ override fun setValue(expression: XExpression, callback: XValueModifier.XModificationCallback) {
+ variable.valueModifier!!.setValue(variable, expression.expression, evaluateContext)
+ .doneRun {
+ value = null
+ callback.valueModified()
+ }
+ .onError { callback.errorOccurred(it.message!!) }
+ }
+ }
+ }
+
+ fun getValue(): Value? = variable.value
+
+ override fun canNavigateToSource(): Boolean = value is FunctionValue && value?.valueString?.contains("[native code]") != true
+ || viewSupport.canNavigateToSource(variable, context)
+
+ override fun computeSourcePosition(navigatable: XNavigatable) {
+ if (value is FunctionValue) {
+ (value as FunctionValue).resolve()
+ .onSuccess { function ->
+ vm!!.scriptManager.getScript(function)
+ .onSuccess {
+ navigatable.setSourcePosition(it?.let { viewSupport.getSourceInfo(null, it, function.openParenLine, function.openParenColumn) }?.let {
+ object : XSourcePositionWrapper(it) {
+ override fun createNavigatable(project: Project): Navigatable {
+ return PsiVisitors.visit(myPosition, project) { _, element, _, _ ->
+ // element will be "open paren", but we should navigate to function name,
+ // we cannot use specific PSI type here (like JSFunction), so, we try to find reference expression (i.e. name expression)
+ var referenceCandidate: PsiElement? = element
+ var psiReference: PsiElement? = null
+ while (true) {
+ referenceCandidate = referenceCandidate?.prevSibling ?: break
+ if (referenceCandidate is PsiReference) {
+ psiReference = referenceCandidate
+ break
+ }
+ }
+
+ if (psiReference == null) {
+ referenceCandidate = element.parent
+ while (true) {
+ referenceCandidate = referenceCandidate?.prevSibling ?: break
+ if (referenceCandidate is PsiReference) {
+ psiReference = referenceCandidate
+ break
+ }
+ }
+ }
+
+ (if (psiReference == null) element.navigationElement else psiReference.navigationElement) as? Navigatable
+ } ?: super.createNavigatable(project)
+ }
+ }
+ })
+ }
+ }
+ }
+ else {
+ viewSupport.computeSourcePosition(variableName, value!!, variable, context, navigatable)
+ }
+ }
+
+ override fun computeInlineDebuggerData(callback: XInlineDebuggerDataCallback): ThreeState = viewSupport.computeInlineDebuggerData(variableName, variable, context, callback)
+
+ override fun getEvaluationExpression(): String? {
+ if (!watchableAsEvaluationExpression()) {
+ return null
+ }
+ if (context.variableName == null) return variable.name // top level watch expression, may be call etc.
+
+ val list = SmartList<String>()
+ addVarName(list, parent, variable.name)
+
+ var parent: VariableContext? = context
+ while (parent != null && parent.variableName != null) {
+ addVarName(list, parent.parent, parent.variableName!!)
+ parent = parent.parent
+ }
+ return context.viewSupport.propertyNamesToString(list, false)
+ }
+
+ private fun addVarName(list: SmartList<String>, parent: VariableContext?, name: String) {
+ if (parent == null || parent.variableName != null) list.add(name)
+ else list.addAll(name.split(".").reversed())
+ }
+
+ private class MyFullValueEvaluator(private val value: Value) : XFullValueEvaluator(if (value is StringValue) value.length else value.valueString!!.length) {
+ override fun startEvaluation(callback: XFullValueEvaluator.XFullValueEvaluationCallback) {
+ if (value !is StringValue || !value.isTruncated) {
+ callback.evaluated(value.valueString!!)
+ return
+ }
+
+ val evaluated = AtomicBoolean()
+ value.fullString
+ .onSuccess {
+ if (!callback.isObsolete && evaluated.compareAndSet(false, true)) {
+ callback.evaluated(value.valueString!!)
+ }
+ }
+ .onError { callback.errorOccurred(it.message!!) }
+ }
+ }
+
+ companion object {
+ fun setObjectPresentation(value: ObjectValue, icon: Icon, node: XValueNode) {
+ node.setPresentation(icon, ObjectValuePresentation(getObjectValueDescription(value)), value.hasProperties() != ThreeState.NO)
+ }
+
+ fun setArrayPresentation(value: Value, context: VariableContext, icon: Icon, node: XValueNode) {
+ assert(value.type == ValueType.ARRAY)
+
+ if (value is ArrayValue) {
+ val length = value.length
+ node.setPresentation(icon, ArrayPresentation(length, value.className), length > 0)
+ return
+ }
+
+ val valueString = value.valueString
+ // only WIP reports normal description
+ if (valueString != null && (valueString.endsWith(")") || valueString.endsWith(']')) &&
+ ARRAY_DESCRIPTION_PATTERN.matcher(valueString).find()) {
+ node.setPresentation(icon, null, valueString, true)
+ }
+ else {
+ context.evaluateContext.evaluate("a.length", Collections.singletonMap<String, Any>("a", value), false)
+ .onSuccess(node) { node.setPresentation(icon, null, "Array[${it.value.valueString}]", true) }
+ .onError(node) {
+ logger<VariableView>().error("Failed to evaluate array length: $it")
+ node.setPresentation(icon, null, valueString ?: "Array", true)
+ }
+ }
+ }
+
+ fun getIcon(value: Value): Icon {
+ val type = value.type
+ return when (type) {
+ ValueType.FUNCTION -> AllIcons.Nodes.Function
+ ValueType.ARRAY -> AllIcons.Debugger.Db_array
+ else -> if (type.isObjectType) AllIcons.Debugger.Value else AllIcons.Debugger.Db_primitive
+ }
+ }
+ }
+}
+
+fun getClassName(value: ObjectValue): String {
+ val className = value.className
+ return if (className.isNullOrEmpty()) "Object" else className!!
+}
+
+fun getObjectValueDescription(value: ObjectValue): String {
+ val description = value.valueString
+ return if (description.isNullOrEmpty()) getClassName(value) else description!!
+}
+
+internal fun trimFunctionDescription(value: Value): String {
+ return trimFunctionDescription(value.valueString ?: return "")
+}
+
+fun trimFunctionDescription(value: String): String {
+ var endIndex = 0
+ while (endIndex < value.length && !StringUtil.isLineBreak(value[endIndex])) {
+ endIndex++
+ }
+ while (endIndex > 0 && Character.isWhitespace(value[endIndex - 1])) {
+ endIndex--
+ }
+ return value.substring(0, endIndex)
+}
+
+private fun createNumberPresentation(value: String): XValuePresentation {
+ return if (value == PrimitiveValue.NA_N_VALUE || value == PrimitiveValue.INFINITY_VALUE) XKeywordValuePresentation(value) else XNumericValuePresentation(value)
+}
+
+private val ARRAY_DESCRIPTION_PATTERN = Pattern.compile("^[a-zA-Z\\d]+[\\[(]\\d+[\\])]$")
+
+private class ArrayPresentation(length: Int, className: String?) : XValuePresentation() {
+ private val length = Integer.toString(length)
+ private val className = if (className.isNullOrEmpty()) "Array" else className!!
+
+ override fun renderValue(renderer: XValuePresentation.XValueTextRenderer) {
+ renderer.renderSpecialSymbol(className)
+ renderer.renderSpecialSymbol("[")
+ renderer.renderSpecialSymbol(length)
+ renderer.renderSpecialSymbol("]")
+ }
+}
+
+private const val PROTOTYPE_PROP = "__proto__" \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/VariablesGroup.kt b/platform/script-debugger/debugger-ui/src/VariablesGroup.kt
new file mode 100644
index 00000000..8726e4b9
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/VariablesGroup.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2016 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.debugger
+
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XValueGroup
+
+internal class VariablesGroup(private val start: Int, private val end: Int, private val variables: List<Variable>, private val context: VariableContext, name: String) : XValueGroup(name) {
+ constructor(name: String, variables: List<Variable>, context: VariableContext) : this(0, variables.size, variables, context, name) {
+ }
+
+ override fun computeChildren(node: XCompositeNode) {
+ node.setAlreadySorted(true)
+ node.addChildren(createVariablesList(variables, start, end, context, null), true)
+ }
+}
+
+internal fun createArrayRangeGroup(variables: List<Variable>, start: Int, end: Int, variableContext: VariableContext): VariablesGroup {
+ val name = "[${variables[start].name} \u2026 ${variables[end - 1].name}]"
+ return VariablesGroup(start, end, variables, variableContext, name)
+}
diff --git a/platform/script-debugger/debugger-ui/src/VmConnection.kt b/platform/script-debugger/debugger-ui/src/VmConnection.kt
new file mode 100644
index 00000000..082e776a
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/VmConnection.kt
@@ -0,0 +1,110 @@
+// 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 file.
+package org.jetbrains.debugger.connection
+
+import com.intellij.ide.browsers.WebBrowser
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.util.Disposer
+import com.intellij.util.EventDispatcher
+import com.intellij.util.containers.ContainerUtil
+import com.intellij.util.io.socketConnection.ConnectionState
+import com.intellij.util.io.socketConnection.ConnectionStatus
+import com.intellij.util.io.socketConnection.SocketConnectionListener
+import org.jetbrains.annotations.TestOnly
+import org.jetbrains.concurrency.*
+import org.jetbrains.debugger.DebugEventListener
+import org.jetbrains.debugger.Vm
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
+import javax.swing.event.HyperlinkListener
+
+abstract class VmConnection<T : Vm> : Disposable {
+ open val browser: WebBrowser? = null
+
+ private val stateRef = AtomicReference(ConnectionState(ConnectionStatus.NOT_CONNECTED))
+
+ protected open val dispatcher: EventDispatcher<DebugEventListener> = EventDispatcher.create(DebugEventListener::class.java)
+ private val connectionDispatcher = ContainerUtil.createLockFreeCopyOnWriteList<(ConnectionState) -> Unit>()
+
+ @Volatile var vm: T? = null
+ protected set
+
+ private val opened = AsyncPromise<T>()
+ private val closed = AtomicBoolean()
+
+ val state: ConnectionState
+ get() = stateRef.get()
+
+ fun addDebugListener(listener: DebugEventListener) {
+ dispatcher.addListener(listener)
+ }
+
+ @TestOnly
+ fun opened(): Promise<*> = opened
+
+ fun executeOnStart(consumer: (vm: T) -> Unit) {
+ opened.then(consumer)
+ }
+
+ protected fun setState(status: ConnectionStatus, message: String? = null, messageLinkListener: HyperlinkListener? = null) {
+ val newState = ConnectionState(status, message, messageLinkListener)
+ val oldState = stateRef.getAndSet(newState)
+ if (oldState == null || oldState.status != status) {
+ if (status == ConnectionStatus.CONNECTION_FAILED) {
+ opened.setError(newState.message)
+ }
+ for (listener in connectionDispatcher) {
+ listener(newState)
+ }
+ }
+ }
+
+ fun stateChanged(listener: (ConnectionState) -> Unit) {
+ connectionDispatcher.add(listener)
+ }
+
+ // backward compatibility, go debugger
+ fun addListener(listener: SocketConnectionListener) {
+ stateChanged { listener.statusChanged(it.status) }
+ }
+
+ protected val debugEventListener: DebugEventListener
+ get() = dispatcher.multicaster
+
+ protected open fun startProcessing() {
+ vm?.let { opened.setResult(it) }
+ }
+
+ fun close(message: String?, status: ConnectionStatus) {
+ if (!closed.compareAndSet(false, true)) {
+ return
+ }
+
+ if (opened.isPending) {
+ opened.setError("closed")
+ }
+ setState(status, message)
+ Disposer.dispose(this, false)
+ }
+
+ override fun dispose() {
+ vm = null
+ }
+
+ open fun detachAndClose(): Promise<*> {
+ if (opened.isPending) {
+ opened.setError(createError("detached and closed"))
+ }
+
+ val currentVm = vm
+ val callback: Promise<*>
+ if (currentVm == null) {
+ callback = nullPromise()
+ }
+ else {
+ vm = null
+ callback = currentVm.attachStateManager.detach()
+ }
+ close(null, ConnectionStatus.DISCONNECTED)
+ return callback
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.kt b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.kt
new file mode 100644
index 00000000..c88042d1
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2016 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 com.intellij.javascript.debugger
+
+import com.intellij.openapi.editor.Document
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiElement
+import com.intellij.xdebugger.evaluation.ExpressionInfo
+import org.jetbrains.concurrency.Promise
+
+interface ExpressionInfoFactory {
+ fun create(element: PsiElement, document: Document): Promise<ExpressionInfo>
+
+ fun createNameMapper(file: VirtualFile, document: Document): NameMapper?
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt
new file mode 100644
index 00000000..c75eae1f
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2000-2016 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 com.intellij.javascript.debugger
+
+import com.google.common.base.CharMatcher
+import com.intellij.openapi.editor.Document
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiNamedElement
+import gnu.trove.THashMap
+import org.jetbrains.debugger.sourcemap.MappingEntry
+import org.jetbrains.debugger.sourcemap.Mappings
+import org.jetbrains.debugger.sourcemap.SourceMap
+import org.jetbrains.rpc.LOG
+
+private val S1 = ",()[]{}="
+// don't trim trailing .&: - could be part of expression
+private val OPERATOR_TRIMMER = CharMatcher.INVISIBLE.or(CharMatcher.anyOf(S1))
+
+val NAME_TRIMMER: CharMatcher = CharMatcher.INVISIBLE.or(CharMatcher.anyOf(S1 + ".&:"))
+
+// generateVirtualFile only for debug purposes
+open class NameMapper(private val document: Document, private val transpiledDocument: Document, private val sourceMappings: Mappings, protected val sourceMap: SourceMap, private val transpiledFile: VirtualFile? = null) {
+ var rawNameToSource: MutableMap<String, String>? = null
+ private set
+
+ // PsiNamedElement, JSVariable for example
+ // returns generated name
+ open fun map(identifierOrNamedElement: PsiElement): String? {
+ return doMap(identifierOrNamedElement, false)
+ }
+
+ protected fun doMap(identifierOrNamedElement: PsiElement, mapBySourceCode: Boolean): String? {
+ val offset = identifierOrNamedElement.textOffset
+ val line = document.getLineNumber(offset)
+
+ val sourceEntryIndex = sourceMappings.indexOf(line, offset - document.getLineStartOffset(line))
+ if (sourceEntryIndex == -1) {
+ return null
+ }
+
+ val sourceEntry = sourceMappings.getByIndex(sourceEntryIndex)
+ val next = sourceMappings.getNextOnTheSameLine(sourceEntryIndex, false)
+ if (next != null && sourceMappings.getColumn(next) == sourceMappings.getColumn(sourceEntry)) {
+ warnSeveralMapping(identifierOrNamedElement)
+ return null
+ }
+
+ val generatedName: String?
+ try {
+ generatedName = extractName(getGeneratedName(transpiledDocument, sourceMap, sourceEntry))
+ }
+ catch (e: IndexOutOfBoundsException) {
+ LOG.warn("Cannot get generated name: source entry (${sourceEntry.generatedLine}, ${sourceEntry.generatedColumn}). Transpiled File: " + transpiledFile?.path)
+ return null
+ }
+ if (generatedName == null || generatedName.isEmpty()) {
+ return null
+ }
+
+ var sourceName = sourceEntry.name
+ if (sourceName == null || mapBySourceCode) {
+ sourceName = (identifierOrNamedElement as? PsiNamedElement)?.name ?: identifierOrNamedElement.text ?: sourceName ?: return null
+ }
+
+ addMapping(generatedName, sourceName)
+ return generatedName
+ }
+
+ fun addMapping(generatedName: String, sourceName: String) {
+ if (rawNameToSource == null) {
+ rawNameToSource = THashMap<String, String>()
+ }
+ rawNameToSource!!.put(generatedName, sourceName)
+ }
+
+ protected open fun extractName(rawGeneratedName: CharSequence):String? = NAME_TRIMMER.trimFrom(rawGeneratedName)
+
+ companion object {
+ fun trimName(rawGeneratedName: CharSequence, isLastToken: Boolean): String? = (if (isLastToken) NAME_TRIMMER else OPERATOR_TRIMMER).trimFrom(rawGeneratedName)
+ }
+}
+
+fun warnSeveralMapping(element: PsiElement) {
+ // see https://dl.dropboxusercontent.com/u/43511007/s/Screen%20Shot%202015-01-21%20at%2020.33.44.png
+ // var1 mapped to the whole "var c, notes, templates, ..." expression text + unrelated text " ;"
+ LOG.warn("incorrect sourcemap, several mappings for named element ${element.text}")
+}
+
+private fun getGeneratedName(document: Document, sourceMap: SourceMap, sourceEntry: MappingEntry): CharSequence {
+ val lineStartOffset = document.getLineStartOffset(sourceEntry.generatedLine)
+ val nextGeneratedMapping = sourceMap.generatedMappings.getNextOnTheSameLine(sourceEntry)
+ val endOffset: Int
+ if (nextGeneratedMapping == null) {
+ endOffset = document.getLineEndOffset(sourceEntry.generatedLine)
+ }
+ else {
+ endOffset = lineStartOffset + nextGeneratedMapping.generatedColumn
+ }
+ return document.immutableCharSequence.subSequence(lineStartOffset + sourceEntry.generatedColumn, endOffset)
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/execution/DebuggableProgramRunner.kt b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/execution/DebuggableProgramRunner.kt
new file mode 100644
index 00000000..cc190be2
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/execution/DebuggableProgramRunner.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-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 com.intellij.javascript.debugger.execution
+
+import com.intellij.execution.ExecutionResult
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.execution.configurations.RunProfileState
+import com.intellij.execution.configurations.RunnerSettings
+import com.intellij.execution.executors.DefaultDebugExecutor
+import com.intellij.execution.runners.AsyncProgramRunner
+import com.intellij.execution.runners.DebuggableRunProfileState
+import com.intellij.execution.runners.ExecutionEnvironment
+import com.intellij.execution.ui.RunContentDescriptor
+import com.intellij.openapi.fileEditor.FileDocumentManager
+import com.intellij.xdebugger.XDebugProcess
+import com.intellij.xdebugger.XDebugProcessStarter
+import com.intellij.xdebugger.XDebugSession
+import com.intellij.xdebugger.XDebuggerManager
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.resolvedPromise
+import org.jetbrains.debugger.DebuggableRunConfiguration
+
+open class DebuggableProgramRunner : AsyncProgramRunner<RunnerSettings>() {
+ override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise<RunContentDescriptor?> {
+ FileDocumentManager.getInstance().saveAllDocuments()
+ val configuration = environment.runProfile as DebuggableRunConfiguration
+ val socketAddress = configuration.computeDebugAddress(state)
+ val starter = { executionResult: ExecutionResult? ->
+ startSession(environment) { configuration.createDebugProcess(socketAddress, it, executionResult, environment) }.runContentDescriptor
+ }
+ @Suppress("IfThenToElvis")
+ if (state is DebuggableRunProfileState) {
+ return state.execute(socketAddress.port).then(starter)
+ }
+ else {
+ return resolvedPromise(starter(null))
+ }
+ }
+
+ override fun getRunnerId(): String = "debuggableProgram"
+
+ override fun canRun(executorId: String, profile: RunProfile): Boolean {
+ return DefaultDebugExecutor.EXECUTOR_ID == executorId && profile is DebuggableRunConfiguration && profile.canRun(executorId, profile)
+ }
+}
+
+inline fun startSession(environment: ExecutionEnvironment, crossinline starter: (session: XDebugSession) -> XDebugProcess): XDebugSession {
+ return XDebuggerManager.getInstance(environment.project).startSession(environment, xDebugProcessStarter(starter))
+}
+
+inline fun xDebugProcessStarter(crossinline starter: (session: XDebugSession) -> XDebugProcess): XDebugProcessStarter = object : XDebugProcessStarter() {
+ override fun start(session: XDebugSession) = starter(session)
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/FileUrlMapper.kt b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/FileUrlMapper.kt
new file mode 100644
index 00000000..861a3b88
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/FileUrlMapper.kt
@@ -0,0 +1,34 @@
+package com.jetbrains.javascript.debugger
+
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.fileEditor.OpenFileDescriptor
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.pom.Navigatable
+import com.intellij.util.Url
+import org.jetbrains.debugger.sourcemap.SourceFileResolver
+
+abstract class FileUrlMapper {
+ companion object {
+ @JvmField
+ val EP_NAME: ExtensionPointName<FileUrlMapper> = ExtensionPointName.create<FileUrlMapper>("com.jetbrains.fileUrlMapper")
+ }
+
+ open fun getUrls(file: VirtualFile, project: Project, currentAuthority: String?): List<Url> = emptyList()
+
+ /**
+ * Optional to implement, useful if default navigation position to source file is not equals to 0:0 (java file for example)
+ */
+ open fun getNavigatable(url: Url, project: Project, requestor: Url?): Navigatable? = getFile(url, project, requestor)?.let { OpenFileDescriptor(project, it) }
+
+ abstract fun getFile(url: Url, project: Project, requestor: Url?): VirtualFile?
+
+ /**
+ * Optional to implement, sometimes you cannot build URL, but can match.
+ * Lifetime: resolve session lifetime. Could be called multiple times: n <= total sourcemap count
+ */
+ open fun createSourceResolver(file: VirtualFile, project: Project): SourceFileResolver? = null
+
+ open fun getFileType(url: Url): FileType? = null
+}
diff --git a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.kt b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.kt
new file mode 100644
index 00000000..007cd6dc
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2000-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 com.jetbrains.javascript.debugger
+
+import com.intellij.javascript.debugger.ExpressionInfoFactory
+import com.intellij.javascript.debugger.NameMapper
+import com.intellij.openapi.editor.Document
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.openapi.fileTypes.LanguageFileType
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.xdebugger.breakpoints.XLineBreakpointType
+import com.intellij.xdebugger.evaluation.ExpressionInfo
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.debugger.MemberFilter
+
+/**
+ * @see com.intellij.javascript.debugger.JavaScriptDebugAwareBase
+ */
+abstract class JavaScriptDebugAware {
+ companion object {
+ val EP_NAME: ExtensionPointName<JavaScriptDebugAware> = ExtensionPointName.create<JavaScriptDebugAware>("com.jetbrains.javaScriptDebugAware")
+
+ @JvmStatic
+ fun isBreakpointAware(fileType: FileType): Boolean {
+ val aware = getBreakpointAware(fileType)
+ return aware != null && aware.breakpointTypeClass == null
+ }
+
+ fun getBreakpointAware(fileType: FileType): JavaScriptDebugAware? {
+ for (debugAware in EP_NAME.extensions) {
+ if (fileType == debugAware.fileType) {
+ return debugAware
+ }
+ }
+ return null
+ }
+ }
+
+ protected open val fileType: LanguageFileType?
+ get() = null
+
+ open val breakpointTypeClass: Class<out XLineBreakpointType<*>>?
+ get() = null
+
+ /**
+ * Return false if you language could be natively executed in the VM
+ * You must not specify it and it doesn't matter if you use not own breakpoint type - (Kotlin or GWT use java breakpoint type, for example)
+ */
+ open val isOnlySourceMappedBreakpoints: Boolean
+ get() = true
+
+ fun canGetEvaluationInfo(file: PsiFile): Boolean = file.fileType == fileType
+
+ open fun getEvaluationInfo(element: PsiElement, document: Document, expressionInfoFactory: ExpressionInfoFactory): Promise<ExpressionInfo?>? = null
+
+ open fun createMemberFilter(nameMapper: NameMapper?, element: PsiElement, end: Int): MemberFilter? = null
+
+ open fun getNavigationElementForSourcemapInspector(file: PsiFile): PsiElement? = null
+
+ // return null if unsupported
+ // cannot be in MemberFilter because creation of MemberFilter could be async
+ // the problem - GWT mangles name (https://code.google.com/p/google-web-toolkit/issues/detail?id=9106 https://github.com/sdbg/sdbg/issues/6 https://youtrack.jetbrains.com/issue/IDEA-135356), but doesn't add name mappings
+ open fun normalizeMemberName(name: String): String? = null
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/CustomPropertiesValuePresentation.kt b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/CustomPropertiesValuePresentation.kt
new file mode 100644
index 00000000..6133321a
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/CustomPropertiesValuePresentation.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2016 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.debugger
+
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.xdebugger.XDebuggerBundle
+import com.intellij.xdebugger.frame.XValueNode
+import com.intellij.xdebugger.frame.presentation.XValuePresentation
+import org.jetbrains.debugger.values.ObjectValue
+import org.jetbrains.debugger.values.StringValue
+import org.jetbrains.debugger.values.ValueType
+
+class CustomPropertiesValuePresentation(private val value: ObjectValue, private val properties: List<Variable>) : XValuePresentation() {
+ override fun renderValue(renderer: XValuePresentation.XValueTextRenderer) {
+ renderer.renderComment(getObjectValueDescription(value))
+ renderer.renderSpecialSymbol(" {")
+ var isFirst = true
+ for (property in properties) {
+ if (isFirst) {
+ isFirst = false
+ }
+ else {
+ renderer.renderSpecialSymbol(", ")
+ }
+
+ renderer.renderValue(property.name, DefaultLanguageHighlighterColors.INSTANCE_FIELD)
+ renderer.renderSpecialSymbol(": ")
+
+ val value = property.value!!
+ when (value.type) {
+ ValueType.BOOLEAN, ValueType.NULL, ValueType.UNDEFINED, ValueType.SYMBOL -> renderer.renderKeywordValue(value.valueString!!)
+
+ ValueType.NUMBER, ValueType.BIGINT -> renderer.renderNumericValue(value.valueString!!)
+
+ ValueType.STRING -> {
+ val string = value.valueString
+ renderer.renderStringValue(string!!, "\"\\", XValueNode.MAX_VALUE_LENGTH)
+ val actualStringLength = (value as? StringValue)?.length ?: string.length
+ if (actualStringLength > XValueNode.MAX_VALUE_LENGTH) {
+ renderer.renderComment(XDebuggerBundle.message("node.text.ellipsis.truncated", actualStringLength))
+ }
+ }
+
+ ValueType.FUNCTION -> renderer.renderComment(trimFunctionDescription(value))
+
+ ValueType.OBJECT -> renderer.renderComment(getObjectValueDescription(value as ObjectValue))
+
+ else -> renderer.renderValue(value.valueString!!)
+ }
+ }
+ renderer.renderSpecialSymbol("}")
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggableRunConfiguration.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggableRunConfiguration.java
new file mode 100644
index 00000000..cc8fe28f
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggableRunConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2016 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.debugger;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.ExecutionResult;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.util.net.NetUtils;
+import com.intellij.xdebugger.XDebugProcess;
+import com.intellij.xdebugger.XDebugSession;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+public interface DebuggableRunConfiguration extends RunConfiguration {
+ @NotNull
+ default InetSocketAddress computeDebugAddress(RunProfileState state) throws ExecutionException {
+ try {
+ return new InetSocketAddress(InetAddress.getLoopbackAddress(), NetUtils.findAvailableSocketPort());
+ }
+ catch (IOException e) {
+ throw new ExecutionException("Cannot find available port", e);
+ }
+ }
+
+ @NotNull
+ XDebugProcess createDebugProcess(@NotNull InetSocketAddress socketAddress,
+ @NotNull XDebugSession session,
+ @Nullable ExecutionResult executionResult,
+ @NotNull ExecutionEnvironment environment) throws ExecutionException;
+
+ default boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggerSupportUtils.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggerSupportUtils.java
new file mode 100644
index 00000000..9b82da06
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/DebuggerSupportUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2015 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.debugger;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.xdebugger.XDebuggerUtil;
+import com.intellij.xdebugger.XSourcePosition;
+import org.jetbrains.annotations.Nullable;
+
+public final class DebuggerSupportUtils {
+ @Nullable
+ public static XSourcePosition calcSourcePosition(@Nullable PsiElement element) {
+ if (element != null) {
+ return XDebuggerUtil.getInstance().createPositionByElement(element.getNavigationElement());
+ }
+ return null;
+ }
+}
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/LazyVariablesGroup.kt b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/LazyVariablesGroup.kt
new file mode 100644
index 00000000..52ec2cfb
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/LazyVariablesGroup.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2000-2015 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.debugger
+
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XValueChildrenList
+import com.intellij.xdebugger.frame.XValueGroup
+import org.jetbrains.debugger.values.ObjectValue
+import org.jetbrains.debugger.values.ValueType
+import java.util.*
+
+internal fun lazyVariablesGroup(variables: ObjectValue, start: Int, end: Int, context: VariableContext) = LazyVariablesGroup(variables, start, end, context)
+
+class LazyVariablesGroup(private val value: ObjectValue, private val startInclusive: Int, private val endInclusive: Int, private val context: VariableContext, private val componentType: ValueType? = null, private val sparse: Boolean = true) : XValueGroup(String.format("[%,d \u2026 %,d]", startInclusive, endInclusive)) {
+ override fun computeChildren(node: XCompositeNode) {
+ node.setAlreadySorted(true)
+
+ val bucketThreshold = XCompositeNode.MAX_CHILDREN_TO_SHOW
+ if (!sparse && endInclusive - startInclusive > bucketThreshold) {
+ node.addChildren(XValueChildrenList.topGroups(computeNotSparseGroups(value, context, startInclusive, endInclusive + 1, bucketThreshold)), true)
+ return
+ }
+
+ value.getIndexedProperties(startInclusive, endInclusive + 1, bucketThreshold, object : VariableView.ObsolescentIndexedVariablesConsumer(node) {
+ override fun consumeRanges(ranges: IntArray?) {
+ if (ranges == null) {
+ val groupList = XValueChildrenList()
+ addGroups(value, ::lazyVariablesGroup, groupList, startInclusive, endInclusive, XCompositeNode.MAX_CHILDREN_TO_SHOW, context)
+ node.addChildren(groupList, true)
+ }
+ else {
+ addRanges(value, ranges, node, context, true)
+ }
+ }
+
+ override fun consumeVariables(variables: List<Variable>) {
+ node.addChildren(createVariablesList(variables, context, null), true)
+ }
+ }, componentType)
+ }
+}
+
+fun computeNotSparseGroups(value: ObjectValue, context: VariableContext, _fromInclusive: Int, toExclusive: Int, bucketThreshold: Int): List<XValueGroup> {
+ var fromInclusive = _fromInclusive
+ val size = toExclusive - fromInclusive
+ val bucketSize = Math.pow(bucketThreshold.toDouble(), Math.ceil(Math.log(size.toDouble()) / Math.log(bucketThreshold.toDouble())) - 1).toInt()
+ val groupList = ArrayList<XValueGroup>(Math.ceil((size / bucketSize).toDouble()).toInt())
+ while (fromInclusive < toExclusive) {
+ groupList.add(LazyVariablesGroup(value, fromInclusive, fromInclusive + (Math.min(bucketSize, toExclusive - fromInclusive) - 1), context, ValueType.NUMBER, false))
+ fromInclusive += bucketSize
+ }
+ return groupList
+}
+
+fun addRanges(value: ObjectValue, ranges: IntArray, node: XCompositeNode, context: VariableContext, isLast: Boolean) {
+ val groupList = XValueChildrenList(ranges.size / 2)
+ var i = 0
+ val n = ranges.size
+ while (i < n) {
+ groupList.addTopGroup(LazyVariablesGroup(value, ranges[i], ranges[i + 1], context))
+ i += 2
+ }
+ node.addChildren(groupList, isLast)
+}
+
+internal fun <T> addGroups(data: T,
+ groupFactory: (data: T, start: Int, end: Int, context: VariableContext) -> XValueGroup,
+ groupList: XValueChildrenList,
+ _from: Int,
+ limit: Int,
+ bucketSize: Int,
+ context: VariableContext) {
+ var from = _from
+ var to = Math.min(bucketSize, limit)
+ var done = false
+ do {
+ val groupFrom = from
+ var groupTo = to
+
+ from += bucketSize
+ to = from + Math.min(bucketSize, limit - from)
+
+ // don't create group for only one member
+ if (to - from == 1) {
+ groupTo++
+ done = true
+ }
+ groupList.addTopGroup(groupFactory(data, groupFrom, groupTo, context))
+ if (from >= limit) {
+ break
+ }
+ }
+ while (!done)
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Location.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Location.java
new file mode 100644
index 00000000..2dea7ccf
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Location.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2016 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.debugger;
+
+import com.intellij.util.Url;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * We use term "location" instead of "position" because webkit uses term "location"
+ */
+public final class Location {
+ private final Url url;
+ private final Script script;
+
+ private final int line;
+ private final int column;
+
+ public Location(@NotNull Url url, int line, int column) {
+ this.url = url;
+ this.line = line;
+ this.column = column;
+ script = null;
+ }
+
+ public Location(@NotNull Script script, int line, int column) {
+ this.url = script.getUrl();
+ this.line = line;
+ this.column = column;
+ this.script = script;
+ }
+
+ public Location(@NotNull Url url, int line) {
+ this(url, line, -1);
+ }
+
+ @NotNull
+ public Location withoutColumn() {
+ return script == null ? new Location(url, line) : new Location(script, line, -1);
+ }
+
+ @NotNull
+ public Url getUrl() {
+ return url;
+ }
+
+ @Nullable
+ public Script getScript() {
+ return script;
+ }
+
+ public int getLine() {
+ return line;
+ }
+
+ public int getColumn() {
+ return column;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Location location = (Location)o;
+ return column == location.column && line == location.line && url.equals(location.url);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = url.hashCode();
+ result = 31 * result + line;
+ result = 31 * result + column;
+ return result;
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/MemberFilterWithNameMappings.kt b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/MemberFilterWithNameMappings.kt
new file mode 100644
index 00000000..c30d7c87
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/MemberFilterWithNameMappings.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-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.debugger
+
+open class MemberFilterWithNameMappings(private val rawNameToSource: Map<String, String> = emptyMap()) : MemberFilter {
+ override fun hasNameMappings(): Boolean = !rawNameToSource.isEmpty()
+
+ override fun rawNameToSource(variable: Variable): String {
+ val name = variable.name
+ val sourceName = rawNameToSource.get(name)
+ return sourceName ?: normalizeMemberName(name)
+ }
+
+ protected open fun normalizeMemberName(name: String): String = name
+
+ override fun sourceNameToRaw(name: String): String? {
+ if (!hasNameMappings()) {
+ return null
+ }
+
+ for ((key, value) in rawNameToSource) {
+ if (value == name) {
+ return key
+ }
+ }
+ return null
+ }
+}
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/ObjectValuePresentation.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/ObjectValuePresentation.java
new file mode 100644
index 00000000..8231b08a
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/ObjectValuePresentation.java
@@ -0,0 +1,17 @@
+package org.jetbrains.debugger;
+
+import com.intellij.xdebugger.frame.presentation.XValuePresentation;
+import org.jetbrains.annotations.NotNull;
+
+public class ObjectValuePresentation extends XValuePresentation {
+ private final String myValue;
+
+ public ObjectValuePresentation(@NotNull String value) {
+ myValue = value;
+ }
+
+ @Override
+ public void renderValue(@NotNull XValueTextRenderer renderer) {
+ renderer.renderComment(myValue);
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/PsiVisitors.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/PsiVisitors.java
new file mode 100644
index 00000000..96b80600
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/PsiVisitors.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2000-2016 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.debugger;
+
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement;
+import com.intellij.psi.templateLanguages.OuterLanguageElement;
+import com.intellij.util.DocumentUtil;
+import com.intellij.xdebugger.XSourcePosition;
+import org.jetbrains.annotations.NotNull;
+
+public final class PsiVisitors {
+ public static <RESULT> RESULT visit(@NotNull XSourcePosition position, @NotNull Project project, @NotNull Visitor<RESULT> visitor) {
+ return visit(position, project, visitor, null);
+ }
+
+ /**
+ * Read action will be taken automatically
+ */
+ public static <RESULT> RESULT visit(@NotNull XSourcePosition position, @NotNull Project project, @NotNull Visitor<? extends RESULT> visitor, RESULT defaultResult) {
+ return ReadAction.compute(()->{
+ Document document = FileDocumentManager.getInstance().getDocument(position.getFile());
+ PsiFile file = document == null || document.getTextLength() == 0 ? null : PsiDocumentManager.getInstance(project).getPsiFile(document);
+ if (file == null) {
+ return defaultResult;
+ }
+
+ int positionOffset;
+ int column = position instanceof SourceInfo ? Math.max(((SourceInfo)position).getColumn(), 0) : 0;
+ try {
+ positionOffset = column == 0 ? DocumentUtil.getFirstNonSpaceCharOffset(document, position.getLine()) : document.getLineStartOffset(position.getLine()) + column;
+ }
+ catch (IndexOutOfBoundsException ignored) {
+ return defaultResult;
+ }
+
+ PsiElement element = file.findElementAt(positionOffset);
+ return element == null ? defaultResult : visitor.visit(position, element, positionOffset, document);
+ });
+ }
+
+ public interface Visitor<RESULT> {
+ RESULT visit(@NotNull XSourcePosition position, @NotNull PsiElement element, int positionOffset, @NotNull Document document);
+ }
+
+ public static abstract class FilteringPsiRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor {
+ @Override
+ public void visitElement(PsiElement element) {
+ if (!(element instanceof ForeignLeafPsiElement) && element.isPhysical()) {
+ super.visitElement(element);
+ }
+ }
+
+ @Override
+ public void visitWhiteSpace(PsiWhiteSpace space) {
+ }
+
+ @Override
+ public void visitComment(PsiComment comment) {
+ }
+
+ @Override
+ public void visitOuterLanguageElement(OuterLanguageElement element) {
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RejectErrorReporter.kt b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RejectErrorReporter.kt
new file mode 100644
index 00000000..3d6ccc1e
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RejectErrorReporter.kt
@@ -0,0 +1,15 @@
+// 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 file.
+package org.jetbrains.debugger
+
+import com.intellij.xdebugger.XDebugSession
+import org.jetbrains.concurrency.errorIfNotMessage
+import org.jetbrains.rpc.LOG
+import java.util.function.Consumer
+
+class RejectErrorReporter @JvmOverloads constructor(private val session: XDebugSession, private val description: String? = null) : Consumer<Throwable> {
+ override fun accept(error: Throwable) {
+ if (LOG.errorIfNotMessage(error)) {
+ session.reportError("${if (description == null) "" else "$description: "}${error.message}")
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RemoteDebugConfiguration.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RemoteDebugConfiguration.java
new file mode 100644
index 00000000..b76f76ad
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/RemoteDebugConfiguration.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2000-2017 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.
+ */
+package org.jetbrains.debugger;
+
+import com.intellij.execution.Executor;
+import com.intellij.execution.configuration.EmptyRunProfileState;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.LocatableConfigurationBase;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.GuiUtils;
+import com.intellij.ui.PortField;
+import com.intellij.util.ThreeState;
+import com.intellij.util.ui.FormBuilder;
+import com.intellij.util.xmlb.SerializationFilter;
+import com.intellij.util.xmlb.SkipEmptySerializationFilter;
+import com.intellij.util.xmlb.XmlSerializer;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+public abstract class RemoteDebugConfiguration extends LocatableConfigurationBase implements RunConfigurationWithSuppressedDefaultRunAction, DebuggableRunConfiguration {
+ private final SerializationFilter serializationFilter = new SkipEmptySerializationFilter() {
+ @Override
+ protected ThreeState accepts(@NotNull String name, @NotNull Object beanValue) {
+ return name.equals("port") ? ThreeState.fromBoolean(!beanValue.equals(defaultPort)) : ThreeState.UNSURE;
+ }
+ };
+
+ private String host;
+
+ private int port;
+ private final int defaultPort;
+
+ public RemoteDebugConfiguration(Project project, @NotNull ConfigurationFactory factory, String name, int defaultPort) {
+ super(project, factory, name);
+
+ port = defaultPort;
+ this.defaultPort = defaultPort;
+ }
+
+ @Nullable
+ @Attribute
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(@Nullable String value) {
+ if (StringUtil.isEmpty(value) || value.equals("localhost") || value.equals("127.0.0.1")) {
+ host = null;
+ }
+ else {
+ host = value;
+ }
+ }
+
+ @Attribute
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ @NotNull
+ @Override
+ public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
+ return new RemoteDebugConfigurationSettingsEditor();
+ }
+
+ @Nullable
+ @Override
+ public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment env) {
+ return EmptyRunProfileState.INSTANCE;
+ }
+
+ @Override
+ public RunConfiguration clone() {
+ RemoteDebugConfiguration configuration = (RemoteDebugConfiguration)super.clone();
+ configuration.host = host;
+ configuration.port = port;
+ return configuration;
+ }
+
+ @Override
+ public void readExternal(@NotNull Element element) throws InvalidDataException {
+ super.readExternal(element);
+
+ XmlSerializer.deserializeInto(this, element);
+ if (port <= 0) {
+ port = defaultPort;
+ }
+ }
+
+ @Override
+ public void writeExternal(@NotNull Element element) throws WriteExternalException {
+ super.writeExternal(element);
+
+ XmlSerializer.serializeInto(this, element, serializationFilter);
+ }
+
+ @NotNull
+ @Override
+ public InetSocketAddress computeDebugAddress(RunProfileState state) {
+ if (host == null) {
+ return new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
+ }
+ else {
+ return new InetSocketAddress(host, getPort());
+ }
+ }
+
+ private final class RemoteDebugConfigurationSettingsEditor extends SettingsEditor<RemoteDebugConfiguration> {
+ private final JTextField hostField;
+ private final PortField portField;
+
+ RemoteDebugConfigurationSettingsEditor() {
+ hostField = GuiUtils.createUndoableTextField();
+ portField = new PortField(defaultPort, 1024);
+ }
+
+ @Override
+ protected void resetEditorFrom(@NotNull RemoteDebugConfiguration configuration) {
+ hostField.setText(StringUtil.notNullize(configuration.host, "localhost"));
+ portField.setNumber(configuration.port);
+ }
+
+ @Override
+ protected void applyEditorTo(@NotNull RemoteDebugConfiguration configuration) {
+ configuration.setHost(hostField.getText());
+ configuration.setPort(portField.getNumber());
+ }
+
+ @NotNull
+ @Override
+ protected JComponent createEditor() {
+ return FormBuilder.createFormBuilder().addLabeledComponent("&Host:", hostField).addLabeledComponent("&Port:", portField).getPanel();
+ }
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/VariableViewBase.java b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/VariableViewBase.java
new file mode 100644
index 00000000..82d8e129
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/VariableViewBase.java
@@ -0,0 +1,18 @@
+package org.jetbrains.debugger;
+
+import com.intellij.xdebugger.frame.XNamedValue;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.debugger.values.ValueType;
+
+// todo remove when Firefox implementation will use SDK
+public abstract class VariableViewBase extends XNamedValue {
+ protected VariableViewBase(@NotNull String name) {
+ super(name);
+ }
+
+ public abstract ValueType getValueType();
+
+ public boolean isDomPropertiesValue() {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Variables.kt b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Variables.kt
new file mode 100644
index 00000000..6460cc6e
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/src/org/jetbrains/debugger/Variables.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2000-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.debugger
+
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.util.SmartList
+import com.intellij.xdebugger.frame.XCompositeNode
+import com.intellij.xdebugger.frame.XValueChildrenList
+import org.jetbrains.concurrency.Obsolescent
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.then
+import org.jetbrains.concurrency.thenAsync
+import org.jetbrains.debugger.values.ValueType
+import java.util.*
+import java.util.regex.Pattern
+
+private val UNNAMED_FUNCTION_PATTERN = Pattern.compile("^function[\\t ]*\\(")
+
+private val NATURAL_NAME_COMPARATOR = Comparator<Variable> { o1, o2 -> naturalCompare(o1.name, o2.name) }
+
+// start properties loading to achieve, possibly, parallel execution (properties loading & member filter computation)
+fun processVariables(context: VariableContext,
+ variables: Promise<List<Variable>>,
+ obsolescent: Obsolescent,
+ consumer: (memberFilter: MemberFilter, variables: List<Variable>) -> Unit): Promise<Unit> {
+ return context.memberFilter
+ .thenAsync(obsolescent) { memberFilter ->
+ variables
+ .then(obsolescent) {
+ consumer(memberFilter, it)
+ }
+ }
+}
+
+fun processScopeVariables(scope: Scope,
+ node: XCompositeNode,
+ context: VariableContext,
+ isLast: Boolean): Promise<Unit> {
+ return processVariables(context, scope.variablesHost.get(), node) { memberFilter, variables ->
+ val additionalVariables = memberFilter.additionalVariables
+
+ val exceptionValue = context.vm?.suspendContextManager?.context?.exceptionData?.exceptionValue
+ val properties = ArrayList<Variable>(variables.size + additionalVariables.size + (if (exceptionValue == null) 0 else 1))
+
+ exceptionValue?.let {
+ properties.add(VariableImpl("Exception", it))
+ }
+
+ val functions = SmartList<Variable>()
+ for (variable in variables) {
+ if (memberFilter.isMemberVisible(variable) && variable.name != RECEIVER_NAME && variable.name != memberFilter.sourceNameToRaw(RECEIVER_NAME)) {
+ val value = variable.value
+ if (value != null && value.type == ValueType.FUNCTION && value.valueString != null && !UNNAMED_FUNCTION_PATTERN.matcher(
+ value.valueString).lookingAt()) {
+ functions.add(variable)
+ }
+ else {
+ properties.add(variable)
+ }
+ }
+ }
+
+ addAditionalVariables(additionalVariables, properties, memberFilter)
+
+ val comparator = if (memberFilter.hasNameMappings()) Comparator { o1, o2 ->
+ naturalCompare(memberFilter.rawNameToSource(o1), memberFilter.rawNameToSource(o2))
+ }
+ else NATURAL_NAME_COMPARATOR
+ properties.sortWith(comparator)
+ functions.sortWith(comparator)
+
+ if (!properties.isEmpty()) {
+ node.addChildren(createVariablesList(properties, context, memberFilter), functions.isEmpty() && isLast)
+ }
+
+ if (!functions.isEmpty()) {
+ node.addChildren(XValueChildrenList.bottomGroup(VariablesGroup("Functions", functions, context)), isLast)
+ }
+ else if (isLast && properties.isEmpty()) {
+ node.addChildren(XValueChildrenList.EMPTY, true)
+ }
+ }
+}
+
+fun processNamedObjectProperties(variables: List<Variable>,
+ node: XCompositeNode,
+ context: VariableContext,
+ memberFilter: MemberFilter,
+ maxChildrenToAdd: Int,
+ defaultIsLast: Boolean): List<Variable>? {
+ val list = filterAndSort(variables, memberFilter)
+ if (list.isEmpty()) {
+ if (defaultIsLast) {
+ node.addChildren(XValueChildrenList.EMPTY, true)
+ }
+ return null
+ }
+
+ val to = Math.min(maxChildrenToAdd, list.size)
+ val isLast = to == list.size
+ node.addChildren(createVariablesList(list, 0, to, context, memberFilter), defaultIsLast && isLast)
+ if (isLast) {
+ return null
+ }
+ else {
+ node.tooManyChildren(list.size - to)
+ return list
+ }
+}
+
+fun filterAndSort(variables: List<Variable>, memberFilter: MemberFilter): List<Variable> {
+ if (variables.isEmpty()) {
+ return emptyList()
+ }
+
+ val additionalVariables = memberFilter.additionalVariables
+ val result = ArrayList<Variable>(variables.size + additionalVariables.size)
+ for (variable in variables) {
+ if (memberFilter.isMemberVisible(variable)) {
+ result.add(variable)
+ }
+ }
+ result.sortWith(NATURAL_NAME_COMPARATOR)
+
+ addAditionalVariables(additionalVariables, result, memberFilter)
+ return result
+}
+
+private fun addAditionalVariables(additionalVariables: Collection<Variable>,
+ result: MutableList<Variable>,
+ memberFilter: MemberFilter,
+ functions: MutableList<Variable>? = null) {
+ val oldSize = result.size
+ ol@ for (variable in additionalVariables) {
+ if (!memberFilter.isMemberVisible(variable)) continue
+
+ for (i in 0..(oldSize - 1)) {
+ val vmVariable = result[i]
+ if (memberFilter.rawNameToSource(vmVariable) == memberFilter.rawNameToSource(variable)) {
+ // we prefer additionalVariable here because it is more smart variable (e.g. NavigatableVariable)
+ val vmValue = vmVariable.value
+ // to avoid evaluation, use vm value directly
+ if (vmValue != null) {
+ variable.value = vmValue
+ }
+
+ result.set(i, variable)
+ continue@ol
+ }
+ }
+
+ if (functions != null) {
+ for (function in functions) {
+ if (memberFilter.rawNameToSource(function) == memberFilter.rawNameToSource(variable)) {
+ continue@ol
+ }
+ }
+ }
+
+ result.add(variable)
+ }
+}
+
+// prefixed '_' must be last, uppercase after lowercase, fixed case sensitive natural compare
+private fun naturalCompare(string1: String?, string2: String?): Int {
+ //noinspection StringEquality
+ if (string1 === string2) {
+ return 0
+ }
+ if (string1 == null) {
+ return -1
+ }
+ if (string2 == null) {
+ return 1
+ }
+
+ val string1Length = string1.length
+ val string2Length = string2.length
+ var i = 0
+ var j = 0
+ while (i < string1Length && j < string2Length) {
+ var ch1 = string1[i]
+ var ch2 = string2[j]
+ if ((StringUtil.isDecimalDigit(ch1) || ch1 == ' ') && (StringUtil.isDecimalDigit(ch2) || ch2 == ' ')) {
+ var startNum1 = i
+ while (ch1 == ' ' || ch1 == '0') {
+ // skip leading spaces and zeros
+ startNum1++
+ if (startNum1 >= string1Length) {
+ break
+ }
+ ch1 = string1[startNum1]
+ }
+ var startNum2 = j
+ while (ch2 == ' ' || ch2 == '0') {
+ // skip leading spaces and zeros
+ startNum2++
+ if (startNum2 >= string2Length) {
+ break
+ }
+ ch2 = string2[startNum2]
+ }
+ i = startNum1
+ j = startNum2
+ // find end index of number
+ while (i < string1Length && StringUtil.isDecimalDigit(string1[i])) {
+ i++
+ }
+ while (j < string2Length && StringUtil.isDecimalDigit(string2[j])) {
+ j++
+ }
+ val lengthDiff = (i - startNum1) - (j - startNum2)
+ if (lengthDiff != 0) {
+ // numbers with more digits are always greater than shorter numbers
+ return lengthDiff
+ }
+ while (startNum1 < i) {
+ // compare numbers with equal digit count
+ val diff = string1[startNum1] - string2[startNum2]
+ if (diff != 0) {
+ return diff
+ }
+ startNum1++
+ startNum2++
+ }
+ i--
+ j--
+ }
+ else if (ch1 != ch2) {
+ fun reverseCase(ch: Char) = when {
+ ch.isUpperCase() -> ch.toLowerCase()
+ ch.isLowerCase() -> ch.toUpperCase()
+ else -> ch
+ }
+
+ when {
+ ch1 == '_' -> return 1
+ ch2 == '_' -> return -1
+ else -> return reverseCase(ch1) - reverseCase(ch2)
+ }
+ }
+ i++
+ j++
+ }
+ // After the loop the end of one of the strings might not have been reached, if the other
+ // string ends with a number and the strings are equal until the end of that number. When
+ // there are more characters in the string, then it is greater.
+ if (i < string1Length) {
+ return 1
+ }
+ else if (j < string2Length) {
+ return -1
+ }
+ return string1Length - string2Length
+}
+
+@JvmOverloads fun createVariablesList(variables: List<Variable>, variableContext: VariableContext, memberFilter: MemberFilter? = null): XValueChildrenList {
+ return createVariablesList(variables, 0, variables.size, variableContext, memberFilter)
+}
+
+fun createVariablesList(variables: List<Variable>, from: Int, to: Int, variableContext: VariableContext, memberFilter: MemberFilter?): XValueChildrenList {
+ val list = XValueChildrenList(to - from)
+ var getterOrSetterContext: VariableContext? = null
+ for (i in from until to) {
+ val variable = variables[i]
+ val normalizedName = memberFilter?.rawNameToSource(variable) ?: variable.name
+ list.add(VariableView(normalizedName, variable, variableContext))
+ if (variable is ObjectProperty) {
+ if (variable.getter != null) {
+ if (getterOrSetterContext == null) {
+ getterOrSetterContext = NonWatchableVariableContext(variableContext)
+ }
+ list.add(VariableView(VariableImpl("get $normalizedName", variable.getter!!), getterOrSetterContext))
+ }
+ if (variable.setter != null) {
+ if (getterOrSetterContext == null) {
+ getterOrSetterContext = NonWatchableVariableContext(variableContext)
+ }
+ list.add(VariableView(VariableImpl("set $normalizedName", variable.setter!!), getterOrSetterContext))
+ }
+ }
+ }
+ return list
+}
+
+private class NonWatchableVariableContext(variableContext: VariableContext) : VariableContextWrapper(variableContext, null) {
+ override fun watchableAsEvaluationExpression() = false
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/testSrc/Content.java b/platform/script-debugger/debugger-ui/testSrc/Content.java
new file mode 100644
index 00000000..ff53d141
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/testSrc/Content.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2016 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.debugger;
+
+import com.intellij.util.SmartList;
+
+import java.util.List;
+
+public final class Content {
+ public final List<TestCompositeNode> topGroups = new SmartList<>();
+ public final List<TestValueNode> values = new SmartList<>();
+ public final List<TestCompositeNode> bottomGroups = new SmartList<>();
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/testSrc/TestCompositeNode.java b/platform/script-debugger/debugger-ui/testSrc/TestCompositeNode.java
new file mode 100644
index 00000000..7886a05a
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/testSrc/TestCompositeNode.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2000-2017 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.
+ */
+package org.jetbrains.debugger;
+
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.Conditions;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.Function;
+import com.intellij.xdebugger.frame.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.concurrency.AsyncPromise;
+import org.jetbrains.concurrency.Promise;
+import org.jetbrains.concurrency.Promises;
+import org.jetbrains.debugger.values.ObjectValue;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestCompositeNode implements XCompositeNode {
+ private final AsyncPromise<XValueChildrenList> result = new AsyncPromise<>();
+ private final XValueChildrenList children = new XValueChildrenList();
+
+ private final XValueGroup valueGroup;
+ public Content content;
+
+ public TestCompositeNode() {
+ valueGroup = null;
+ }
+
+ public TestCompositeNode(@NotNull XValueGroup group) {
+ valueGroup = group;
+ }
+
+ @NotNull
+ public XValueGroup getValueGroup() {
+ return valueGroup;
+ }
+
+ @Override
+ public void addChildren(@NotNull XValueChildrenList children, boolean last) {
+ for (XValueGroup group : children.getTopGroups()) {
+ this.children.addTopGroup(group);
+ }
+ for (int i = 0; i < children.size(); i++) {
+ this.children.add(children.getName(i), children.getValue(i));
+ }
+ for (XValueGroup group : children.getBottomGroups()) {
+ this.children.addBottomGroup(group);
+ }
+
+ if (last) {
+ result.setResult(this.children);
+ }
+ }
+
+ @Override
+ public void tooManyChildren(int remaining) {
+ result.setResult(children);
+ }
+
+ @Override
+ public void setAlreadySorted(boolean alreadySorted) {
+ }
+
+ @Override
+ public void setErrorMessage(@NotNull String errorMessage) {
+ result.setError(errorMessage);
+ }
+
+ @Override
+ public void setErrorMessage(@NotNull String errorMessage, @Nullable XDebuggerTreeNodeHyperlink link) {
+ setErrorMessage(errorMessage);
+ }
+
+ @Override
+ public void setMessage(@NotNull String message, @Nullable Icon icon, @NotNull SimpleTextAttributes attributes, @Nullable XDebuggerTreeNodeHyperlink link) {
+ }
+
+ @NotNull
+ public Promise<XValueChildrenList> getResult() {
+ return result;
+ }
+
+ @NotNull
+ public Promise<Content> loadContent(@NotNull final Condition<XValueGroup> groupContentResolveCondition, @NotNull final Condition<VariableView> valueSubContentResolveCondition) {
+ assert content == null;
+
+ content = new Content();
+ return result.thenAsync(new Function<XValueChildrenList, Promise<Content>>() {
+ private void resolveGroups(@NotNull List<XValueGroup> valueGroups, @NotNull List<TestCompositeNode> resultNodes, @NotNull List<Promise<?>> promises) {
+ for (XValueGroup group : valueGroups) {
+ TestCompositeNode node = new TestCompositeNode(group);
+ boolean computeChildren = groupContentResolveCondition.value(group);
+ if (computeChildren) {
+ group.computeChildren(node);
+ }
+ resultNodes.add(node);
+ if (computeChildren) {
+ promises.add(node.loadContent(Conditions.alwaysFalse(), valueSubContentResolveCondition));
+ }
+ }
+ }
+
+ @NotNull
+ @Override
+ public Promise<Content> fun(XValueChildrenList list) {
+ List<Promise<?>> promises = new ArrayList<>();
+ resolveGroups(children.getTopGroups(), content.topGroups, promises);
+
+ for (int i = 0; i < children.size(); i++) {
+ XValue value = children.getValue(i);
+ TestValueNode node = new TestValueNode();
+ node.myName = children.getName(i);
+ value.computePresentation(node, XValuePlace.TREE);
+ content.values.add(node);
+ promises.add(node.getResult());
+
+ // myHasChildren could be not computed yet
+ if (value instanceof VariableView && ((VariableView)value).getValue() instanceof ObjectValue && valueSubContentResolveCondition.value((VariableView)value)) {
+ promises.add(node.loadChildren(value));
+ }
+ }
+
+ resolveGroups(children.getBottomGroups(), content.bottomGroups, promises);
+
+ return Promises.all(promises, content);
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/testSrc/TestValueNode.java b/platform/script-debugger/debugger-ui/testSrc/TestValueNode.java
new file mode 100644
index 00000000..85baf270
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/testSrc/TestValueNode.java
@@ -0,0 +1,44 @@
+// 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 file.
+package org.jetbrains.debugger;
+
+import com.intellij.openapi.util.Conditions;
+import com.intellij.xdebugger.XTestValueNode;
+import com.intellij.xdebugger.frame.XValue;
+import com.intellij.xdebugger.frame.presentation.XValuePresentation;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.concurrency.AsyncPromise;
+import org.jetbrains.concurrency.Promise;
+
+import javax.swing.*;
+
+public class TestValueNode extends XTestValueNode {
+ private final AsyncPromise<XTestValueNode> result = new AsyncPromise<>();
+
+ private volatile Content children;
+
+ @NotNull
+ public Promise<XTestValueNode> getResult() {
+ return result;
+ }
+
+ @NotNull
+ public Promise<Content> loadChildren(@NotNull XValue value) {
+ TestCompositeNode childrenNode = new TestCompositeNode();
+ value.computeChildren(childrenNode);
+ return childrenNode.loadContent(Conditions.alwaysFalse(), Conditions.alwaysFalse())
+ .onSuccess(content -> children = content);
+ }
+
+ @Nullable
+ public Content getChildren() {
+ return children;
+ }
+
+ @Override
+ public void applyPresentation(@Nullable Icon icon, @NotNull XValuePresentation valuePresentation, boolean hasChildren) {
+ super.applyPresentation(icon, valuePresentation, hasChildren);
+
+ result.setResult(this);
+ }
+} \ No newline at end of file
diff --git a/platform/script-debugger/debugger-ui/testSrc/testUtil.kt b/platform/script-debugger/debugger-ui/testSrc/testUtil.kt
new file mode 100644
index 00000000..fc7d2f77
--- /dev/null
+++ b/platform/script-debugger/debugger-ui/testSrc/testUtil.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2000-2015 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.debugger
+
+import com.intellij.xdebugger.XDebugSession
+import org.jetbrains.debugger.frame.CallFrameView
+
+val XDebugSession.topFrameView: CallFrameView
+ get() = currentStackFrame as CallFrameView \ No newline at end of file